自訂後期處理

前言

Godot 提供許多內建的後期處理效果,包括泛光(Bloom)、景深(DOF)與螢幕空間環境光遮蔽(SSAO),詳見 環境與後製處理。然而,進階應用可能需要自訂效果。本文將說明如何撰寫自訂的後期處理效果。

實作自訂後期處理著色器最簡單的方法,是利用 Godot 內建的螢幕紋理讀取功能。如果你還不熟悉,建議先閱讀 螢幕讀取著色器教學

單通道後期處理

後期處理效果是 Godot 將影格算繪完成後套用到該影格的著色器。要將著色器套用到影格,請建立一個 CanvasLayer,並新增一個 ColorRect。將新的 ShaderMaterial 指派給新建的 ColorRect,並將 ColorRect 的錨點預設設為 Full Rect:

在 ColorRect 節點上將錨點預設設為 Full Rect

在 ColorRect 節點上將錨點預設設為 Full Rect

你的場景樹大致會長這樣:

../../_images/post_tree1.png

備註

另一種更有效率的方法,是使用 BackBufferCopy 將螢幕區域複製到緩衝區,然後透過帶有 hint_screen_texturesampler2D 在著色器腳本中存取。

備註

截至撰寫本文時,Godot 尚未支援同時渲染到多個緩衝區。你的後期處理著色器無法存取 Godot 未公開的其他渲染通道與緩衝區(如深度或法線/粗糙度)。你僅能存取經 Godot 以取樣器形式公開的已渲染畫面及緩衝區。

本範例將使用這張小羊的 Sprite

../../_images/post_example1.png

將新的 Shader 指定給 ColorRectShaderMaterial。你可以透過帶有 hint_screen_texturesampler2D 以及內建的 SCREEN_UV uniform 來存取該影格的紋理與 UV。

將以下程式碼複製到你的著色器。下方範例是來自 arlez80 的六角像素化著色器,

shader_type canvas_item;

uniform vec2 size = vec2(32.0, 28.0);
// If you intend to read from mipmaps with `textureLod()` LOD values greater than `0.0`,
// use `filter_nearest_mipmap` instead. This shader doesn't require it.
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

void fragment() {
        vec2 norm_size = size * SCREEN_PIXEL_SIZE;
        bool less_than_half = mod(SCREEN_UV.y / 2.0, norm_size.y) / norm_size.y < 0.5;
        vec2 uv = SCREEN_UV + vec2(norm_size.x * 0.5 * float(less_than_half), 0.0);
        vec2 center_uv = floor(uv / norm_size) * norm_size;
        vec2 norm_uv = mod(uv, norm_size) / norm_size;
        center_uv += mix(vec2(0.0, 0.0),
                         mix(mix(vec2(norm_size.x, -norm_size.y),
                                 vec2(0.0, -norm_size.y),
                                 float(norm_uv.x < 0.5)),
                             mix(vec2(0.0, -norm_size.y),
                                 vec2(-norm_size.x, -norm_size.y),
                                 float(norm_uv.x < 0.5)),
                             float(less_than_half)),
                         float(norm_uv.y < 0.3333333) * float(norm_uv.y / 0.3333333 < (abs(norm_uv.x - 0.5) * 2.0)));

        COLOR = textureLod(screen_texture, center_uv, 0.0);
}

小羊會呈現出下圖的效果:

../../_images/post_example2.png

多通道後期處理

某些後期處理效果(如模糊)相當消耗資源。如果將這些效果拆成多個階段處理,可以大幅提升效能。在多通道材質中,每一次通道會接收前一階段的結果進行處理。

要製作多通道後期處理著色器,可以堆疊多個 CanvasLayerColorRect 節點。在上述範例中,你利用 CanvasLayer 來以下層的畫面作為著色器輸入。除了節點結構不同外,其餘步驟與單通道後期處理著色器相同。

你的場景樹大致會長這樣:

../../_images/post_tree2.png

舉例來說,你可以將下列程式碼片段分別掛載在各個 ColorRect 上,實作全螢幕高斯模糊效果。著色器的執行順序取決於場景樹中 CanvasLayer 的排序,越上層的會先執行。對於這個模糊著色器來說,順序並不影響結果。

shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

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

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

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

執行上述程式碼後,你將會得到如下的全螢幕模糊效果。

../../_images/post_example3.png