Yours3!f 1532 Report post Posted July 24, 2012 (edited) 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 July 24, 2012 by Yours3!f 0 Share this post Link to post Share on other sites
Yours3!f 1532 Report post Posted July 28, 2012 (edited) 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 July 28, 2012 by Yours3!f 0 Share this post Link to post Share on other sites
MJP 19812 Report post Posted July 28, 2012 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. 0 Share this post Link to post Share on other sites
Yours3!f 1532 Report post Posted July 29, 2012 [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. 0 Share this post Link to post Share on other sites
Yours3!f 1532 Report post Posted August 3, 2012 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 0 Share this post Link to post Share on other sites
MJP 19812 Report post Posted August 5, 2012 [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] 0 Share this post Link to post Share on other sites
Yours3!f 1532 Report post Posted August 5, 2012 [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!!! 0 Share this post Link to post Share on other sites