Time for Action: Normal Mapping in Action

Let's cover an example showcasing normal mapping in action:

  1. Open the ch10_03_normal-map.html file in a browser:

  1. Rotate the cube to see the effect that the normal map has on the lit cube. Keep in mind that the profile of the cube has not changed. Let's examine how this effect is achieved.
  2. First, we need to add a new attribute to our vertex buffers. There are three vectors needed to calculate the tangent space coordinates for lighting: the normal, the tangent, and the bitangent:

  1. We have already covered normals, so let's investigate the other two vectors. The tangent represents the up (positive y) vector for the texture relative to the polygon surface. The bitangent represents the left (positive x) vector for the texture relative to the polygon surface.
  2. We only need to provide two of the three vectors as vertex attributes. Traditionally, the normal and tangent suffice, as the third vector is calculated as the cross-product of the other two in the vertex shader.
  3. It is common for 3D modeling packages to generate tangents for you. However, if they aren't provided, they can be calculated from the vertex positions and texture coordinates, similar to calculating vertex normals:
Tangent Generation Algorithm
We won't cover this algorithm here, but for reference, it has been implemented in common/js/utils.js as calculateTangents and used in scene.add.
const tangentBufferObject = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, tangentBufferObject);

gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(utils.calculateTangents(
object.vertices,
object.textureCoords,
object.indices
)),
gl.STATIC_DRAW
);
  1. In the vertex shader, at the top of ch10_03_normal-map.html, the tangent needs to be transformed by the Normal matrix. The two transformed vectors can be used to calculate the third:
// Transformed normal position
vec3 normal = vec3(uNormalMatrix * vec4(aVertexNormal, 1.0));
vec3 tangent = vec3(uNormalMatrix * vec4(aVertexTangent, 1.0));
vec3 bitangent = cross(normal, tangent);
  1. 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
);
  1. Unlike before, where we applied lighting in the vertex shader, the bulk of the lighting calculations needs to happen in the fragment shader so that we can incorporate normals from the texture. That being said, we do transform the light direction into tangent space in the vertex shader before passing it to the fragment shader as a varying:
// Light direction, from light position to vertex
vec3 lightDirection = uLightPosition - vertex.xyz;

vTangentEyeDir = eyeDirection * tbnMatrix;
  1. In the fragment shader, we start by extracting the tangent space normal from the normal map texture. Since texture texels don't store negative values, the normal components must be encoded to map from a [-1, 1] to a [0, 1] range. Therefore, they must be unpacked into the correct range before being used in the shader. The algorithm to perform this operation can be easily expressed in ESSL:
// Unpack tangent-space normal from texture
vec3 normal = normalize(2.0 * (texture(uNormalSampler, vTextureCoords).rgb - 0.5));

  1. Lighting is calculated nearly the same as the vertex-lit model, which is done by 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);

// Calculate Specular level
vec3 eyeDirection = normalize(vTangentEyeDir);
vec3 reflectDir = reflect(-lightDirection, normal);
float Is = pow(clamp(dot(reflectDir, eyeDirection), 0.0, 1.0), 8.0);

// Combine lighting and material colors
vec4 Ia = uLightAmbient * uMaterialAmbient;
vec4 Id = uLightDiffuse * uMaterialDiffuse * texture(uSampler, vTextureCoords) * lambertTerm;

fragColor = Ia + Id + Is;
  1. To help accentuate the normal mapping effect, the code sample also includes the calculation of a specular term.

What just happened?

We've seen how we can use normal information that's been 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
3.144.84.155