First of all, for every 3D vertex there is infinite tangent and bi-tangent vectors. The below image explains why there is an infinite number of tangent spaces for each vertex, the tangent and bitangent can have any direction in the shown plane.
So in order to properly calculate the most useful1 tangent space, we want our tangent space to be aligned such that the x axis (the tangent) corresponds to the u direction in the bump map and the y axis (bitangent) corresponds to the v direction in the bump map, we should already have normal of the vertex which already corresponds to the Z direction in tangent space.
(1) most useful because in the end we want normal vectors to be sampled from the texture
That best be explained with pictures, we want our tangent space to be aligned like (u, v)
shown below.
Source of the image though not strictly related to computer graphics
In computer graphics developers usually use (u,v)
also known as texture coordinates.
We will assume T is the tangent and B is the bitangent, and P0
is our target vertex, that is part of the triangle (P0,P1,P2)
.
First remember what we wanted to do, is to calculate tangent and bitanget that:
- T aligned with u and B aligned with v.
- T and B lays in the plane with the vertex normal (the plane shown in the above image).
The point is we already assumed that T and B lays in the same plane and corresponds to U and V now if we can know their values we can cross product and the third vector to construct a transformation matrix from world to tangent space.
Given that we know that any 2D vector can be written as a linear combination of two independent vectors2 and since we already have the triangle points (edges), shown in the above image. We can write:
E1 = (u1-u0)T + (v1-v0)B
E2 = (u2-u0)T + (v2-v0)B
(2) actually that's is how basis matrix is derived
The above equation can be written in a matrix form,
| E1x E1y E1z | | deltaU1 deltaV1 | * | Tx Ty Tz |
| E2x E2y E2z | = | deltaU2 deltaV2 | | Bx By Bz |
By solving the matrixs equation we can determine T and B values we can construct a transformation matrix.
The full source code in C++
#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;
}
Full source code and derivation can be found here.