Up to date
This page is up to date for Godot 4.2
.
If you still find outdated information, please open an issue.
Utilisation d'un Viewport comme texture¶
Introduction¶
Ce tutoriel va vous présenter l'utilisation de Viewport comme une texture qui peut être appliquée à des objets 3D. Pour ce faire, il vous guidera à travers le processus de fabrication d'une planète procédurale comme celle ci-dessous :
Note
Ce tutoriel ne couvre pas la façon de coder une atmosphère dynamique comme celle de cette planète.
This tutorial assumes you are familiar with how to set up a basic scene including: a Camera3D, a light source, a MeshInstance3D with a Primitive Mesh, and applying a StandardMaterial3D to the mesh. The focus will be on using the Viewport to dynamically create textures that can be applied to the mesh.
Durant ce tutoriel, nous couvrirons les sujets suivant :
Comment utiliser un Viewport comme texture de rendu
Mappage d'une texture sur une sphère avec un mappage équirectangulaire
Techniques fragment shader pour planètes procédurales
Paramétrage d'une carte de rugosité à partir d'une Viewport Texture
Configuration de la fenêtre d'affichage¶
D'abord, ajoutez un Viewport à la scène.
Ensuite, définissez la taille du Viewport à (1024, 512)
. Le Viewport peut en fait être de n'importe quelle taille tant que la largeur est le double de la hauteur. La largeur doit être le double de la hauteur pour que l'image soit correctement représentée sur la sphère en utilisant la projection équirectangulaire, mais nous y reviendrons plus tard.
Ensuite, désactivez le HDR et désactivez la 3D. Nous n'avons pas besoin de HDR parce que la surface de notre planète ne sera pas particulièrement brillante, donc des valeurs entre 0
et 1
seront très bien. Et nous allons utiliser un ColorRect pour rendre la surface, donc nous n'avons pas besoin de 3D non plus.
Sélectionnez le Viewport et ajoutez un ColorRect comme enfant.
Réglez les ancres "Right" et "Bottom" sur 1
, puis assurez-vous que toutes les marges sont réglées sur 0
. Ceci assurera que le ColorRect occupe la totalité du Viewport.
Ensuite, on ajoute un Shader Material au ColorRect (ColorRect > CanvasItem > Material > Material > New ShaderMaterial
).
Note
Une connaissance de base du shading est recommandée pour ce tutoriel. Cependant, même si vous êtes nouveau dans les shaders, tout le code sera fourni, donc vous ne devriez pas avoir de problème pour suivre.
ColorRect > CanvasItem > Material > Material > click / Edit > ShaderMaterial > Shader > New Shader
> click / Edit :
shader_type canvas_item;
void fragment() {
COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
}
Le code ci-dessus rend un gradient comme celui ci-dessous.
Maintenant, nous avons les bases d'un Viewport que nous rendons et nous avons une image unique que nous pouvons appliquer à la sphère.
Appliquer la texture¶
MeshInstance3D > GeometryInstance > Geometry > Material Override > New StandardMaterial3D
:
Now we go into the MeshInstance3D and add a StandardMaterial3D to it. No need for a special Shader Material (although that would be a good idea for more advanced effects, like the atmosphere in the example above).
MeshInstance3D > GeometryInstance > Geometry > Material Override > click
/ Edit
:
Open the newly created StandardMaterial3D and scroll down to the "Albedo" section and click beside the "Texture" property to add an Albedo Texture. Here we will apply the texture we made. Choose "New ViewportTexture"
Ensuite, dans le menu qui apparaît, sélectionnez le Viewport que nous avons rendu précédemment.
Votre sphère devrait maintenant être colorée avec les couleurs que nous avons rendues dans le Viewport.
Remarquez-vous la jointure laide qui se forme à l'endroit où la texture s'enroule ? C'est parce que nous choisissons une couleur basée sur les coordonnées UV et que les coordonnées UV ne s'enroulent pas autour de la texture. C'est un problème classique dans la projection de cartes 2D. Les développeurs de jeux ont souvent une carte en 2 dimensions qu'ils veulent projeter sur une sphère, mais quand elle s'enroule autour, elle a de larges jointures. Il existe une solution élégante à ce problème, que nous illustrerons dans la prochaine section.
Créer la texture de la planète¶
Donc maintenant, quand nous rendons notre Viewport, il apparaît magiquement sur la sphère. Mais il y a une vilaine jointure créée par nos coordonnées de texture. Alors comment obtenir une gamme de coordonnées qui s'enroulent autour de la sphère de façon agréable ? Une solution est d'utiliser une fonction qui se répète sur le domaine de notre texture. "sin" et "cos" sont deux de ces fonctions. Appliquons-les à la texture et voyons ce qui se passe.
COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);
Not too bad. If you look around, you can see that the seam has now disappeared, but in its place, we have pinching at the poles. This pinching is due to the way Godot maps textures to spheres in its StandardMaterial3D. It uses a projection technique called equirectangular projection, which translates a spherical map onto a 2D plane.
Note
Si vous êtes intéressé par un peu plus d'informations sur la technique, nous allons convertir des coordonnées sphériques en coordonnées cartésiennes. Les coordonnées sphériques représentent la longitude et la latitude de la sphère, tandis que les coordonnées cartésiennes sont, à toutes fins utiles, un vecteur allant du centre de la sphère au point.
Pour chaque pixel, nous allons calculer sa position 3D sur la sphère. A partir de là, nous allons utiliser un bruit 3D pour déterminer une valeur de couleur. En calculant le bruit en 3D, nous résolvons le problème du pincement aux pôles. Pour comprendre pourquoi, imaginez le bruit calculé sur la surface de la sphère plutôt que sur le plan 2D. Lorsque vous calculez sur la surface de la sphère, vous ne touchez jamais un bord, et donc vous ne créez jamais de couture ou de point de pincement sur le pôle. Le code suivant convertit les UVs
en coordonnées cartésiennes.
float theta = UV.y * 3.14159;
float phi = UV.x * 3.14159 * 2.0;
vec3 unit = vec3(0.0, 0.0, 0.0);
unit.x = sin(phi) * sin(theta);
unit.y = cos(theta) * -1.0;
unit.z = cos(phi) * sin(theta);
unit = normalize(unit);
Et si nous utilisons unit
comme valeur de sortie COLOR
, nous obtenons :
Maintenant que nous pouvons calculer la position 3D de la surface de la sphère, nous pouvons utiliser le bruit 3D pour faire la planète. Nous utiliserons cette fonction de bruit directement depuis un Shadertoy :
vec3 hash(vec3 p) {
p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
dot(p, vec3(269.5, 183.3, 246.1)),
dot(p, vec3(113.5, 271.9, 124.6)));
return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}
float noise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
return mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),
mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y),
mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),
mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z );
}
Note
Tout les crédits vont à l'auteur, Inigo Quilez, Cela est publié sous une licence MIT
.
Maintenant pour utiliser le noise
, ajoutez ce qui suit à la fonction fragment
:
float n = noise(unit * 5.0);
COLOR.xyz = vec3(n * 0.5 + 0.5);
Note
Afin de mettre en valeur la texture, nous avons réglé le matériau sur unshaded.
Vous pouvez voir maintenant que le bruit s'enroule en effet sans jointure autour de la sphère. Bien que cela ne ressemble en rien à la planète qu'on vous a promise. Alors passons à quelque chose de plus coloré.
Colorer la planète¶
Maintenant pour faire les couleurs de la planète. Bien qu'il y ait de nombreuses façons de le faire, pour l'instant, nous nous en tiendrons à un gradient entre l'eau et la terre.
Pour faire un gradient dans GLSL, on utilise la fonction mix
. mix
prend deux valeurs à interpoler entre elles et un troisième argument pour choisir la quantité à interpoler entre elles ; en substance, il mixe les deux valeurs ensemble. Dans d'autres API, cette fonction est souvent appelée lerp
. Cependant, lerp
est typiquement réservé au mélange de deux flottants ensemble ; mix
peut prendre n'importe quelle valeur, que ce soit des flottants ou des types vecteur.
COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n * 0.5 + 0.5);
La première couleur est le bleu pour l'océan. La deuxième couleur est une sorte de couleur rougeâtre (parce que toutes les planètes extraterrestres ont besoin d'un terrain rouge). Et finalement, ils sont mélangés par n * 0.5 + 0.5
. n
varie en douceur entre -1
et 1
. Donc nous le mappons dans l'intervalle 0-1
que mix
attend. Maintenant vous pouvez voir que les couleurs changent entre le bleu et le rouge.
C'est un peu plus flou que ce que nous voulons. Les planètes ont généralement une séparation relativement nette entre la terre et la mer. Pour ce faire, nous allons changer le dernier terme en smoothstep(-0.1, 0.0, n)
. Et ainsi toute la ligne devient :
COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n));
Ce que fait smoothstep
est de retourner 0
si le troisième argument est en dessous du premier et 1
si le troisième argument est plus grand que le second et se mélange doucement entre 0
et 1
si le troisième nombre est entre le premier et le second. Donc, dans cette ligne, smoothstep
renvoie 0
lorsque n
est inférieur à -0.1
et 1
lorsque n
est supérieur à 0
.
Une dernière chose pour rendre ça un peu plus planétaire. La terre ne devrait pas être aussi tachetée ; rendons les bords un peu plus rugueux. Une astuce souvent utilisée dans les shaders pour rendre un terrain rugueux avec du bruit consiste à superposer des niveaux de bruit à différentes fréquences. Nous utilisons une couche pour faire la structure globale des continents. Ensuite, une autre couche casse un peu les bords, puis une autre, et ainsi de suite. Ce que nous allons faire, c'est calculer n
avec quatre lignes de code de shader au lieu d'une seule. n
devient :
float n = noise(unit * 5.0) * 0.5;
n += noise(unit * 10.0) * 0.25;
n += noise(unit * 20.0) * 0.125;
n += noise(unit * 40.0) * 0.0625;
Et maintenant, la planète ressemble à :
Et avec le shading réactivé, cela ressemble à :
Créer un océan¶
Une dernière chose pour que ça ressemble plus à une planète. L'océan et la terre reflètent la lumière différemment. Nous voulons donc que l'océan brille un peu plus que la terre. Nous pouvons faire cela en passant une quatrième valeur dans le canal alpha
de notre sortie COLOR
et en l'utilisant comme carte de rugosité.
COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n);
Cette ligne renvoie 0.3
pour l'eau et 1.0
pour la terre. Cela signifie que le terrain sera assez accidenté, tandis que l'eau sera assez lisse.
Et ensuite, dans le matériau, sous la section "Metallic", assurez-vous que Metallic
est réglé à 0
et Specular
est réglé à 1
. La raison en est que l'eau réfléchit très bien la lumière, mais n'est pas métallique. Ces valeurs ne sont pas physiquement exactes, mais elles sont suffisamment bonnes pour cette démo.
Ensuite, dans la section "Roughness", mettez Roughness
à 1
et assignez la texture de rugosité à un Viewport Texture pointant sur la texture de notre planète Viewport. Enfin, réglez le Texture Channel
sur Alpha
. Ceci indique au moteur de rendu d'utiliser le canal alpha
de notre sortie COLOR
comme valeur de Roughness
.
Vous remarquerez que très peu de changements, sauf que la planète ne reflète plus le ciel. Cela se produit parce que, par défaut, quand quelque chose est rendu avec une valeur alpha, il est dessiné comme un objet transparent sur l'arrière-plan. Et comme l'arrière-plan par défaut de Viewport est opaque, le canal alpha
du Viewport Texture est 1
, ce qui fait que la texture de la planète est dessinée avec des couleurs légèrement plus faibles et une valeur de Roughness
de 1
partout. Pour corriger cela, nous allons dans le Viewport et activons la propriété "Transparent Bg". Puisque nous rendons maintenant un objet transparent par-dessus un autre, nous voulons activer blend_premul_alpha
:
render_mode blend_premul_alpha;
Ceci pré-multiplie les couleurs par la valeur alpha
et les mélange ensuite correctement ensemble. Typiquement, en mélangeant une couleur transparente sur une autre, même si le fond a un alpha
de 0
(comme c'est le cas ici), vous vous retrouvez avec d'étranges problèmes de saignement de couleur. Le paramètre blend_premul_alpha
corrige cela.
Now the planet should look like it is reflecting light on the ocean but not the land. If you haven't done so already, add an OmniLight3D to the scene so you can move it around and see the effect of the reflections on the ocean.
Et voilà, vous l'avez. Une planète procédurale générée en utilisant un Viewport.