从固定功能管线迁移至GLSL OpenGL:如何修改视图与透视矩阵?
问题分析与解决方案
从固定功能管线切换到现代GLSL时,视图和投影矩阵的适配问题核心在于坐标系定义、矩阵作用方向的差异——固定管线隐式处理了很多细节,而现代GLSL需要你手动对齐矩阵的变换逻辑。结合你提供的代码,我直接指出两个函数的问题并给出修正方案:
1. 视图矩阵_getLookAt的核心问题
你的代码当前生成的是相机的世界变换矩阵(描述相机在世界空间中的位置和朝向),但现代GLSL需要的视图矩阵是它的逆矩阵——视图矩阵的作用是将世界空间的点转换到相机空间(相机位于原点,看向-Z方向的右手坐标系)。
固定管线的gluLookAt直接生成视图矩阵,而你的代码逻辑完全搞反了。修正后的代码如下:
void Matrix4f::_getLookAt(Vector3f& pos, Vector3f& lookat, Vector3f& upAxis) { _up.set(upAxis); _forward.sub(lookat, pos); _forward.normalize(); _right.cross(_up, _forward); _right.normalize(); _up.cross(_forward, _right); _up.normalize(); // 旋转部分:原旋转矩阵的转置(正交矩阵的逆等于转置) m00 = _right.x; m01 = _up.x; m02 = _forward.x; m03 = 0.f; m10 = _right.y; m11 = _up.y; m12 = _forward.y; m13 = 0.f; m20 = _right.z; m21 = _up.z; m22 = _forward.z; m23 = 0.f; // 平移部分:-旋转转置矩阵 * 相机位置向量 m30 = -(_right.x * pos.x + _right.y * pos.y + _right.z * pos.z); m31 = -(_up.x * pos.x + _up.y * pos.y + _up.z * pos.z); m32 = -(_forward.x * pos.x + _forward.y * pos.y + _forward.z * pos.z); m33 = 1.f; }
修正逻辑说明:
- 视图矩阵是相机世界矩阵的逆,对于刚体变换(旋转+平移),旋转部分的逆是原旋转矩阵的转置,平移部分是旋转转置矩阵乘以相机位置的负数。
- 原代码把相机位置直接放在第四列,这是世界矩阵的写法,视图矩阵需要将相机“拉回”原点,所以平移项是反向的。
2. 透视投影矩阵_getPerspectiveByFov的核心问题
你的代码生成的投影矩阵与现代GLSL的NDC(标准化设备坐标)坐标系不匹配:固定管线的glFrustum默认是右手系,相机空间Z轴指向屏幕外,而现代GLSL要求透视除法后Z值落在[-1, 1]范围内,你的原矩阵导致Z轴符号反转,出现镜像。
修正后的代码如下:
void Matrix4f::_getPerspectiveByFov(float fov, float aspectRatio, float nearZ, float farZ, int matrixType) { float halfFov = MathUtil::degToRad(fov) * 0.5f; float width, height; float _tanFOV = (float)tan(halfFov); if(aspectRatio < 1.f) { width = 2.f * nearZ * _tanFOV; height = width / aspectRatio; } else { height = 2.f * nearZ * _tanFOV; width = height * aspectRatio; } float w = 2.f * nearZ / width; // 等价于 1/tan(fov*0.5f) float h = 2.f * nearZ / height; // 等价于 1/(tan(fov*0.5f)/aspectRatio) // 修正Z轴映射逻辑,匹配GLSL的NDC[-1,1]范围 float A = -(farZ + nearZ) / (farZ - nearZ); float B = -2.f * farZ * nearZ / (farZ - nearZ); m00 = w; m01 = 0.f; m02 = 0.f; m03 = 0.f; m10 = 0.f; m11 = h; m12 = 0.f; m13 = 0.f; m20 = 0.f; m21 = 0.f; m22 = A; m23 = B; m30 = 0.f; m31 = 0.f; m32 = -1.f; m33 = 0.f; }
修正逻辑说明:
- 将第四行的
m32从1.f改为-1.f:这决定了透视除法时gl_Position.w = -z(相机空间Z值),确保NDC的Z值符号正确。 - 修正第三行的
m22为A(原代码的C值):匹配右手系下Z轴从nearZ到farZ映射到[-1,1]的公式。
最后验证
在GLSL着色器中,确保矩阵乘法顺序正确:
gl_Position = projMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
因为你提到矩阵是行主导,传给GLSL时已经转置为列主导,所以这个顺序是正确的。
内容的提问来源于stack exchange,提问作者Hongkun Wang
相关产品推荐
相关产品推荐

