从深度缓冲区获取真实的z值

Han*_*esh 52 opengl graphics shader

从着色器中的深度缓冲区进行采样,返回0到1之间的值,如预期的那样.给定相机的近平面和远平面平面,如何计算此时的真实z值,即距相机的距离?

Cal*_*602 56

来自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));
}
Run Code Online (Sandbox Code Playgroud)

[编辑]所以这里是解释(有2个错误,请参阅下面的Christian的评论):

OpenGL透视矩阵如下所示: 来自songho.ca

当你将这个矩阵乘以一个齐次点[x,y,z,1]时,它会给你:[不关心,不关心,Az + B,-z](A和B是两个大的组成部分)在矩阵中).

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

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

然后将该最终值存储在缓冲区中.

上面的代码完全相反:

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

相反的功能是:

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 虽然一般都是一个很好的解释,但我觉得你有些不对劲.首先,将`Az + B`除以`-z`后,得到`-AB/z`而不是`-A/zB`.然后是在透视分割之后,值在[-1,1]中并且在写入深度缓冲区之前需要缩放到[0,1],而不是相反(尽管你的代码确实如此)它是对的,这只是解释是错的). (11认同)
  • 我认为你的解释还是有一个小错误。我猜你想说“z_n 将 z_b 从 [0,1] 线性变换到 [-1,1]”而不是相反的方式。至少,这就是您的代码的作用。 (2认同)

dav*_*ink 12

我知道这是一个古老而古老的问题,但我发现自己在不同的场合不止一次回到这里,所以我想我会分享我的代码来进行正向和反向转换.

这是基于@ 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 此外,非线性 - >线性减少为:zFar*zNear /(zFar + depthSample*(zNear - zFar)) (5认同)
  • 线性 - >非线性似乎减少到:4*远*(1近/线性)/(远近).https://www.desmos.com/calculator/oonxyoo3to (3认同)