原文地址:http://www.terathon.com/code/tangent.html
为一个任意网格模型计算其切线空间的基本向量(即切线空间的T B N三个向量)
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.
现在的bump mapping(或者normal mapping)需要每个顶点的切面的基本向量。这篇文章描述了逐顶点计算任意三角模型的切线空间原理,并且提供了实现这个数学理论的源代码。
Mathematical Derivation 数学来源
[This derivation also appears in Mathematics for 3D Game Programming and Computer Graphics, 2nd ed., Section 6.8.]
We want our tangent space to be aligned such that the x axis corresponds to the u direction in the bump map and the y axis corresponds to the v direction in the bump map. That is, if Q represents a point inside the triangle, we would like to be able to write
我们想我们的切线空间像这样对齐,比如; X轴相当于bump图中的U方向,而Y轴相当于bump图中的V方向。于是,如果Q表示三角形上的一点,则我们可以得出如下等式
Q − P0 = (u − u0)T + (v − v0)B,
where T and B are tangent vectors aligned to the texture map, P0 is the position of one of the vertices of the triangle, and (u0, v0) are the texture coordinates at that vertex. The letter B stands for bitangent, but in many places it is stilled called binormal because of a mix-up in terms when tangent-space bump mapping first became widespread. (See “Bitangent versus Binormal” below.)
等式中的T是与纹理对应的切线向量,P0 是三角形的其中一个顶点。(u0, v0) 是对应顶点的纹理坐标。 B表示副(双)切线(bitangent),但是由于在切线空间的bump mapping第一次被传播开来的时候,有人混淆这个定义,所以在许多地方它始终被称为副法线(binormal )。(参见下面的Bitangent VS Binormal)
Suppose that we have a triangle whose vertex positions are given by the points P0, P1, and P2, and whose corresponding texture coordinates are given by (u0, v0), (u1, v1), and (u2, v2). Our calculations can be made much simpler by working relative to the vertex P0, so we let
假设三角形的三个顶点分别为P0, P1, and P2, 其分别对应的纹理坐标为(u0, v0), (u1, v1), and (u2, v2). 。那我们的计算就可以简化下为下面的方程式组
Q1 = P1 − P0
Q2 = P2 − P0
设
(s1, t1) = (u1 − u0, v1 − v0)
(s2, t2) = (u2 − u0, v2 − v0).
We need to solve the following equations for T and B.
于是,我们最终要解决的就是下面这个方程式
Q1 = s1T + t1B
Q2 = s2T + t2B
This is a linear system with six unknowns (three for each T and B) and six equations (the x, y, and z components of the two vector equations). We can write this in matrix form as follows.
这个有着6个未知数的线性方程组(T,B是向量,每个有三个量)。我们可以把它写成如下的矩阵方式
Multiplying both sides by the inverse of the (s, t) matrix, we have
两边都乘以(s,t)的逆矩阵
This gives us the (unnormalized) T and B tangent vectors for the triangle whose vertices are P0, P1, and P2. To find the tangent vectors for a single vertex, we average the tangents for all triangles sharing that vertex in a manner similar to the way in which vertex normals are commonly calculated. In the case that neighboring triangles have discontinuous texture mapping, vertices along the border are generally already duplicated since they have different mapping coordinates anyway. We do not average tangents from such triangles because the result would not accurately represent the orientation of the bump map for either triangle.
由此,我们解出等式后,就得到了未单位化的T和B。 为了找到单个顶点的切线向量,我们平均共享这个顶点的所有切三角形的的切线。法线也可以通过类似的方式计算出来。 而在这种情况下,相邻的两个三角形则会形成不连续的纹理映射,处于边缘的顶点由于有不同的纹理坐标而经常被复制。我们没有平均这些三角形的切线,因为我们的计算结果就可以不精确地描述每个三角形的切线朝向。
Once we have the normal vector N and the tangent vectors T and B for a vertex, we can transform from tangent space into object space using the matrix
一旦我们得到了一个顶点的法向量N和切线T和B。我们就可以用它们来构造一个由正切空间到对象空间的矩阵。
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′ and B′ are given by
为了向反方向变换(从对象空间变换到正切空间,[法线贴图时]我们需这样处理光照方向),我们可以简单地使用这个矩阵的逆矩阵。 但我们要注意切线向量并非总是与其它两个向量,或法向量垂直的。于是,我们的逆矩阵并非总是等于其转置矩阵。但是,可以安全地认为,这三个向量是非常接近正交关系的。 所以使用我们可以使用Gram-Schmidt算法来对其进行正交化,就不会出现不可接受的误差了。
T′ = T − (N · T)N
B′ = 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 matrixto 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.
单位化这些向量并将其存为一个顶点的tangent 和bitangent 。
于是,我们可以用下面这个矩阵将光方向从对象空间转换到切线空间。然后将转换后的光照方向和bump图中的采样值点乘,然后再处理某些矫正因子就可以完成光照的计算。
It is not necessary to store an extra array containing the per-vertex bitangent since the cross product N × T′ can be used to obtain mB′, where m = ±1 represents the handedness of the tangent space. The handedness value must be stored per-vertex since the bitangent B′ obtained from N × T′ may point in the wrong direction. The value of m is equal to the determinant of the matrix in Equation (*). One may find it convenient to store the per-vertex tangent vector T′ as a four-dimensional entity whose w coordinate holds the value of m. Then the bitangent B′ can be computed using the formula
我们也不必要存储bitangent这个值。因为N × T′可以得出我们的mB′,这里的m = ±1,表示切空间的左右手坐标系习惯。表示左右手坐标系习惯的这个值必须逐顶点存储。因为bitangent B′ 是由N × T′计算得来,就可能指向错误的方向。m的值必须要和(*)中的决定保持一致。 所以我们可以用T′的w分量来存储m值。最后B′的值就可以向下面一样计算
B′ = T’w·(N × T′),
where the cross product ignores the w coordinate. This works nicely for vertex programs by avoiding the need to specify an additional array containing the per-vertex m values.
向量的叉乘忽略了w分量,因此不会造成任何影响。这样就可以很好地写顶点程序,而不用另外用空间来存储m值。
Bitangent versus Binormal
The term binormal is commonly used as the name of the second tangent direction (that is perpendicular to the surface normal and u-aligned tangent direction). This is a misnomer. The term binormal pops up in the study of curves and completes what is known as a Frenet frame about a particular point on a curve. Curves have a single tangent direction and two orthogonal normal directions, hence the terms normal and binormal. When discussing a coordinate frame at a point on a surface, there is one normal direction and two tangent directions, which should be called the tangent and bitangent.
(上面讲的就是binormal 和bitangent的区别)。
Source Code
The code below generates a four-component tangent T in which the handedness of the local coordinate system is stored as ±1 in the w-coordinate. The bitangent vector B is then given by B = (N × T) · Tw.
下面的代码产生了一个4维的T,其第4个分量用于存储左右手坐标系习惯。B 是由 B = (N × T) · Tw.计算而来。
#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 article
Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001. http://www.terathon.com/code/tangent.html