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

67

在着色器中从深度缓冲区进行采样会返回期望的0到1之间的值。 鉴于相机的近裁剪面和远裁剪面,我如何计算该点的真实z值,即与相机的距离?


11
深度缓冲区不包含相机到物体的距离值,而是包含相机平面垂直距离值。 - Nicol Bolas
11
我理解,但我认为我的意思已经很清楚了。我只需要深度值的线性化处理。 - Hannesh
3个回答

68

来源: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));
}

[编辑] 所以这是解释(有两个错误,见下面Christian的评论):

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

当你将该矩阵乘以一个齐次点[x,y,z,1]时,它会给出:[不关心,不关心,Az + B,-z](其中A和B是矩阵中的两个大组件)。

然后,OpenGl进行透视除法:它将该向量除以其w分量。此操作不在着色器中执行(除了像阴影映射这样的特殊情况),而是在硬件中执行;您无法控制它。 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上计算并作为uniform发送,顺便说一下。

相反的函数是:

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;
}

14
虽然总体上是一个好的解释,但我认为你有一些错误。首先,在将 Az+B 除以 -z 后,得到的结果应该是 -A-B/z 而不是 -A/z-B。而且在透视矫正之后,值是在 [-1,1] 范围内,并且需要进行比例缩放和偏移来使其落在 [0,1] 的范围内,然后再写入深度缓冲区,而不是反过来(虽然您的代码做得对,但是解释是错误的)。 - Christian Rau
@wil 谢谢!我添加了对立函数,以防万一。 Christian:哎呀,是的,但我没时间纠正,所以我会参考你的评论 :/ - Calvin1602
1
一个澄清的说明:在上面的方程中,zNear和zFar被理解为负值(这是正确的,因为它们是相机前面的z坐标)。相比之下,提供矩阵中的n和f值是正值,遵循glOrtho使用近和远的绝对值的约定。 - Kyle Simek
2
我认为你的解释中仍然有一个小错误。我猜你想说“z_n将[0,1]中的z_b线性变换到[-1,1]”,而不是相反的方式。至少,这是你的代码所做的。 - DanceIgel
Christian Rau的评论非常重要,让读者知道这个答案有很多关键性的错误。我很想知道这个答案是如何得到这么多赞同的。 - undefined
显示剩余3条评论

17

我知道这是一个古老的问题,但我在各种场合多次回到这里,所以我想分享我的代码,可以进行前向和反向转换。

这基于@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;
}

3
线性到非线性似乎可以简化为:4far(1-near/linear)/(far-near)。https://www.desmos.com/calculator/oonxyoo3to - Wivlaro
5
同时,非线性到线性的转换可以简化为:zFar * zNear / (zFar + depthSample * (zNear - zFar))。 - Wivlaro

2

当我遇到类似问题时,尝试解决并最终来到这里。在Nicol Bolas在本页面上的评论让我意识到我的错误。如果你想要得到相机距离而不是相机平面距离,可以按以下方式计算(使用GLSL):

float GetDistanceFromCamera(float depth, 
                            vec2 screen_pixel, 
                            vec2 resolution) {
  float fov = ... 
  float near = ...
  float far = ...
  float distance_to_plane = near / (far - depth * (far - near)) * far;


  vec2 center = resolution / 2.0f - 0.5;
  float focal_length = (resolution.y / 2.0f) / tan(fov / 2.0f);
  float diagonal = length(vec3(screen_pixel.x - center.x,
                               screen_pixel.y - center.y,
                               focal_length));

  return distance_to_plane * (diagonal / focal_length);
}

感谢Github用户cassfalg提供的源代码: https://github.com/carla-simulator/carla/issues/2287

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接