The following is a brief explanation of how the tangent and binormal vectors are calculated for polygonal mesh geometry in Maya.
Inputs: For each vertex of a triangle we pass in: v (vertex position, 3-float), n (normal position, 3-float) t (tex coord value, 2 float) : v[3][3], n[3][3] t[3][2];
Outputs: For each vertex we compute and return a tangent vector (3-float) : tangentArray[3][3];
float $edge1[3]; float $edge2[3]; float $crossP[3]; float $tangentArray[3][3]; //============================================== // x, s, t // S & T vectors get used several times in this vector, // but are only computed once. //============================================== $edge1[0] = $v[1][0] - $v[0][0]; $edge1[1] = $t[1][0] - $t[0][0]; // s-vector - don't need to compute this multiple times $edge1[2] = $t[1][1] - $t[0][1]; // t-vector $edge2[0] = $v[2][0] - $v[0][0]; $edge2[1] = $t[2][0] - $t[0][0]; // another s-vector $edge2[2] = $t[2][1] - $t[0][1]; // another t-vector $crossP = crossProduct( $edge1, $edge2 ) ; normalize( $crossP ); bool $degnerateUVTangentPlane = equivalent( $crossP[0], 0.0f ); if (degnerateUVTangentPlane) $crossP[0] = 1.0f; float $tanX = -$crossP[1]/$crossP[0]; $tangentArray[0][0] = $tanX; $tangentArray[1][0] = $tanX; $tangentArray[2][0] = $tanX; //-------------------------------------------------------- // y, s, t //-------------------------------------------------------- $edge1[0] = $v[1][1] - $v[0][1]; $edge2[0] = $v[2][1] - $v[0][1]; $edge2[1] = $t[2][0] - $t[0][0]; $edge2[2] = $t[2][1] - $t[0][1]; $crossP = crossProduct( $edge1, $edge2 ); normalize( $crossP ); degnerateUVTangentPlane = equivalent( $crossP[0], 0.0f ); if (degnerateUVTangentPlane) $crossP[0] = 1.0f; float $tanY = -$crossP[1]/$crossP[0]; $tangentArray[0][1] = $tanY; $tangentArray[1][1] = $tanY; $tangentArray[2][1] = $tanY; //------------------------------------------------------ // z, s, t //------------------------------------------------------ $edge1[0] = $v[1][2] - $v[0][2]; $edge2[0] = $v[2][2] - $v[0][2]; $edge2[1] = $t[2][0] - $t[0][0]; $edge2[2] = $t[2][1] - $t[0][1]; $crossP = crossProduct( $edge1 , $edge2 ); normalize( $crossP ); degnerateUVTangentPlane = equivalent( $crossP[0], 0.0f ); if (degnerateUVTangentPlane) $crossP[0] = 1.0f; float $tanZ = -$crossP[1]/$crossP[0]; $tangentArray[0][2] = $tanZ; $tangentArray[1][2] = $tanZ; $tangentArray[2][2] = $tanZ; // Orthnonormalize to normal for( int $i = 0; $i < 3; $i++) { $tangentArray[i] -= $n[i] * $tangentArray[i].dot( n[i]); } // Normalize tangents normalize( $tangentArray[0] ); normalize( $tangentArray[1] ); normalize( $tangentArray[2] ); return $tangentArray;
The tangent computation is based on the texture coordinates (uv) of a given texture coordinate set (uvset) used. The normals used are the geometric normals which consider smoothing and user normals.
For each triangle on a face the tangents for each vertex of the triangle are computed. If the triangle is mapped (that is, has uv's) and those uvs are non-degenerate then the computation in Per triangle tangent computation) is performed.
Degenerate uv's means having a 0 length vector in uv space along any edge of the triangle. If the triangle is either not mapped or degenerate, the geometric edges are used to compute the tangent. For each vertex (i) of the triangle the following is computed:
tangent[i] = vertex[i+1 % 3] - vertex[i]; tangent[i] = tangent[i] - normal[i] * dotProduct( tangent[i], normal[i]); normalize( tangent[i] );
For mapped face, for each uvId on a vertex we keep of list of shared tangents if and only if the associated normalId for that vertex is the same.
The uvId is the offset into the uv set data array, the normalId is the offset into the normal data array. The tangent data array will match the size of uvId data array. Each list is normalized to compute the final shared tangent vector.
Binormals are computed as the normalized cross product of the tangent and normal vectors at a given vertex on a face. Binormals are computed and cached on demand.