我阅读了很多关于使用片段着色器获取深度的信息。
如
http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=234519
但我仍然不知道是否gl_FragCoord.z
是线性的。
GLSL 规范说它的范围是屏幕空间中的 [0,1],但没有提到它是否是线性的。
我认为线性至关重要,因为我将使用渲染模型来匹配来自 Kinect 的深度图。
那么如果不是线性的,如何在世界空间中线性化呢?
我阅读了很多关于使用片段着色器获取深度的信息。
如
http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=234519
但我仍然不知道是否gl_FragCoord.z
是线性的。
GLSL 规范说它的范围是屏幕空间中的 [0,1],但没有提到它是否是线性的。
我认为线性至关重要,因为我将使用渲染模型来匹配来自 Kinect 的深度图。
那么如果不是线性的,如何在世界空间中线性化呢?
但我仍然不知道 gl_FragCoord.z 是否是线性的。
是否gl_FragCoord.z
线性取决于投影矩阵。虽然正交投影gl_FragCoord.z
是线性的,但透视投影不是线性的。
一般来说,深度 (gl_FragCoord.z
和gl_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;
投影矩阵描述了从场景的 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
在正交投影中,Z 分量由线性函数计算:
z_ndc = z_eye * -2/(f-n) - (f+n)/(f-n)
在透视投影中,投影矩阵描述了从针孔相机看到的世界中的 3D 点到视口的 2D 点的映射。
相机平截头体(截断的金字塔)中的眼睛空间坐标映射到立方体(标准化设备坐标)。
透视投影矩阵:
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 * (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 坐标,必须知道投影(正交或透视)以及近平面和远平面。
正交投影
n = near, f = far
z_eye = depth * (f-n) + n;
透视投影
n = near, f = far
z_ndc = 2.0 * depth - 1.0;
z_eye = 2.0 * n * f / (f + n - z_ndc * (f - n));
如果透视投影矩阵已知,则可以按如下方式完成:
A = prj_mat[2][2]
B = prj_mat[3][2]
z_eye = B / (A + z_ndc)
另见答案
假设一个通常的透视投影矩阵,一旦执行透视除法(由gl_Position.w
)步骤,深度就会失去其线性,因此gl_FragCoord.z
不是线性的。有关更详细的解释,请阅读@Dreamer 的回答。
要恢复到线性,您应该执行 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_FragCoord.z
当您的变换矩阵分配一个常数值时是线性的(这gl_Position.w
通常发生在正交gl_Position.w
投影矩阵中),并且当取决于输入向量的x
、y
或坐标时是非线性z
的(这发生在透视投影矩阵中)。
由您决定是否需要线性 Z,一切都取决于您的投影矩阵。你可以读到这个:
http://www.songho.ca/opengl/gl_projectionmatrix.html
这很好地解释了投影矩阵的工作原理。使用非线性 Z 可能会更好,以便在前景中获得更好的精度,在背景中获得更少的精度,在远处时深度伪影不太明显......