Normal Mapping

You tell your friend you want a cake for your birthday. This is mistake, because your friend is a 3D artist. What you get is an OBJ file with a 100 million vertices. If your renderer and graphics card could handle such a massive model, you'd see the most intricate cake, with carefully chiseled contours of frosting and spiral paths winding up the candles. Alas, there are just too many vertices.

An artist who cared about interactive frame rates would have given you a low-poly model and moved the detail into textures. Earlier you saw how color is moved into textures. But normals can be too, and also the shininess terms for specular lighting.

Consider this albedo texture of the diamond plate pattern that is often found on steel floors, ramps, and running boards on trucks:

Metal albedo map
Image courtesy of Josip Kladaric, CC-BY 3.0.

The albedo map alone may be enough to convince the viewer that the floor is made of steel. But the shading reveals that the floor is just a perfectly flat quadrilateral. The indentations in the pattern can be made to behave like real, physical indentations if the albedo map is accompanied by a normal map:

Metal normal map
Image courtesy of Josip Kladaric, CC-BY 3.0.

On a very flat surface like a floor, the fragment will grab its normal from the texture and not from the vertex shader. On more complex surfaces, the normal from the normal map will be added to the incoming normal from the model itself, resulting in slight perturbations of the model's surface. Such bump mapping is often used to put wrinkles, scales, pores, and other irregularities on skin.

Normal maps encode 3D vectors in the red, green, and blue channels of the texture. When you perform a texture lookup, you get back a vector whose components are in [0, 1]. Normals can have negative components, but colors can't. How then can normals be stored in textures? Before being written into the normal map, the normal components are scaled and offset from [-1, 1] to [0, 1]:

$$ \mathrm{normal'} = (\mathrm{normal} + 1) \times 0.5 $$

When you read the normal from the texture, you must undo this scaling and offset to get back the original:

$$ \mathrm{normal} = \mathrm{normal'} \times 2 - 1 $$

The normal is then used to compute the diffuse and specular terms. Additionally, the shininess used to compute the specular term may be read from a specular map like this one:

Metal specular map
Image courtesy of Josip Kladaric, CC-BY 3.0.

This renderer uses all three maps to apply Blinn-Phong illumination to a quadrilateral:

Use the controls to switch between unshaded albedo, diffuse-only shading using the albedo and normals, and full diffuse and specular shading that uses albedo, normals, and shininess. With all three maps in use, there's a fair amount of grittiness between just four vertices.