Sign in to follow this  

motion blur frustration

This topic is 1957 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

I'm implementing motion blur, in particular this one: [url="http://mynameismjp.wordpress.com/samples-tutorials-tools/motion-blur-sample/"]http://mynameismjp.w...on-blur-sample/[/url]
All went well, however I noticed a tiny problem: MJP doesn't encode the velocity information. I thought no problem, let's do a scale-bias, so that the coordinates get transformed from [-1, 1] to [0, 1], and then I can store them in an RGBA texture's xy channels.
Then came the surprise, when I do motion blur, the whole scene became blurred a bit, and when I moved around I saw screen tearing everywhere. The first symptom's cause is obvious, for some reason the precision isn't that good, and therefore 0 * 0.5 + 0.5 = x became x > 0.5.
Where x is the encoded velocity. Now where x should be 0 (no movement) it was around 0.005.
So I added a threshold, if x < 0.006 then no blur. Hooray this solved the blurring problem, but not the tearing.
So I started to investigate this further, as the technique seemed to be very easy to implement I suppose I did it correctly. Now When I masked the screen ( white where blur, black where no blur) I noticed that as I move around certain areas fall below the threshold and therefore they don't get blurred. This symptom is most noticable when I move horizontally. After a certain point the screen is no longer blurred. See on video:
[media]http://youtu.be/GRewBOtJ56I[/media]
http://youtu.be/2oyYFE_XbJw

and here's the source code:

c++ g-buffer rendering code:
[CODE]
[c++ code, rendering]
o->mvm.push_matrix();

mm::vec4 diff_color( 0.9f, 0.9f, 0.9f, 1.0f );
coloring_shader->bind();

coloring_shader->pass_m4x4( o->ppl.get_model_view_matrix(), "modelview" );
coloring_shader->pass_m4x4( o->ppl.get_projection_matrix(), "proj" );
coloring_shader->pass_m3x3( o->ppl.get_normal_matrix( false ), "normal_mat" );
coloring_shader->pass_vec4( diff_color, "diff_color" );
coloring_shader->pass_float( -1.0f / event::get()->get_resize()->get_data()->far, "inv_neg_far" );
//in the first frame I upload a identity matrix as the previous, in the next frame it is fixed.
coloring_shader->pass_m4x4( o->ppl.get_projection_matrix() * coloring_prev, "previous_mat" );
coloring_prev = o->mvm.get_matrix();

render_mesh( test_mesh );

coloring_shader->unbind();

o->mvm.pop_matrix();
[/CODE]

G-buffer rendering (including velocity buffer)
[CODE]
[vertex shader]
#version 410

uniform mat4 modelview, proj;
uniform mat3 normal_mat;
uniform mat4 previous_mat;

in vec3 in_normal;
in vec4 in_vertex;

out cross_shader_data
{
vec3 vs_normal;
vec4 vs_position;
vec4 prev_vector;
vec4 curr_vector;
} o;

void main()
{
o.vs_normal = normalize(normal_mat * in_normal);
o.vs_position = modelview * in_vertex;

o.prev_vector = previous_mat * in_vertex;
o.curr_vector = proj * o.vs_position;

gl_Position = o.curr_vector;
}

[pixel shader]
#version 410

uniform vec4 diff_color;
uniform float inv_neg_far;

in cross_shader_data
{
vec3 vs_normal;
vec4 vs_position;
vec4 prev_vector;
vec4 curr_vector;
} i;

out vec4 albedo;
out vec4 normal;
out vec4 depth;
out vec4 extra;

void main()
{
albedo = diff_color;
normal = vec4(i.vs_normal * 0.5 + 0.5, 1.0);
depth.x = i.vs_position.z * inv_neg_far;
//transform clip space to ndc, then scale / bias to store it
extra = vec4((i.curr_vector.xy / i.curr_vector.w - i.prev_vector.xy / i.prev_vector.w) * 0.5 + 0.5, 0, 0);
}
[/CODE]

blurring pass:
[CODE]
#version 410

uniform sampler2D texture0; //shading result
uniform sampler2D texture1; //velocity buffer

in cross_shader_data
{
vec2 tex_coord;
} i;

out vec4 color;

void main()
{
vec2 velocity_vec = texture(texture1, i.tex_coord).xy * 2.0 - 1.0; //scale and bias back

if(length(velocity_vec) < 0.006)
{
color = texture(texture0, i.tex_coord);
return;
}

vec4 result = vec4(0.0);
int num_of_samples = 16;

//clamp the velocity to make sure that there's no undersampling
vec2 max_velocity = (2.0 * float(num_of_samples)) / textureSize(texture0, 0);
velocity_vec = clamp(velocity_vec, -max_velocity, max_velocity);

for(int c = 0; c < num_of_samples; c++)
{
vec2 sample_coord = i.tex_coord + (float(velocity_vec) * (c / float(num_of_samples)));
//sample & add to result:
result += texture(texture0, sample_coord);
}
color = result / float(num_of_samples);
}
[/CODE]

any ideas what am I doing wrong / how to solve this? Edited by Yours3!f

Share this post


Link to post
Share on other sites
ok, I eliminated most of the unneded blurring properly. Firstly I had to make the velocity buffer to use nearest sampling then I had to modify the g-buffer shader, because it outputted the velocity vectors in wrong range:
[CODE]
extra = vec4((i.curr_vector.xy / i.curr_vector.w - i.prev_vector.xy / i.prev_vector.w) * 0.5 + 0.5, 0, 0);
-->
extra = vec4((i.curr_vector.xy / i.curr_vector.w - i.prev_vector.xy / i.prev_vector.w) * 0.25 + 0.5, 0, 0);
[/CODE]

then I had to change the checking in the motion blur shader:
[CODE]
vec2 velocity_vec = texture(texture1, i.tex_coord).xy;

//128 (0.5 in shader) / 255 is something like 0.501... this is why we do it like this
if(velocity_vec * 255 == vec2(128))
{
color = texture(texture0, i.tex_coord);
return;
}

velocity_vec = velocity_vec * 2.0 - 1.0; //scale and bias back
[/CODE]

the tearing problem is still present although it is less noticable... Edited by Yours3!f

Share this post


Link to post
Share on other sites
In my experience, 10 bits has generally not been enough precision for encoding velocity. The quantization can result in noticable banding, and of course you have the "0.5" problem. Currently I like to store velocity in a 1010102 format, where X and Y are velocity, Z is depth (since doing a decent motion blur requires comparing both depth and velocity of each sample), and W is the sign bits. Having the sign bits separate lets you avoid the 0.5 problem.

Share this post


Link to post
Share on other sites
[quote name='MJP' timestamp='1343500564' post='4964041']
In my experience, 10 bits has generally not been enough precision for encoding velocity. The quantization can result in noticable banding, and of course you have the "0.5" problem. Currently I like to store velocity in a 1010102 format, where X and Y are velocity, Z is depth (since doing a decent motion blur requires comparing both depth and velocity of each sample), and W is the sign bits. Having the sign bits separate lets you avoid the 0.5 problem.
[/quote]

Thank you MJP for the reply,

I'll try this out.

Share this post


Link to post
Share on other sites
hi,

I have a few quiestions concerning the packing. I currently put the velocity vectors simply as before in the xy channels:
[CODE]
vec2 ndc_vel = i.curr_vector.xy / i.curr_vector.w - i.prev_vector.xy / i.prev_vector.w; //in range -2 ... 2 because of subtraction
extra = vec4(ndc_vel * 0.25 + 0.5, 0, 0); //this is now RGB10_A2 (xy in range 0...1)
[/CODE]
the quality has improved significantly, now I can't see any tearing!
How do I extract the sign bit of each vector? I looked up bit masking / flagging but I can't think of how to use that.
If I could store 2 bits unsigned, the I could flag each bit like 2 would mean the x vector is negative and 1 would mean that the y is. How do I do this in glsl?
unsigned char sign_bits = 0;
sign_bits |= 2;
sign_bits |= 1;
also should I store the velocity vectors abs()-ed? (because the sign is stored in A)
encoding the depth isn't a problem thanks to your tutorials :)

Share this post


Link to post
Share on other sites
[quote name='Yours3!f' timestamp='1344009525' post='4965868']
How do I extract the sign bit of each vector? I looked up bit masking / flagging but I can't think of how to use that.
If I could store 2 bits unsigned, the I could flag each bit like 2 would mean the x vector is negative and 1 would mean that the y is. How do I do this in glsl?
[/quote]

Yeah you have right idea. You'll want bit 0 to the the sign bit for X, and bit 1 to be the sign bit for Y. If you have support for integer operations (I'm not sure which GLSL version is required for integer ops), you can do it just the way you described by OR'ing the shifted bit if the corresponding value is negative. So you'd do

[code]
uint sign_bits = 0;
if(velocity.x < 0.0f)
sign_bits |= (1 << 0); // set bit 0
if(velocity.y < 0.0f)
sign_bits |= (1 << 1); // set bit 1
[/code]

If you don't have integer ops you can do it with floating point:

[code]
float sign_bits = 0.0f;
f(velocity.x < 0.0f)
sign_bits += 1.0f;
if(velocity.y < 0.0f)
sign_bits += 3.0f;
[/code]

Then you'll need to convert the sign bits to a [0, 1] value, since that's what the texture format expects. To do that you can just divide by 3.0f.

[quote name='Yours3!f' timestamp='1344009525' post='4965868']
also should I store the velocity vectors abs()-ed? (because the sign is stored in A)
[/quote]

Yes, you'll want to store the absolute value of your velocity. Then when you sample the velocity texture later on, you'll need to extract the sign bits from the alpha value and negate the corresponding velocity component based on the value of those bits.

[code]
uint sign_bits = uint(velocity_alpha * 3.0f);
uint x_sign = sign_bits & 0x1; // mask out everything but bit 0
uint y_sign = (sign bits & 0x3) >> 1; // mask out everything but bit 1, and shift it to the right 1 bit
if(x_sign)
velocity.x *= -1.0f;
if(y_sign)
velocity.y *= -1.0f;
[/code]

Share this post


Link to post
Share on other sites
[quote name='MJP' timestamp='1344132235' post='4966231']
[quote name='Yours3!f' timestamp='1344009525' post='4965868']
How do I extract the sign bit of each vector? I looked up bit masking / flagging but I can't think of how to use that.
If I could store 2 bits unsigned, the I could flag each bit like 2 would mean the x vector is negative and 1 would mean that the y is. How do I do this in glsl?
[/quote]

Yeah you have right idea. You'll want bit 0 to the the sign bit for X, and bit 1 to be the sign bit for Y. If you have support for integer operations (I'm not sure which GLSL version is required for integer ops), you can do it just the way you described by OR'ing the shifted bit if the corresponding value is negative. So you'd do

[code]
uint sign_bits = 0;
if(velocity.x < 0.0f)
sign_bits |= (1 << 0); // set bit 0
if(velocity.y < 0.0f)
sign_bits |= (1 << 1); // set bit 1
[/code]

If you don't have integer ops you can do it with floating point:

[code]
float sign_bits = 0.0f;
f(velocity.x < 0.0f)
sign_bits += 1.0f;
if(velocity.y < 0.0f)
sign_bits += 3.0f;
[/code]

Then you'll need to convert the sign bits to a [0, 1] value, since that's what the texture format expects. To do that you can just divide by 3.0f.

[quote name='Yours3!f' timestamp='1344009525' post='4965868']
also should I store the velocity vectors abs()-ed? (because the sign is stored in A)
[/quote]

Yes, you'll want to store the absolute value of your velocity. Then when you sample the velocity texture later on, you'll need to extract the sign bits from the alpha value and negate the corresponding velocity component based on the value of those bits.

[code]
uint sign_bits = uint(velocity_alpha * 3.0f);
uint x_sign = sign_bits & 0x1; // mask out everything but bit 0
uint y_sign = (sign bits & 0x3) >> 1; // mask out everything but bit 1, and shift it to the right 1 bit
if(x_sign)
velocity.x *= -1.0f;
if(y_sign)
velocity.y *= -1.0f;
[/code]
[/quote]

hi,

I tried it and works wonderfully! Thank you!!!

Share this post


Link to post
Share on other sites

This topic is 1957 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this