Understanding normal maps

Tangent space Normal maps

Normal maps are used to store normal vectors. These vectors are encoded by colors in a specific way.

Normal maps use three channels (3 bytes) to encode this information. We know that a normal vector represents a face normal. The vector is not used to represent a magnitude, it represents only a direction. So the normal vector has one unit length : (|N| = 1)
Direction itself is three dimentional – x, y and z.
Normal maps store these normal components in specific channels – red, green and blue.
The red channel represents the ‘x’ component,the green channel represents the ‘y’ component and the blue channel represents the ‘z’ component. We know that unit directions can be inversed, so we need to encode the negative information too. This is the reason why
the [-1,1] range must be encoded into [0,1] color range.
(Keep in mind that some applications encode the blue channel without the negative part: [0-255] will go in [0-1] range).

A color value of 0.5 should represent a null component.
A color value of 0.0 should represent -1 component.
A color value of 1.0 should represent +1 component.

We see the points (0.5 , 0) , (0 , -1) , (1 , 1) lay on the same straight line: y = k*x + b

0.5 = k*0 + b   , b = 0.5
0 = k *(-1) + b  , k = b = 0.5

y = k*x + b = x*0.5 + 0.5 = (x+1)/2

color = (component + 1)/2


red   = (Nx+1)/2;
green = (Ny+1)/2;
blue  = (Nz+1)/2;

There are several things you should know about tangent normal maps.
These normal vectors are encoded in texture(TBN) space. In TBN space the normal that points to us has parameters x=0 , y=0, z=1 , it means the stored RGB color is (0.5 , 0.5 , 1) – this is actually a bluish color – our predominant color for tangent space normal maps.
Normal maps don't contain color values below 0.5 in the blue channel because these would be pointing behind the surface, therefore we don’t need to encode (and store) this information.
To covert from a color to a vector, the reverse operation is:
component = color*2 - 1

This is our diffuse map:

Let us create the normal map:

As you can see on the upper left stone, the right edge is red colored, because the normal vector is almost parallel to the tangent direction. The bottom edge if the same stone is green coloured, the normal vector is pointing almost parallel with the binormal(bitangent) direction.

To use this map in OpenGL we need to invert the green channel.

Normal maps usually are used to fake a high resolution mesh on what is actually a low resolution mesh. The color values in the normal map control what direction each of the pixels on the low-poly model would be facing, controlling how much lighting each pixel would receive, and thus creating the illusion of more surface details.

Ok, what about the rainbow coloured normal maps ?

There are several types of normal maps.
Object space normal maps encode normal vectors in object space – it means the UP direction is straight up. These maps are not bluish maps,they have rainbow colors.
World space normal maps are somehow useless, because our model is still not transformed to world space, so that in this sense of understanding – world space and object space are actually the same space at this point.

What are the advantages of object space normal maps ?

They render fast – because we don’t have to worry about TBN information, we go round entirely TBN recalculation.

The disadvantages of these maps are many:

  1. each mesh needs an unique normal map;
  2. objects should not perform vertex deformation (animation or shader deformation), because our data is stored in object space and we don’t have the TBN recalculation – we would get wrong lighting results;
  3. difficult to tile;
  4. harder to create bump specific details;
  5. no mirroring – at least this would require a specific shader;