透视投影和视图矩阵:在 OpenGL 中,深度缓冲区和三角面方向都颠倒了

Perspective projection and view matrix: Both depth buffer and triangle face orientation are reversed in OpenGL

我在 OpenGL 场景中遇到问题。应该更远的物体被拉得更近等并且前面的三角形被剔除而不是背面的三角形。它们以正确的方向绘制,因为它是我以前使用过的包。我确信这与我的投影或 veiwModel 矩阵有关。不过我看不出这些有什么问题!

AV4X4FLOAT formProjMatrix(float FOVangle,float aspect,float nearz,float farz)
{
    AV4X4FLOAT A;

    A.m[0] = 1/(aspect*tanf(FOVangle/2));
    A.m[5] = 1/tanf(FOVangle/2);
    A.m[10] = farz/(farz-nearz);
    A.m[11] = -nearz*farz/(farz-nearz);
    A.m[14] = 1;
    return A;
}

AV4X4FLOAT formViewModelMatrix(AV4FLOAT pos,AV4FLOAT target,AV4FLOAT up)
{ 
    AV4X4FLOAT M;
    AV4X4FLOAT R;
    AV4FLOAT u;
    AV4FLOAT v;
    AV4FLOAT W;

    W.x = -pos.x + target.x;
    W.y = -pos.y + target.y;
    W.z = -pos.z + target.z;

    W.w = 0;
    W.normalize();

    u.x = up.y*W.z-W.y*up.z;
    u.y = -up.x*W.z+W.x*up.z;
    u.z = up.x*W.y-W.x*up.y;
    u.w = 0;
    u.normalize();

    v.x = W.y*u.z-u.y*W.z;
    v.y = -W.x*u.z+u.x*W.z;
    v.z = W.x*u.y-u.x*W.y;
    v.w = 0;

    M.m[0]  = u.x;  M.m[1]  = u.y;  M.m[2]  = u.z;  M.m[3]  = 0;
    M.m[4]  = v.x;  M.m[5]  = v.y;  M.m[6]  = v.z;  M.m[7]  = 0;
    M.m[8]  = -W.x; M.m[9]  = -W.y; M.m[10] = -W.z; M.m[11] = 0;
    M.m[12] = 0;    M.m[13] = 0;    M.m[14] = 0;    M.m[15] = 1;

    R.m[0] = 1;
    R.m[5] = 1;
    R.m[10] = 1;
    R.m[15] = 1;
    R.m[12] = -pos.x;
    R.m[13] = -pos.y;
    R.m[14] = -pos.z;

//the opposite of what you expect because of the way we overload mult operator!
    M.display ();
    R.display ();
    return M*R;
}

我画画的时候就是这么叫的

glMatrixMode(GL_PROJECTION);
glLoadMatrixf(projMatrix.m);

glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(viewModelMatrix.m);

一些其他信息,

是的,我启用了深度测试!

投影矩阵的计算存在一些问题。您必须像这样调整您的代码:

AV4X4FLOAT formProjMatrix(float FOVangle,float aspect,float nearz,float farz)
{
    AV4X4FLOAT A;

    A.m[0]  = 1.0 / (aspect*tanf(FOVangle/2));
    A.m[5]  = 1.0 / tanf(FOVangle/2);
    A.m[10] =  (nearz+farz)/(farz-nearz);
    A.m[11] = - 2.0 * nearz*farz/(farz-nearz);
    A.m[14] = - 1.0;
    return A;
}

透视投影矩阵如下所示:

r = right, l = left, b = bottom, t = top, n = near, f = far

2*n/(r-l)      0              0               0
0              2*n/(t-b)      0               0
(r+l)/(r-l)    (t+b)/(t-b)    -(f+n)/(f-n)   -1    
0              0              -2*f*n/(f-n)    0

如下:

aspect = w / h
tanFov = tan( fov_y * 0.5 );

p[0][0] = 2*n/(r-l) = 1.0 / (tanFov * aspect)
p[1][1] = 2*n/(t-b) = 1.0 / tanFov

以下函数将计算与 gluPerspectiveglm::perspective 相同的投影矩阵:

#include <array>

const float cPI = 3.14159265f;
float ToRad( float deg ) { return deg * cPI / 180.0f; }

using TVec4  = std::array< float, 4 >;
using TMat44 = std::array< TVec4, 4 >;

TMat44 Perspective( float fov_y, float aspect )
{
    float fn = far + near
    float f_n = far - near;
    float r = aspect;
    float t = 1.0f / tan( ToRad( fov_y ) / 2.0f );

    return TMat44{ 
        TVec4{ t / r, 0.0f,  0.0f,                 0.0f },
        TVec4{ 0.0f,  t,     0.0f,                 0.0f },
        TVec4{ 0.0f,  0.0f, -fn / f_n,            -1.0f },
        TVec4{ 0.0f,  0.0f, -2.0f*far*near / f_n,  0.0f }
    };
}


在视口上,X 轴指向左侧,Y 轴指向上方,Z 轴指向视图外(请注意,在右手系统中,Z 轴是 X 轴和 Y 轴的叉积轴)。

以下代码与 gluLookAtglm::lookAt 的作用相同:

using TVec3  = std::array< float, 3 >;
using TVec4  = std::array< float, 4 >;
using TMat44 = std::array< TVec4, 4 >;

TVec3 Cross( TVec3 a, TVec3 b ) { return { a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] }; }
float Dot( TVec3 a, TVec3 b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
void Normalize( TVec3 & v )
{
    float len = sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
    v[0] /= len; v[1] /= len; v[2] /= len;
}

TMat44 Camera::LookAt( const TVec3 &pos, const TVec3 &target, const TVec3 &up )
{ 
    TVec3 mz = { pos[0] - target[0], pos[1] - target[1], pos[2] - target[2] };
    Normalize( mz );
    TVec3 my = { up[0], up[1], up[2] };
    TVec3 mx = Cross( my, mz );
    Normalize( mx );
    my = Cross( mz, mx );

    TMat44 v{
        TVec4{ mx[0], my[0], mz[0], 0.0f },
        TVec4{ mx[1], my[1], mz[1], 0.0f },
        TVec4{ mx[2], my[2], mz[2], 0.0f },
        TVec4{ Dot(mx, pos), Dot(my, pos), -Dot(mz, pos), 1.0f }
    };

    return v;
}

像这样调整您的代码:

AV4X4FLOAT formViewModelMatrix(AV4FLOAT pos,AV4FLOAT target,AV4FLOAT up)
{ 
    AV4FLOAT mz;
    mz.x = pos.x - target.x; mz.y = pos.y - target.y; mz.z = pos.z - target.z; mz.w = 1.0f;
    mz.normalize();

    AV4FLOAT my;
    my.x = up.x; my.y = up.y; my.z = up.z; my.w = 1.0f;

    AV4FLOAT mx;
    mx.x = my.y*mz.z - my.z*mz.y; mx.y = my.z*mz.x - my.x*mz.z; mx.z = my.x*mz.y - my.y*mz.x; mx.w = 1.0f;
    mx.normylize();

    my.x = mz.y*mx.z - mz.z*mx.y; my.y = mz.z*mx.x - mz.x*mx.z; my.z = mz.x*mx.y - mz.y*mx.x; my.w = 1.0f;

    AV4FLOAT t;
    t.x = mx.x*pos.x + mx.y*pos.y + mx.z*pos.z; 
    t.y = my.x*pos.x + my.y*pos.y + my.z*pos.z; 
    t.z = -(mz.x*pos.x + mz.y*pos.y + mz.z*pos.z); 

    AV4X4FLOAT m;
    m[0]  = mx.x;  m[1]  = my.x;  m[2]  = mz.x;  m[3]  = 0.0f;
    m[4]  = mx.y;  m[5]  = my.y;  m[6]  = mz.y;  m[7]  = 0.0f;
    m[8]  = mx.z;  m[9]  = my.z;  m[10] = mz.z;  m[11] = 0.0f;
    m[12] = t.x;   m[13] = t.y;   m[14] = t.z;   m[15] = 1.0f;

    return m
}


进一步查看以下问题的答案:

  • How to render depth linearly in modern OpenGL with gl_FragCoord.z in fragment shader?
  • How to recover view space position given view space depth value and ndc xy