Vous voulez faire des shaders dans Godot 4.x sans écrire de code, et quand même comprendre “ce qu’il y a derrière” ? Accrochez-vous, c’est exactement ce qu’on va faire ensemble : on démarre en Visual Shader Editor (no-code, hyper accessible), on construit trois effets concrets (eau/lave 2D, outline, dissolve), puis on bascule doucement vers ShaderLang (le langage textuel) pour que tout devienne clair (et modifiable à fond).
1) Prérequis et mise en place (5 minutes, chrono)
L’idée est simple : un shader s’applique via un ShaderMaterial. Dans ce matériau, vous mettez soit un VisualShader (graphique, en nœuds), soit un Shader (texte, ShaderLang). La logique est la même dans les deux cas : on fabrique une couleur finale pixel par pixel, et Godot l’applique sur votre Sprite2D.
Créer la scène de test
On va volontairement faire minimal : l’objectif est d’avoir un Sprite2D visible à l’écran, parce qu’un shader sans image à afficher… c’est vite frustrant.
- Créez un projet Godot 4.x (vide).
- Faites une scène 2D.
- Ajoutez un Sprite2D (glissez une texture dedans, sinon vous ne verrez pas grand-chose).
Attacher un ShaderMaterial au Sprite
Ici, vous “branchez” le shader sur votre Sprite2D. Retenez juste cette idée : le Sprite n’a pas un shader directement, il a un Material, et ce Material contient un shader.
- Sélectionnez le Sprite2D
- Inspecteur → CanvasItem > Material
- Cliquez sur le champ vide → New ShaderMaterial
- Dans ce ShaderMaterial, cliquez sur Shader → New VisualShader
- Dans le VisualShader, mettez Type = Canvas Item (c’est le type pour Sprite2D, UI, etc.)
Comprendre vite fait l’éditeur (sans se perdre)
Le Visual Shader Editor peut impressionner au début, mais en pratique vous n’avez besoin que de trois concepts.
D’abord, le graphe : c’est votre recette. Ensuite, les nœuds : ce sont les ingrédients (UV, temps, bruit, mix…). Enfin, Output (Fragment) : c’est la sortie finale, donc la couleur de chaque pixel du sprite.
- Le nœud Output (Fragment), c’est le résultat final.
- Activez la preview (icône œil) : ça permet de voir ce que vous faites en temps réel, sinon vous travaillez un peu à l’aveugle.
Astuces débutant (qui sauvent la vie)
Le quotidien dans l’éditeur se résume souvent à ça :
- Clic droit dans le graphe = ajouter un nœud.
- Molette = zoom (et oui, ça devient vite un grand tableau).
- Les ports ont des couleurs selon le type (scalaire, vecteur, couleur…). Si vous bloquez, Godot vous dira souvent “mismatch type”.
Le truc important : le Visual Shader génère du code ShaderLang automatiquement. Vous pouvez afficher le “code généré” pour apprendre. Gardez juste en tête que ce code n’est pas forcément “joli” : il est surtout correct.
2) Premier shader visuel : comprendre les bases (dégradé simple via UV)
Avant de faire des effets “waouh”, on va faire un shader qui donne un résultat immédiat et qui explique un concept central : les UV. Les UV sont des coordonnées 2D (X,Y) qui vont de 0 à 1 sur votre texture. Si vous comprenez ça, vous comprenez déjà une énorme partie des shaders 2D.
Objectif
On veut une couleur basée sur UV :
- Rouge = UV.x
- Vert = UV.y
- Bleu = constant (par exemple 0.5)
- Alpha = 1
L’intérêt ? Vous “voyez” les UV directement sur le sprite : rouge vers la droite, vert vers le bas (selon convention), etc.
Visual Shader (pas à pas)
Commencez par repérer Output (Fragment) : c’est là que vous allez connecter votre couleur finale.
Ensuite :
- Ajoutez un nœud Input > UV (coordonnées 2D).
- Fabriquez une couleur vec4 (R,G,B,A) :
- UV.x pour le rouge
- UV.y pour le vert
- un Float Constant à
0.5pour le bleu - un
1.0pour l’alpha
- Combinez tout via un nœud de composition (selon les nœuds disponibles : Vector/Compose, Color/Compose, etc.).
- Connectez le résultat sur la sortie couleur (souvent Albedo ou directement Color selon l’output CanvasItem).
Résultat : vous voyez un dégradé. Et surtout, vous comprenez que UV est une “carte” du sprite, pas la couleur du sprite.
Équivalent ShaderLang
shader_type canvas_item;
void fragment() {
COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
}
À retenir : UV et COLOR, c’est des “built-ins” (variables déjà dispo). C’est simple, mais c’est puissant.
3) Cas concret n°1 : eau / lave 2D (ondulation + animation + bruit)
L’effet “eau/lave”, c’est un classique : on ne modifie pas la géométrie du sprite, on triche en déformant les UV. En gros, on dit au shader : “au lieu de lire la texture à UV, lis-la à UV + petit décalage”. Et si ce décalage varie avec le temps, vous obtenez une animation.
Ce qu’on fabrique (l’idée)
La recette mentale est la suivante :
- On part de
UV - On ajoute une oscillation
sin(TIME * speed) * amplitude - On ajoute un bruit (procédural ou texture de bruit) pour casser l’aspect trop régulier
- On échantillonne la texture du sprite avec ces UV modifiés
- On applique une teinte (bleu eau, rouge lave)
Visual Shader : pipeline conseillé
Dans le graphe, vous allez construire ce pipeline, étape par étape.
- Prenez Input > UV.
- Récupérez le temps via Time > Time (ou un nœud équivalent pour TIME).
- Passez le temps dans un Sin : ça donne une variation régulière.
- Multipliez par un Float Constant (amplitude, par exemple
0.05à0.15) : ça règle “combien ça bouge”. - Transformez ça en décalage UV :
- soit vous décalez seulement X :
(sin * amp, 0) - soit vous faites X et Y différemment (plus vivant)
- soit vous décalez seulement X :
- Ajoutez ce décalage à UV (nœud Add).
- Ajoutez ensuite un Noise (via NoiseTexture2D ou via sampling d’une texture de bruit). L’astuce classique pour “faire couler” le bruit est de le faire défiler dans le temps, par exemple
UV + TIME * vitesse. - Additionnez une petite part de bruit au UV (genre
* 0.02) : c’est souvent la différence entre “trop propre” et “organique”. - Échantillonnez la texture du sprite (nœud texture/canvas texture).
- Teintez avec un Multiply par une couleur (bleu eau ou rouge lave).
- Connectez au Output Fragment.
Paramètres (uniforms) à exposer
Le but est de pouvoir régler l’effet dans l’inspecteur sans toucher au graphe.
speed(vitesse)amplitude(force de l’ondulation)noise_strength(impact du bruit)tint(couleur)
Dans Visual, ça passe par des nœuds de paramètres (type “Uniform/Parameter”), selon l’interface exacte. L’idée à retenir : “ce slider dans l’inspecteur contrôle mon shader”.
Équivalent ShaderLang (version propre et cohérente)
shader_type canvas_item;
uniform float speed : hint_range(0.0, 5.0) = 1.0;
uniform float amplitude : hint_range(0.0, 0.2) = 0.08;
uniform float noise_strength : hint_range(0.0, 0.1) = 0.02;
uniform vec4 tint : source_color = vec4(0.2, 0.6, 1.0, 1.0);
// Option 1: un bruit via une texture (recommandé en jeu)
uniform sampler2D noise_tex;
void fragment() {
vec2 uv = UV;
float wave = sin(TIME * speed) * amplitude;
uv += vec2(wave, 0.0);
// Bruit qui défile
vec2 noise_uv = UV * 2.0 + vec2(TIME * 0.2 * speed, TIME * 0.15 * speed);
float n = texture(noise_tex, noise_uv).r; // 0..1
n = (n * 2.0 - 1.0); // -1..1
uv += vec2(n, n) * noise_strength;
vec4 tex = texture(TEXTURE, uv);
COLOR = tex * tint;
}
Note importante (vraiment) : utiliser SCREEN_TEXTURE pour faire du bruit “comme ça” n’est pas le même délire (c’est un grab de l’écran, pas une vraie noise map). Pour un effet eau/lave sur un sprite, un noise_tex est généralement plus logique (et plus contrôlable).
4) Cas concret n°2 : outline sur un sprite (contour lumineux)
L’outline 2D “propre”, c’est souvent un petit piège : mesurer “la distance au centre” ne détecte pas le contour réel du sprite (ça fait plutôt un anneau). Le contour, le vrai, se fait en regardant l’alpha de la texture autour du pixel (sampling des voisins). En gros : si le pixel courant est transparent mais qu’un voisin est opaque, alors vous êtes sur un bord.
Ce qu’on fabrique (idée)
On va :
- lire l’alpha du pixel courant
- lire l’alpha autour (haut/bas/gauche/droite, voire diagonales)
- décider “outline ou pas” à partir de cette comparaison
C’est une logique simple, mais elle marche très bien en 2D.
Visual Shader : méthode robuste
Dans le graphe, l’enjeu principal est de fabriquer des UV “voisins”, puis d’échantillonner la texture plusieurs fois.
- Prenez UV.
- Calculez un “pas” en UV correspondant à l’épaisseur.
- Version simple : un uniforme
thicknessen UV. - Version plus propre : calculer selon la taille texture (mais c’est une étape en plus).
- Version simple : un uniforme
- Samplez la texture à :
UVUV + (thickness, 0)UV + (-thickness, 0)UV + (0, thickness)UV + (0, -thickness)
- Récupérez les alpha de ces samples.
- Calculez un masque, par exemple avec
max(neighbor_alpha) - center_alphaou une logique de seuil. - Faites un mix :
- si edge > 0 : outline_color
- sinon : couleur normale
Équivalent ShaderLang (simple et efficace)
shader_type canvas_item;
uniform vec4 outline_color : source_color = vec4(1.0, 0.2, 0.2, 1.0);
uniform float thickness : hint_range(0.001, 0.05) = 0.01;
void fragment() {
vec4 c = texture(TEXTURE, UV);
float a = c.a;
float ar = texture(TEXTURE, UV + vec2(thickness, 0.0)).a;
float al = texture(TEXTURE, UV + vec2(-thickness, 0.0)).a;
float au = texture(TEXTURE, UV + vec2(0.0, thickness)).a;
float ad = texture(TEXTURE, UV + vec2(0.0, -thickness)).a;
float neighbor = max(max(ar, al), max(au, ad));
// Outline là où le centre est transparent mais un voisin est opaque
float outline_mask = step(0.01, neighbor) * (1.0 - step(0.01, a));
vec4 out_col = mix(c, outline_color, outline_mask);
// On garde un alpha cohérent (outline visible)
out_col.a = max(c.a, outline_mask * outline_color.a);
COLOR = out_col;
}
Ce qui est “top, mais…” : l’épaisseur en UV dépend de la taille du sprite/texture. Donc si vous changez l’échelle ou si vos textures ont des résolutions très différentes, votre outline semblera plus fin ou plus gros. Pour un outline constant en pixels, on peut calculer des offsets en fonction de la taille (faisable, mais c’est une étape en plus).
5) Cas concret n°3 : dissolve / transition (fondu “organique” au bruit)
Le dissolve, c’est un masque de bruit + un slider amount. Chaque pixel compare sa valeur de bruit à un seuil. En-dessous : il disparaît. Au-dessus : il reste. Et si vous remplacez la comparaison “binaire” par un smoothstep, vous obtenez une transition plus douce, plus “organique”.
Visual Shader : logique claire
Le pipeline est très direct :
- Prenez UV.
- Samplez un bruit (NoiseTexture2D ou
noise_tex).- Pour animer le dissolve : faites défiler le bruit, par exemple
noise_uv = UV + TIME * speed.
- Pour animer le dissolve : faites défiler le bruit, par exemple
- Comparez bruit vs
dissolve_amount. - Utilisez smoothstep pour obtenir une zone de transition (au lieu d’un cut net).
- Appliquez le masque :
- soit sur l’alpha (doux, plus “UI-friendly”)
- soit via discard (cut net, très “FX”)
Teinte de bord (option stylée)
La bordure “brûlure” est souvent ce qui rend l’effet vraiment cool : vous colorez uniquement la petite bande autour du seuil. En pratique, on construit une “bande” avec deux smoothstep et on soustrait l’un de l’autre.
Équivalent ShaderLang (propre, avec bord)
shader_type canvas_item;
uniform sampler2D noise_tex;
uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.5;
uniform float edge_width : hint_range(0.0, 0.2) = 0.05;
uniform vec4 edge_color : source_color = vec4(1.0, 0.8, 0.1, 1.0);
void fragment() {
vec4 tex = texture(TEXTURE, UV);
float n = texture(noise_tex, UV * 2.0 + vec2(TIME * 0.1, TIME * 0.07)).r;
// masque principal (0 = visible, 1 = dissous) ou l’inverse selon votre goût
float d = smoothstep(dissolve_amount - edge_width, dissolve_amount + edge_width, n);
// Bande d’edge (zone fine autour du seuil)
float edge = smoothstep(dissolve_amount - edge_width, dissolve_amount, n)
- smoothstep(dissolve_amount, dissolve_amount + edge_width, n);
vec4 col = tex;
// Ajoute une coloration de bord
col.rgb = mix(col.rgb, edge_color.rgb, edge * edge_color.a);
// Transparence selon dissolve
col.a *= (1.0 - d);
// Cut net si vous voulez (sinon laissez tomber)
if (col.a < 0.01) discard;
COLOR = col;
}
Le détail qui change tout : discard, c’est brutal (et stylé). Sans discard, vous gardez une transparence progressive (plus “propre” pour des transitions UI ou des sprites avec antialiasing).
6) Passer du Visual Shader à ShaderLang (sans se faire peur)
Vous vous demandez peut-être : “ok, je sais faire en nœuds… pourquoi apprendre ShaderLang ?” Parce que dès que vos graphes grossissent, vous allez ressentir trois limites : ça devient plus lent à éditer, moins flexible (difficile de factoriser/réutiliser), et moins lisible quand on cherche un détail précis.
ShaderLang, c’est l’étape où vous reprenez le contrôle.
La passerelle la plus simple
La méthode la plus douce est toujours la même :
- Dans le Visual Shader, affichez le code généré.
- Copiez-le dans un Shader (texte).
- Puis simplifiez / nettoyez / paramétrez.
C’est une aventure classique : d’abord on “bricole visuellement”, ensuite on “reprend le contrôle”.
Ce qu’il faut connaître (les essentiels)
Vous n’avez pas besoin de tout apprendre d’un coup. Pour 90% des shaders 2D simples, ces points suffisent :
shader_type canvas_item;pour Sprite2D/UI.fragment()pour la couleur (pixels).vertex()pour bouger des sommets (effets de vague, wobble, etc.).- Les built-ins :
UV(coord texture)TIME(temps en secondes)TEXTURE(texture du Sprite2D)COLOR(sortie finale)
- Les
uniform, c’est vos paramètres exposés à l’inspecteur (slider, couleur, texture).
Pièges fréquents (et oui, ça arrive à tout le monde)
- UV est en 0..1, donc une “épaisseur” fixe en UV change selon la taille affichée.
- Le bruit : procédural vs texture. En 2D sprite, une noise texture est souvent la voie la plus simple (et stable).
- CanvasItem vs Spatial : les nœuds et built-ins ne sont pas les mêmes (donc vérifiez bien le type du shader).
- Les shaders, c’est rapide, mais un outline qui sample 9 fois la texture sur 200 sprites, c’est “pas si sûr” côté perf (à doser).
7) Mini check-list pour intégrer ces shaders dans un vrai projet
Quand vous sortez de la scène de test, l’erreur classique est de tout mélanger et de perdre le contrôle des variantes. Le plus simple est d’être un peu “organisé” dès le début.
- Dupliquez votre ShaderMaterial pour créer des variantes (eau1, eau2, lave, etc.).
- Exposez 3–5 uniforms max au début (sinon vous passez votre vie à tweaker).
- Gardez un sprite “laboratoire” dans une scène test (ça évite de casser votre jeu).
- Une fois l’effet validé, passez en ShaderLang si vous avez besoin de :
- nettoyage
- optimisation
- logique plus avancée (bords, masques, fonctions utilitaires)
Si vous me dites votre cas exact (jeu pixel art ? UI ? sprites HD ? taille typique des textures ?), je peux vous proposer une version “prête à copier-coller” de chaque shader (avec des uniforms adaptés et une épaisseur d’outline constante en pixels, par exemple).