Up to date
This page is up to date for Godot 4.2
.
If you still find outdated information, please open an issue.
Votre premier shader 3D¶
You have decided to start writing your own custom Spatial shader. Maybe you saw a cool trick online that was done with shaders, or you have found that the StandardMaterial3D isn't quite meeting your needs. Either way, you have decided to write your own and now you need to figure out where to start.
Ce tutoriel explique comment écrire un shader spatial et couvre plus de sujets que le tutoriel CanvasItem.
Les shaders spatiaux ont plus de fonctionnalités intégrées que les shaders CanvasItem. On s'attend à ce que les shaders spatiaux offrent déjà les fonctionnalités nécessaires pour les cas d'utilisation courants et que l'utilisateur n'ait qu'à définir les paramètres appropriés dans le shader. C'est particulièrement vrai pour un flux de travail PBR (rendu basé physiquement).
This is a two-part tutorial. In this first part we will create terrain using vertex displacement from a heightmap in the vertex function. In the second part we will take the concepts from this tutorial and set up custom materials in a fragment shader by writing an ocean water shader.
Note
Ce tutoriel suppose quelques connaissances de base sur les shaders, telles que les types (vec2
, float
, sampler2D
), et les fonctions. Si vous n'êtes pas à l'aise avec ces concepts, il est préférable d'avoir une douce introduction avec The Book of Shaders avant de faire ce tutoriel.
Où assigner mon matériel¶
En 3D, les objets sont dessinés en utilisant des Maillages. Les Maillages sont un type de ressource qui stocke la géométrie (la forme de votre objet) et les matériaux (la couleur et la façon dont l'objet réagit à la lumière) dans des unités appelées "surfaces". Un Maillage peut avoir plusieurs surfaces, ou une seule. En général, vous importerez un Maillage d'un autre programme (par exemple Blender). Mais Godot a aussi quelques PrimitiveMeshes qui permettent d'ajouter une géométrie de base à une scène sans importer de Maillages.
There are multiple node types that you can use to draw a mesh. The main one is MeshInstance3D, but you can also use GPUParticles3D, MultiMeshes (with a MultiMeshInstance3D), or others.
Typically, a material is associated with a given surface in a mesh, but some nodes, like MeshInstance3D, allow you to override the material for a specific surface, or for all surfaces.
If you set a material on the surface or mesh itself, then all MeshInstance3Ds that share that mesh will share that material. However, if you want to reuse the same mesh across multiple mesh instances, but have different materials for each instance then you should set the material on the MeshInstance3D.
For this tutorial we will set our material on the mesh itself rather than taking advantage of the MeshInstance3D's ability to override materials.
Mise en place¶
Add a new MeshInstance3D node to your scene.
Dans l'onglet inspecteur à côté de "Mesh", cliquez sur "[empty]" et sélectionnez "New PlaneMesh". Cliquez ensuite sur l'image d'un plan qui apparaît.
Cela ajoute un PlaneMesh à notre scène.
Ensuite, dans le viewport, cliquez dans le coin supérieur gauche sur le bouton "Perspective". Un menu apparaîtra. Au milieu du menu se trouvent des options sur la façon d'afficher la scène. Sélectionnez 'Affichage en fil de fer'.
Cela vous permettra de voir les triangles qui composent le plan.
Now set Subdivide Width
and Subdivide Depth
of the PlaneMesh to 32
.
You can see that there are now many more triangles in the MeshInstance3D. This will give us more vertices to work with and thus allow us to add more detail.
PrimitiveMeshes, like PlaneMesh, only have one surface, so instead of an array of materials there is only one. Click beside "Material" where it says "[empty]" and select "New ShaderMaterial". Then click the sphere that appears.
Cliquez maintenant à côté de "Shader" où il est écrit "[empty]" et sélectionnez "New Shader".
L'éditeur de shader devrait maintenant afficher un pop up et vous êtes maintenant prêt à commencer à écrire votre premier shader Spatial !
La magie des Shaders¶
The new shader is already generated with a shader_type
variable and the fragment()
function.
The first thing Godot shaders need is a declaration
of what type of shader they are.
In this case the shader_type
is set to spatial
because this is a spatial shader.
shader_type spatial;
For now ignore the fragment()
function
and define the vertex()
function. The vertex()
function
determines where the vertices of your MeshInstance3D appear in
the final scene. We will be using it to offset the height of each vertex and
make our flat plane appear like a little terrain.
Nous définissons ainsi le shader de vertex :
void vertex() {
}
Sans rien dans la fonction vertex()
, Godot utilisera son shader de vertex par défaut. Nous pouvons facilement commencer à apporter des changements en ajoutant une seule ligne :
void vertex() {
VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z);
}
En ajoutant cette ligne, vous devriez obtenir une image comme celle ci-dessous.
Bon, déballons tout ça. La valeur y
de VERTEX
est augmentée. Et nous passons les composantes x
et z
de VERTEX
comme arguments à cos
et sin
; cela nous donne une apparence ondulée sur les axes x
et z
.
Ce que nous voulons obtenir, c'est l'aspect de petites collines ; après tout. Les cos
et les sin
ressemblent déjà à des collines. Nous le faisons en mettant les entrées à l'échelle des fonctions cos
et sin
.
void vertex() {
VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0);
}
Cela semble mieux, mais c'est encore trop pointu et répétitif, rendons-le un peu plus intéressant.
Heightmap bruit¶
Le bruit est un outil très populaire pour simuler l'aspect du terrain. Pensez-y comme à la fonction cosinus où vous avez des collines répétitives, sauf que, avec le bruit, chaque colline a une hauteur différente.
Godot provides the NoiseTexture2D resource for generating a noise texture that can be accessed from a shader.
Pour accéder à une texture dans un shader, ajoutez le code suivant près du haut de votre shader, en dehors de la fonction vertex()
.
uniform sampler2D noise;
Cela vous permettra d'envoyer une texture de bruit au shader. Regardez maintenant dans l'inspecteur sous votre matériau. Vous devriez voir une section appelée "Shader Params". Si vous l’ouvrez, vous verrez une section appelée "noise".
Click beside it where it says "[empty]" and select "New NoiseTexture2D". Then in your NoiseTexture2D click beside where it says "Noise" and select "New FastNoiseLite".
Note
FastNoiseLite is used by the NoiseTexture2D to generate a heightmap.
Une fois que vous l'aurez configuré et il devrait ressembler à ceci.
Now, access the noise texture using the texture()
function. texture()
takes a texture as the first argument and a vec2
for the position on the
texture as the second argument. We use the x
and z
channels of
VERTEX
to determine where on the texture to look up. Note that the PlaneMesh
coordinates are within the [-1,1] range (for a size of 2), while the texture
coordinates are within [0,1], so to normalize we divide by the size of the
PlaneMesh by 2.0 and add 0.5. texture()
returns a vec4
of the r, g, b,
a
channels at the position. Since the noise texture is grayscale, all of the
values are the same, so we can use any one of the channels as the height. In
this case we'll use the r
, or x
channel.
void vertex() {
float height = texture(noise, VERTEX.xz / 2.0 + 0.5).x;
VERTEX.y += height;
}
Note : xyzw
est le même que rgba
dans GLSL, donc au lieu de texture().x
ci-dessus, nous pourrions utiliser texture().r
. Voir la documentation OpenGL pour plus de détails.
En utilisant ce code, vous pouvez voir que la texture crée des collines d'apparence aléatoire.
Actuellement, c'est trop pointu, nous voulons adoucir un peu les collines. Pour ce faire, nous utiliserons un uniform. Vous avez déjà utilisé un uniform ci-dessus pour transmettre la texture de bruit, maintenant apprenons comment ils fonctionnent.
Uniforms¶
Les variables uniforms vous permettent de faire passer les données du jeu dans le shader. Elles sont très utiles pour contrôler les effets du shader. Les uniforms peuvent être presque tous les types de données qui peuvent être utilisés dans le shader. Pour utiliser un uniform, vous devez le déclarer dans votre Shader en utilisant le mot-clé uniform
.
Faisons un uniform qui change la hauteur du terrain.
uniform float height_scale = 0.5;
Godot lets you initialize a uniform with a value; here, height_scale
is set
to 0.5
. You can set uniforms from GDScript by calling the function
set_shader_parameter()
on the material corresponding to the shader. The value
passed from GDScript takes precedence over the value used to initialize it in
the shader.
# called from the MeshInstance3D
mesh.material.set_shader_parameter("height_scale", 0.5)
Note
Changing uniforms in Spatial-based nodes is different from
CanvasItem-based nodes. Here, we set the material inside the PlaneMesh
resource. In other mesh resources you may need to first access the
material by calling surface_get_material()
. While in the
MeshInstance3D you would access the material using
get_surface_material()
or material_override
.
Remember that the string passed into set_shader_parameter()
must match the name
of the uniform variable in the Shader. You can use the
uniform variable anywhere inside your Shader. Here, we will
use it to set the height value instead of arbitrarily multiplying by 0.5
.
VERTEX.y += height * height_scale;
Maintenant, ça a l'air beaucoup mieux.
Using uniforms, we can even change the value every frame to animate the height of the terrain. Combined with Tweens, this can be especially useful for animations.
Interagir avec la lumière¶
Tout d'abord, désactiver l'affichage en fil de fer. Pour ce faire, cliquez à nouveau en haut à gauche de la fenêtre de visualisation, où il est indiqué "Perspective", et sélectionnez "Affichage normal".
Remarquez comment la couleur du maillage est plate. Cela s'explique par le fait que son éclairage est plat. Ajoutons une lumière !
First, we will add an OmniLight3D to the scene.
Vous pouvez voir la lumière qui affecte le terrain, mais elle semble étrange. Le problème est que la lumière affecte le terrain comme s'il s'agissait d'un plan plat. C'est parce que le shader de lumière utilise les normales du Mesh pour calculer la lumière.
Les normales sont stockées dans le Mesh, mais nous changeons la forme du Mesh dans le shader, donc les normales ne sont plus correctes. Pour y remédier, nous pouvons recalculer les normales dans le shader ou utiliser une texture de normales qui correspond à notre bruit. Godot nous facilite les deux possibilités.
Vous pouvez calculer la nouvelle normale manuellement dans la fonction vertex et ensuite simplement définir NORMAL
. Avec NORMAL
défini, Godot fera pour nous les calculs d'éclairage complexes. Nous aborderons cette méthode dans la prochaine partie de ce tutoriel, pour l'instant nous allons lire les normales à partir d'une texture.
Au lieu de cela, nous nous fierons à nouveau au NoiseTexture pour calculer les normales pour nous. Nous le faisons en passant une deuxième texture de bruit.
uniform sampler2D normalmap;
Set this second uniform texture to another NoiseTexture2D with another FastNoiseLite. But this time, check As Normalmap.
Maintenant, comme il s'agit d'une normalmap et non de normale par sommet, nous allons l'assigner à la fonction fragment()
. Cette fonction fragment()
sera expliquée plus en détail dans la prochaine partie de ce tutoriel.
void fragment() {
}
When we have normals that correspond to a specific vertex we set NORMAL
, but
if you have a normalmap that comes from a texture, set the normal using
NORMAL_MAP
. This way Godot will handle the wrapping of texture around the
mesh automatically.
Enfin, afin de s'assurer que nous lisons aux mêmes endroits sur la texture de bruit et sur la texture de la normalmap, nous allons passer la position VERTEX.xz
de la fonction vertex()
à la fonction fragment()
. Nous le faisons avec des varyings.
Au-dessus de vertex()
définissez un vec2
appelé tex_position
. Et à l'intérieur de la fonction vertex()
assignez VERTEX.xz
à tex_position
.
varying vec2 tex_position;
void vertex() {
...
tex_position = VERTEX.xz / 2.0 + 0.5;
float height = texture(noise, tex_position).x;
...
}
Et maintenant nous avons accès à tex_position
depuis la fonction fragment()
.
void fragment() {
NORMAL_MAP = texture(normalmap, tex_position).xyz;
}
Avec les normales en place, la lumière réagit maintenant dynamiquement à la hauteur du maillage.
Nous pouvons même faire glisser la lumière et l'éclairage se mettra automatiquement à jour.
Voici le code complet pour ce tutoriel. Vous pouvez voir que ce n'est pas très long car Godot s'occupe de la plupart des choses difficiles pour vous.
shader_type spatial;
uniform float height_scale = 0.5;
uniform sampler2D noise;
uniform sampler2D normalmap;
varying vec2 tex_position;
void vertex() {
tex_position = VERTEX.xz / 2.0 + 0.5;
float height = texture(noise, tex_position).x;
VERTEX.y += height * height_scale;
}
void fragment() {
NORMAL_MAP = texture(normalmap, tex_position).xyz;
}
C'est tout pour cette partie. Espérons que vous comprenez maintenant les bases des shaders de vertex dans Godot. Dans la prochaine partie de ce tutoriel, nous allons écrire une fonction de fragment pour accompagner cette fonction de vertex et nous allons couvrir une technique plus avancée pour transformer ce terrain en un océan de vagues en mouvement.