As I’ve been practicing normal mapping recently, I wanted to find out a little more about how normal maps for game assets work on a lower level and how they interact with shaders.
Surface Normal Direction
In a standard rgb normal map, each channel corresponds to a surface normal direction.
R = X Surface Normal, G = Y Surface Normal, B = Z Surface Normal
Tangent space is used to specify coordinates for a poly face. This is mapped in a similar way to a UV coordinate, however the third axis represents the normal of the face, aka the direction that the face “sticks out in”.
X = Tangent (U), Y = Bitangent (V) , Z = Face Normal (N)
R = U, G = V, B = N
The values for these coordinates represent the direction that values increase in across the face.
The world space explicitly states the normals of an object in relation to the world. Regardless of object orientation, world space normals will always face the same direction in world.
Light rays are calculated in world space, whereas normals are calculated in tangent space. So I guess we have a problem here! This is where the tangent bias comes in. This is used to convert from world space to tangent space.
The tangent bias compares incoming light rays against the normal directions in the map, which then determines how each pixel is lit.
There are many different way to calculate tangent bias. It is important to match these between baking and shading applications, in order to get correct results.
Some engines may skip tangent bias calculation, instead opting to convert the tangent space normal from the map into world space within the shader, before comparing against light rays.
When saving a normal map out from a baking or painting application, it is limited to a positive range per channel, e.g 0 – 255. When this is used in a shader, it is parsed as 0-1. However, being directional coords, normals need to use negative values, so we “unpack it”, remapping it into -1 – 1. This gives us access to all channels, including alpha, as well as the normal vector length.
Why Are Normal Maps Blue?
The 0, 0, 1 colour created on bake represents a flat surface in a normal map, with the blue value representing a completely outward facing normal direction. When this is remapped into 0 – 255 to become a texture map, it becomes 128, 128, 255, giving us a purpley blue colour.