You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义Perspective矩阵无深度效果,表现类Orthographic矩阵问题求助

透视矩阵无深度效果问题排查与解决

自己实现透视矩阵后,球体能正常渲染,但完全没深度效果,表现得跟正交投影一样,Z轴没有任何变化。基于glm的perspective函数做了修改,附相机初始化代码,求解决方法和相关参考内容,目标是让球体显示正确的深度。

渲染效果:球体正交化渲染结果

自定义透视矩阵代码

Matrix4 MATRIX::Perspective(float Width, float Height, float ZNear, float ZFar, float fieldOfView) {
    Matrix4 Result = Identity();
    Result[0][0] = (Height / Width) * fieldOfView;
    Result[1][1] = fieldOfView;
    Result[2][2] = -(ZFar + ZNear) / (ZFar - ZNear);
    Result[2][3] = -1.0f;
    Result[3][2] = -(2.0f * ZFar * ZNear) / (ZFar - ZNear);
    return Result;
}

相机初始化代码

Camera::Camera()
    : position(Vec3()), fieldOfView(45.0f), forward(Vec3(0.0f, 0.0f, -1.0f)), up(Vec3(0.0f, 1.0f, 0.0f)),
    right(Vec3()), worldUp(up), nearPlane(2.0f), farPlane(50.0f),
    yaw(45.0f), pitch(45.0f), perspective(Matrix4()) {
    perspective = Perspective(APP_INIT_WINDOW_WIDTH,
        APP_INIT_WINDOW_HEIGHT,
        nearPlane, farPlane, fieldOfView);
    UpdateCameraVector();
}

glm原生perspective函数(原函数未完全生效)

template <typename T>
GLM_FUNC_QUALIFIER detail::tmat4x4<T, defaultp> perspective
(
    T const & fovy,
    T const & aspect,
    T const & zNear,
    T const & zFar
)
{
    assert(aspect != static_cast<T>(0));
    assert(zFar != zNear);

    T const rad = fovy;
    T const tanHalfFovy = tan(rad / static_cast<T>(2));

    detail::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][2] = - (zFar + zNear) / (zFar - zNear);
    Result[2][3] = - static_cast<T>(1);
    Result[3][2] = - (static_cast<T>(2) * zFar * zNear) / (zFar - zNear);
    return Result;
}

问题根源

自定义矩阵和glm的实现有两个致命差异,直接导致透视效果失效:

  1. 视场角处理错误:
    glm会把视场角转成弧度,再计算半视场角的正切值tanHalfFovy,用1/tanHalfFovy作为Y轴缩放因子。而你直接用原始角度值(45.0f)代入,完全不符合透视投影的数学推导——透视缩放必须基于半视场角的正切,不是角度本身。
  2. 宽高比逻辑颠倒:
    glm的aspect是宽/高,X轴缩放因子是1/(aspect * tanHalfFovy);你用的是Height/Width * fieldOfView,不仅没用到正切值,宽高比的分子分母还搞反了。

修正后的透视矩阵代码

Matrix4 MATRIX::Perspective(float Width, float Height, float ZNear, float ZFar, float fieldOfView) {
    Matrix4 Result = Identity();
    // 角度转弧度
    float rad = glm::radians(fieldOfView);
    float tanHalfFovy = tan(rad / 2.0f);
    float aspect = Width / Height;

    Result[0][0] = 1.0f / (aspect * tanHalfFovy);
    Result[1][1] = 1.0f / tanHalfFovy;
    Result[2][2] = -(ZFar + ZNear) / (ZFar - ZNear);
    Result[2][3] = -1.0f;
    Result[3][2] = -(2.0f * ZFar * ZNear) / (ZFar - ZNear);
    // 注意:如果你的Matrix4是行主序,需要调整元素位置,glm默认是列主序
    return Result;
}

额外要检查的点

  • 矩阵存储顺序:确认你的Matrix4是列主序(和glm一致)还是行主序。如果是行主序,矩阵元素的索引需要重新对应,行主序和列主序的透视矩阵结构差异很大。
  • 深度测试状态:确保渲染管线开了深度测试(比如OpenGL里glEnable(GL_DEPTH_TEST)),不然就算矩阵对了,也不会有深度效果。
  • 矩阵乘法顺序:视图矩阵和透视矩阵的乘法顺序要对,通常是投影矩阵 * 视图矩阵 * 模型矩阵,顺序错了也会导致透视失效。

参考内容

  • 《Real-Time Rendering》第4章:详细讲解透视投影矩阵的推导与实现细节
  • OpenGL官方规范文档:透视投影矩阵的标准定义
  • glm源码注释:透视矩阵的设计思路与参数含义

内容的提问来源于stack exchange,提问作者Илина Бокарева

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:13:36