OpenGL:为什么我的相机上下颠倒?

OpenGL: Why is my camera upside down and backwards?

我的相机在透视模式下颠倒和向后渲染(例如,应该在相机前面的物体在它后面),在正交模式下它是颠倒的,即使物体在相机后面也会渲染。旋转的X轴和Y轴好像也颠倒了

这是我如何构建矩阵的快速版本:

Matrix model = gameObject->transform->GetMatrix();
Matrix view = camera->transform->GetMatrix();
view.Invert();

Matrix projection;
projection.setOrtho(-aspectRatio * ortographicSize, aspectRatio * ortographicSize, -1 * ortographicSize, 1 * ortographicSize, clipMin, clipMax);
// or
projection.SetPerspective(60, aspectRatio, clipMin, clipMax);

Matrix mvp = model * view * projection;

你可以找到我的 Matrix class on Github。投影分别在SetPerspective和SetOrtho方法中设置,但问题也可能出在视图矩阵上,它使用了Invert方法。

我正在查看您的 Matrix::SetOrtho()Matrix::SetProjection() 函数,并将它们与 GLM 的版本进行比较。

你的函数:

void Matrix::SetOrtho( float left, float right, 
                       float top, float bottom, 
                       float clipMin, float clipMax ) { 
    memset(data, 0, sizeof(float) * 16); 
    data[0] = 2 / (right - left); 
    data[5] = 2 / (top - bottom); 
    data[10] = -2 / (clipMax - clipMin); 
    data[15] = 1; 
} 

void Matrix::SetPerspective( float angle, float aspect, 
                             float clipMin, float clipMax ) { 
    float tangent = WolfMath::Tan(WolfMath::DegToRad(angle / 2)); 
    memset(data, 0, sizeof(float) * 16); 
    data[0] = 0.5f / tangent; 
    data[5] = 0.5f * aspect / tangent; 
    //data[10] = -(clipMax + clipMin) / (clipMax - clipMin); 
    data[11] = -1; 
    data[14] = (-2 * clipMax * clipMin) / (clipMax - clipMin); 
} 

GLM 的 - GLM

// GLM::ortho
template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> ortho( T left, T right,
                                               T bottom, T top,
                                               T zNear, T zFar ) {
#if GLM_COORDINATE_SYSTEM == GLM_LEFT_HANDED
    return orthoLH(left, right, bottom, top, zNear, zFar);
#else
    return orthoRH(left, right, bottom, top, zNear, zFar);
#endif
}

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> orthoLH ( T left, T right,
                                                  T bottom, T top,
                                                  T zNear, T zFar ) {
    tmat4x4<T, defaultp> Result(1);
    Result[0][0] = static_cast<T>(2) / (right - left);
    Result[1][1] = static_cast<T>(2) / (top - bottom);
    Result[3][0] = - (right + left) / (right - left);
    Result[3][1] = - (top + bottom) / (top - bottom);

#if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE
    Result[2][2] = static_cast<T>(1) / (zFar - zNear);
    Result[3][2] = - zNear / (zFar - zNear);
#else
    Result[2][2] = static_cast<T>(2) / (zFar - zNear);
    Result[3][2] = - (zFar + zNear) / (zFar - zNear);
#endif
    return Result;
}

template <typename T> 
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> orthoRH( T left, T right,
                                                 T bottom, T top,
                                                 T zNear, T zFar ) {
    tmat4x4<T, defaultp> Result(1);
    Result[0][0] = static_cast<T>(2) / (right - left);
    Result[1][1] = static_cast<T>(2) / (top - bottom);
    Result[3][0] = - (right + left) / (right - left);
    Result[3][1] = - (top + bottom) / (top - bottom);

#if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE
    Result[2][2] = - static_cast<T>(1) / (zFar - zNear);
    Result[3][2] = - zNear / (zFar - zNear);
#else
    Result[2][2] = - static_cast<T>(2) / (zFar - zNear);
    Result[3][2] = - (zFar + zNear) / (zFar - zNear);
#endif
    return Result;
}

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> ortho( T left, T right,
                                               T bottom, T top ) {
    tmat4x4<T, defaultp> Result(static_cast<T>(1));
    Result[0][0] = static_cast<T>(2) / (right - left);
    Result[1][1] = static_cast<T>(2) / (top - bottom);
    Result[2][2] = - static_cast<T>(1);
    Result[3][0] = - (right + left) / (right - left);
    Result[3][1] = - (top + bottom) / (top - bottom);
    return Result;
}

// GLM::perspective (This is a little more involved 
// due to the frustum & fov components)
template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> frustum( T left, T right,
                                                 T bottom, T top,
                                                 T nearVal, T farVal ) {
#if GLM_COORDINATE_SYSTEM == GLM_LEFT_HANDED
    return frustumLH(left, right, bottom, top, nearVal, farVal);
#else
    return frustumRH(left, right, bottom, top, nearVal, farVal);
#endif
}

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> frustumLH( T left, T right,
                                                   T bottom, T top,
                                                   T nearVal, T farVal ) {
    tmat4x4<T, defaultp> Result(0);
    Result[0][0] = (static_cast<T>(2) * nearVal) / (right - left);
    Result[1][1] = (static_cast<T>(2) * nearVal) / (top - bottom);
    Result[2][0] = (right + left) / (right - left);
    Result[2][1] = (top + bottom) / (top - bottom);
    Result[2][3] = static_cast<T>(1);

#if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE
    Result[2][2] = farVal / (farVal - nearVal);
    Result[3][2] = -(farVal * nearVal) / (farVal - nearVal);
#else
    Result[2][2] = (farVal + nearVal) / (farVal - nearVal);
    Result[3][2] = - (static_cast<T>(2) * farVal * nearVal) / (farVal - nearVal);
#endif
    return Result;
}

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> frustumRH( T left, T right,
                                                   T bottom, T top,
                                                   T nearVal, T farVal ) {
    tmat4x4<T, defaultp> Result(0);
    Result[0][0] = (static_cast<T>(2) * nearVal) / (right - left);
    Result[1][1] = (static_cast<T>(2) * nearVal) / (top - bottom);
    Result[2][0] = (right + left) / (right - left);
    Result[2][1] = (top + bottom) / (top - bottom);
    Result[2][3] = static_cast<T>(-1);

#if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE
    Result[2][2] = farVal / (nearVal - farVal);
Result[3][2] = -(farVal * nearVal) / (farVal - nearVal);
#else
    Result[2][2] = - (farVal + nearVal) / (farVal - nearVal);
Result[3][2] = - (static_cast<T>(2) * farVal * nearVal) / (farVal - nearVal);
#endif
    return Result;
}

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> perspective( T fovy, T aspect, 
                                                     T zNear, T zFar ) {
#if GLM_COORDINATE_SYSTEM == GLM_LEFT_HANDED
    return perspectiveLH(fovy, aspect, zNear, zFar);
#else
    return perspectiveRH(fovy, aspect, zNear, zFar);
#endif
}

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> perspectiveRH( T fovy, T aspect, 
                                                       T zNear, T zFar ) {
    assert(abs(aspect - std::numeric_limits<T>::epsilon()) > static_cast<T>(0));
    T const tanHalfFovy = tan(fovy / static_cast<T>(2));
    tmat4x4<T, defaultp> Result(static_cast<T>(0));
    Result[0][0] = static_cast<T>(1) / (aspect * tanHalfFovy);
    Result[1][1] = static_cast<T>(1) / (tanHalfFovy);
    Result[2][3] = - static_cast<T>(1);

#if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE
    Result[2][2] = zFar / (zNear - zFar);
    Result[3][2] = -(zFar * zNear) / (zFar - zNear);
#else
    Result[2][2] = - (zFar + zNear) / (zFar - zNear);
    Result[3][2] = - (static_cast<T>(2) * zFar * zNear) / (zFar - zNear);
#endif
    return Result;
}

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> perspectiveLH( T fovy, T aspect,
                                                       T zNear, T zFar ) {
    assert(abs(aspect - std::numeric_limits<T>::epsilon()) > static_cast<T>(0));
    T const tanHalfFovy = tan(fovy / static_cast<T>(2));
    tmat4x4<T, defaultp> Result(static_cast<T>(0));
    Result[0][0] = static_cast<T>(1) / (aspect * tanHalfFovy);
    Result[1][1] = static_cast<T>(1) / (tanHalfFovy);
    Result[2][3] = static_cast<T>(1);

#if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE
    Result[2][2] = zFar / (zFar - zNear);
    Result[3][2] = -(zFar * zNear) / (zFar - zNear);
#else
    Result[2][2] = (zFar + zNear) / (zFar - zNear);
    Result[3][2] = - (static_cast<T>(2) * zFar * zNear) / (zFar - zNear);
#endif
    return Result;
}

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> perspectiveFov( T fov, T width, T height, 
                                                        T zNear, T zFar ) {
#if GLM_COORDINATE_SYSTEM == GLM_LEFT_HANDED
    return perspectiveFovLH(fov, width, height, zNear, zFar);
#else
    return perspectiveFovRH(fov, width, height, zNear, zFar);
#endif
}

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> perspectiveFovRH( T fov, T width, T height, 
                                                          T zNear, T zFar ) {
    assert(width > static_cast<T>(0));
    assert(height > static_cast<T>(0));
    assert(fov > static_cast<T>(0));

    T const rad = fov;
    T const h = glm::cos(static_cast<T>(0.5) * rad) / glm::sin(static_cast<T>(0.5) * rad);
    T const w = h * height / width; ///todo max(width , Height) / min(width , Height)?

    tmat4x4<T, defaultp> Result(static_cast<T>(0));
    Result[0][0] = w;
    Result[1][1] = h;
    Result[2][3] = - static_cast<T>(1);

#if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE
    Result[2][2] = zFar / (zNear - zFar);
    Result[3][2] = -(zFar * zNear) / (zFar - zNear);
#else
    Result[2][2] = - (zFar + zNear) / (zFar - zNear);
    Result[3][2] = - (static_cast<T>(2) * zFar * zNear) / (zFar - zNear);
#endif
    return Result;
}

template <typename T>
GLM_FUNC_QUALIFIER tmat4x4<T, defaultp> perspectiveFovLH( T fov, T width, T height, 
                                                          T zNear, T zFar ) {
    assert(width > static_cast<T>(0));
    assert(height > static_cast<T>(0));
    assert(fov > static_cast<T>(0));

    T const rad = fov;
    T const h = glm::cos(static_cast<T>(0.5) * rad) / glm::sin(static_cast<T>(0.5) * rad);
    T const w = h * height / width; ///todo max(width , Height) / min(width , Height)?

    tmat4x4<T, defaultp> Result(static_cast<T>(0));
    Result[0][0] = w;
    Result[1][1] = h;
    Result[2][3] = static_cast<T>(1);

#if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE
    Result[2][2] = zFar / (zFar - zNear);
    Result[3][2] = -(zFar * zNear) / (zFar - zNear);
#else
    Result[2][2] = (zFar + zNear) / (zFar - zNear);
    Result[3][2] = - (static_cast<T>(2) * zFar * zNear) / (zFar - zNear);
#endif
    return Result;
}

在我分解你的函数和 GLM 之间的矩阵比较之前,link 显示索引 float[16] 和 float[4][4] 之间的区别。

webstaff: Matrix indexing, C++ and OpenGL

主要区别在于您使用的是 float[16],而 GLM 使用的是 float[4][4],因此索引不同但结果应该相同:

在你的 Ortho 中,你似乎只在对角线上设置值,而且你似乎只使用一种惯用手,但我不确定你使用的是哪一种:是左手吗或相对湿度?使用一维数组时,您正在设置 4x4 矩阵的索引:

// I'll be using (l = left, r = right, t = top, b = bottom, f = zFar, n = zNear)
// f = (clipMax), n = (clipMin)

| 0, 4,  8, 12 |    | (2/(r-l)),         4,          8,  12  | 
| 1, 5,  9, 13 |  = |         1, (2/(t-b)),          9,  13  |
| 2, 6, 10, 14 |    |         2,         6, (-2/(f-n)),  14  |
| 3, 7, 11, 15 |    |         3,         7,          1,  (1) |

GLM 在使用浮动 [4][4] 方案的地方使用手坐标系进行分支,但他们还根据 #if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE 标志做出另一个分支决策,让我们看看他们的矩阵。

// I'll be using (l = left, r = right, t = top, b = bottom, f = zFar, n = zNear)

// Ortho LH  : IF DEPTH_CLIP_SPACE == ZERO_TO_ONE
| 00, 01, 02, 03 |     |      (2/(r-l)),             01,           02, 03 |
| 10, 11, 12, 13 |  =  |             10,      (2/(t-b)),           12, 13 |
| 20, 21, 22, 23 |     |             20,             21,    (1/(f-n)), 23 |
| 30, 31, 32, 33 |     | (-(r+l)/(r-l)), (-(t+b)/(t-b)), (-(n/(f-n))), 33 |

// Ortho LH  : ELSE 
| 00, 01, 02, 03 |     |      (2/(r-l)),             01,             02, 03 |
| 10, 11, 12, 13 |  =  |             10,      (2/(t-b)),             12, 13 |
| 20, 21, 22, 23 |     |             20,             21,      (2/(f-n)), 23 |
| 30, 31, 32, 33 |     | (-(r+l)/(r-1)), (-(t+b)/(t-b)), (-(f+n)/(f-n)), 33 |


// Ortho RH  : IF DEPTH_CLIP_SPACE == ZERO_TO_ONE    
| 00, 01, 02, 03 |     |      (2/(r-l)),             01,           02, 03 |
| 10, 11, 12, 13 |  =  |             10,      (2/(t-b)),           12, 13 |
| 20, 21, 22, 23 |     |             20,             21,  (-(1/(f-n)), 23 |
| 30, 31, 32, 33 |     | (-(r+l)/(r-l)), (-(t+b)/(t-b)), (-(n/(f-n))), 33 |

// Ortho RH : ELSE
| 00, 01, 02, 03 |     |       (2/r-l)),             01,             02, 03 |
| 10, 11, 12, 13 |  =  |              10,     (2/(t-b)),             12, 13 |
| 20, 21, 22, 23 |     |              20,            21,    (-(2/(f-n)), 23 |
| 30, 31, 32, 33 |     | (-(r+l)/(r-l)), (-(t+b)/(t-b)), (-(f+n)/(f-n)), 33 |

// However they do have a basic Orhto function that doesn't consider 
// the handedness nor the clip space and you can see their matrix here:

// Ortho 
| 00, 01, 02, 03 |     |       (2/r-l)),            01,   02, 03 |
| 10, 11, 12, 13 |  =  |             10,     (2/(t-b)),   12, 13 |
| 20, 21, 22, 23 |     |             20,            21,  (1), 23 |
| 30, 31, 32, 33 |     | (-(r+l)/(r-l)), (-(t+b)/(t-b)),  32, 33 |

注意: - 我不是 100% 确定,但我确实认为 OrthoLH 和 OrthoRH 是围绕 3D 设计,而普通 Ortho 是围绕 2D 设计的,因为它没有考虑透视划分以及深度缓冲区或 z-buffer.


从上面的矩阵你可以看到我做了什么,现在你可以比较它们;您可以对您的版本和 GLM 之间的视角和视图执行完全相同的方法。我不会在这里做它们,因为这已经是一个很长的答案了,所以我会把它们留给你作为练习。您必须考虑您使用的是哪种惯用手、clip-space、平截头体、fov 以及所使用的角度类型(度数或弧度)。

在确保您的矩阵正确之后,这还没有结束。当您开始应用仿射变换(平移、缩放和旋转)或(倾斜)时,完成的顺序很重要,并且顺序将在坐标系之间发生变化。当您将顶点信息从一个矩阵转移到下一个矩阵时,您还必须考虑在内;从模型到世界到剪辑到视图(屏幕 - 相机)space;特别是在 3D 设置中工作时,由于透视划分,其中 2D 有点不同,因为它们不涉及 z-component 或深度缓冲区。其他需要注意的事情是顶点的缠绕顺序以及是否打开或关闭背面剔除以及混合(透明胶片)。我希望这可以帮助您找到您的错误。

编辑: - 我在其中一个 GLM 正交矩阵中确实有错误。它位于元素 [3][2]#if GLM_DEPTH_CLIP_SPACE == GLM_DEPTH_ZERO_TO_ONE 中的 orthoRH(),而不是 #else 版本。我最终得到 (-(f+n)/(f-n)) 这是错误的。现在已使用适当的表达式 (-(n/(f-n)))

更正和更新