Time for action — normal mapping in action

  1. Open the file ch10_NormalMap.html in an HTML5 browser.
    Time for action — normal mapping in action
  2. Rotate the cube to see the effect that the normal map has on how the cube is lit. Also observe how the profile of the cube has not changed. Let's examine how this effect is achieved.
  3. First, we need to add a new attribute to our vertex buffers. There are actually three vectors that are needed to calculate the tangent space coordinates that the lighting is calculated in: the normal, the tangent, and bitangent.
    Time for action — normal mapping in action

    We already know what the normal represents, so let's look at the other two vectors. The tangent essentially represents the up (positive Y) vector for the texture relative to the polygon surface. Likewise, the bitangent represents the left (positive X) vector for the texture relative to the polygon surface.

    We only need to provide two of the three vectors as vertex attributes, traditionally the normal and tangent. The third vector can be calculated as the cross-product of the other two in the vertex shader code.

  4. Many times 3D modeling packages will generate tangents for you, but if they aren't provided, they can be calculated from the vertex positions and texture coordinates, similar to how we can calculate the vertex normals. We won't cover the algorithm here, but it has been implemented in js/webgl/Utils.js as calculateTangents and used in Scene.addObject.
    var tangentBufferObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, tangentBufferObject);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(Utils.calculateTangents(object.vertices, object.texture_coords, object.indices)), gl.STATIC_DRAW);
    
  5. In the vertex shader, seen at the top of ch10_NormalMap.html, the tangent needs to be transformed by the Normal matrix just like the normal does to ensure that it's appropriately oriented relative to the world-space mesh. The two transformed vectors can be used to calculate the third as mentioned earlier.
    vec3 normal = vec3(uNMatrix * vec4(aVertexNormal, 1.0));
    vec3 tangent = vec3(uNMatrix * vec4(aVertexTangent, 1.0));
    vec3 bitangent = cross(normal, tangent);
    

    The three vectors can then be used to create a matrix that transforms vectors into tangent space.

    mat3 tbnMatrix = mat3(
    tangent.x, bitangent.x, normal.x,
    tangent.y, bitangent.y, normal.y,
    tangent.z, bitangent.z, normal.z
    );
    
  6. Instead of applying lighting in the vertex shader, as we did previously, the bulk of the lighting calculations need to happen in the fragment shader here so that they can incorporate the normals from the texture. We do transform the light direction into tangent space in the vertex shader, however, and pass it to the fragment shader as a varying.
    //light direction, from light position to vertex
    vec3 lightDirection = uLightPosition - vertex.xyz;
    vTangentLightDir = lightDirection * tbnMatrix;
    
  7. In the fragment shader, first we extract the tangent space normal from the normal map texture. Since textures texels don't store negative values, the normal components must be encoded to map from the [-1,1] range into the [0,1] range. Therefore, they must be unpacked back into the correct range before use in the shader. Fortunately, the algorithm to do so is simple to express in ESSL:
    vec3 normal = normalize(2.0 * (texture2D(uNormalSampler, vTextureCoord).rgb - 0.5));
    
  8. At this point, lighting is calculated almost identically to the vertex-lit model, using the texture normal and tangent space light direction.
    // Normalize the light direction and determine how much light is hitting this point
    vec3 lightDirection = normalize(vTangentLightDir);
    float lambertTerm = max(dot(normal,lightDirection),0.20);
    // Combine lighting and material colors
    vec4 Ia = uLightAmbient * uMaterialAmbient;
    vec4 Id = uLightDiffuse * uMaterialDiffuse * texture2D(uSampler, vTextureCoord) * lambertTerm;
    gl_FragColor = Ia + Id;
    

    The code sample also includes calculation of a specular term, to help accentuate the normal mapping effect.

What just happened?

We've seen how to use normal information encoded into a texture to add a new level of complexity to our lit models without additional geometry.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.2.15