Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

XR 全屏特效

如果你想给 XR 应用添加自定义的全屏特效,有一种常用的做法就是使用一个全屏四边形(full screen quad),并将特效应用到这个四边形的着色器(shader)上。在你的场景中,添加一个 MeshInstance3D 节点,并把它作为 XRCamera3D 的子节点。接着,把它的 mesh 属性设置为一个 QuadMesh 。最后,将这个四边形的宽度和高度都设置为 2

../../_images/xr_full_screen_effects_starting_quad.webp

接下来,你可以给四边网格(quad)添加一个着色器(shader),让它铺满整个屏幕。具体做法是:把顶点着色器(vertex shader)的内置变量 POSITION 设置为 vec4(VERTEX.xy, 1.0, 1.0) 。不过,如果你是想制作一个在玩家视野正前方居中的特效(比如暗角效果),你会发现最终在 XR 里呈现出来的效果可能是错位的。

下图展示了使用暗角(vignette)着色器时,右眼视角的实际画面截图,分别包含头显内的视角以及渲染目标(render target)本身的画面。左侧的截图使用的是未经修改的着色器,而右侧的截图则是使用了投影矩阵对全屏四边网格(full screen quad)进行调整后的效果。可以看到,左侧的画面虽然在渲染目标中是居中的,但在头显的实际视角里却是偏移的。而在应用了投影矩阵之后,我们就能发现,特效在头显内终于完美居中了。

../../_images/xr_full_screen_effects_vignette_before_after.webp

应用投影矩阵

为了让特效能够完美居中,全屏四边网格(full screen quad)的 POSITION (位置属性)需要把非对称视场角(asymmetric field of view)考虑进去。为了在做到这一点的同时,还能确保这个网格完全覆盖整个渲染目标(render target),我们可以把这个网格进行细分(subdivide),然后把投影矩阵应用到它内侧的顶点上。接下来,让我们增加这个四边网格在宽度和深度上的细分程度吧。

../../_images/xr_full_screen_effects_ending_quad.webp

接下来,在我们 Shader 的顶点函数(vertex function)中,我们需要利用投影矩阵(projection matrix)对内侧的顶点施加一个偏移量。下面是一个具体的例子,展示了如何将这种方法应用到上面那个简单的暗角(vignette)Shader 中:

shader_type spatial;
render_mode depth_test_disabled, skip_vertex_transform, unshaded, cull_disabled;

// Modify VERTEX.xy using the projection matrix to correctly center the effect.
void vertex() {
        vec2 vert_pos = VERTEX.xy;

        if (length(vert_pos) < 0.99) {
                vec4 offset = PROJECTION_MATRIX * vec4(0.0, 0.0, 1.0, 1.0);
                vert_pos += (offset.xy / offset.w);
        }

        POSITION = vec4(vert_pos, 1.0, 1.0);
}

void fragment() {
        ALBEDO = vec3(0.0);
        ALPHA = dot(UV * 2.0 - 1.0, UV * 2.0 - 1.0) * 2.0;
}

备注

如果想要了解更多关于非对称视场角(asymmetric FOV)及其作用的详细信息,可以查看这篇 Meta Asymmetric Field of View FAQ

限制

对于像上面提到的暗角(vignette)着色器这种逐像素处理的效果,这种全屏特效方法完全不用担心性能问题。但是,在使用这种技术时,强烈不建议去读取屏幕纹理(screen texture)。任何需要读取屏幕纹理的全屏特效,实际上都会让 XR 里的所有渲染性能优化失效。这是因为,一旦读取屏幕纹理,Godot 就必须对渲染缓冲区(render buffer)进行一次完整的复制;这会急剧增加 GPU 的工作负载,从而引发严重的性能隐患。