从clipspace.xyz和(inv)投影矩阵计算clipspace.w

6
我正在使用对数深度算法,结果会将someFunc(clipspace.z)写入深度缓冲区中,并且没有隐式的透视除法。
我正在进行RTT/后期处理,因此在片段着色器中,我想重新计算眼坐标xyz,给定ndc.xy(来自片段坐标)和clipspace.z(从深度缓冲区中存储的值通过someFuncInv()函数反推得到)。
请注意,我没有clipspace.w的值,而我的存储值也不是clipspace.z/clipspace.w(这是使用固定函数深度时的情况),所以大致上可以这样实现...
float clip_z = ...; /* [-1 .. +1] */
vec2 ndc = vec2(FragCoord.xy / viewport * 2.0 - 1.0);
vec4 clipspace = InvProjMatrix * vec4(ndc, clip_z, 1.0));
clipspace /= clipspace.w;

这里不能运行...

那么,是否有一种方法可以根据投影矩阵或其逆矩阵计算出clip空间中的clip.w,而只知道clip.xyz呢?


我们不会在问题中放置答案。如果您想与他人分享解决方案代码,您可以编写一个回答并将其放在那里。 - Nicol Bolas
1个回答

11
clipspace.xy = FragCoord.xy / viewport * 2.0 - 1.0;
这在术语上是错误的。 "裁剪空间"是顶点着色器(或最后一个顶点处理阶段)输出的空间。 在裁剪空间和窗口空间之间是归一化设备坐标(NDC)空间。 NDC空间是裁剪空间除以裁剪空间W坐标得到的:
vec3 ndcspace = clipspace.xyz / clipspace.w;

那么第一步是将我们的窗口空间坐标取出并得到NDC(归一化设备坐标)空间坐标,这很容易:

vec3 ndcspace = vec3(FragCoord.xy / viewport * 2.0 - 1.0, depth);
现在,我会假设您的深度值depth是正确的NDC空间深度。我假设您从深度纹理中获取该值,然后使用它所呈现的深度范围近/远值将其映射到[-1,1]范围内。如果没有这样做,那么您应该这样做。
现在,既然我们有了ndcspace,那么如何计算clipspace?这很明显:
vec4 clipspace = vec4(ndcspace * clipspace.w, clipspace.w);

显而易见并不是很有帮助,因为我们没有 clipspace.w。那么我们该怎么办呢?

要获得它,我们需要看一下第一次计算 clipspace 的方法:

vec4 clipspace = Proj * cameraspace;

这意味着通过将cameraspaceProj的第四行进行点积运算,可以计算出clipspace.w

如果我们查看Proj的第四行,会更有帮助。当然,您可能正在使用任何投影矩阵,如果您不使用典型的投影矩阵,则此计算变得更加困难(可能是不可能的)。

典型投影矩阵中,Proj的第四行实际上只是这样:

[0, 0, -1, 0]
这意味着clipspace.w实际上就是-cameraspace.z。那有什么帮助呢?
它有所帮助的原因在于记住这一点:
ndcspace.z = clipspace.z / clipspace.w;
ndcspace.z = clipspace.z / -cameraspace.z;

这很不错,但只是将一个未知量换成了另一个;我们仍然有一个带有两个未知量(clipspace.zcameraspace.z)的方程。不过,我们还知道一些其他信息: clipspace.z 是由将 cameraspace 与投影矩阵的第三行做点积得出来的。传统的投影矩阵的第三行如下所示:

[0, 0, T1, T2]

其中T1和T2是非零数字。暂时忽略这些数字的含义。因此,clippsace.z实际上只是T1 * cameraspace.z + T2 * cameraspace.w。如果我们知道cameraspace.w是1.0(通常是这样),那么我们可以将其删除:

ndcspace.z = (T1 * cameraspace.z + T2) / -cameraspace.z;
所以,我们仍然有一个问题。实际上,我们没有问题。为什么?因为这个方程式中只有一个未知量。记住:我们已经知道ndcspace.z。因此,我们可以使用ndcspace.z来计算cameraspace.z:
ndcspace.z = -T1 + (-T2 / cameraspace.z);
ndcspace.z + T1 = -T2 / cameraspace.z;
cameraspace.z = -T2 / (ndcspace.z + T1);

T1T2直接来自于我们的投影矩阵(场景最初渲染时使用的矩阵)。我们已经有了ndcspace.z。所以我们可以计算cameraspace.z。而且我们知道:

clispace.w = -cameraspace.z;

因此,我们可以这样做:

vec4 clipspace = vec4(ndcspace * clipspace.w, clipspace.w);

显然,你需要使用浮点数来表示clipspace.w而不是文字代码,但你已经明白了我的意思。一旦你有了clipspace,为了得到摄像机空间,就需要乘以投影矩阵的逆矩阵:

vec4 cameraspace = InvProj * clipspace;

非常感谢您详细的解释(我根据您的建议修正了我的变量名称)。您的帖子恰好反映了我的思路 - 我强烈感觉到投影矩阵是“正常”的,允许重构w(只要原始向量是齐次的)- 但这只是直觉,我应该写下点积来实现它...我还没有真正尝试过它(我刚刚看到了您的回复),但这是完全有道理的,所以我认为我的问题已经解决了。再次感谢。 :-) - Felix Nawothnig
3
记录一下 - 在我昨天提出问题和今天在这里找到答案之间,有人在这个页面上写了第二部分,涉及到这个主题......虽然它不完全解决我的问题,但其中的数学概念非常接近。 - Felix Nawothnig

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接