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 :

../../_images/planet_example.png

Note

Ce tutoriel ne couvre pas la façon de coder une atmosphère dynamique comme celle de cette planète.

Ce tutoriel suppose que vous êtes familier avec la façon de mettre en place une scène de base comprenant : une Camera, une source de lumière, une Mesh Instance avec un Primitive Mesh, et en appliquant un Spatial Material au maillage. L'accent sera mis sur l'utilisation de Viewport pour créer dynamiquement des textures qui peuvent être appliquées au maillage.

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.

../../_images/planet_new_viewport.png

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.

../../_images/planet_new_colorrect.png

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.

../../_images/planet_gradient.png

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

MeshInstance > GeometryInstance > Geometry > Material Override > New SpatialMaterial :

Maintenant, nous allons dans le Mesh Instance et nous y ajoutons un Spatial Material. Pas besoin d'un Shader Material spécial (bien que ce serait une bonne idée pour des effets plus avancés, comme l'atmosphère dans l'exemple ci-dessus).

MeshInstance > GeometryInstance > Geometry > Material Override > click / Edit :

Ouvrez le nouveau Spatial Material créé et descendez jusqu'à la section "Albedo" et cliquez à côté de la propriété "Texture" pour ajouter une Albedo Texture. Ici, nous allons appliquer la texture que nous avons faite. Choisissez "New ViewportTexture"

../../_images/planet_new_viewport_texture.png

Ensuite, dans le menu qui apparaît, sélectionnez le Viewport que nous avons rendu précédemment.

../../_images/planet_pick_viewport_texture.png

Votre sphère devrait maintenant être colorée avec les couleurs que nous avons rendues dans le Viewport.

../../_images/planet_seam.png

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);
../../_images/planet_sincos.png

Pas trop mal. Si vous regardez autour de vous, vous pouvez voir que la jointure a maintenant disparu, mais à sa place, nous avons des pincements aux pôles. Ce pincement est dû à la façon dont Godot mappe les textures aux sphères dans son Spatial Material. Il utilise une technique de projection appelée projection équi-rectangulaire, qui traduit une carte sphérique sur un plan 2D.

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 :

../../_images/planet_normals.png

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);
../../_images/planet_noise.png

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.

../../_images/planet_noise_color.png

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.

../../_images/planet_noise_smooth.png

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 à :

../../_images/planet_noise_fbm.png

Et avec le shading réactivé, cela ressemble à :

../../_images/planet_noise_fbm_shaded.png

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.

../../_images/planet_ocean.png

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.

Maintenant, la planète devrait avoir l'air de réfléchir la lumière sur l'océan mais pas sur la terre. Si vous ne l'avez pas déjà fait, ajoutez un OmniLight à la scène pour pouvoir la déplacer et voir l'effet des reflets sur l'océan.

../../_images/planet_ocean_reflect.png

Et voilà, vous l'avez. Une planète procédurale générée en utilisant un Viewport.