Réflexions caustiques réalistes


La plupart des artistes techniques à un moment donné de leur carrière essaient de créer des reflets plausibles des caustiques. Si vous êtes un développeur de jeux, l'une des principales raisons de lire Twitter est le flot incessant d'inspiration que vous pouvez en tirer. Il y a quelques jours, Florian Gelzenlichter ( kolyaTQ sur Twitter) a publié un GIF de l'effet caustique créé dans Unity à l'aide de shaders. La publication (présentée ci-dessous) a rapidement gagné 1,5 mille likes, ce qui montre un intérêt sincère pour ce type de contenu.


Bien que je sois généralement plus attiré par des séries d'articles plus longues et techniquement complexes (par exemple, sur la diffusion volumétrique de la lumière atmosphérique [ traduction sur Habré] et la cinématique inverse [ première et deuxième parties de la traduction sur Habré), je n'ai pas pu résister à la tentation d'écrire un tutoriel court et mignon sur les effets de Florian .

À la fin de cet article, il y a un lien pour télécharger le package Unity et tous les actifs nécessaires.

Qu'est-ce que caustique


Vous ne connaissez peut-être pas le concept des caustiques , bien que vous rencontriez cet effet quotidiennement. Les caustiques sont des reflets de lumière causés par des surfaces courbes. Dans le cas général, toute surface incurvée peut se comporter comme une lentille, focalisant la lumière à certains points et la diffusant à d'autres. Les médias les plus courants fournissant un tel effet sont le verre et l'eau, qui génèrent les ondes dites caustiques (voir ci-dessous).


Les caustiques peuvent prendre d'autres formes. Un arc-en-ciel, par exemple, est un phénomène optique qui se produit lorsque la lumière est réfractée dans les gouttes de pluie. Par conséquent, à proprement parler, c'est caustique.

Anatomie de l'effet


Une caractéristique reconnaissable des ondes caustiques est la façon dont elles se déplacent; vous l'avez probablement vu si vous avez déjà regardé le fond de la piscine. Recréer un véritable caustique est très coûteux car il nécessite la simulation de nombreux rayons lumineux.

Florian a réussi à créer un effet plausible, à commencer par une seule texture caustique. Pour créer mon tutoriel, j'ai utilisé la texture illustrée ci-dessous, tirée d' OpenGameArt .


Une propriété importante qui permet de réaliser cet effet est que le motif caustique illustré ci-dessus est sans couture . Cela signifie que vous pouvez placer deux images l'une à côté de l'autre et qu'il n'y aura pas de couture visible entre elles. Puisque nous voulons utiliser cet effet sur de grandes surfaces, il est important que nous ayons la possibilité d'étirer cette texture sans larmes qui peuvent détruire l'illusion.

Ayant reçu la texture, Florian suggère de suivre trois étapes:

  • Appliquer un motif caustique deux fois sur la surface du modèle, à chaque fois en utilisant différentes tailles et vitesses
  • Mélanger deux motifs avec l'opérateur min
  • Séparez les canaux RVB lors de l'échantillonnage.

Voyons comment vous pouvez implémenter chacune des étapes dans Unity.

Création de shader


La première étape consiste à créer un nouveau shader. Étant donné que cet effet est susceptible d'être utilisé dans un jeu 3D qui dispose également d'un véritable éclairage, il est préférable de commencer avec un shader de surface . Les shaders de surface sont l'un des nombreux types de shaders pris en charge par Unity (tels que les shaders de vertex et de fragments pour les matériaux non éclairés, les shaders d'écran pour les effets de post-traitement et les shaders de calcul pour les simulations hors écran).

Le nouveau shader de surface n'a que quelques fonctionnalités. Pour créer cet effet, nous devons transférer des informations vers le shader. Le premier est la texture caustique. Deuxièmement, il s'agit du paramètre utilisé pour le mettre à l'échelle et le compenser.

Créons deux propriétés de shader :

 Properties { ... [Header(Caustics)] _CausticsTex("Caustics (RGB)", 2D) = "white" {} // Tiling X, Tiling Y, Offset X, Offset Y _Caustics_ST("Caustics ST", Vector) = (1,1,0,0) } 

et les variables Cg correspondantes:

 sampler2D _CausticsTex; float4 _Caustics_ST; 

Les propriétés du shader correspondent aux champs affichés dans l'inspecteur de matériaux Unity. Les variables Cg correspondantes sont les valeurs elles-mêmes, qui peuvent être utilisées dans le code du shader.

Comme vous pouvez le voir dans le code ci-dessus, _Caustics_ST est float4 , c'est-à-dire qu'il contient quatre valeurs. Nous les utiliserons pour contrôler l'échantillonnage de la texture caustique. À savoir:

  • _Caustics_ST.x : échelle de la texture caustique le long de l'axe X;
  • _Caustics_ST.y : échelle de la texture caustique le long de l'axe Y;
  • _Caustics_ST.z : déplacement de la texture caustique le long de l'axe X;
  • _Caustics_ST.w : déplacement de la texture caustique le long de l'axe Y _Caustics_ST.w

Pourquoi la variable s'appelle _Caustics_ST?
Si vous avez déjà un peu d'expérience avec les shaders, vous avez déjà vu d'autres propriétés se terminant par le suffixe _ST . Dans Unity, _ST peut être utilisé pour ajouter des informations supplémentaires sur la façon dont la texture est échantillonnée.

Par exemple, si vous créez la variable Cg _MainTex_ST , vous pouvez l'utiliser pour définir la taille et le décalage lors de l'application de la texture au modèle.

Les variables _ST pas besoin de propriétés car elles sont automatiquement affichées dans l'inspecteur. Cependant, dans ce cas particulier, nous ne pouvons pas nous y fier car nous devons échantillonner la texture deux fois, à chaque fois avec une échelle et un décalage différents. À l'avenir, nous devons dupliquer cette variable en deux variables différentes.

Texture d'échantillonnage


Chaque shader de surface contient une fonction, communément appelée surf , qui est utilisée pour déterminer la couleur de chaque pixel rendu. La fonction de surf «standard» ressemble à ceci:

 void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } 

La couleur finale est déterminée par le nombre de champs que le shader doit initialiser et renvoyer dans une structure appelée SurfaceOutputStandard . Nous devons changer l' Albedo , qui correspond à peu près à la couleur de l'objet éclairé par la lumière blanche.

Dans le shader de surface nouvellement créé, l'albédo est extrait d'une texture appelée _MainTex . Étant donné que l'effet caustique est superposé à la texture existante, nous devrons effectuer un échantillonnage supplémentaire de la texture dans _CausticsTex .

Une technique appelée superposition UV vous permet de comprendre quelle partie de la texture doit être échantillonnée en fonction de la partie de la géométrie qui doit être rendue. Cela se fait en utilisant uv_MainTex - la variable float2 , stockée à chaque sommet du modèle 3D et indiquant la coordonnée de la texture.

Notre idée est d'utiliser _Caustics_ST pour _Caustics_ST à l'échelle et compenser uv_MainTex pour étirer et déplacer la texture caustique à travers le modèle.

 void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Caustics sampling fixed2 uv = IN.uv_MainTex * _Caustics_ST.xy + _Caustics_ST.zw; fixed3 caustics = tex2D(_CausticsTex, uv).rgb; // Add o.Albedo.rgb += caustics; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } 

Que se passe-t-il si Albedo dépasse 1?
Dans le code ci-dessus, nous ajoutons deux textures. La couleur est généralement comprise entre 0avant 1Cependant, rien ne garantit qu'en conséquence, certaines valeurs ne dépasseront pas cet intervalle.

Dans les shaders plus anciens, cela pourrait provoquer un problème. Ici, c'est en fait une fonctionnalité . Si la valeur de couleur des pixels dépasse l'unité, cela signifie que son influence devrait «s'étendre» au-delà de ses frontières et affecter les pixels voisins.

C'est exactement ce qui se produit lorsque des réflexions spéculaires très lumineuses sont obtenues. Cependant, cet effet ne doit pas être créé uniquement par un ombrage de surface. Pour que l'effet fonctionne, la caméra doit avoir le HDR activé. Cette propriété signifie High Dynamic Range ; il permet aux valeurs de couleur de dépasser 1. De plus, pour brouiller une quantité excessive de couleurs sur les pixels voisins, un effet de post-traitement est requis.

Unity a sa propre pile de post-traitement, qui a un filtre de floraison qui fait exactement cela. Vous pouvez en savoir plus à ce sujet sur le blog Unity: PostFX v2 - Incroyable visuels, mis à niveau .

Les résultats préliminaires sont présentés ci-dessous:


Caustiques animés


L'une des caractéristiques les plus importantes des caustiques est leur mouvement. Pour le moment, ils sont simplement projetés statiquement sur la surface du modèle en tant que seconde texture.

L'animation des matériaux dans les shaders peut être implémentée à l'aide de la propriété Unity _Time . Il peut être utilisé pour accéder au temps de jeu actuel, c'est-à-dire ajouter du temps aux équations.

Le moyen le plus simple consiste à simplement décaler la texture en fonction de l'heure actuelle.

 // Caustics UV fixed2 uv = IN.uv_MainTex * _Caustics_ST.xy + _Caustics_ST.zw; uv += _CausticsSpeed * _Time.y; // Sampling fixed3 caustics = tex2D(_CausticsTex, uv).rgb; // Add o.Albedo.rgb += caustics; 

Le champ _Time.y contient le temps de lecture actuel en secondes . Si la réflexion se déplace trop rapidement, vous pouvez la multiplier par un facteur. Pour cela, la variable _CausticsSpeed de type float2 utilisée dans le code présenté ci-dessus.

Vous devrez peut-être faire vibrer la texture caustique dans une sinusoïde à vos fins. Il est important de comprendre ici qu'il n'y a pas de moyen standard de réaliser l'effet. Selon vos besoins, vous pouvez faire bouger les réflexions caustiques de manière complètement différente.

Les résultats ci-dessous sont encore assez médiocres. C'est normal: nous avons encore beaucoup à faire pour rendre les reflets magnifiques.


Échantillonnage multiple


L'effet devient vivant si vous échantillonnez la texture caustique non pas une, mais deux fois. Si vous les posez les uns sur les autres et les déplacez à des vitesses différentes, le résultat sera complètement différent.

Tout d'abord, nous _CausticsSpeed propriétés _Caustics_ST et _CausticsSpeed afin que les échantillons des deux textures aient des échelles, des déplacements et des vitesses différents:

 [Header(Caustics)] _CausticsTex("Caustics (RGB)", 2D) = "white" {} // Tiling X, Tiling Y, Offset X, Offset Y _Caustics1_ST("Caustics 1 ST", Vector) = (1,1,0,0) _Caustics2_ST("Caustics 1 ST", Vector) = (1,1,0,0) // Speed X, Speed Y _Caustics1_Speed("Caustics 1 Speed", Vector) = (1, 1, 0 ,0) _Caustics2_Speed("Caustics 2 Speed", Vector) = (1, 1, 0 ,0) 

Maintenant que nous avons deux échantillons caustiques, ils peuvent être mélangés à l'aide de l'opérateur min . Si vous prenez simplement la valeur moyenne, le résultat ne sera pas très bon.

 // Caustics samplings fixed3 caustics1 = ... fixed3 caustics2 = ... // Blend o.Albedo.rgb += min(caustics1, caustics2); 

Un si petit changement fait une énorme différence:


Pour garder le code beau, vous pouvez également envelopper le code d'échantillonnage caustique dans votre propre fonction:

 // Caustics fixed3 c1 = causticsSample(_CausticsTex, IN.uv_MainTex, _Caustics1_ST, _Caustics1_Speed); fixed3 c2 = causticsSample(_CausticsTex, IN.uv_MainTex, _Caustics2_ST, _Caustics2_Speed); o.Albedo.rgb += min(c1, c2); 

Séparation RVB


Pour que les reflets caustiques semblent bons, vous devez faire le dernier tour. En passant à travers une tranche, la lumière de différentes longueurs d'onde est réfractée différemment. Cela signifie que lorsque vous vous déplacez dans l'eau, la lumière peut "se diviser" en différentes couleurs.

Pour simuler cet effet, nous pouvons diviser chaque échantillon caustique en trois, un pour chaque canal de couleur. En échantillonnant les canaux rouge, vert et bleu avec un léger biais, nous obtenons un décalage de couleur.

Commençons par ajouter la propriété _SplitRGB , qui indique la force de l'effet de _SplitRGB - _SplitRGB :

 // Caustics UV fixed2 uv = IN.uv_MainTex * _Caustics_ST.xy + _Caustics_ST.zw; uv += _CausticsSpeed * _Time.y; // RGB split fixed s = _SplitRGB; fixed r = tex2D(tex, uv + fixed2(+s, +s)).r; fixed g = tex2D(tex, uv + fixed2(+s, -s)).g; fixed b = tex2D(tex, uv + fixed2(-s, -s)).b; fixed3 caustics = fixed3(r, g, b); 

La quantité de décalage des canaux RVB peut être sélectionnée arbitrairement, mais même avec ce décalage simple, une image très convaincante est obtenue:


Conclusion et téléchargements


Si vous êtes intéressé à apprendre à créer des textures caustiques sans couture, alors vous devriez lire l'article intéressant Textures caustiques périodiques .

Pendant ce temps, Florian continue de travailler sur son shader caustique et a apporté des améliorations assez intéressantes qui peuvent être vues.


Un package complet pour ce tutoriel est disponible sur Patreon, il comprend tous les atouts nécessaires pour recréer cette technique. Le package a été exporté depuis Unity 2019.2 et nécessite la pile de post-traitement v2.

Source: https://habr.com/ru/post/fr467887/


All Articles