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

How to render depth linearly in modern OpenGL with gl_FragCoord.z in fragment shader?

我阅读了许多有关使用片段着色器深入了解的信息。

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

但是我仍然不知道gl_FragCoord.z是否是线性的。

GLSL规范表示,其范围在屏幕空间中为[0,1],而未提及其是否为线性。

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

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


but I still don't know whether or not the gl_FragCoord.z is linear.

gl_FragCoord.z是否为线性取决于投影矩阵。
对于正交投影,gl_FragCoord.z是线性的,对于透视投影,它不是线性的。

通常,深度(gl_FragCoord.zgl_FragDepth)的计算方式如下(请参阅GLSL gl_FragCoord.z计算和设置gl_FragDepth):

1
2
float ndc_depth = clip_space_pos.z / clip_space_pos.w;
float depth = (((farZ-nearZ) * ndc_depth) + nearZ + farZ) / 2.0;

投影矩阵描述了从场景的3D点到视口的2D点的映射。它从眼睛空间转换为剪辑空间,并且通过将剪辑坐标的w分量除以将剪辑空间中的坐标转换为归一化设备坐标(NDC)

正投影

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

Orthographic Projection

正射投影矩阵:

1
2
3
4
5
6
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

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

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

Orthographic Z function

透视投影

在"透视投影"中,投影矩阵描述了从针孔相机看到的世界3D点到视口的2D点的映射。
摄像机视锥中的眼睛空间坐标(截断的金字塔)映射到立方体(规范化的设备坐标)。

Perspective Projection

透视投影矩阵:

1
2
3
4
5
6
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

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

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

Perspective Z function

深度缓冲

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

1
depth = (z_ndc + 1) / 2

Then if it is not linear, how to linearize it in the world space?

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

正投影

1
2
3
n = near, f = far

z_eye = depth * (f-n) + n;

透视投影

1
2
3
4
n = near, f = far

z_ndc = 2.0 * depth - 1.0;
z_eye = 2.0 * n * f / (f + n - z_ndc * (f - n));

如果已知透视投影矩阵,则可以执行以下操作:

1
2
3
A = prj_mat[2][2]
B = prj_mat[3][2]
z_eye = B / (A + z_ndc)

另请参阅答案

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


一旦投影,它将失去线性,gl_FragCoord.z不是线性的。

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

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

1
z = gl_FragCoord.z * 2.0 - 1.0

2)应用投影矩阵(IP)的逆矩阵。 (您可以对x和y使用任意值),并对最后一个分量进行规范化。

1
2
unprojected = IP * vec4(0, 0, z, 1.0)
unprojected /= unprojected.w

您将在视图空间(或您称其为相机空间)中获得一个在znear和zfar之间具有线性z的点。


gl_FragCoord.z是否为线性取决于您的变换矩阵。通过为三角形的所有顶点计算gl_Position.z / gl_Position.w,然后将结果插值到该三角形的所有片段上来确定gl_FragCoord.z

因此,当您的变换矩阵为gl_Position.w分配常数值(通常在正交投影矩阵中发生)时,gl_FragCoord.z是线性的,而当gl_Position.w取决于xygl_FragCoord.z是非线性的>输入向量的坐标(在透视投影矩阵中发生)。


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

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

这很好地解释了投影矩阵的工作原理。非线性Z可能更好一些,以便在前景中具有更好的精度,而在背景中具有更少的精度,当距离较远时,深度伪影不太明显...