Introduction aux shaders

Cette page explique ce que sont les shaders et vous donnera un aperçu de leur fonctionnement dans Godot. Pour une référence détaillée du langage de shader du moteur, voir Langue de shading.

Les shaders sont un type particulier de programme qui fonctionne sur les unités de traitement graphique (GPU). Ils étaient initialement utilisés pour ombrer les scènes 3D mais peuvent aujourd'hui faire beaucoup plus. Vous pouvez les utiliser pour contrôler la façon dont le moteur dessine la géométrie et les pixels à l'écran, ce qui vous permet d'obtenir toutes sortes d'effets.

Les moteurs de rendu modernes comme Godot dessinent tout avec des shaders : les cartes graphiques peuvent exécuter des milliers d'instructions en parallèle, ce qui permet une vitesse de rendu incroyable.

Cependant, en raison de leur nature parallèle, les shaders ne traitent pas les informations comme le fait un programme classique. Le code du shader s'exécute sur chaque sommet ou pixel de manière isolée. Vous ne pouvez pas non plus stocker des données entre les images. Par conséquent, lorsque vous travaillez avec des shaders, vous devez coder et penser différemment des autres langages de programmation.

Supposons que vous vouliez mettre à jour tous les pixels d'une texture à une couleur donnée. En GDScript, votre code utiliserait des boucles for :

for x in range(width):
  for y in range(height):
    set_color(x, y, some_color)

Votre code fait déjà partie d'une boucle dans un shader, donc le code correspondant ressemblerait à ceci.

void fragment() {
  COLOR = some_color;
}

Note

La carte graphique appelle la fonction fragment() une fois ou plus pour chaque pixel qu'elle doit dessiner. Nous y reviendrons plus loin.

Shaders dans Godot

Godot fournit un langage de shader basé sur le populaire langage de shader OpenGL (GLSL), mais simplifié. Le moteur gère une partie du travail d'initialisation de bas niveau pour vous, ce qui facilite l'écriture de shaders complexes.

Dans Godot, les shaders sont constitués de trois fonctions principales : vertex(), fragment(), et light().

  1. La fonction vertex() s'exécute pour tous les sommets de la mesh et définit leur position ainsi que quelques autres variables par sommets.

  2. La fonction fragment() s'exécute pour chaque pixel couvert par la mesh. Elle utilise les valeurs de sorties de la fonction vertex(), interpolées entre les sommets.

  3. La fonction light() s'exécute pour chaque pixel et chaque lumière. Elle prends des variables de la fonction fragment() et de ses exécutions précédentes.

Avertissement

La fonction light() ne sera pas exécutée si le mode de rendu vertex_lighting est activé, ou si Rendering > Quality > Shading > Force Vertex Shading est activé dans les paramètres du projet. C'est activée par défaut sur les plateformes mobiles.

Types de shader

Au lieu de fournir une configuration générale pour tous les usages (2D, 3D, particules), vous devez spécifier le type de shader que vous écrivez. Différents types supportent différents modes de rendu, variables intégrées et fonctions de traitement.

Dans Godot, tous les shaders doivent spécifier leur type dans la première ligne, comme ceci :

shader_type spatial;

Voici les types disponibles :

Mode de rendu

Les shaders ont des modes de rendu optionnels que vous pouvez spécifier sur la deuxième ligne, après le type de shader, comme suit :

shader_type spatial;
render_mode unshaded, cull_disabled;

Les modes de rendu modifient la façon dont Godot applique le shader. Par exemple, le mode unshaded fait ignorer au moteur la fonction intégrée de processeur de lumière.

Chaque type de shader possède différents modes de rendu. Consultez la référence de chaque type de shader pour obtenir une liste complète des modes de rendu.

Fonctions processeur

Selon le type de shader, vous pouvez réécrire différentes fonctions de traitement. Pour spatial et canvas_item, vous avez accès à vertex(), fragment(), et light(). Pour particles, vous avez uniquement accès à vertex().

Processeur de sommet

La fonction de traitement vertex() est appelée une fois pour chaque vertex dans les shaders spatial et canvas_item. Pour les shaders particles, elle est appelé une fois pour chaque particule.

Chaque sommet de la géométrie de votre monde possède des propriétés telles qu'une position et une couleur. Cette fonction modifie ces valeurs et les transmet à la fonction fragment. Vous pouvez également l'utiliser pour envoyer des données supplémentaires à la fonction fragment en utilisant des varyings.

Par défaut, Godot transforme vos informations de vertex pour vous, ce qui est nécessaire pour projeter la géométrie sur l'écran. Vous pouvez utiliser les modes de rendu pour transformer les données vous-même ; voir le Spatial shader doc pour un exemple.

Processeur de fragments

La fonction de traitement fragment() est utilisée pour définir les paramètres des matériaux Godot pour chaque pixel. Ce code s'exécute sur chaque pixel visible que l'objet ou la primitive dessine. Il est uniquement disponible dans les shaders spatial et canvas_item.

L'utilisation standard de la fonction de fragment consiste à définir les propriétés des matériaux qui seront utilisées pour calculer l'éclairage. Par exemple, vous pouvez définir des valeurs pour ROUGHNESS, RIM, ou TRANSMISSION qui indiquent à la fonction de lumière comment les lumières réagissent à ce fragment. Cela permet de contrôler un pipeline de shading complexe sans que l'utilisateur n'ait à écrire beaucoup de code. Si vous n'avez pas besoin de cette fonctionnalité intégrée, vous pouvez l'ignorer et écrire votre propre fonction de traitement de la lumière et Godot l'optimisera. Par exemple, si vous n'écrivez pas de valeur à RIM, Godot ne calculera pas l'éclairage rim. Lors de la compilation, Godot vérifie si RIM est utilisé ; si ce n'est pas le cas, il coupe tout le code correspondant. Par conséquent, vous ne gaspillerez pas les calculs sur des effets que vous n'utilisez pas.

Processeur de lumière

Le traitement light() fonctionne également par pixel, et il s'exécute une fois pour chaque lumière qui affecte l'objet. Il ne s'exécute pas si aucune lumière n'affecte l'objet. Il existe sous la forme d'une fonction appelée à l'intérieur du traitement fragment() et opère généralement sur les propriétés du matériau configurées à l'intérieur de la fonction fragment().

Le traitement light() fonctionne différemment en 2D et en 3D ; pour une description du fonctionnement de chacun, consultez leur documentation, CanvasItem shaders et Spatial shaders, respectivement.