Post-traitement personnalisé

Introduction

Godot fournit de nombreux effets de post-traitement prêts à l'emploi, notamment Bloom, DOF et SSAO. Parfois, vous voulez écrire votre propre effet personnalisé. Voici comment vous pouvez le faire.

Les effets de post-traitement sont des shaders appliqués à une image après que Godot l'ait rendue. Vous voulez d'abord rendre votre scène dans un Viewport, puis rendre le Viewport dans un ViewportTexture et le montrer à l'écran.

La façon la plus simple de mettre en œuvre un shader de post-traitement personnalisé est d'utiliser la capacité intégrée de Godot à lire la texture de l'écran. Si vous n'êtes pas familier avec cela, vous devriez lire le Screen Reading Shaders Tutorial d'abord.

Note

Au moment où nous écrivons ces lignes, Godot ne supporte pas le rendu vers plusieurs tampons en même temps. Votre shader de post-traitement n'aura pas accès aux normales ou aux autres passes de rendu. Vous n'avez accès qu'à la trame rendue.

Monopasse post-traitement

Vous aurez besoin d'un Viewport pour rendre votre scène, et d'une scène pour rendre votre Viewport à l'écran. Vous pouvez utiliser un ViewportContainer pour afficher votre Viewport sur l'écran entier ou à l'intérieur d'un autre Control nœud.

Note

Le rendu à l'aide d'un Viewport vous donne le contrôle sur la façon dont la scène est rendue, y compris la fréquence d'images, et vous pouvez utiliser le ViewportContainer pour rendre des objets 3D dans une scène 2D.

Pour cette démo, nous allons utiliser un Node2D avec un ViewportContainer et enfin un Viewport. Votre onglet Scene devrait ressembler à ça :

../../_images/post_hierarchy1.png

A l'intérieur du Viewport, vous pouvez avoir tout ce que vous voulez. Ceci contiendra votre scène principale. Pour ce tutoriel, nous utiliserons un champ de boites aléatoires :

../../_images/post_boxes.png

Ajouter un nouveau ShaderMaterial au ViewportContainer, et lui assigner une nouvelle ressource de shader. Vous pouvez accéder à votre Viewport rendu avec la TEXTURE uniforme intégré.

Note

Vous pouvez choisir de ne pas utiliser un ViewportContainer, mais si vous le faites, vous devrez créer votre propre uniforme dans le shader et passer la texture Viewport manuellement, comme ça :

// Inside the Shader.
uniform sampler2D ViewportTexture;

Et vous pouvez passer la texture dans le shader depuis GDScript comme ça :

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

Copiez le code suivant sur votre shader. Le code ci-dessus est un filtre de détection de bord à passe unique, un Filtre 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;
}

Note

Le filtre Sobel lit les pixels dans une grille 9x9 autour du pixel courant et les additionne en utilisant le poids. Ce qui le rend intéressant, c'est qu'il attribue des poids à chaque pixel ; +1 pour chacun des huit autour du centre et -8 pour le pixel central. Le choix des poids est appelé "kernel". Vous pouvez utiliser différents kernels pour créer des filtres de détection de bords, de contours et toutes sortes d'effets.

../../_images/post_outline.png

Post-traitement multi-passe

Certains effets de post-traitement comme le flou sont gourmands en ressources. Cependant, si vous les décomposez en plusieurs passes, vous pouvez les faire éxecuter beaucoup plus vite. Dans un materiel en plusieurs passes, chaque passe prend le résultat de la passe précédente comme entrée et le traite.

Pour faire un shader de post-traitement multi-passes, vous empilez des nœuds Viewport. Dans l'exemple ci-dessus, vous avez rendu le contenu d'un objet Viewport dans la racine Viewport, par l'intermédiaire d'un nœud ViewportContainer. Vous pouvez faire la même chose pour un shader multi-passes en rendant le contenu d'un Viewport dans un autre et ensuite en rendant ce dernier Viewport dans le Viewport racine.

Votre hiérarchie de scène devrait ressembler à cela :

../../_images/post_hierarchy2.png

Godot rendra le nœud Viewport du bas en premier. Donc si l'ordre des passes est important pour vos shaders, assurez-vous d'assigner le shader que vous voulez appliquer en premier au ViewportContainer le plus bas dans l'arbre.

Note

Vous pouvez également rendre vos Viewports séparément sans les imbriquer comme ceci. Il suffit d'utiliser deux Viewports et de les rendre l'un après l'autre.

En dehors de la structure des nœuds, les étapes sont les mêmes que pour le shader de post-traitement à un seul passage.

Par exemple, vous pouvez écrire un effet de flou gaussien plein écran en attachant les morceaux de code suivants à chacun des ViewportContainers. L'ordre dans lequel vous appliquez les shaders n'a pas d'importance :

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;
}

En utilisant le code ci-dessus, vous devriez vous retrouver avec un effet de flou plein écran comme ci-dessous.

../../_images/post_blur.png

Pour plus d'informations sur le fonctionnement des nœuds Viewport, voir le Tutoriel sur les Viewports.