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

tom*_*234 9 opengl shader glsl depth-buffer depth

我阅读了很多关于使用片段着色器获取深度的信息.

http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=234519

但我还是不知道这是否gl_FragCoord.z是线性的.

GLSL规范称屏幕上的范围为[0,1]而未提及线性与否.

我认为线性是至关重要的,因为我将使用渲染模型来匹配Kinect的深度图.

那么如果它不是线性的,那么如何在世界空间中线性化呢?

Rab*_*d76 23

但我还是不知道gl_FragCoord.z是否是线性的.

是否gl_FragCoord.z线性取决于投影矩阵.虽然对于正投影gl_FragCoord.z是线性的,但对于透视投影,它不是线性的.

通常,深度(gl_FragCoord.zgl_FragDepth)计算如下(参见GLSL gl_FragCoord.z计算和设置gl_FragDepth):

float ndc_depth = clip_space_pos.z / clip_space_pos.w;
float depth = (((farZ-nearZ) * ndc_depth) + nearZ + farZ) / 2.0;
Run Code Online (Sandbox Code Playgroud)

投影矩阵描述了从场景的3D点到视口的2D点的映射.它从眼睛空间转换到剪辑空间,并通过用剪辑坐标的w分量进行划分,将剪辑空间中的坐标转换为规范化设备坐标(NDC)

正投影

在正交投影中,眼睛空间中的坐标线性映射到标准化设备坐标.

正投影

正投影矩阵:

r = right, l = left, b = bottom, t = top, n = near, f = far 

2/(r-l)         0               0               0
0               2/(t-b)         0               0
0               0               -2/(f-n)        0
-(r+l)/(r-l)    -(t+b)/(t-b)    -(f+n)/(f-n)    1
Run Code Online (Sandbox Code Playgroud)

在正交投影中,Z分量由线性函数计算:

z_ndc = z_eye * -2/(f-n) - (f+n)/(f-n)
Run Code Online (Sandbox Code Playgroud)

正交Z函数

透视投影

在透视投影中,投影矩阵描述了从针孔相机到视口的2D点看世界中3D点的映射.
相机平截头体(截头金字塔)中的眼睛空间坐标被映射到立方体(标准化设备坐标).

透视投影

透视投影矩阵:

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

在Perspective Projection中,Z分量由有理函数计算:

z_ndc = ( -z_eye * (f+n)/(f-n) - 2*f*n/(f-n) ) / -z_eye
Run Code Online (Sandbox Code Playgroud)

透视Z功能

深度缓冲区

由于归一化设备坐标在范围(-1,-1,-1)到(1,1,1)范围内,因此Z坐标必须映射到范围[0,1]的深度缓冲区:

depth = (z_ndc + 1) / 2 
Run Code Online (Sandbox Code Playgroud)


那么如果它不是线性的,如何在世界空间中线性化它呢?

要将深度缓冲区的深度转换为原始Z坐标,必须知道投影(正交或透视)以及近平面和远平面.

正投影

n = near, f = far

z_eye = depth * (f-n) + n;
Run Code Online (Sandbox Code Playgroud)

透视投影

n = near, f = far

z_ndc = 2.0 * depth - 1.0;
z_eye = 2.0 * n * f / (f + n - z_ndc * (f - n));
Run Code Online (Sandbox Code Playgroud)

如果透视投影矩阵已知,则可以按如下方式完成:

A = prj_mat[2][2]
B = prj_mat[3][2]
z_eye = B / (A + z_ndc)
Run Code Online (Sandbox Code Playgroud)

另见答案

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

  • @Makogan [OpenOffice](https://www.openoffice.org/?redirect=soft) 绘图 -> 导出 PNG。(或 [LibreOffice](https://www.libreoffice.org/))。我用 WebGL 应用程序绘制的数学函数,我自己编写了 https://rabbid76.github.io/graphics-snippets/html/tools/function_plotter.html (2认同)

pyg*_*iel 10

一旦它被预测它失去线性,gl_FragCoord.z不是线性的.

要恢复为线性,您应该执行两个步骤:

1)将变量转换为gl_FragCoord.z[-1,1]范围内的标准化设备坐标

z = gl_FragCoord.z * 2.0 - 1.0 
Run Code Online (Sandbox Code Playgroud)

2)应用投影矩阵(IP)的逆.(您可以对x和y使用任意值),并对最后一个组件进行标准化.

unprojected = IP * vec4(0, 0, z, 1.0)
unprojected /= unprojected.w
Run Code Online (Sandbox Code Playgroud)

您将获得在znear和zfar之间具有线性z的视图空间(或相机空间,您可以命名)中的点.


neo*_*phi 3

由您决定是否需要线性 Z,一切都取决于您的投影矩阵。您可以阅读以下内容:

http://www.songho.ca/opengl/gl_projectionmatrix.html

这很好地解释了投影矩阵的工作原理。最好使用非线性 Z,以便在前景中获得更好的精度,在背景中获得更少的精度,当距离很远时,深度伪像不太明显......