从深度缓冲区获得真正的z值

如预期,从着色器中的深度缓冲区采样返回值介于0和1之间。 鉴于相机的近平面和远平面,我该如何计算真实的z值,即距相机的距离?

http://web.archive.org/web/20130416194336/http://olivers.posterous.com/linear-depth-in-glsl-for-real

// == Post-process frag shader =========================================== uniform sampler2D depthBuffTex; uniform float zNear; uniform float zFar; varying vec2 vTexCoord; void main(void) { float z_b = texture2D(depthBuffTex, vTexCoord).x; float z_n = 2.0 * z_b - 1.0; float z_e = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear)); } 

所以这里是解释(有两个错误,见下面的基督徒的评论):

一个OpenGL透视matrix看起来像这样: 来自songho.ca

当你把这个matrix乘以一个同质点[x,y,z,1]时,它会给你:[不关心,不关心,Az + B,-z](用A和B表示两个大的成分在matrix中)。

OpenGl接下来做了透视分割:它将这个向量除以它的w分量。 这个操作不是在着色器中完成的(除了像shadowmapping这样的特殊情况),而是在硬件中; 你无法控制它。 w = -z,所以Z值变成-A / z-B。

我们现在处于标准化设备坐标。 Z值介于0和1之间。由于某些愚蠢的原因,OpenGL要求将它移动到[-1,1]范围(就像x和y一样)。 缩放和偏移量被应用。

这个最终值然后被存储在缓冲区中。

上面的代码完全相反:

  • z_b是存储在缓冲区中的原始值
  • z_n将z_b从[-1,1]线性转换为[0,1]
  • z_e与z_n = -A / z_e -B是相同的公式,但是解决了z_e。 这相当于z_e = -A /(z_n + B)。 A和B应该在CPU上计算,并作为制服发送,顺便说一句。

相反的function是:

 varying float depth; // Linear depth, in world units void main(void) { float A = gl_ProjectionMatrix[2].z; float B = gl_ProjectionMatrix[3].z; gl_FragDepth = 0.5*(-A*depth + B) / depth + 0.5; } 

我知道这是一个古老而古老的问题,但我在不同的场合不止一次地回到了这里,所以我想我可以分享我的代码,这个代码可以进行正向和反向的转换。

这是基于@ Calvin1602的答案。 这些工作在GLSL或普通的旧C代码。

 uniform float zNear = 0.1; uniform float zFar = 500.0; // depthSample from depthTexture.r, for instance float linearDepth(float depthSample) { depthSample = 2.0 * depthSample - 1.0; float zLinear = 2.0 * zNear * zFar / (zFar + zNear - depthSample * (zFar - zNear)); return zLinear; } // result suitable for assigning to gl_FragDepth float depthSample(float linearDepth) { float nonLinearDepth = (zFar + zNear - 2.0 * zNear * zFar / linearDepth) / (zFar - zNear); nonLinearDepth = (nonLinearDepth + 1.0) / 2.0; return nonLinearDepth; }