自定义后期处理

简介

Godot provides many post-processing effects out of the box, including Bloom, DOF, and SSAO. Sometimes you want to write your own custom effect. Here's how you can do so.

后处理效果是在Godot渲染后应用于帧的着色器。 首先想要将场景渲染为 Viewport,然后在 ViewportTexture 中渲染 Viewport 并在屏幕上显示它。

实现自定义后处理着色器的最简单方法是使用Godot的内置功能从屏幕纹理中读取。 如果您不熟悉这个,您应该先阅读 屏幕阅读着色器教程

注解

As of the time of writing, Godot does not support rendering to multiple buffers at the same time. Your post-processing shader will not have access to normals or other render passes. You only have access to the rendered frame.

单通后处理

您需要一个 Viewport 来渲染场景,一个场景在屏幕上渲染您的 Viewport 。 您可以使用 ViewportContainer 在整个屏幕上或另一个屏幕内显示您的``Viewport`` Control 节点。

注解

Rendering using a Viewport gives you control over how the scene render, including the framerate, and you can use the ViewportContainer to render 3D objects in a 2D scene.

For this demo, we will use a Node2D with a ViewportContainer and finally a Viewport. Your Scene tab should look like this:

../../_images/post_hierarchy1.png

Inside the Viewport, you can have whatever you want. This will contain your main scene. For this tutorial, we will use a field of random boxes:

../../_images/post_boxes.png

Add a new ShaderMaterial to the ViewportContainer, and assign a new shader resource to it. You can access your rendered Viewport with the built-in TEXTURE uniform.

注解

You can choose not to use a ViewportContainer, but if you do so, you will need to create your own uniform in the shader and pass the Viewport texture in manually, like so:

// Inside the Shader.
uniform sampler2D ViewportTexture;

您可以将纹理从GDScript传递到着色器中,如下所示:

# In GDScript.
func _ready():
  $Sprite.material.set_shader_param("ViewportTexture", $Viewport.get_texture())

将以下代码复制到着色器。 上面的代码是单通道边缘检测滤波器, Sobel滤波器

shader_type canvas_item;

void fragment() {
    vec3 col = -8.0 * texture(TEXTURE, SCREEN_UV).xyz;
    col += texture(TEXTURE, SCREEN_UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz;
    col += texture(TEXTURE, SCREEN_UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz;
    col += texture(TEXTURE, SCREEN_UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz;
    col += texture(TEXTURE, SCREEN_UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz;
    col += texture(TEXTURE, SCREEN_UV + SCREEN_PIXEL_SIZE.xy).xyz;
    col += texture(TEXTURE, SCREEN_UV - SCREEN_PIXEL_SIZE.xy).xyz;
    col += texture(TEXTURE, SCREEN_UV + vec2(-SCREEN_PIXEL_SIZE.x, SCREEN_PIXEL_SIZE.y)).xyz;
    col += texture(TEXTURE, SCREEN_UV + vec2(SCREEN_PIXEL_SIZE.x, -SCREEN_PIXEL_SIZE.y)).xyz;
    COLOR.xyz = col;
}

注解

Sobel滤波器读取当前像素周围9x9网格中的像素,并使用权重将它们加在一起。 令人感兴趣的是它为每个像素分配权重; 围绕中心的八个中的每一个都是+1,对于中心像素是-8。 权重的选择称为“核心”。 您可以使用不同的内核来创建边缘检测过滤器,轮廓和各种效果。

../../_images/post_outline.png

多通后处理

像模糊这样的后处理效果是资源密集型的。 但是如果您在多次通过中将它们分解,您可以让它们运行得更快。 在多通道材质中,每次传递都将前一次传递的结果作为输入并对其进行处理。

To make a multi-pass post-processing shader, you stack Viewport nodes. In the example above, you rendered the content of one Viewport object into the root Viewport, through a ViewportContainer node. You can do the same thing for a multi-pass shader by rendering the content of one Viewport into another and then rendering the last Viewport into the root Viewport.

您的场景层次结构将如下所示:

../../_images/post_hierarchy2.png

Godot将首先渲染底部的 Viewport 节点。 因此,如果遍历的顺序对着色器很重要,请确保将要首先应用的着色器指定给树中最低的 ViewportContainer

注解

您也可以单独渲染视区,而不必像这样嵌套它们。 您只需要使用两个视区并一个接一个地渲染它们。

Apart from the node structure, the steps are the same as with the single-pass post-processing shader.

例如,您可以通过将以下代码段附加到以下每个代码来编写全屏高斯模糊效果 ViewportContainers。 应用着色器的顺序无关紧要:

shader_type canvas_item;

// Blurs the screen in the X-direction.
void fragment() {
    vec3 col = texture(TEXTURE, SCREEN_UV).xyz * 0.16;
    col += texture(TEXTURE, SCREEN_UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15;
    col += texture(TEXTURE, SCREEN_UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15;
    col += texture(TEXTURE, SCREEN_UV + vec2(2.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12;
    col += texture(TEXTURE, SCREEN_UV + vec2(2.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12;
    col += texture(TEXTURE, SCREEN_UV + vec2(3.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09;
    col += texture(TEXTURE, SCREEN_UV + vec2(3.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09;
    col += texture(TEXTURE, SCREEN_UV + vec2(4.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
    col += texture(TEXTURE, SCREEN_UV + vec2(4.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
    COLOR.xyz = col;
}
shader_type canvas_item;

// Blurs the screen in the Y-direction.
void fragment() {
    vec3 col = texture(TEXTURE, SCREEN_UV).xyz * 0.16;
    col += texture(TEXTURE, SCREEN_UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz * 0.15;
    col += texture(TEXTURE, SCREEN_UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz * 0.15;
    col += texture(TEXTURE, SCREEN_UV + vec2(0.0, 2.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.12;
    col += texture(TEXTURE, SCREEN_UV + vec2(0.0, 2.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.12;
    col += texture(TEXTURE, SCREEN_UV + vec2(0.0, 3.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.09;
    col += texture(TEXTURE, SCREEN_UV + vec2(0.0, 3.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.09;
    col += texture(TEXTURE, SCREEN_UV + vec2(0.0, 4.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
    col += texture(TEXTURE, SCREEN_UV + vec2(0.0, 4.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
    COLOR.xyz = col;
}

Using the above code, you should end up with a full screen blur effect like below.

../../_images/post_blur.png

For more information on how Viewport nodes work, see the Viewports Tutorial.