3DSMaxSDK 导出顶点tangent、binormal、normal的方法

注:该方法是以CryEngine所使用的方法为基础略微改进而来的。

 先看一下效果图。

最终效果

显示顶点的TBN(T、B、N分别用R、G、B颜色轴显示)

所使用的法线贴图

具体办法如下:

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;

    } 

 

下面两张图是比较有用的 一张是uv图,一张是法线图。

                                 

uv图可用于直观的检查导出的顶点TBN是否正确。检查方法为,看顶点的T轴和B轴是否分别和UV图中U和V的箭头方向一致。如果一致则正确。

法线图可以检查UV镜像和UV旋转的问题。检查方法为,打上灯光看光照效果是否正确,即有无明显接缝。

 

 下面是一些模型的测试截图

有UV镜像的一个面片 最终效果

模型顶点、面以及uv分布情况

顶点TBN(注意T轴和B轴分别与U和V的方向一至)

 

所使用的法线贴图

3DMax中的茶壶 最终效果

茶壶顶点TBN

茶壶盖顶部特写,有uv旋转

posted @ 2011-12-08 10:11  醉翁之家  阅读(3925)  评论(2编辑  收藏