我目前正在使用以下 GLSL 代码在后期处理景深着色器中读取深度纹理:
vec4 depthSample = texture2D(sDepthTexture, tcScreen);
float depth = depthSample.x * 255.0 / 256.0 +
depthSample.y * 255.0 / 65536.0 +
depthSample.z * 255.0 / 16777216.0;
然后根据近平面和远平面距离将深度值转换为 View 空间距离:
float zDistance = (zNear * zFar) / (zFar - depth * (zFar - zNear));
这一切似乎都运行良好,但是我很想知道如何仅基于当前投影矩阵进行上述计算,而无需单独的
zNear
和 zFar
值。我最初的尝试涉及乘法
(vec4(tcscreen.x, tcScreen.y, depth, 1.0) * 2.0 - 1.0)
通过投影矩阵的逆,将结果除以 w
,然后取结果 z
值作为距离,但这似乎不起作用。这里的正确方法是什么?此外,当使用斜截头体裁剪将近平面移动到选定的裁剪平面时,每个像素的近平面距离现在是否可能不同?如果是这样,那么这是否意味着任何计算与深度纹理距离的着色器都需要注意这种情况,而不是假设一个恒定的近平面距离?
谢谢!
最佳答案
事实证明,我忘记否定最终的 Z 值以获得近平面前方的正距离(OpenGL 相机向下看 -Z)。为了将来引用,用于获取近平面前方距离的 GLSL 代码是:
float depth = /* sampled from depth texture as in the original question */ ;
vec4 screenPos = vec4(tcScreen.x, tcScreen.y, depth, 1.0) * 2.0 - 1.0;
vec4 viewPosition = projectionMatrixInverse * screenPos;
float z = -(viewPosition.z / viewPosition.w);
如果您想要一个世界空间位置(就像 SuperPro 使用的那样),那么可以通过组合 View 和投影矩阵,然后使用该矩阵的逆矩阵而不是仅使用逆投影矩阵来找到。
因为只有
viewPosition
的 Z 和 W 分量需要上述 GLSL 进行计算 viewPosition
可以简化一些。两个点积代替完整矩阵乘法就足够了,并且不需要将完整的逆投影矩阵输入到着色器中,因为只需要底部两行:vec2 viewPositionZW = vec2(
dot(projectionMatrixInverseRow2, screenPos),
dot(projectionMatrixInverseRow3, screenPos)
);
float z = -(viewPositionZW.x / viewPositionZW.y);
这样做的性能比使用近距离和远距离要差一些,大概是因为额外的点积,我得到了大约 5% 的减少。也可以通过馈送
(zNear * zFar)
来优化近距和远距数学。和 (zFar - zNear)
作为常量,但我没有看到这样做有任何可衡量的改进。有趣的是,当您将上述内容与使用斜截头锥体裁剪的投影矩阵相结合时,我无法从中得到任何合理的结果,但是在使用具有相同投影矩阵的近距和远距方程时,我确实得到了合理的输出,尽管似乎是深度值的一些失真(尽管这可能只是由于斜视锥体剪切中固有的深度精度损失)。如果有人能阐明数学上到底发生了什么,我将不胜感激,尽管也许这应该是一个不同的问题。
关于math - 将深度纹理样本转换为距离,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1153114/