glReadPixels() 如何获取实际深度而不是标准化值?
glReadPixels() how to get actual depth instead of normalized values?
我正在使用 pyopengl 获取深度图。
我可以使用 glReadPixels()
获得归一化的深度图。如何将标准化值恢复为世界坐标中的实际深度?
我试过 glDepthRange()
,但它总是执行一些规范化。我可以完全禁用标准化吗?
当您绘制几何图形时,您的顶点着色器应该通过 view/projection 矩阵。没有办法避免它,超出此范围的所有内容都会被剪裁(或夹紧,如果启用深度夹紧)。然后,将这些设备坐标转换为 window 坐标 - X 和 Y 坐标映射到 glViewport
指定的范围,Z 映射到 glDepthRange
.
设置的范围
您不能禁用归一化,因为最终值需要在 0..1 范围内。但是您可以应用反向转换:首先,将您的深度值映射回 -1..1 范围(如果您没有使用 glDepthRange
,您所要做的就是将它们乘以 2 再减去 1)。然后,您需要应用投影矩阵的逆矩阵——您可以通过计算它的逆矩阵来明确地做到这一点,或者通过查看透视矩阵的计算方式来避免矩阵运算。对于一个典型的矩阵,逆变换将是
zNorm = 2 * zBuffer - 1
zView = 2 * near * far / ((far - near) * zNorm - near - far)
(请注意,zView 将为负值,介于 -near 和 -far 之间,因为在 OpenGL 中,您的 Z 轴通常指向相机)。
尽管通常您不只需要深度 - 您需要完整的 3D 点,因此您不妨在标准化坐标中重建矢量,然后应用逆 projection/view 变换。
投影到视口后,场景坐标为归一化设备坐标(NDC)。归一化后的设备space是一个立方体,左下前坐标为(-1, -1, -1),右上后后坐标为(1, 1, 1)。此立方体中的几何体在视口上为 "visible"(除非它被覆盖)。
归一化设备的Z坐标space,映射到深度范围(glDepthRange
),一般在[0, 1]。
如何将视图的 z 坐标 space 转换为归一化设备 Z 坐标并进一步转换为深度,取决于投影矩阵。
而在Orthographic Projection时,Z分量由线性函数计算,在Perspective Projection时,Z分量由有理函数计算。
参见 How to render depth linearly in modern OpenGL with gl_FragCoord.z in fragment shader?。
这意味着,要将深度缓冲区的深度转换为原始 Z 坐标、投影(正交或透视)以及近平面和远平面必须已知。
下面假设深度范围在[0, 1],depth
是这个范围内的值:
正射投影
n = near, f = far
z_eye = depth * (f-n) + n;
z_linear = z_eye
透视投影
n = near, f = far
z_ndc = 2 * depth - 1.0;
z_eye = 2 * n * f / (f + n - z_ndc * (f - n));
如果透视投影矩阵已知,可以按如下方式完成:
A = prj_mat[2][2]
B = prj_mat[3][2]
z_eye = B / (A + z_ndc)
请注意,在任何情况下,通过 逆投影矩阵 进行的变换都会将归一化的设备坐标变换为视图中的坐标 space。
我正在使用 pyopengl 获取深度图。
我可以使用 glReadPixels()
获得归一化的深度图。如何将标准化值恢复为世界坐标中的实际深度?
我试过 glDepthRange()
,但它总是执行一些规范化。我可以完全禁用标准化吗?
当您绘制几何图形时,您的顶点着色器应该通过 view/projection 矩阵。没有办法避免它,超出此范围的所有内容都会被剪裁(或夹紧,如果启用深度夹紧)。然后,将这些设备坐标转换为 window 坐标 - X 和 Y 坐标映射到 glViewport
指定的范围,Z 映射到 glDepthRange
.
您不能禁用归一化,因为最终值需要在 0..1 范围内。但是您可以应用反向转换:首先,将您的深度值映射回 -1..1 范围(如果您没有使用 glDepthRange
,您所要做的就是将它们乘以 2 再减去 1)。然后,您需要应用投影矩阵的逆矩阵——您可以通过计算它的逆矩阵来明确地做到这一点,或者通过查看透视矩阵的计算方式来避免矩阵运算。对于一个典型的矩阵,逆变换将是
zNorm = 2 * zBuffer - 1
zView = 2 * near * far / ((far - near) * zNorm - near - far)
(请注意,zView 将为负值,介于 -near 和 -far 之间,因为在 OpenGL 中,您的 Z 轴通常指向相机)。
尽管通常您不只需要深度 - 您需要完整的 3D 点,因此您不妨在标准化坐标中重建矢量,然后应用逆 projection/view 变换。
投影到视口后,场景坐标为归一化设备坐标(NDC)。归一化后的设备space是一个立方体,左下前坐标为(-1, -1, -1),右上后后坐标为(1, 1, 1)。此立方体中的几何体在视口上为 "visible"(除非它被覆盖)。
归一化设备的Z坐标space,映射到深度范围(glDepthRange
),一般在[0, 1]。
如何将视图的 z 坐标 space 转换为归一化设备 Z 坐标并进一步转换为深度,取决于投影矩阵。
而在Orthographic Projection时,Z分量由线性函数计算,在Perspective Projection时,Z分量由有理函数计算。
参见 How to render depth linearly in modern OpenGL with gl_FragCoord.z in fragment shader?。
这意味着,要将深度缓冲区的深度转换为原始 Z 坐标、投影(正交或透视)以及近平面和远平面必须已知。
下面假设深度范围在[0, 1],depth
是这个范围内的值:
正射投影
n = near, f = far
z_eye = depth * (f-n) + n;
z_linear = z_eye
透视投影
n = near, f = far
z_ndc = 2 * depth - 1.0;
z_eye = 2 * n * f / (f + n - z_ndc * (f - n));
如果透视投影矩阵已知,可以按如下方式完成:
A = prj_mat[2][2]
B = prj_mat[3][2]
z_eye = B / (A + z_ndc)
请注意,在任何情况下,通过 逆投影矩阵 进行的变换都会将归一化的设备坐标变换为视图中的坐标 space。