Friday, December 24, 2010

Normal mapping

Normal mapping is a very simple technique, that has been widely used in games, and is still extensively used, because of it's aptitude to increase lighting realism tremendously, and because it's very cheap in terms or computing requirements.

The technique consist of creating a second texture for a given mesh, the normal map, that contains a normal vector at every texel (stored in R,G and B channels), and to do the lighting computations with those normals. This appear to increase the geometrical complexity, but in fact it simply trick the diffuse and specular lighting terms.

Here is a screenshot of my implementation:



The algorithm is simple. We transform the lightPos and the viewDir into tangent-space in the Vertex shader with the TBN matrix. If you don't know what is the TBN matrix, you can check out my post about it here. After that, we do standard lighting computation, but instead of using the interpolated vertex normal, we use the normal at the texel corresponding to the current pixel, provided by our normal map. If you're not sure to understand how standard lighting computations are done, you can check out my post about it here.

The complete GLSL code:


[Vertex_Shader]
varying vec3 lightPos;
varying vec3 viewDir;
attribute vec4 glTangent4f; //Provided by the engine
void main(void)
{
   gl_Position = ftransform();
   gl_TexCoord[0] = gl_MultiTexCoord0;

   //Get the normal and the tangent, and compute the binormal
   vec3 n = normalize(gl_NormalMatrix * gl_Normal);
   vec3 t = normalize(gl_NormalMatrix * glTangent4f.xyz);
   vec3 b = cross(n, t);

   vec3 v;
   vec3 vVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
   vec3 lVec = gl_LightSource[0].position.xyz - vVertex;

   //Transform lightPos in tangent-space
   v.x = dot(lVec, t);
   v.y = dot(lVec, b);
   v.z = dot(lVec, n);
   lightPos = v;

   vec3 vVec = -vVertex;
   v.x = dot(vVec, t);
   v.y = dot(vVec, b);
   v.z = dot(vVec, n);
   viewDir = v;
}

[Pixel_Shader]
varying vec3 lightPos; //Interpolated by pixel
varying vec3 viewDir;  //Idem
uniform sampler2D colorMap;
uniform sampler2D normalMap;

void main (void)
{
   //Get the texel of the diffuse texture
   vec4 base = texture2D(colorMap, vec2(-gl_TexCoord[0].s, -gl_TexCoord[0].t));
   vec4 final_color = vec4(0.20.20.21.0) * base;

   //If you don't normalize there will be some lighting inconsistencies
   vec3 lVec = normalize(lightPos);
   vec3 vVec = normalize(viewDir);

   //Scale the normal from [-1, 1] to [0, 1]
   vec3 bump = normalize(texture2D(normalMap, vec2(-gl_TexCoord[0].s,   
      -gl_TexCoord[0].t)).xyz * 2.0 - 1.0);

   float diffuse = maxdot(lVec, bump), 0.0 );

   vec4 vDiffuse = gl_FrontLightProduct[0].diffuse * diffuse * base;   
   final_color += vDiffuse;

   float specular = pow(clamp(dot(reflect(-vVec, bump), lVec), 0.01.0), 
      gl_FrontMaterial.shininess);

   vec4 vSpecular = gl_FrontLightProduct[0].specular * specular * diffuse;  
   final_color += vSpecular;

   gl_FragColor = final_color;
}

1 comment:

  1. please, explain, how to bypass values to:
    varying vec3 lightPos;
    varying vec3 viewDir;
    ?
    thank you.

    ReplyDelete