[譯]為任意網格計算tangent空間的基向量
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.
現代凹凸映射(也被稱為法線映射)要求為網格中的每個頂點計算出其tangent平面基向量。本文描述了對任意三角形網格計算逐頂點tangent空間的數學理論,還提供了實現數學計算的源代碼。
Mathematical Derivation 數學推導
[This derivation also appears in Mathematics for 3D Game Programming and Computer Graphics, 3rd ed., Section 7.8.]
[這一推導也出現在Mathematics for 3D Game Programming and Computer Graphics, 3rd ed.,章節7.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
我們想讓我們的tangent空間這樣布局:其x軸對應凹凸貼圖的u方向,y軸對應凹凸貼圖的v方向。(譯者注:這話的意思是,想象一下,將正方形貼圖拿起來,將其(0, 0)位置固定到頂點上,此時,其u方向就是我們要找的tangent空間的x軸,其v方向就是我們要找的tangent空間的y軸,三角形面的法線方向就是我們要找的tangent空間的z軸。顯然,這樣的x軸y軸對,有無數個,我們需要的,是其中唯一的一個。是哪個呢?繼續看原文。。。)換句話說,設Q代表三角形內部的一個點,我們希望有
where P0 is the position of one of the vertices of the triangle, and (u0, v0) are the texture coordinates at that vertex. The vectors T and B are the tangent and bitangent vectors aligned to the texture map, and these are what we’d like to calculate.
其中P0是三角形的某個頂點,(u0, v0)是此頂點的紋理坐標。向量T和B分別是tangent 和bitangent 向量,它們分別與紋理貼圖的軸平行,是我們想求得的未知數。(譯者注:這個公式的意思是,當Q位於P0P1的中點時,u就位於u0u1的中點,等等,當Q位於P0P1P2內部的某點時,通過縮放紋理,會讓(u, v)位於同貼圖上同樣的位置上。頂點P0P1P2的位置構成的三角形A,與頂點P0P1P2的紋理坐標構成的三角形B,兩者上的點,即Q和(u, v),建立了一個線性的對應關系。能夠使這2個三角形A和B的這樣線性關系成立的那個T和B,就是我們需要的唯一的結果。)
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和P2給定,其對應的紋理坐標由(u0, v0)、(u1, v1)和(u2, v2)給定。通過相對於P0(進行歸零),我們的計算可以簡化很多,所以我們讓
and
且
(譯者注:這其實就是讓P0和(u0, v0)歸零,將三角形A和B都放到原點上。由於A和B是線性關系,所以這樣的平移不會影響其線性關系。)
We need to solve the following equations for T and B.
我們需要求解下述等式中的T和B。
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各3個)和6個等式(2個向量等式的x、y和z分量)。我們可以將它改寫為下述矩陣形式。
Multiplying both sides by the inverse of the (s, t) matrix, we have
兩邊同時乘以(s, t)矩陣的逆矩陣(譯者注:然后將等號左右互換位置),我們有
This gives us the (unnormalized) T and B 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.
這給出了頂點為P0、P1和P2的三角形的(譯者注:即face的)(未標准化的)T和B向量。為了找到頂點的tangent向量,我們對所有共享此頂點的三角形的tangent取平均值,類似於計算頂點的法線那樣的方式。當相鄰的三角形的紋理映射不連續時,其共享邊上的頂點一般都已經被復制了一份,因為它們的紋理坐標畢竟是不同的。(譯者注:想象一個人頭部模型的三維網格及其貼圖,貼圖上的后腦勺部分肯定是被分開的,鼻子部分肯定是挨着的,所以網格上描述后腦勺的頂點是被復制了一份,是可以“掰開”的。還是不能想象的話,上網找找模型及其貼圖,或者看看電影《畫皮》。)我們不把這樣的三角形計入平均化過程,因為其結果無法准確地代表(兩個三角形中任意一個三角形的)凹凸貼圖的朝向。(譯者注:由於后腦勺的頂點被復制了一份,就不再共享邊了,所以也不會計入平均化過程。)
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和tangent向量T和B,我們就可以實施從tangent空間到object空間的轉換了(用矩陣的形式)
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
為了向反方向變換(從object空間到tangent空間——我們想對光照方向這樣做),我們直接使用此矩陣的逆矩陣即可。2個tangent向量未必是互相垂直的,它們也未必與法線向量垂直,所以此矩陣的逆矩陣未必等於其轉置矩陣。(譯者注:這是個數學定理吧,由3個互相垂直的向量構成的矩陣,其逆矩陣等於其轉置矩陣。書到用時方恨少,有空學時最貪玩。)但可以安全地假設,這3個向量是接近互相垂直的,所以使用Gram-Schmidt算法來正交化它們,應該不會引起任何不可接受的失真。使用這個方法,新的(仍舊是未標准化的)tangent向量T′和B′由下述公式給出
Normalizing these vectors and storing them as the tangent and bitangent for a vertex lets us use the matrix
標准化這些向量,將其保存為頂點的tangent和bitangent,這樣我們就可以使用矩陣
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.
來將光線方向從object空間變換到tangent空間。對(變換后的光照方向)和(來自凹凸貼圖的采樣值)使用點積,就會產生正確的Lambertian漫反射光照值。
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 (*). You might 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,表示tangent空間的慣用手(譯者注:左手系or右手系)。慣用手值必須逐頂點地保存,因為用N × T′得到的bitangent B′可能指向錯誤的(譯者注:即相反的)方向。m的值等於等式(*)中的矩陣的行列式(譯者注:行列式其實就是一個數值)。你可能發現了,用四維向量保存逐頂點的tangent向量T′,其w分量保存m,比較方便。這樣,bitangent B′就可以用下述公式計算
where the cross product ignores the w coordinate. This works nicely for vertex shaders by avoiding the need to specify an additional array containing the per-vertex m values.
其中的叉積忽略了w分量。這樣就可以在頂點着色器中,避免指定一個額外的用於保存逐頂點的m值的數組,棒棒噠。
Bitangent versus Binormal 副切線 versus 副法線
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 通常用作第二個切線方向的名字(即垂直於表面法線和u方向的切線)。這是個誤稱。名詞binormal出現在對曲線(curves )的研究中,它是Frenet坐標系的一部分,用於描述曲線上的點。曲線有一個切線方向和2個垂直的法線方向,因此有了名詞normal和binormal。當討論一個表面(surface)上一點處的坐標系時,有1個法線和2個切線方向,所以應當被稱為tangent和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.
下述代碼計算了四維tangent T,其tangent坐標系的慣用手(其值為±1)保存在w分量。之后,bitangent向量B可以通過B = (N × T) · Tw給出。

1 #include "Vector4D.h" 2 3 4 struct Triangle 5 { 6 unsigned short index[3]; 7 }; 8 9 10 void CalculateTangentArray(long vertexCount, const Point3D *vertex, const Vector3D *normal, 11 const Point2D *texcoord, long triangleCount, const Triangle *triangle, Vector4D *tangent) 12 { 13 Vector3D *tan1 = new Vector3D[vertexCount * 2]; 14 Vector3D *tan2 = tan1 + vertexCount; 15 ZeroMemory(tan1, vertexCount * sizeof(Vector3D) * 2); 16 17 for (long a = 0; a < triangleCount; a++) 18 { 19 long i1 = triangle->index[0]; 20 long i2 = triangle->index[1]; 21 long i3 = triangle->index[2]; 22 23 const Point3D& v1 = vertex[i1]; 24 const Point3D& v2 = vertex[i2]; 25 const Point3D& v3 = vertex[i3]; 26 27 const Point2D& w1 = texcoord[i1]; 28 const Point2D& w2 = texcoord[i2]; 29 const Point2D& w3 = texcoord[i3]; 30 31 float x1 = v2.x - v1.x; 32 float x2 = v3.x - v1.x; 33 float y1 = v2.y - v1.y; 34 float y2 = v3.y - v1.y; 35 float z1 = v2.z - v1.z; 36 float z2 = v3.z - v1.z; 37 38 float s1 = w2.x - w1.x; 39 float s2 = w3.x - w1.x; 40 float t1 = w2.y - w1.y; 41 float t2 = w3.y - w1.y; 42 43 float r = 1.0F / (s1 * t2 - s2 * t1); 44 Vector3D sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, 45 (t2 * z1 - t1 * z2) * r); 46 Vector3D tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, 47 (s1 * z2 - s2 * z1) * r); 48 49 tan1[i1] += sdir; 50 tan1[i2] += sdir; 51 tan1[i3] += sdir; 52 53 tan2[i1] += tdir; 54 tan2[i2] += tdir; 55 tan2[i3] += tdir; 56 57 triangle++; 58 } 59 60 for (long a = 0; a < vertexCount; a++) 61 { 62 const Vector3D& n = normal[a]; 63 const Vector3D& t = tan1[a]; 64 65 // Gram-Schmidt orthogonalize 66 tangent[a] = (t - n * Dot(n, t)).Normalize(); 67 68 // Calculate handedness 69 tangent[a].w = (Dot(Cross(n, t), tan2[a]) < 0.0F) ? -1.0F : 1.0F; 70 } 71 72 delete[] tan1; 73 }
How to cite this article 如何引用本文
Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software, 2001. http://terathon.com/code/tangent.html
Copyright © 2001–2017, Terathon Software LLC