代码之家  ›  专栏  ›  技术社区  ›  Tom Savage

计算三维切线空间

  •  2
  • Tom Savage  · 技术社区  · 15 年前

    为了在GLSL明暗器中使用法线映射,需要知道每个顶点的法向量、切线向量和位切线向量。通过提供自己的预定义变量,renderMonkey可以轻松实现这一点( rm_tangent rm_binormal 为此。我正在尝试将此功能添加到我自己的3D引擎中。显然,可以使用每个顶点的XYZ坐标、UV纹理坐标和法向量计算三角形中每个顶点的切线和双切线。经过一些搜索,我设计了这个函数来计算三角形结构中每个顶点的切线和位范围。

    void CalculateTangentSpace(void) {
        float x1 = m_vertices[1]->m_pos->Get(0) - m_vertices[0]->m_pos->Get(0);
        float x2 = m_vertices[2]->m_pos->Get(0) - m_vertices[0]->m_pos->Get(0);
        float y1 = m_vertices[1]->m_pos->Get(1) - m_vertices[0]->m_pos->Get(1);
        float y2 = m_vertices[2]->m_pos->Get(1) - m_vertices[0]->m_pos->Get(1);
        float z1 = m_vertices[1]->m_pos->Get(2) - m_vertices[0]->m_pos->Get(2);
        float z2 = m_vertices[2]->m_pos->Get(2) - m_vertices[0]->m_pos->Get(2);
    
        float u1 = m_vertices[1]->m_texCoords->Get(0) - m_vertices[0]->m_texCoords->Get(0);
        float u2 = m_vertices[2]->m_texCoords->Get(0) - m_vertices[0]->m_texCoords->Get(0);
        float v1 = m_vertices[1]->m_texCoords->Get(1) - m_vertices[0]->m_texCoords->Get(1);
        float v2 = m_vertices[2]->m_texCoords->Get(1) - m_vertices[0]->m_texCoords->Get(1);
    
        float r = 1.0f/(u1 * v2 - u2 * v1);
    
        Vec3<float> udir((v2 * x1 - v1 * x2) * r, (v2 * y1 - v1 * y2) * r, (v2 * z1 - v1 * z2) * r);
        Vec3<float> vdir((u1 * x2 - u2 * x1) * r, (u1 * y2 - u2 * y1) * r, (u1 * z2 - u2 * z1) * r);
    
        Vec3<float> tangent[3];
        Vec3<float> tempNormal;
    
        tempNormal = *m_vertices[0]->m_normal;
        tangent[0]=(udir-tempNormal*(Vec3Dot(tempNormal, udir)));
        m_vertices[0]->m_tangent=&(tangent[0].Normalize());
        m_vertices[0]->m_bitangent=Vec3Cross(m_vertices[0]->m_normal, m_vertices[0]->m_tangent);
    
        tempNormal = *m_vertices[1]->m_normal;
        tangent[1]=(udir-tempNormal*(Vec3Dot(tempNormal, udir)));
        m_vertices[1]->m_tangent=&(tangent[1].Normalize());
        m_vertices[1]->m_bitangent=Vec3Cross(m_vertices[1]->m_normal, m_vertices[1]->m_tangent);
    
        tempNormal = *m_vertices[2]->m_normal;
        tangent[2]=(udir-tempNormal*(Vec3Dot(tempNormal, udir)));
        m_vertices[2]->m_tangent=&(tangent[2].Normalize());
        m_vertices[2]->m_bitangent=Vec3Cross(m_vertices[2]->m_normal, m_vertices[2]->m_tangent);
    }
    

    当我使用这个函数并将计算出的值发送给我的明暗器时,模型看起来就像在renderMonkey中一样,但是它们以一种非常奇怪的方式闪烁。我将问题追踪到发送OpenGL的切线和位切线。这让我怀疑我的代码有问题。有人能看到任何问题或对其他尝试方法有什么建议吗?

    我还应该指出,上面的代码非常粗糙,我对所发生的事情背后的数学知识了解得很少。

    2 回复  |  直到 15 年前
        1
  •  4
  •   Tom Savage    15 年前

    找到了解决方案。更简单(但仍然有点老土)的代码:

    void CalculateTangentSpace(void) {
        float x1 = m_vertices[1]->m_pos->Get(0) - m_vertices[0]->m_pos->Get(0);
        float y1 = m_vertices[1]->m_pos->Get(1) - m_vertices[0]->m_pos->Get(1);
        float z1 = m_vertices[1]->m_pos->Get(2) - m_vertices[0]->m_pos->Get(2);
    
        float u1 = m_vertices[1]->m_texCoords->Get(0) - m_vertices[0]->m_texCoords->Get(0);
    
        Vec3<float> tangent(x1/u1, y1/u1, z1/u1);
        tangent = tangent.Normalize();
    
        m_vertices[0]->m_tangent = new Vec3<float>(tangent);
        m_vertices[1]->m_tangent = new Vec3<float>(tangent);
        m_vertices[2]->m_tangent = new Vec3<float>(tangent);
    
        m_vertices[0]->m_bitangent=new Vec3<float>(Vec3Cross(m_vertices[0]->m_normal, m_vertices[0]->m_tangent)->Normalize());
        m_vertices[1]->m_bitangent=new Vec3<float>(Vec3Cross(m_vertices[1]->m_normal, m_vertices[1]->m_tangent)->Normalize());
        m_vertices[2]->m_bitangent=new Vec3<float>(Vec3Cross(m_vertices[2]->m_normal, m_vertices[2]->m_tangent)->Normalize());
    }
    
        2
  •  0
  •   crunchdog    15 年前

    对于U1、U2、V1和V2的某些值,在“r”计算中会得到零除,从而导致“r”的行为未知。你应该小心点。弄清楚如果分母为零,R应该是什么,这可以解决你的问题。我对这背后的数学也不太了解。

    如果分母为零,则设置r=0的建议实现:

    #include <cmath>
    ...
    static float PRECISION = 0.000001f;
    ...
    float denominator = (u1 * v2 - u2 * v1);
    float r = 0.f; 
    if(fabs(denominator) > PRECISION) {    
        r = 1.0f/denominator;
    }
    ...