Sign in to follow this  

Normal mapping in deferred and forward rendering

Recommended Posts

I have implemented normal mapping in forward and deferred rendering paths but the results are slightly different (see the attached images and maybe download to compare them better). In forward rendering I'm doing the calculations in tangent space and in deferred rendering I'm doing the calculations in world space. I guess the difference comes from interpolating values between vertex and fragment shader. Let's see the most interesting parts of the shader code (I have narrowed the problem down to the value of "float diff"):

 

Forward

Vertex shader

N = normalize(mat3(M) * normal); // M = model to world space matrix
T = normalize(mat3(M) * tangent);
B = cross(T, N);
mat3 TBN = transpose(mat3(T, B, N)); // Transpose to get transform from world to tangent space
lightPos_Tan = TBN * lightPos_World; // Output to fragment shader
vertexPos_Tan = TBN * vertexPos_World; // Output to fragment shader

Fragment shader

vec3 normal_Tan = normalize(texture(normalTexture, texCoord).rgb * 2.0 - vec3(1.0));
vec3 lightDir_Tan = normalize(lightPos_Tan - vertexPos_Tan); // Inputs from vertex shader
float diff = clamp(dot(normal_Tan, lightDir_Tan), 0, 1);

Deferred

GBuffer vertex shader

vec3 N = normalize(mat3(M) * normal); // M = model to world space matrix
vec3 T = normalize(mat3(M) * tangent);
vec3 B = cross(T, N);
TBN = mat3(T, B, N); // Output to fragment shader

GBuffer fragment shader

vec3 normal = normalize(texture(normalTexture, texCoord).rgb * 2.0 - vec3(1.0));
outNormal = normalize(TBN * normal); // TBN = Input from vertex shader

Deferred lighting fragment shader

vec3 normal = texture(normalTexture, texCoord).rgb; // Normal in world space written in gbuffer fragment shader
vec3 lightDir = normalize(lightPos - vertexPos); // Positions in world space
float diff = clamp(dot(normal, lightDir), 0, 1);

Do the shaders look OK? My guess is that the difference comes from interpolation between vertex and fragment shader: in forward rendering there are two vec3s and in deferred rendering there is a mat3. If in forward rendering the TBN matrix is created on fragment shader the results seem nearly identical (but this would be inefficient). Hence my questions are:

 

1. What could explain the small difference in the attached images?

2. How is mat3 interpolated between vertex and fragment shader?

3. What should be done to make results identical between forward and deferred rendering?

Share this post


Link to post
Share on other sites
I would implement world-space shading in forward too, and then work to make your TS and WS forward shaders look the same. This will be simpler by removing the other differences of forward vs deferred.

The issue is likely that, while WS is well defined and consistent for the whole world, TS is defined per vertex, and we then interpolate it to get a new TS definition per pixel.

When interpolating a direction vector, you should normalize it in the pixel shader to avoid any unintended scaling. When interpolating three basis vectors, you should reorthonormalize them in the pixel shader (use cross to make sure they're all at right angles) to avoid unintended skewing.

Share this post


Link to post
Share on other sites
I treat the TBN matrix as 3 (2 + 1 extrapolated) orthonormal vectors that are interpolated by the fragment shader.

// attributes
attribute vec3 vtx_t;
attribute vec3 vtx_n;

//variables
varying vec3 t_interp, n_interp;

//vertex shader
t_interp = M * vec4( vtx_t, 0.0 );
n_interp = M * vec4( vtx_n, 0.0 );


//pixel shader
vec3 t, b, n;
t = normalize( t_interp );
b = normalize( cross( n_interp, t_interp ) );
n = cross( t, b );

vec3 norm = normalize( 2.0 * texture2D( tex, uv ).rgb - vec3(1.0) );

norm = norm.x * t + norm.y * b + norm.z * n;


The gotcha here is that like the vertex normal, the tangent and bitangent vectors are usually interpolated across the fragment shader, and unless they are the same across all 3 vertices, they will be skewed.

I tend to interpolate only two and extract the third during the re-orthonormalization process.

Share this post


Link to post
Share on other sites

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