屏幕读取着色器

介绍

通常,需要使一个明暗器从它正在写入的同一屏幕上读取。由于内部硬件的限制,诸如OpenGL或DirectX等3D API使得这变得非常困难。GPU极为并行,因此读写会导致各种缓存和一致性问题。因此,即使是最现代的硬件也不能正确地支持这一点。

解决方法是将屏幕或屏幕的一部分复制到后台缓冲区,然后在绘图时从中读取。Godot提供了一些工具,使这个过程容易!

屏幕纹理内置纹理

Godot 底纹语言 有一个特殊的纹理,“屏幕纹理”(和“深度纹理”的深度,在三维情况下)。它将屏幕的UV作为论据,并返回带有颜色的vec3 rgb。一个特殊的内置变化:屏幕紫外线可以用来获得当前片段的紫外线。因此,这个简单的二维片段着色器:

void fragment() {}
    COLOR = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0);
}

结果是一个看不见的物体,因为它只显示了背后的东西。

必须使用TextureLod的原因是,当Godot复制回一块屏幕时,它也会对其mipmap进行有效的分离高斯模糊。

这不仅允许从屏幕上阅读,而且允许以不同的模糊度免费阅读。

屏幕纹理示例

屏幕纹理可以用于许多事情。有一个特别的演示 屏幕空间明暗器 ,您可以下载以查看和学习。一个例子是一个简单的明暗器来调整亮度、对比度和饱和度:

shader_type canvas_item;

uniform float brightness = 1.0;
uniform float contrast = 1.0;
uniform float saturation = 1.0;

void fragment() {
    vec3 c = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0).rgb;

    c.rgb = mix(vec3(0.0), c.rgb, brightness);
    c.rgb = mix(vec3(0.5), c.rgb, contrast);
    c.rgb = mix(vec3(dot(vec3(1.0), c.rgb) * 0.33333), c.rgb, saturation);

    COLOR.rgb = c;
}

幕后

虽然这看起来很神奇,但事实并非如此。当第一次在将要绘制的节点中发现屏幕纹理时,内置的屏幕纹理会将全屏复制到后台缓冲区。在明暗器中使用它的后续节点将不会为它们复制屏幕,因为这会导致效率低下。

因此,如果使用“屏幕纹理”的明暗器重叠,则第二个明暗器将不会使用第一个明暗器的结果,从而导致意外的视觉效果:

../../_images/texscreen_demo1.png

在上面的图像中,第二个球体(右上角)与下面的第一个球体使用相同的屏幕纹理源,因此第一个球体“消失”或不可见。

在3D中,这是不可避免的,因为复制是在不透明渲染完成时进行的。

在2d中,可以通过 BackBufferCopy 节点,可以在两个球体之间实例化。backbuffercopy可以通过指定屏幕区域或整个屏幕来工作:

../../_images/texscreen_bbc.png

使用正确的后缓冲区复制,两个球体正确混合:

../../_images/texscreen_demo2.png

后缓冲逻辑

所以,为了更清楚地说明,下面是后台缓冲区复制逻辑在godot中的工作原理:

  • 如果一个节点使用屏幕纹理,那么在绘制该节点之前,整个屏幕将被复制到后台缓冲区。这种情况只在第一次发生;后续节点不会触发这种情况。

  • 如果在上述点中的情况之前处理了backbuffercopy节点(即使未使用屏幕纹理),则不会发生上述点中描述的行为。换句话说,只有在第一次在一个节点中使用屏幕纹理,并且之前没有发现任何树顺序的backbuffercopy节点(未禁用)时,才会自动复制整个屏幕。

  • backbuffercopy可以复制整个屏幕或区域。如果设置为仅区域(而不是整个屏幕),并且您的明暗器使用的像素不在复制的区域中,则该读取的结果是未定义的(很可能是来自以前帧的垃圾)。换句话说,可以使用backbuffercopy复制屏幕的一个区域,然后在另一个区域上使用屏幕纹理。避免这种行为!

DEPTH_TEXTURE

对于3D着色,也可以访问屏幕深度缓冲区。为此,使用内置的深度纹理。此纹理不是线性的;必须通过反向投影矩阵进行转换。

以下代码检索所绘制像素下方的3D位置:

void fragment() {
    float depth = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).r;
    vec4 upos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
    vec3 pixel_position = upos.xyz / upos.w;
}