1.导出Mesh所有三角面顶点的位置 Point3 pos、纹理坐标 Point2 uv以及对应的索引值。
导出方法略。注意:位置个数numPos和纹理坐标个数numUV通常不相等。索引个数为面数numFaces*3。
2.遍历Mesh的每一个三角面,用三角面的三个pos和三个uv 计算每个三角面的TBN。
计算方法为:设三角形的三个顶点为Point3 pos[3], 其分别对应的纹理坐标为Point2 uv[3]。那么两条边为
Point3 edgeA = pos[1] - pos[0];
Point3 edgeB = pos[2] - pos[0];
计算法线
Point3 nor = FNormalize( edgeA ^ edgeB );
计算
float deltaU1 = uvw[1].x - uvw[0].x;
float deltaU2 = uvw[2].x - uvw[0].x;
float deltaV1 = uvw[1].y - uvw[0].y;
float deltaV2 = uvw[2].y - uvw[0].y;
float div = ( deltaU1 * deltaV2 - deltaU2 * deltaV1 );
根据div的值算tan和bin, 使用三角形面积作为权值。
Point3 tan = Point3( 1.0f, 0.0f, 0.0f );
Point3 bin = Point3( 0.0f, 1.0f, 0.0f );
if( div != 0.0 )
{
float areaW = fabsf( div ); // 三角形面积*2
float a = deltaV2 / div;
float b = -deltaV1 / div;
float c = -deltaU2 / div;
float d = deltaU1 / div;
tag = FNormalize( edgeA*a + edgeB*b ) * areaW;
bin = FNormalize( edgeA*c + edgeB*d ) * areaW;
}
3.遍历Mesh每一个三角面,用三角面的N、三个顶点Pos 以及 光滑组信息,计算每个顶点的法线.
方法为:将共享该顶点的所有三角面法线根据平滑组信息分组相加(可能会有多组),同时为了解决L型问题,需要用三角形中顶点的两条边的夹角做权重乘以面法线。
如果只导出法线,到此就可以结束了。
注意此时的顶点法线数numVerNor很可能已经 > 位置个数numPos了,换句话说已经根据光滑组分裂了顶点。
4.遍历Mesh每一个三角面,计算三角形中每一个顶点的T和B。计算的方法和法线一样。(要根据顶点T、B,以及uv来决定是否需要分裂顶点)
顶点累加结果verTBN.tan和verTBN.bin初始为Point3(0,0,0),verTBN.nor为第3步算出的顶点法线。计算时仍然需要使用夹角作为权重。
注意这步完成后得到的T、B、N的数量是相等的,且>=第3步中的numVerNor。
4.1 如果存在UV镜像,那么分裂顶点
判定是否存在UV镜像的方法是;
面TBN的手向性 bool faceParity = DotProd( faceTBN.tan^faceTBN.bin, faceTBN.nor ) > 0.0f;
顶点TBN的手向性 bool verParity = DotProd( verTBN.tan^verTBN.bin, verTBN.nor ) > 0.0f; 。
如果 faceParity != verParity 那么存在uv镜像,需分裂顶点。
4.2 如果UV旋转角度大于90,那么分裂顶点
判定方法为:
Point3 vrefUV = verTBN.tan + verTBN.bin;
Point3 vRotHalf = vrefUV - faceTBN.nor * DotProd(faceTBN.nor, vrefUV);
如果 DotProd( (faceTBN.tag + faceTBN.bin), vRotHalf ) <= 0.0f 那么 uv旋转角度大于90度,需分裂顶点
5.用思密特正交化处理顶点TBN,并记录TBN的手向性到T.w中。
将U和V分别投影到N所在的平面,然后单位化得到最终的verTBN.tan和verTBN.bin.
通过叉乘verTBN.tan和verTBN.bin计算新的newNor,并单位化。
检查新法线和第4步计算法线之间的夹角,即最终顶点TBN的手向性。
if( DotProd( newNor, verTBN.nor ) < 0.0f )
{
newNor *= -1.0f;
verTBN.tan.w = -1.0f;