## Computing Tangent Space Basis Vectors for an Arbitrary Mesh (Lengyel’s Method)Modern bump mapping (also known as normal mapping) requires that tangent plane basis vectors be calculated for each vertex in a mesh. This article presents the theory behind the computation of per-vertex tangent spaces for an arbitrary triangle mesh and provides source code that implements the proper mathematics. ## Mathematical Derivation[This derivation also appears in
We want our tangent space to be aligned such that the Q − P_{0} = (u − u_{0})T + (v − v_{0})B,where Suppose that we have a triangle whose vertex positions are given by the points Q_{1} = P_{1} − P_{0}Q_{2} = P_{2} − P_{0}and (
s_{1}, t_{1}) = (u_{1} − u_{0}, v_{1} − v_{0})( s_{2}, t_{2}) = (u_{2} − u_{0}, v_{2} − v_{0}).We need to solve the following equations for Q_{1} = s_{1}T + t_{1}BQ_{2} = s_{2}T + t_{2}BThis is a linear system with six unknowns (three for each
Multiplying both sides by the inverse of the (
This gives us the (unnormalized) Once we have the normal vector
To transform in the opposite direction (from object space to tangent space—what we want to do to the light direction), we can
simply use the inverse of this matrix. It is not necessarily true that the tangent vectors are perpendicular to each other or to the
normal vector, so the inverse of this matrix is not generally equal to its transpose. It is safe to assume, however, that the three
vectors will at least be close to orthogonal, so using the Gram-Schmidt algorithm to orthogonalize them should not cause any unacceptable
distortions. Using this process, new (still unnormalized) tangent vectors T′ = T − (N · T)NB′ = B − (N · B)N − (T′ · B)T′/T′^{2}Normalizing these vectors and storing them as the tangent and bitangent for a vertex lets us use the matrix
to transform the direction to light from object space into tangent space. Taking the dot product of the transformed light direction with a sample from the bump map then produces the correct Lambertian diffuse lighting value. It is not necessary to store an extra array containing the per-vertex bitangent since the cross product B′ = T′(_{w}N × T′),where the cross product ignores the ## Bitangent versus BinormalThe term ## Source CodeThe code below generates a four-component tangent #include "Vector4D.h" struct Triangle { unsigned short index[3]; }; void CalculateTangentArray(long vertexCount, const Point3D *vertex, const Vector3D *normal, const Point2D *texcoord, long triangleCount, const Triangle *triangle, Vector4D *tangent) { Vector3D *tan1 = new Vector3D[vertexCount * 2]; Vector3D *tan2 = tan1 + vertexCount; ZeroMemory(tan1, vertexCount * sizeof(Vector3D) * 2); for (long a = 0; a < triangleCount; a++) { long i1 = triangle->index[0]; long i2 = triangle->index[1]; long i3 = triangle->index[2]; const Point3D& v1 = vertex[i1]; const Point3D& v2 = vertex[i2]; const Point3D& v3 = vertex[i3]; const Point2D& w1 = texcoord[i1]; const Point2D& w2 = texcoord[i2]; const Point2D& w3 = texcoord[i3]; float x1 = v2.x - v1.x; float x2 = v3.x - v1.x; float y1 = v2.y - v1.y; float y2 = v3.y - v1.y; float z1 = v2.z - v1.z; float z2 = v3.z - v1.z; float s1 = w2.x - w1.x; float s2 = w3.x - w1.x; float t1 = w2.y - w1.y; float t2 = w3.y - w1.y; float r = 1.0F / (s1 * t2 - s2 * t1); Vector3D sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); Vector3D tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); tan1[i1] += sdir; tan1[i2] += sdir; tan1[i3] += sdir; tan2[i1] += tdir; tan2[i2] += tdir; tan2[i3] += tdir; triangle++; } for (long a = 0; a < vertexCount; a++) { const Vector3D& n = normal[a]; const Vector3D& t = tan1[a]; // Gram-Schmidt orthogonalize tangent[a] = (t - n * Dot(n, t)).Normalize(); // Calculate handedness tangent[a].w = (Dot(Cross(n, t), tan2[a]) < 0.0F) ? -1.0F : 1.0F; } delete[] tan1; } ## How to cite this articleLengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001. http://www.terathon.com/code/tangent.html |