Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Votre deuxième shader 3D

Au plus haut niveau, ce que fait Godot, c'est donner à l'utilisateur un tas de paramètres qui peuvent être définis de manière optionnelle (AO, SSS_Strength, RIM, etc.). Ces paramètres correspondent à différents effets complexes (Ambient Occlusion, SubSurface Scattering, Rim Lighting, etc.) Lorsqu'il n'est pas écrit, le code est jeté avant d'être compilé et le shader n'encourt donc pas le coût de la fonction supplémentaire. Il est ainsi facile pour les utilisateurs d'avoir des shaders complexes PBR-correct, sans avoir à écrire des shaders complexes. Bien sûr, Godot vous permet également d'ignorer tous ces paramètres et d'écrire un shader entièrement personnalisé.

Pour une liste complète de ces paramètres, voir le document de référence spatial shader.

A difference between the vertex function and a fragment function is that the vertex function runs per vertex and sets properties such as VERTEX (position) and NORMAL, while the fragment shader runs per pixel and, most importantly, sets the ALBEDO color of the MeshInstance3D.

Votre première fonction fragment spatial

Comme mentionné dans la partie précédente de ce tutoriel. L'utilisation standard de la fonction fragment dans Godot est de mettre en place différentes propriétés matérielles et de laisser Godot s'occuper du reste. Afin d'offrir encore plus de flexibilité, Godot fournit également des choses appelées modes de rendu. Les modes de rendu sont définis en haut du shader, directement en dessous de shader_type, et ils spécifient le type de fonctionnalité que vous voulez que les aspects intégrés du shader aient.

Par exemple, si vous ne voulez pas que les lumières affectent un objet, réglez le mode de rendu sur unshaded :

render_mode unshaded;

Vous pouvez également empiler plusieurs modes de rendu. Par exemple, si vous voulez utiliser toon au lieu de PBR plus réaliste, réglez le mode diffus et le mode spéculaire sur toon :

render_mode diffuse_toon, specular_toon;

Ce modèle de fonctionnalité intégrée vous permet d'écrire des shaders personnalisés complexes en ne modifiant que quelques paramètres.

Pour une liste complète des modes de rendu, voir le Spatial shader reference.

Dans cette partie du tutoriel, nous allons voir comment prendre le terrain bosselé de la partie précédente et le transformer en océan.

Commençons par définir la couleur de l'eau. Nous le faisons en définissant ALBEDO.

ALBEDO est un vec3 qui contient la couleur de l'objet.

Mettons une belle nuance de bleu.

void fragment() {
  ALBEDO = vec3(0.1, 0.3, 0.5);
}
../../../_images/albedo.png

Nous l'avons réglé sur une teinte de bleu très sombre car la plus grande partie du bleu de l'eau proviendra des reflets du ciel.

Le modèle de PBR que Godot utilise repose sur deux paramètres principaux : METALLIC et ROUGHNESS.

Le terme ROUGHNESS indique le degré de lissage ou de rugosité de la surface d'un matériau. Une faible ROUGHNESS fait apparaître un matériau comme un plastique brillant, tandis qu'une forte rugosité fait apparaître un matériau de couleur plus solide.

Le terme METALLIC indique à quel point l'objet ressemble à un métal. Il est préférable qu'il soit proche de 0 ou 1. Pensez que le METALLIC modifie l'équilibre entre le reflet et la couleur ALBEDO. Un haut METALLIC ignore presque complètement ALBEDO et ressemble à un miroir du ciel. Alors qu'un METALLIC bas a une représentation plus égale de la couleur du ciel et de la couleur ALBEDO.

ROUGHNESS passe de 0 à 1 de gauche à droite, tandis que la METALLIC passe de 0 à 1 de haut en bas.

../../../_images/PBR.png

Note

METALLIC devrait être proche de 0 ou 1 pour un shading PBR correct. Il ne doit être placé entre eux que pour les mélanges entre matériaux.

L'eau n'est pas un métal, nous allons donc fixer sa propriété METALLIC à 0.0. L'eau est également très réfléchissante, aussi nous allons fixer sa propriété de ROUGHNESS à un niveau assez bas également.

void fragment() {
  METALLIC = 0.0;
  ROUGHNESS = 0.01;
  ALBEDO = vec3(0.1, 0.3, 0.5);
}
../../../_images/plastic.png

Nous avons maintenant une surface lisse d'aspect plastique. Il est temps de réfléchir à certaines propriétés particulières de l'eau que nous voulons imiter. Il y en a deux principales qui vont faire passer cette eau d'une étrange surface en plastique à une belle eau stylisée. La première est celle des réflexions spéculaires. Les réflexions spéculaires sont ces points lumineux que vous voyez d'où le soleil se reflète directement dans votre œil. La seconde est la réflectance de Fresnel. La réflectance de Fresnel est la propriété des objets à devenir plus réfléchissants à des angles faibles. C'est la raison pour laquelle vous pouvez voir dans l'eau en dessous de vous, mais plus loin, elle reflète le ciel.

Afin d'augmenter les réflexions spéculaires, nous ferons deux choses. Tout d'abord, nous allons changer le mode de rendu de spéculaire à toon parce que le mode de rendu toon a de plus larges lumières spéculaires.

render_mode specular_toon;
../../../_images/specular-toon.png

Ensuite, nous allons ajouter de l'éclairage de bord. Cela va augmenter l'effet de la lumière aux angles rasants. L'éclairage de bord est habituellement utilisé pour imiter le comportement du tissu ou des bords d'un objet vis a vis de la lumière, mais nous allons l'utiliser pour obtenir un sympathique effet d'eau.

void fragment() {
  RIM = 0.2;
  METALLIC = 0.0;
  ROUGHNESS = 0.01;
  ALBEDO = vec3(0.1, 0.3, 0.5);
}
../../../_images/rim.png

In order to add fresnel reflectance, we will compute a fresnel term in our fragment shader. Here, we aren't going to use a real fresnel term for performance reasons. Instead, we'll approximate it using the dot product of the NORMAL and VIEW vectors. The NORMAL vector points away from the mesh's surface, while the VIEW vector is the direction between your eye and that point on the surface. The dot product between them is a handy way to tell when you are looking at the surface head-on or at a glancing angle.

float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));

And mix it into both ROUGHNESS and ALBEDO. This is the benefit of ShaderMaterials over StandardMaterial3Ds. With StandardMaterial3D, we could set these properties with a texture, or to a flat number. But with shaders we can set them based on any mathematical function that we can dream up.

void fragment() {
  float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));
  RIM = 0.2;
  METALLIC = 0.0;
  ROUGHNESS = 0.01 * (1.0 - fresnel);
  ALBEDO = vec3(0.1, 0.3, 0.5) + (0.1 * fresnel);
}
../../../_images/fresnel.png

Et maintenant, avec seulement 5 lignes de code, vous pouvez avoir une eau d'apparence complexe. Maintenant que nous avons de la lumière, cette eau semble trop claire. Assombrissons la. Cela se fait facilement en diminuant les valeurs du vec3 que l'on passe dans ALBEDO. Réglons-les sur vec3(0.01, 0.03, 0.05).

../../../_images/dark-water.png

Animation avec TIME

Pour en revenir à la fonction vertex, nous pouvons animer les vagues en utilisant la variable intégrée TIME.

TIME est une variable intégrée qui est accessible à partir des fonctions vertex et fragment.

Dans le dernier tutoriel, nous avons calculé la hauteur en lisant une heightmap. Pour ce tutoriel, nous ferons de même. Mettez le code de la heightmap dans une fonction appelée height().

float height(vec2 position) {
  return texture(noise, position / 10.0).x; // Scaling factor is based on mesh size (this PlaneMesh is 10×10).
}

Pour pouvoir utiliser TIME dans la fonction height(), nous devons le passer dedans.

float height(vec2 position, float time) {
}

Et assurez-vous de le faire passer correctement à l'intérieur de la fonction vertex.

void vertex() {
  vec2 pos = VERTEX.xz;
  float k = height(pos, TIME);
  VERTEX.y = k;
}

Au lieu d'utiliser une normalmap pour calculer les normales. Nous allons les calculer manuellement dans la fonction vertex(). Pour ce faire, utilisez la ligne de code suivante.

NORMAL = normalize(vec3(k - height(pos + vec2(0.1, 0.0), TIME), 0.1, k - height(pos + vec2(0.0, 0.1), TIME)));

Nous devons calculer NORMAL manuellement car dans la prochaine section, nous utiliserons les mathématiques pour créer des vagues d'apparence complexe.

Maintenant, nous allons rendre la fonction height() un peu plus compliquée en compensant la position par le cosinus de TIME.

float height(vec2 position, float time) {
  vec2 offset = 0.01 * cos(position + time);
  return texture(noise, (position / 10.0) - offset).x;
}

Il en résulte des vagues qui se déplacent lentement, mais pas de manière très naturelle. La section suivante approfondit l'utilisation des shaders pour créer des effets plus complexes, dans ce cas des vagues réalistes, en ajoutant quelques fonctions mathématiques supplémentaires.

Effets avancés : les vagues

Ce qui rend les shaders si puissants, c'est que vous pouvez obtenir des effets complexes en utilisant les mathématiques. Pour illustrer cela, nous allons faire passer nos vagues au niveau supérieur en modifiant la fonction height() et en introduisant une nouvelle fonction appelée wave().

wave() a un paramètre, position, qui est le même que dans height().

Nous allons appeler wave() plusieurs fois dans height() afin de simuler l'aspect des vagues.

float wave(vec2 position){
  position += texture(noise, position / 10.0).x * 2.0 - 1.0;
  vec2 wv = 1.0 - abs(sin(position));
  return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0);
}

Au début, cela semble compliqué. Voyons donc cela ligne par ligne.

position += texture(noise, position / 10.0).x * 2.0 - 1.0;

Décalez la position par la texture noise. Cela courbera les vagues, de sorte qu'elles ne soient pas des lignes droites complètement alignées sur la grille.

vec2 wv = 1.0 - abs(sin(position));

Définir une fonction de type vague en utilisant sin() et position. Normalement, les vagues sin() sont très rondes. Nous utilisons abs() pour leur donner une crête nette et les contraindre à la plage 0-1. Et puis nous soustrayons 1.0 pour mettre le pic au-dessus.

return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0);

Multipliez la vague directionnelle x par la vague directionnelle y et augmentez-la à une puissance pour affiner les pics. Ensuite, soustrayez cela de 1.0 pour que les crêtes deviennent des pics et augmentez cela à une puissance pour aiguiser les crêtes.

Nous pouvons maintenant remplacer le contenu de notre fonction height() avec wave().

float height(vec2 position, float time) {
  float h = wave(position);
  return h;
}

En utilisant ceci, vous obtenez :

../../../_images/wave1.png

La forme de la vague de sin est trop évidente. Écartons donc un peu les vagues. Nous le faisons en modifiant l'échelle de position.

float height(vec2 position, float time) {
  float h = wave(position * 0.4);
  return h;
}

Maintenant, ça a l'air beaucoup mieux.

../../../_images/wave2.png

Nous pouvons faire encore mieux si nous superposons plusieurs vagues à des fréquences et des amplitudes variables. Cela signifie que nous allons mettre à l'échelle la position de chacune pour rendre les vagues plus fines ou plus larges (fréquence). Et nous allons multiplier la sortie de la vague pour les rendre plus courtes ou plus hautes (amplitude).

Voici un exemple de la façon dont vous pourriez superposer les quatre vagues pour obtenir de plus belles vagues.

float height(vec2 position, float time) {
  float d = wave((position + time) * 0.4) * 0.3;
  d += wave((position - time) * 0.3) * 0.3;
  d += wave((position + time) * 0.5) * 0.2;
  d += wave((position - time) * 0.6) * 0.2;
  return d;
}

Notez que nous ajoutons le temps à deux et le soustrayons aux deux autres. Cela fait que les vagues se déplacent dans différentes directions, créant un effet complexe. Notez également que les amplitudes (le nombre par lequel le résultat est multiplié) s'additionnent toutes à 1.0. Cela permet de maintenir la vague dans la fourchette 0-1.

Avec ce code vous devriez vous retrouver avec des vagues d'apparence plus complexe et tout ce que vous aviez à faire était d'ajouter un peu de mathématiques !

../../../_images/wave3.png

Pour plus d'informations sur les Spatial shaders, consultez le document Langue des shaders doc et le document Spatial Shaders doc. Regardez aussi les tutoriels plus avancés dans les sections Shading section et 3D.