透视投影和视图矩阵:深度缓冲区和三角形面方向在 OpenGL 中反转

2021-12-19 00:00:00 opengl c++ perspectivecamera

我在 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);上午[14] = 1;返回A;}

AV4X4FLOAT formViewModelMatrix(AV4FLOAT pos,AV4FLOAT target,AV4FLOAT up){AV4X4FLOAT M;AV4X4FLOAT R;AV4FLOAT你;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;//由于我们重载mult运算符的方式,与您期望的相反!M.display();R.display();返回 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);上午[14] = - 1.0;返回A;}

透视投影矩阵如下所示:

r = 右,l = 左,b = 底部,t = 顶部,n = 近,f = 远2*n/(r-l) 0 0 00 2*n/(t-b) 0 0(r+l)/(r-l) (t+b)/(t-b) -(f+n)/(f-n) -10 0 -2*f*n/(f-n) 0

如下:

aspect = w/htanFov = 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 

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

使用 TVec3 = std::array<浮动,3>;使用 TVec4 = std::array<浮动,4>;使用 TMat44 = std::array;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] };}浮动点(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] - 目标[0], pos[1] - 目标[1], pos[2] - 目标[2] };归一化( mz );TVec3 my = { up[0], up[1], up[2] };TVec3 mx = Cross(my, mz);归一化(mx);我的 = 交叉(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{ 点(mx, pos), 点(my, pos), -Dot(mz, pos), 1.0f }};返回 v;}

像这样调整你的代码:

AV4X4FLOAT formViewModelMatrix(AV4FLOAT pos,AV4FLOAT target,AV4FLOAT up){AV4FLOAT mz;mz.x = pos.x - 目标.x;mz.y = pos.y - target.y;mz.z = pos.z - target.z;mz.w = 1.0f;mz.normalize();AV4FLOAT 我的;my.x = up.x;my.y = up.y;my.z = up.z;我的.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;我的.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[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;米[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;返回米}


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

  • 如何在现代 OpenGL 中使用片段着色器中的 gl_FragCoord.z 线性渲染深度?
  • 如何在给定视图空间深度值和 ndc xy 的情况下恢复视图空间位置
  • 变换模型矩阵
  • 自定义视图矩阵的拉伸问题

I am having trouble with my scene in OpenGL. Objects that are supposed to be further away are drawn closer etc AND front facing triangles are being culled instead of back facing ones. They are drawn in the correct orientation as it is a package I have used before. I am convinced that it is something to do with my projection or veiwModel matrix. I can not see anything wrong with these though!

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;
}

This is what I call in my drawing routine.

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

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

Some otherinfo,

Yes, I have enabled depth testing!

解决方案

There are some issues with in the calculation of the projection matrix. You have to adapt your code like this:

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;
}

The Perspective Projection Matrix looks like this:

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

it follows:

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

The following function will calculate the same projection matrix as gluPerspective or glm::perspective does:

#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 }
    };
}


On the viewport the X-axis points to the left, the Y-axis up and the Z-axis out of the view (Note in a right hand system the Z-Axis is the cross product of the X-Axis and the Y-Axis).

The following code does the same as gluLookAt or glm::lookAt does:

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;
}

Adapt your code like this:

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
}


See further the answers to the following question:

  • 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
  • Transform the modelMatrix
  • Stretching Issue with Custom View Matrix

相关文章