透视投影中恢复视图空间位置的3种解决方案
投影矩阵描述了从场景的 3D 点到视口的 2D 点的映射。它从视图(眼睛)空间变换到剪辑空间,并且剪辑空间中的坐标除以w剪辑坐标的组成部分。 NDC 的范围为 (-1,-1,-1) 到 (1,1,1)。
在透视投影中,投影矩阵描述了从针孔相机看到的世界上的 3D 点到视口的 2D 点的映射。
相机平截头体(截棱锥体)中的眼睛空间坐标被映射到立方体(标准化设备坐标)。
![](https://i.stack.imgur.com/9dzTm.png)
透视投影矩阵:
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 );
prjMat[0][0] = 2*n/(r-l) = 1.0 / (tanFov * aspect)
prjMat[1][1] = 2*n/(t-b) = 1.0 / tanFov
在透视投影中,Z 分量由以下公式计算有理函数:
z_ndc = ( -z_eye * (f+n)/(f-n) - 2*f*n/(f-n) ) / -z_eye
深度 (gl_FragCoord.z
and gl_FragDepth) 计算如下:
z_ndc = clip_space_pos.z / clip_space_pos.w;
depth = (((farZ-nearZ) * z_ndc) + nearZ + farZ) / 2.0;
1. 视野和纵横比
由于投影矩阵是由视场和纵横比定义的,因此可以利用视场和纵横比来恢复视口位置。假设它是对称透视投影并且标准化设备坐标,则深度以及近平面和远平面是已知的。
恢复视图空间中的 Z 距离:
z_ndc = 2.0 * depth - 1.0;
z_eye = 2.0 * n * f / (f + n - z_ndc * (f - n));
通过 XY 标准化设备坐标恢复视图空间位置:
ndc_x, ndc_y = xy normalized device coordinates in range from (-1, -1) to (1, 1):
viewPos.x = z_eye * ndc_x * aspect * tanFov;
viewPos.y = z_eye * ndc_y * tanFov;
viewPos.z = -z_eye;
2. 投影矩阵
由视场和纵横比定义的投影参数存储在投影矩阵中。因此,可以通过对称透视投影的投影矩阵的值来恢复视口位置。
注意投影矩阵、视场和纵横比之间的关系:
prjMat[0][0] = 2*n/(r-l) = 1.0 / (tanFov * aspect);
prjMat[1][1] = 2*n/(t-b) = 1.0 / tanFov;
prjMat[2][2] = -(f+n)/(f-n)
prjMat[3][2] = -2*f*n/(f-n)
恢复视图空间中的 Z 距离:
A = prj_mat[2][2];
B = prj_mat[3][2];
z_ndc = 2.0 * depth - 1.0;
z_eye = B / (A + z_ndc);
通过 XY 标准化设备坐标恢复视图空间位置:
viewPos.x = z_eye * ndc_x / prjMat[0][0];
viewPos.y = z_eye * ndc_y / prjMat[1][1];
viewPos.z = -z_eye;
3. 逆投影矩阵
当然,可以通过逆投影矩阵来恢复视口位置。
mat4 inversePrjMat = inverse( prjMat );
vec4 viewPosH = inversePrjMat * vec3( ndc_x, ndc_y, 2.0 * depth - 1.0, 1.0 )
vec3 viewPos = viewPos.xyz / viewPos.w;
另请参阅以下问题的答案:
- 如何在现代 OpenGL 中使用片段着色器中的 gl_FragCoord.z 线性渲染深度?