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

如何使用片段着色器中的gl_FragCoord.z在现代OpenGL中线性渲染深度?,opengl,glsl,shader,depth,depth-buffer,Opengl,Glsl,Shader,Depth,Depth Buffer,我读了很多关于使用片段着色器获得深度的信息 比如 但是我仍然不知道gl_FragCoord.z是否是线性的 GLSL规范称其在screen sapce中的范围为[0,1],但未提及其是否为线性 我认为这是至关重要的,因为我将使用渲染模型匹配Kinect的深度贴图 如果它不是线性的,如何在世界空间中线性化它?由您决定是否需要线性Z,一切取决于您的投影矩阵。您可以阅读以下内容: 这很好地解释了投影矩阵的工作原理。最好使用非线性Z,以便在前景中具有更好的精度,而在背景中具有更少的精度,当距离较远时

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

比如

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

GLSL规范称其在screen sapce中的范围为[0,1],但未提及其是否为线性

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


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

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


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

假设使用常规透视投影矩阵,一旦执行透视分割(通过
gl_Position.w
)步骤,深度将失去其线性,因此,
gl_FragCoord.z
不是线性的。有关更详细的解释,请阅读

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

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

z = gl_FragCoord.z * 2.0 - 1.0 
2) 应用投影矩阵(IP)的逆矩阵。(可以对x和y使用任意值),并对最后一个组件进行规格化

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

您将获得一个在znear和zfar之间具有线性z的视点空间(或您所称的摄影机空间)。

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

因此,当变换矩阵为
gl\u位置.w
指定一个常数值时,
gl\u FragCoord.z
是线性的(这通常发生在正交投影矩阵中),而当
gl\u位置时,
是非线性的。w
取决于
x
y
,或者输入向量的
z
坐标(发生在透视投影矩阵中)

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


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

通常,深度(
gl_FragCoord.z
gl_FragDepth
)的计算如下(请参阅):

投影矩阵描述从场景的三维点到视口的二维点的映射。它从眼睛空间转换到剪辑空间,剪辑空间中的坐标通过与剪辑坐标的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
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分量由线性函数计算:

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

透视投影 在透视投影中,投影矩阵描述了从针孔相机中看到的世界上的三维点到视口中的二维点的映射
摄影机平截头体(截断棱锥体)中的眼空间坐标映射到立方体(规范化设备坐标)

透视投影矩阵:

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
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分量由有理函数计算:

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

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

depth = (z_ndc + 1) / 2 

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

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

正交投影

透视投影

如果已知透视投影矩阵,可按如下方式进行:

A = prj_mat[2][2]
B = prj_mat[3][2]
z_eye = B / (A + z_ndc)
另请参见以下问题的答案:


感谢您的优秀站点,但也有必要确认特定变量在GLSL中的工作方式。除了理论上的解释,我想要一个明确的答案。嗯,GLSL处理浮点数,所以任何值都可以接受。但是,一旦投影坐标,范围[-1,1]对应于屏幕上X轴和Y轴的可见部分,Z缓冲区接受[-1,1]值,因此此范围之外的任何值都可能被钳制。这不是真的。使用透视投影后,投影矩阵必须将输出向量的
w
坐标设置为输入向量的负
z
坐标,以便于透视缩短。一旦你这样做了,转换后就没有办法保持线性深度(因为在透视分割管道步骤中,
x,y
z
将被负的
z
值分割)。你是如何制作图表的?@Makogan Drawing->Export PNG。(或)。我用自己编写的WebGL应用程序绘制的数学函数