如何在片段着色器中将gl_FragCoord转换为世界空间点?

5
我的理解是,如果您拥有视图投影矩阵的逆矩阵、屏幕宽度和屏幕高度,则可以在片段着色器中将gl_FragCoord转换为世界坐标系中的点。首先,将 gl_FragCoord.xgl_FragCoord.y 从屏幕空间转换为规范化设备坐标,分别通过宽度和高度进行除法运算,然后缩放并偏移到 [-1, 1] 范围内。接下来,通过逆视图投影矩阵进行变换,得到一个世界空间的点,但仅能使用该点如果将其除以 w 分量。

以下是我写的不起作用的片段着色器代码。请注意,inverse_proj 实际上被设置为逆视图投影矩阵:
#version 450

uniform mat4 inverse_proj;
uniform float screen_width;
uniform float screen_height;

out vec4 fragment;

void main()
{
    // Convert screen coordinates to normalized device coordinates (NDC)
    vec4 ndc = vec4(
        (gl_FragCoord.x / screen_width - 0.5) * 2,
        (gl_FragCoord.y / screen_height - 0.5) * 2,
        0,
        1);

    // Convert NDC throuch inverse clip coordinates to view coordinates
    vec4 clip = inverse_proj * ndc;
    vec3 view = (1 / ndc.w * clip).xyz;

    // ...
}

1
通过从顶点着色器传递世界位置的变量,会更容易(也更有效)。 - BDL
2个回答

6

首先,您需要将gl_FragCoord.x和gl_FragCoord.y从屏幕坐标转换为标准化设备坐标

同时忽略了NDC空间是三维的(窗口空间也是)。您还忘记了从剪裁空间到NDC空间的变换涉及除法,您没有撤消这个操作。好吧,您有点尝试撤消它,但是在通过反向剪辑转换进行变换之后。

撤销顶点后处理转换使用gl_FragCoord的所有四个分量(虽然您可以只用3个分量)。第一步是撤销视口变换,这需要获取传递给glDepthRange的参数。

这使您获得了NDC坐标。然后您必须撤消透视除法。将gl_FragCoord.w赋值为1/clipW。而clipW是该运算中的除数。因此,您需要除以gl_FragCoord.w即可返回剪裁空间。

从那里,您可以乘以投影矩阵的逆矩阵。虽然如果您想要世界空间,则必须反转投影矩阵必须是世界到投影的,而不仅仅是纯投影(通常为相机到投影)。

代码中:

vec4 ndcPos;
ndcPos.xy = ((2.0 * gl_FragCoord.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1;
ndcPos.z = (2.0 * gl_FragCoord.z - gl_DepthRange.near - gl_DepthRange.far) /
    (gl_DepthRange.far - gl_DepthRange.near);
ndcPos.w = 1.0;

vec4 clipPos = ndcPos / gl_FragCoord.w;
vec4 eyePos = invPersMatrix * clipPos;

其中viewport是一个uniform,包含由glViewport函数指定的四个参数,按照与该函数相同的顺序。


1
嗨,尼古拉,您能否解释一下viewport变量是什么以及它是如何定义的? - travnik
2
@travnik:完成。 - Nicol Bolas

3
我找出了我的代码问题。首先,正如Nicol指出的那样,glFragCoord.z(深度)需要从屏幕坐标中移动。此外,在原始代码中,我写成了1 / ndc.w * clip而不是clip / clip.w
然而,正如BDL所指出的那样,将世界位置作为变量传递到片段着色器中会更有效率。然而,下面的代码是完全通过片段着色器实现所需结果的简短方式(例如,对于没有每个片段的世界位置但您希望每个片段都有视角向量的屏幕空间程序)。
#version 450

uniform mat4 inverse_view_proj;
uniform float screen_width;
uniform float screen_height;

out vec4 fragment;

void main()
{
    // Convert screen coordinates to normalized device coordinates (NDC)
    vec4 ndc = vec4(
        (gl_FragCoord.x / screen_width - 0.5) * 2.0,
        (gl_FragCoord.y / screen_height - 0.5) * 2.0,
        (gl_FragCoord.z - 0.5) * 2.0,
        1.0);

    // Convert NDC throuch inverse clip coordinates to view coordinates
    vec4 clip = inverse_view_proj * ndc;
    vec3 vertex = (clip / clip.w).xyz;

    // ...
}

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