13 Feb 2019, 15:55 UTC
A normal map contains RGB values representing normals (Nx,Ny,Nz).
Tangent space is defined as T = dP/du, B = dP/dv, N = dP/dd where u,v are the texture coordinates, P is position in world space, and d is distance from the surface. By definition, N.T = 0 and N.B = 0, but T.B may not be zero.
This definition of tangent space gives:
dP = du.T + dv.B + dd.N
Where du,dv are changes in U,V coordinates, dd is change in depth, and dP is change in worldspace position.
So one might think we should transform a normal from a normal map as follows:
N' = Nx.T + Ny.B + Nz.N
However, this does not necessarily have the desired effect. If T and B are skewed, normals are not skewed in the expected way, *if* by expected we mean corresponding to a warping of an underlying heightmap which the normals merely represent.
By that definition, the normal is actually the cross product of two heightfield vectors:
N = (c,0,-a) ^ (0,d,-b) = (ad,bc,cd)
Here, -a and -b represent the change in height across a texel, and c and d are normalizing factors.
These two vectors can be transformed by the TBN matrix as follows.
N' = (c.T - a.N)^(d.B - b.N)
= cd.T^B + ad.B^N + bc.N^T + ab.N^N
= cd.T^B + ad.B^N + bc.N^T
In terms of the original normal this gives us:
N' = Nx.B^N + Ny.N^T + Nz.T^B
Note that N and T^B can be recovered from B^N and N^T, so we only have to store these two vectors. Then we can use some vector identities to solve for N and T^B as follows:
A^(B^C) = (A.C)B - (A.B)C
(A^B).(C^D) = (A.C)(B.D) - (B.C)(A.D)
(A^B).C = (B^C).A = (C^A).B
Using these we can show some results:
N^(T^B) = (N.B)T - (N.T)B = 0
Since the tangent plane is perpendicular to the normal, the normal is parallel to T^B. In particular:
T^B = sN
Now we can show that the cross product of the two vectors we will store is exactly T^B:
(B^N)^(N^T) = ((B^N).T)N - ((B^N).N)T
= ((B^N).T)N
= ((T^B).N)N
= (sN.N)N
= sN
= T^V
Note that we have never used the result that |N| = 1. In fact |N| can represent a bump scale: |N| -> 2|N| makes the bumps twice as high. If |T| and |B| are greater than one, |N| staying at one means the bumps stay the same height as the image stretches - so the embossing is shallower. |N| keeping pace with |T| and |B| means the bumps scale up with the stretching, so the overall normals are unchanged. Thus overall,