After adding FBX support the other week, there was yet another reason to finally add support for normal and specular maps. While it’s still not physically-based-rendering but a simple Phong lighting model, it’s still a nice improvement to the overall image quality and helped developing some further multipurpose code.
The first step though was to add support for multiple U/V sets in the renderer backend. The attribute system of the mesh class was handling that for a long time, but adding support to the shaders was a bit more tricky. In the updated code, the renderer uses the material types to analyse the individual maps and references the correct texture coordinate set. For example, in one of the COLLADA test models there was actually no texcoord set 1 but a texcoord set 2 associated with the diffuse map. This now works perfectly fine.
Implementing the specular map was pretty easy. Instead of using one fixed specular color for the whole material, a texture lookup is used in the fragment shader and as a result the metal parts in the shield model are shiny while the darkened parts are dull. Getting the normal map to work was a bit more tricky. For one, one needs to calculate the tangent space (normal, tangent and binormal) to be able to map the normal encoded in the texture to the object’s surface. The required math can easily be found on the internet but what made this a bit more work is that I wanted to support discontinuities in the underlying U/V coordinates. So if for example the U/Vs at the edge of a cube are discontinues in U/V space, the tangent/binormal is not smoothed. But in the middle of a smooth surface, tangent/binormal are smooth for all incident faces.
Once that was complete, what remained was adding the respective code to the shader generator. The math itself is pretty straight forward: use the normal, tangent and binormal as an orthogonal system to map the normal encoded in the normal map texture to an object space vector. Plug that vector in the usual lighting equation and voila. I had done this a decade ago but it’s always astonishing to see how a normal map can turn a low poly surface (the shield model has approx 60 triangles and three 256×256 textures) into a seemingly highly detailed surface.
Things seem to come together quite nicely now. The canopy model below is a single mesh that uses two different material groups, each using a diffuse/normal/spec map combination and the render backend automatically splits it into two separate drawing calls and does all the wiring to the correct texture coordinate sets.