The tangent space normal map stores in each component the distance along each of these vectors. For example, one component of the normal map (usually stored in the blue part) points directly away from the mesh triangle, ie. along the triangle normal. This allows the same normal map to be reused on different parts of a mesh.
void ComputeTangentBasis( const Vec3& P1, const Vec3& P2, const Vec3& P3, const Vec2& UV1, const Vec2& UV2, const Vec2& UV3, Vec3 &tangent, Vec3 &bitangent ) { Vec3 Edge1 = P2 - P1; Vec3 Edge2 = P3 - P1; Vec2 Edge1uv = UV2 - UV1; Vec2 Edge2uv = UV3 - UV1; float cp = Edge1uv.y * Edge2uv.x - Edge1uv.x * Edge2uv.y; if ( cp != 0.0f ) { float mul = 1.0f / cp; tangent = (Edge1 * -Edge2uv.y + Edge2 * Edge1uv.y) * mul; bitangent = (Edge1 * -Edge2uv.x + Edge2 * Edge1uv.x) * mul; tangent.Normalize(); bitangent.Normalize(); } }Since I'm normalizing anyway, the only thing 1.0f/cp can do is flip the vectors depending on the winding of the triangle UV coordinates, so these steps could be redundant. You will have to normalize the vectors eventually, but it could be done after the smoothing step described in the next section.
While the divide by cp isn't important for making a rotation matrix of unit vectors, it does scale the tangent and bitangent to be UV unit length, so it can be important if you compute the world space position from uv coordinates when creating normal maps.
Note that the 3 vectors (normal, tangent, bitangent) may not form an orthogonal basis since the tangent & bitangent directions are based on texture uv coordinates and possibly averaged from multiple vectors. What you can do is use Gram-Schmidt orthogonalization. This is performed simply by subtracting the part of a vector in the direction of another vector then renormalizing. For example the code to make the tangent orthogonal to the normal is:
tangent -= normal * tangent.dot( normal ); tangent.normalize();Once you have two orthogonal vectors you can use the cross product to compute the 3rd. The orthogonalization step seems to be mainly necessary on complex curved models or places with texture seems. Many texture seems however will cause weird lighting no matter what, so if the tangent/bitangent vectors are too far apart you'll probably be better off leaving a split at the seem and not smoothing them together.
float3x3 objToTangentSpace = float3x3( IN.Tangent, IN.Bitangent, IN.Normal ); OUT.halfVec = mul(objToTangentSpace, halfVec ); OUT.lightVec = mul(objToTangentSpace, lightVec);Now we can dot these vectors with a normal from our tangent space normal map, using the same pixel shader as with object space normal mapping.