This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English
Les programmes shaders sont au cœur du rendu graphique. Ce sont des programmes écrits dans un langage proche du C appelé GLSL (GL Shading Language), que le matériel graphique exécute pour effectuer des opérations sur les données 3D sous-jacentes (les sommets) ou sur les pixels qui apparaissent à l’écran (les « fragments »). Les shaders servent à dessiner des sprites, à éclairer des modèles 3D, à créer des effets de post-traitement plein écran et à bien d’autres choses encore.
Ce manuel décrit comment le pipeline de rendu de Defold interagit avec les shaders du GPU. Pour créer des shaders pour votre contenu, vous devez également comprendre le concept de matériau ainsi que le fonctionnement du pipeline de rendu.
Les spécifications d’OpenGL ES 2.0 (OpenGL for Embedded Systems) et d’OpenGL ES Shading Language sont disponibles dans le registre OpenGL de Khronos.
Sur les ordinateurs de bureau, il est possible d’écrire des shaders qui utilisent des fonctionnalités indisponibles dans OpenGL ES 2.0. Le pilote de votre carte graphique peut parfaitement compiler et exécuter du code de shader qui ne fonctionnera pas sur les appareils mobiles.
Un shader de sommets reçoit en entrée des données de sommets (sous forme d’attributes) et des constantes (uniforms). Parmi les constantes courantes figurent les matrices nécessaires pour transformer et projeter la position d’un sommet dans l’espace écran.
La sortie du shader de sommets est la position calculée du sommet à l’écran (gl_Position). Il est également possible de transmettre des données du shader de sommets au shader de fragments au moyen de variables varying.
Un shader de fragments reçoit en entrée des constantes (uniforms) ainsi que les variables varying définies par le shader de sommets.
La sortie du shader de fragments est la valeur de couleur du fragment concerné (gl_FragColor).
Un shader de calcul reçoit en entrée des tampons de constantes (uniforms), des images de texture (image2D), des échantillonneurs (sampler2D) et des tampons de stockage (buffer).
La sortie du shader de calcul n’est pas définie explicitement : aucune sortie particulière ne doit être produite, contrairement aux shaders de sommets et de fragments. Les shaders de calcul étant généralistes, c’est au programmeur de définir le type de résultat que le shader de calcul doit produire.
Lorsqu’un modèle est placé dans le monde de jeu, les coordonnées locales de ses sommets doivent être converties en coordonnées monde. Cette conversion est effectuée par une matrice de transformation monde, qui indique la translation (déplacement), la rotation et l’échelle à appliquer aux sommets d’un modèle pour les placer correctement dans le système de coordonnées du monde de jeu.


position et texcoord0.position et texcoord0.position, textcoord0 et color.position, texcoord0 et color.position, texcoord0 et normal.position, texcoord0, face_color, outline_color et shadow_color.uniform dans le programme shader. Les variables uniformes d’échantillonneur sont ajoutées à la section Samplers du matériau, puis déclarées comme uniform dans le programme shader. Les matrices nécessaires pour effectuer les transformations de sommets dans un shader de sommets sont disponibles sous forme de constantes :
CONSTANT_TYPE_WORLD est la matrice monde qui transforme les coordonnées de l’espace local d’un objet en coordonnées de l’espace monde.CONSTANT_TYPE_VIEW est la matrice de vue qui transforme les coordonnées de l’espace monde en coordonnées de l’espace caméra.CONSTANT_TYPE_PROJECTION est la matrice de projection qui transforme les coordonnées de l’espace caméra en coordonnées de l’espace écran.CONSTANT_TYPE_WORLDVIEW, CONSTANT_TYPE_VIEWPROJ et CONSTANT_TYPE_WORLDVIEWPROJ fournissent les matrices combinées correspondantes.CONSTANT_TYPE_WORLD_INVERSE, CONSTANT_TYPE_VIEW_INVERSE, CONSTANT_TYPE_PROJECTION_INVERSE, CONSTANT_TYPE_VIEWPROJ_INVERSE, CONSTANT_TYPE_WORLDVIEW_INVERSE et CONSTANT_TYPE_WORLDVIEWPROJ_INVERSE fournissent les matrices inverses sans que le shader ait à les calculer.CONSTANT_TYPE_TIME est un vec4 fourni par le moteur : le temps écoulé depuis le démarrage du moteur dans .x, le temps écoulé depuis l’image précédente dans .y, et zéro dans .z et .w.CONSTANT_TYPE_USER est une constante de type vec4 que vous pouvez utiliser comme vous le souhaitez.Le manuel des matériaux explique comment spécifier les constantes.
sampler2D échantillonne une texture d’image 2D.sampler2DArray échantillonne une texture de tableau d’images 2D. Ce type est principalement utilisé pour les atlas paginés.samplerCube échantillonne une texture cubique composée de six images.image2D charge (et peut stocker) des données de texture dans un objet image. Ce type est principalement utilisé pour le stockage dans les shaders de calcul.Vous ne pouvez utiliser un échantillonneur que dans les fonctions de lecture de texture de la bibliothèque standard GLSL. Le manuel des matériaux explique comment spécifier les paramètres des échantillonneurs.

Une carte UV est généralement générée dans le logiciel de modélisation 3D et stockée dans le maillage. Les coordonnées de texture de chaque sommet sont fournies au shader de sommets sous forme d’attribut. Une variable varying sert ensuite à déterminer les coordonnées UV de chaque fragment, par interpolation des valeurs des sommets.
Varying servent à transmettre des informations entre l’étape de traitement des sommets et l’étape de traitement des fragments.

Par exemple, attribuer à une variable varying une valeur de couleur RVB de type vec3 à chaque sommet d’un triangle permet d’interpoler les couleurs sur toute la forme. De même, définir des coordonnées de lecture dans une texture (ou coordonnées UV) à chaque sommet d’un rectangle permet au shader de fragments de lire les valeurs de couleur de la texture sur toute la surface de la forme.

Comme le moteur Defold prend en charge plusieurs plateformes et API graphiques, les développeurs doivent pouvoir écrire facilement des shaders qui fonctionnent partout. Le pipeline de ressources y parvient principalement de deux manières (appelées désormais shader pipelines) :
À partir de Defold 1.9.2, il est recommandé d’écrire des shaders qui utilisent le nouveau pipeline. Pour cela, la plupart des shaders doivent être migrés vers des shaders écrits au minimum en version 140 (OpenGL 3.1). Pour migrer un shader, assurez-vous que les exigences suivantes sont respectées :
Placez au minimum #version 140 au début du shader :
#version 140
C’est ainsi que le pipeline du shader est choisi lors du build, ce qui vous permet de continuer à utiliser les anciens shaders. Si aucune directive de préprocesseur indiquant la version n’est trouvée, Defold utilise le pipeline historique.
Dans les shaders de sommets, remplacez le mot-clé attribute par in :
// instead of:
// attribute vec4 position;
// do:
in vec4 position;
Remarque : les shaders de fragments (et les shaders de calcul) ne reçoivent aucune donnée de sommet en entrée.
Dans les shaders de sommets, les variables interpolées doivent être précédées de out. Dans les shaders de fragments, elles deviennent des variables in :
// In a vertex shader, instead of:
// varying vec4 var_color;
// do:
out vec4 var_color;
// In a fragment shader, instead of:
// varying vec4 var_color;
// do:
in vec4 var_color;
Les types de variables uniformes opaques (échantillonneurs, images, compteurs atomiques, SSBO) ne nécessitent aucune migration ; vous pouvez les utiliser comme vous le faites aujourd’hui :
uniform sampler2D my_texture;
uniform image2D my_image;
Pour les types de variables uniformes non opaques, vous devez les placer dans un uniform block. Un bloc de variables uniformes est simplement un ensemble de variables uniformes, déclaré avec le mot-clé uniform :
uniform vertex_inputs
{
mat4 mtx_world;
mat4 mtx_proj;
mat4 mtx_view;
mat4 mtx_normal;
...
};
void main()
{
// Individual members of the uniform block can be used as-is
gl_Position = mtx_proj * mtx_view * mtx_world * vec4(position, 1.0);
}
Tous les membres du bloc de variables uniformes sont exposés aux matériaux et aux composants sous forme de constantes individuelles. Aucune migration n’est nécessaire pour utiliser les tampons de constantes de rendu, ni go.set et go.get.
Dans les shaders de fragments, gl_FragColor est obsolète à partir de la version 140. Utilisez out à la place :
// instead of:
// gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
// do:
out vec4 color_out;
void main()
{
color_out = vec4(1.0, 0.0, 0.0, 1.0);
}
Les fonctions spécifiques d’échantillonnage de texture, telles que texture2D et texture2DArray, n’existent plus. Utilisez simplement la fonction texture à la place :
uniform sampler2D my_texture;
uniform sampler2DArray my_texture_array;
// instead of:
// vec4 sampler_2d = texture2D(my_texture, uv);
// vec4 sampler_2d_array = texture2DArray(my_texture_array, vec3(uv, slice));
// do:
vec4 sampler_2d = texture(my_texture, uv);
vec4 sampler_2d_array = texture(my_texture_array, vec3(uv, slice));
Defold génère des qualificateurs globaux de précision par défaut lors de la compilation croisée des shaders vers GLSL ES. Les valeurs par défaut sont mediump pour les valeurs à virgule flottante et highp pour les entiers. Vous pouvez les modifier à l’aide des paramètres du projet GLSL ES Default Precision Float (shader.glsl_es_default_precision_float) et GLSL ES Default Precision Int (shader.glsl_es_default_precision_int) ; tous deux acceptent mediump ou highp.
Un qualificateur explicite sur une variable, une entrée ou une sortie a priorité sur la valeur globale par défaut générée. Dans les shaders de fragments OpenGL ES 2.0 et WebGL 1.0, highp n’est pas pris en charge par tous les appareils. Lorsque highp est choisi comme valeur globale par défaut, Defold le protège avec GL_FRAGMENT_PRECISION_HIGH et utilise mediump sur les appareils qui ne le prennent pas en charge.
Voici, comme dernier exemple appliquant l’ensemble de ces règles, les shaders de sprite intégrés convertis au nouveau format :
#version 140
uniform vx_uniforms
{
mat4 view_proj;
};
// positions are in world space
in vec4 position;
in vec2 texcoord0;
out vec2 var_texcoord0;
void main()
{
gl_Position = view_proj * vec4(position.xyz, 1.0);
var_texcoord0 = texcoord0;
}
#version 140
in vec2 var_texcoord0;
out vec4 color_out;
uniform sampler2D texture_sampler;
uniform fs_uniforms
{
vec4 tint;
};
void main()
{
// Premultiply alpha since all runtime textures already are
vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
color_out = texture(texture_sampler, var_texcoord0.xy) * tint_pm;
}
Les shaders de Defold permettent d’inclure du code source provenant de fichiers du projet portant l’extension .glsl. Pour inclure un fichier glsl depuis un shader, utilisez la directive #include avec des guillemets doubles ou des chevrons. Les chemins d’inclusion doivent être relatifs au projet ou au fichier qui effectue l’inclusion :
// In file /main/my-shader.fp
// Absolute path
#include "/main/my-snippet.glsl"
// The file is in the same folder
#include "my-snippet.glsl"
// The file is in a sub-folder on the same level as 'my-shader'
#include "sub-folder/my-snippet.glsl"
// The file is in a sub-folder on the parent directory, i.e /some-other-folder/my-snippet.glsl
#include "../some-other-folder/my-snippet.glsl"
// The file is on the parent directory, i.e /root-level-snippet.glsl
#include "../root-level-snippet.glsl"
La prise en compte des inclusions présente quelques particularités :
/const float #include "my-float-name.glsl" = 1.0 ne fonctionnera pasLes extraits de code peuvent eux-mêmes inclure d’autres fichiers .glsl. Le shader final peut donc contenir plusieurs fois le même code et, selon le contenu des fichiers, vous pouvez rencontrer des problèmes de compilation si les mêmes symboles sont déclarés plusieurs fois. Pour éviter cela, vous pouvez utiliser des gardes d’inclusion, un concept courant dans plusieurs langages de programmation. Exemple :
// In my-shader.vs
#include "math-functions.glsl"
#include "pi.glsl"
// In math-functions.glsl
#include "pi.glsl"
// In pi.glsl
const float PI = 3.14159265359;
Dans cet exemple, la constante PI sera définie deux fois, ce qui provoquera des erreurs de compilation lors de l’exécution du projet. Vous devriez donc protéger le contenu à l’aide de gardes d’inclusion :
// In pi.glsl
#ifndef PI_GLSL_H
#define PI_GLSL_H
const float PI = 3.14159265359;
#endif // PI_GLSL_H
Le code de pi.glsl sera développé deux fois dans my-shader.vs, mais comme vous l’avez entouré de gardes d’inclusion, le symbole PI ne sera défini qu’une seule fois et le shader sera compilé correctement.
Cependant, selon le cas d’utilisation, cette protection n’est pas toujours strictement nécessaire. Si vous souhaitez réutiliser du code localement dans une fonction ou à un autre endroit où les valeurs n’ont pas besoin d’être disponibles globalement dans le code du shader, vous devriez probablement vous passer de gardes d’inclusion. Exemple :
// In red-color.glsl
vec3 my_red_color = vec3(1.0, 0.0, 0.0);
// In my-shader.fp
vec3 get_red_color()
{
#include "red-color.glsl"
return my_red_color;
}
vec3 get_red_color_inverted()
{
#include "red-color.glsl"
return 1.0 - my_red_color;
}
Lorsque les shaders sont rendus dans la fenêtre d’affichage de l’éditeur Defold, une définition de préprocesseur EDITOR est disponible. Elle vous permet d’écrire du code de shader qui se comporte différemment dans l’éditeur et dans le moteur de jeu lui-même.
Cela est particulièrement utile pour :
Utilisez la directive de préprocesseur #ifdef EDITOR pour compiler conditionnellement le code qui ne doit s’exécuter que dans l’éditeur :
#ifdef EDITOR
// This code will only execute when the shader is rendered in the Defold Editor
color_out = vec4(1.0, 0.0, 1.0, 1.0); // Magenta color for editor preview
#else
// This code will execute when running in the game
color_out = texture(texture_sampler, var_texcoord0) * tint_pm;
#endif
Avant d’apparaître à l’écran, les données que vous créez pour votre jeu passent par une série d’étapes :

Tous les composants visuels (sprites, nœuds d’interface graphique, effets de particules ou modèles) sont constitués de sommets, des points du monde 3D qui décrivent la forme du composant. Cela permet d’observer la forme sous n’importe quel angle et à n’importe quelle distance. Le rôle du shader de sommets est de prendre un sommet et de le convertir en une position dans la fenêtre d’affichage pour que la forme puisse apparaître à l’écran. Pour une forme à quatre sommets, le programme shader de sommets s’exécute quatre fois, en parallèle.

Le programme reçoit en entrée la position du sommet (et les autres données d’attribut associées au sommet) et produit en sortie une nouvelle position de sommet (gl_Position), ainsi que les variables varying à interpoler pour chaque fragment.
Le programme shader de sommets le plus simple se contente de placer le sommet de sortie à l’origine (ce qui n’est pas très utile) :
void main()
{
gl_Position = vec4(0.0,0.0,0.0,1.0);
}
Un exemple plus complet est le shader de sommets intégré pour les sprites :
-- sprite.vp
uniform mediump mat4 view_proj; // [1]
attribute mediump vec4 position; // [2]
attribute mediump vec2 texcoord0;
varying mediump vec2 var_texcoord0; // [3]
void main()
{
gl_Position = view_proj * vec4(position.xyz, 1.0); // [4]
var_texcoord0 = texcoord0; // [5]
}
position est déjà transformé dans l’espace monde. texcoord0 contient les coordonnées UV du sommet.gl_Position reçoit la position de sortie du sommet actuel dans l’espace de projection. Cette valeur a quatre composantes : x, y, z et w. La composante w sert à calculer une interpolation corrigée pour la perspective. Cette valeur est normalement de 1.0 pour chaque sommet avant l’application d’une matrice de transformation.Après le traitement des sommets, la forme du composant à l’écran est déterminée : des primitives sont générées et rastérisées, c’est-à-dire que le matériel graphique divise chaque forme en fragments, ou pixels. Il exécute ensuite le programme shader de fragments une fois pour chaque fragment. Pour une image à l’écran de 16x24 pixels, le programme s’exécute 384 fois, en parallèle.

Le programme reçoit en entrée les données envoyées par le pipeline de rendu et le shader de sommets, généralement les coordonnées UV du fragment, les couleurs de teinte, etc. La sortie est la couleur finale du pixel (gl_FragColor).
Le programme shader de fragments le plus simple se contente de définir la couleur de chaque pixel sur le noir (là encore, ce programme n’est pas très utile) :
void main()
{
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
}
Là encore, un exemple plus complet est le shader de fragments intégré pour les sprites :
// sprite.fp
varying mediump vec2 var_texcoord0; // [1]
uniform lowp sampler2D DIFFUSE_TEXTURE; // [2]
uniform lowp vec4 tint; // [3]
void main()
{
lowp vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w); // [4]
lowp vec4 diff = texture2D(DIFFUSE_TEXTURE, var_texcoord0.xy);// [5]
gl_FragColor = diff * tint_pm; // [6]
}
sampler2D est déclarée. L’échantillonneur, associé aux coordonnées de texture interpolées, sert à lire la texture pour l’appliquer correctement au sprite. Comme il s’agit d’un sprite, le moteur associera cet échantillonneur à l’image définie dans la propriété Image du sprite.CONSTANT_TYPE_USER est définie dans le matériau et déclarée comme uniform. Sa valeur permet de teinter le sprite. La valeur par défaut est le blanc pur.gl_FragColor reçoit la couleur de sortie du fragment : la couleur diffuse de la texture multipliée par la valeur de teinte.La valeur du fragment obtenu est ensuite soumise à des tests. Un test courant est le test de profondeur, qui compare la profondeur du fragment à la valeur du tampon de profondeur pour le pixel testé. Selon le test, le fragment peut être rejeté ou une nouvelle valeur peut être écrite dans le tampon de profondeur. Ce test sert couramment à permettre aux éléments graphiques les plus proches de la caméra de masquer ceux qui sont plus éloignés.
Si le test conclut que le fragment doit être écrit dans le tampon d’image, il sera mélangé aux données du pixel déjà présentes dans le tampon. Les paramètres de mélange définis dans le script de rendu permettent de combiner de différentes manières la couleur source (la valeur écrite par le shader de fragments) et la couleur de destination (la couleur de l’image dans le tampon d’image). Le mélange sert couramment à rendre des objets transparents.
Shadertoy contient un très grand nombre de shaders proposés par les utilisateurs. C’est une excellente source d’inspiration pour découvrir diverses techniques de rendu par shaders. De nombreux shaders présentés sur le site peuvent être portés vers Defold avec très peu de travail. Le tutoriel Shadertoy détaille les étapes de conversion d’un shader existant vers Defold.
Le tutoriel sur l’étalonnage des couleurs montre comment créer un effet d’étalonnage des couleurs plein écran à l’aide de textures de tables de correspondance des couleurs.
The Book of Shaders vous apprendra à utiliser et à intégrer des shaders dans vos projets pour améliorer leurs performances et leur qualité graphique.