如何在给定视图空间深度值和ndc xy的情况下恢复视图空间位置

我正在写一个延迟着色器,并试图更加紧密地打包我的gbuffer。 但是,我似乎无法正确计算视图空间深度的视图位置

// depth -> (gl_ModelViewMatrix * vec4(pos.xyz, 1)).z; where pos is the model space position // fov -> field of view in radians (0.62831855, 0.47123888) // p -> ndc position, x, y [-1, 1] vec3 getPosition(float depth, vec2 fov, vec2 p) { vec3 pos; pos.x = -depth * tan( HALF_PI - fov.x/2.0 ) * (px); pos.y = -depth * tan( HALF_PI - fov.y/2.0 ) * (py); pos.z = depth; return pos; } 

计算的位置是错误的。 我知道这一点,因为我仍然在gbuffer中存储正确的位置并使用它进行testing。

我设法让它工作到最后,因为它与上面不同的方法我会详细说明,所以任何看到这个的人都会有解决办法。

  • 通过1:将视图空间的深度值存储到gbuffer
  • 要在第二遍中重新创build(x,y,z)位置:
  • 将弧度的水平和垂直视野传递给着色器。
  • 将近平面距离(近)传递给着色器。 (从相机位置到近平面的距离)
  • 想象一下从相机到碎片位置的光线。 这条射线在某个位置P处与近平面相交。我们在ndc空间中有这个位置,并且想要在视空间中计算这个位置。
  • 现在,我们在视图空间中拥有所有我们需要的值。 我们可以使用相似三angular形的法则来find实际的片段位置P'

     P = P_ndc * near * tan(fov/2.0f) // computation is the same for x, y // Note that by law of similar triangles, P'.x / depth = P/near P'.xy = P/near * -depth; // -depth because in opengl the camera is staring down the -z axis P'.z = depth; 

我写了一个延迟着色器,并使用此代码重新计算屏幕空间定位:

 vec3 getFragmentPosition() { vec4 sPos = vec4(gl_TexCoord[0].x, gl_TexCoord[0].y, texture2D(depthTex, gl_TexCoord[0].xy).x, 1.0); sPos.z = 2.0 * sPos.z - 1.0; sPos = invPersp * sPos; return sPos.xyz / sPos.w; } 

depthTex是保存深度信息的纹理, invPersp是预先计算的反向透视matrix。 你把屏幕的片段位置,并乘以反透视matrix,以获得模型视图坐标。 然后用w除以得到同质的坐标。 乘以二和减一是将深度从[0,1](如存储在纹理中)缩放到[-1,1]。

此外,根据您使用的是哪种types的MRT,重新计算的结果将不会完全等于存储的信息,因为您失去了浮点精度。

3透视投影中恢复视图空间位置的解决scheme

投影matrix描述了从场景的3D点到视口的2D点的映射。 它从视图(眼睛)空间转换到剪辑空间,并且剪辑空间中的坐标通过用剪辑坐标的w分量进行分割而被转换为标准化设备坐标(NDC)。 NDC在(-1,-1,-1)到(1,1,1)的范围内。

在透视投影中,投影matrix描述从针孔摄像机看到的世界中的3D点到视区的2D点的映射。
摄像机视锥体(截断的金字塔)中的眼睛空间坐标被映射到立方体(标准化的设备坐标)。

透视投影

透视投影matrix:

 r = right, l = left, b = bottom, t = top, n = near, f = far 2*n/(rl) 0 0 0 0 2*n/(tb) 0 0 (r+l)/(rl) (t+b)/(tb) -(f+n)/(fn) -1 0 0 -2*f*n/(fn) 0 

它跟随:

 aspect = w / h tanFov = tan( fov_y * 0.5 ); prjMat[0][0] = 2*n/(rl) = 1.0 / (tanFov * aspect) prjMat[1][1] = 2*n/(tb) = 1.0 / tanFov 

在透视投影中,Z分量由有理函数来计算

 z_ndc = ( -z_eye * (f+n)/(fn) - 2*f*n/(fn) ) / -z_eye 

深度( gl_FragCoord.zgl_FragDepth )计算如下:

 z_ndc = clip_space_pos.z / clip_space_pos.w; depth = (((farZ-nearZ) * z_ndc) + nearZ + farZ) / 2.0; 

1.视野和纵横比

由于投影matrix由视野和纵横比限定,所以可以通过视场和纵横比恢复视口位置。 假设它是一个对称的透视投影和标准化的设备坐标,深度和近平面和远平面是已知的。

在视图空间中恢复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.投影matrix

在投影matrix中存储由视场和纵横比决定的投影参数。 因此,视点位置可以通过来自投影matrix的值从对称的透视投影中恢复。

注意投影matrix,视野和宽高比之间的关系:

 prjMat[0][0] = 2*n/(rl) = 1.0 / (tanFov * aspect); prjMat[1][1] = 2*n/(tb) = 1.0 / tanFov; prjMat[2][2] = -(f+n)/(fn) prjMat[3][2] = -2*f*n/(fn) 

在视图空间中恢复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.反投影matrix

当然,视口位置可以通过逆投影matrix来恢复。

 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; 

另请参阅以下问题的答案:

  • 如何在片段着色器中使用gl_FragCoord.z在现代OpenGL中呈现线性深度?