又一篇学习笔记,参考Mathematics for 3D Game Programming and Computer Graphics和ShaderX4上一篇关于tangent space计算的文章写的东西。对于计算时需要分裂顶点的内容看的还不是太清楚-_-b。另外,目前的算法还不能完美处理镜像或者在纹理不连续处可能出现的问题,就算在Farcry中,很多问题也是通过美工来“隐藏”的,再一次应证了之前对美工重要性的结论^^。
算法:
Tangent space在Bump Map中有着重要作用,通常需要把灯光转换到tangent space进行计算。对由参数方程计算出的规则曲面(比如,球体,圆环)来说,很容易通过方程计算出tangent space,但对任意的三角形网格来说,则没有那么简单。
Tangent space是一个三维空间。对3D空间中的一个顶点来说,切空间的三条座标轴分别对应该点的法线N,切线T,和副法线(binormal)B,显然,对不同的顶点来说,切空间是不同的。那么在已知三角形三个顶点及其纹理坐标的时候,如何计算出N,T,B呢?
目前已知的数据有三角形的三个顶点在世界坐标中的位置: P0, P1,P2, 以及相应的纹理坐标在纹理空间中的位置C0 (U0,V0),C1,C2,则有:
P10 = P1 – P0,
P20 = P2 - P1 ,
C10 = C1 – C0 = (U1-U0, V1-V0) = ( U10 ,V10)
C20 = C2 – C0.= (U2-U0, V2-V0) = ( U20 ,V20)
注意,P10在世界坐标中的方向和C10在纹理空间中的方向是一致的(这一点确实比较抽象,偶画图研究了好久才弄明白-_-),同样,P20和C20也是如此,发现这一点很重要,可以说是整个计算的基石。进一步来说,T,B分别和纹理坐标轴U,V是平行的。因此我们有:
P10 = U10T + V10B
P20 = U20T + V20B
把矢量展开得到:
两边乘以[C10 C20]的逆矩阵,最后得到
法线N = T x B
这样我们就得到了坐标从切空间转变到世界坐标下的变换矩阵M = [ T B N ],当然,更加常用的是M的逆矩阵。注意,这里计算得出的只是面法线,如果需要计算每个顶点的法线,则应该对共享该顶点的多个面的法线取均值,求出结果。
实现:
ogre calculate tangent:
Vector3 Math::calculateTangentSpaceVector(
const Vector3& position1, const Vector3& position2, const Vector3& position3,
Real u1, Real v1, Real u2, Real v2, Real u3, Real v3)
{
//side0 is the vector along one side of the triangle of vertices passed in,
//and side1 is the vector along another side. Taking the cross product of these returns the normal.
Vector3 side0 = position1 - position2;
Vector3 side1 = position3 - position1;
//Calculate face normal
Vector3 normal = side1.crossProduct(side0);
normal.normalise();
//Now we use a formula to calculate the tangent.
Real deltaV0 = v1 - v2;
Real deltaV1 = v3 - v1;
Vector3 tangent = deltaV1 * side0 - deltaV0 * side1;
tangent.normalise();
//Calculate binormal
Real deltaU0 = u1 - u2;
Real deltaU1 = u3 - u1;
Vector3 binormal = deltaU1 * side0 - deltaU0 * side1;
binormal.normalise();
//Now, we take the cross product of the tangents to get a vector which
//should point in the same direction as our normal calculated above.
//If it points in the opposite direction (the dot product between the normals is less than zero),
//then we need to reverse the s and t tangents.
//This is because the triangle has been mirrored when going from tangent space to object space.
//reverse tangents if necessary
Vector3 tangentCross = tangent.crossProduct(binormal);
if (tangentCross.dotProduct(normal) < 0.0f)
{
tangent = -tangent;
binormal = -binormal;
}
return tangent;
}