Shaders de dissolution et exploration du monde

Partie 1: Dissolution Shader


Le shader de dissolution renvoie un bel effet, de plus, il est facile à créer et à comprendre; Aujourd'hui, nous allons le faire dans Unity Shader Graph , et aussi écrire sur HLSL .

Voici un exemple de ce que nous allons créer:



Comment ça marche


Pour créer un shader de dissolution , nous devrons travailler avec la valeur AlphaClipThreshold dans le shader «Shader Graph» ou utiliser la fonction HLSL appelée clip .

Essentiellement, nous disons au shader de ne pas rendre le pixel en fonction de la texture et de la valeur transmises. Nous devons savoir ce qui suit: les parties blanches se dissolvent plus rapidement .

Nous utiliserons la texture suivante:


Vous pouvez créer les vôtres - des lignes droites, des triangles, mais n'importe quoi! N'oubliez pas que les parties blanches se dissolvent plus rapidement .

J'ai créé cette texture dans Photoshop en utilisant le filtre Clouds.

Même si vous n'êtes intéressé que par le Shader Graph et que vous ne savez rien de HLSL, je recommande toujours de lire cette partie, car il est utile de comprendre comment fonctionne le Unity Shader Graph à l'intérieur.



Hlsl


En HLSL, nous utilisons la fonction clip (x) . La fonction clip (x) supprime tous les pixels avec une valeur inférieure à zéro . Par conséquent, si nous appelons clip (-1) , nous serons sûrs que le shader ne rendra jamais ce pixel. Vous pouvez en savoir plus sur le clip dans Microsoft Docs .

Les propriétés


Le shader a besoin de deux propriétés, Dissoudre la texture et la quantité (qui indiqueront le processus d'exécution global). Comme pour les autres propriétés et variables, vous pouvez les appeler comme bon vous semble.

Properties { //Your other properties //[...] //Dissolve shader properties _DissolveTexture("Dissolve Texture", 2D) = "white" {} _Amount("Amount", Range(0,1)) = 0 } 

Assurez-vous d'ajouter ce qui suit après CGPROGRAM SubShader (en d'autres termes, déclarez les variables):

 sampler2D _DissolveTexture; half _Amount; 

N'oubliez pas non plus. que leurs noms doivent correspondre aux noms de la section Propriétés.

Fonction


Nous commençons la fonction Surface ou Fragment en échantillonnant la texture de la dissolution et en obtenant la valeur du rouge . PS Notre texture est stockée en niveaux de gris , c'est-à-dire que ses valeurs de R , G et B sont égales, et vous pouvez choisir n'importe laquelle d'entre elles . Par exemple, le blanc est (1,1,1) , le noir est (0,0,0) .

Dans mon exemple, j'utilise un shader de surface:

 void surf (Input IN, inout SurfaceOutputStandard o) { half dissolve_value = tex2D(_DissolveTexture, IN.uv_MainTex).r; //Get how much we have to dissolve based on our dissolve texture clip(dissolve_value - _Amount); //Dissolve! //Your shader body, you can set the Albedo etc. //[...] } 

Et c'est tout! Nous pouvons appliquer ce processus à n'importe quel shader existant et le transformer en shader de dissolution !

Voici le Surface Shader standard du moteur Unity, transformé en shader de dissolution bilatéral :

 Shader "Custom/DissolveSurface" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 //Dissolve properties _DissolveTexture("Dissolve Texutre", 2D) = "white" {} _Amount("Amount", Range(0,1)) = 0 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 Cull Off //Fast way to turn your material double-sided CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; //Dissolve properties sampler2D _DissolveTexture; half _Amount; void surf (Input IN, inout SurfaceOutputStandard o) { //Dissolve function half dissolve_value = tex2D(_DissolveTexture, IN.uv_MainTex).r; clip(dissolve_value - _Amount); //Basic shader function fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } ENDCG } FallBack "Diffuse" } 



Graphique de shader


Si nous devons créer cet effet à l'aide du graphe Unity Shader , nous devons utiliser la valeur AlphaClipThreshold (qui fonctionne différemment du clip (x) de HLSL). Dans cet exemple, j'ai créé un shader PBR.

La fonction AlphaClipThreshold demande au shader de supprimer tous les pixels dont la valeur est inférieure à sa valeur Alpha . Par exemple, s'il est de 0,3f et que notre valeur alpha est de 0,2f , le shader ne rendra pas ce pixel. La fonction AlphaClipThreshold se trouve dans la documentation Unity : PBR Master Node et Unlit Master Node .

Voici notre shader fini:


Nous échantillonnons la texture de dissolution et obtenons la valeur rouge , puis l'ajoutons à la valeur Montant (qui est une propriété que j'ai ajoutée pour indiquer le processus d'exécution global, une valeur de 1 signifie une dissolution complète) et la connectons à AlphaClipThreshold . C'est fait!

Si vous souhaitez l'appliquer à n'importe quel shader existant, copiez simplement les connexions de noeud vers AlphaClipThreshold (ne manquez pas les propriétés nécessaires!). Vous pouvez également le rendre recto-verso et obtenir un résultat encore plus beau!



Shader de dissolution de contour


Et si vous essayez d'y ajouter des contours ? Faisons-le!


Nous ne pouvons pas travailler avec des pixels déjà dissous, car après les avoir déposés , ils disparaissent à jamais . Au lieu de cela, nous pouvons travailler avec des valeurs «presque dissoutes»!

En HLSL, c'est très simple, il suffit d'ajouter quelques lignes de code après avoir calculé le clip :

 void surf (Input IN, inout SurfaceOutputStandard o) { //[...] //After our clip calculations if (dissolve_value - _Amount < .05f) //outline width = .05f o.Emission = fixed3(1, 1, 1); //emits white color //Your shader body, you can set the Albedo etc. //[...] } 

C'est fait!

Lorsque vous travaillez avec Shader Graph, la logique est légèrement différente. Voici le shader fini:




Nous pouvons créer des effets très cool avec un simple shader de dissolution ; Vous pouvez expérimenter différentes textures et valeurs , ainsi que trouver autre chose!

Partie 2: Shader d'exploration du monde


Un shader d' exploration du monde (ou un « shader de dissolution du monde ou dissolution globale ») nous permet de masquer également tous les objets de la scène en fonction de leur distance à la position; maintenant, nous allons créer un tel shader dans le graphique Unity Shader et l'écrire en HLSL .

Voici un exemple de ce que nous allons créer:




La distance comme paramètre


Supposons que nous devions dissoudre un objet dans une scène s'il est trop éloigné du joueur . Nous avons déjà annoncé le paramètre _Amount , qui contrôle la disparition / dissolution de l'objet, nous devons donc le remplacer par la distance entre l'objet et le lecteur.

Pour ce faire, nous devons prendre les positions de joueur et d' objet .

Position du joueur


Le processus sera similaire pour Unity Shader Graph et HLSL : nous devons transférer la position du joueur dans le code.

 private void Update() { //Updates the _PlayerPos variable in all the shaders //Be aware that the parameter name has to match the one in your shaders or it wont' work Shader.SetGlobalVector("_PlayerPos", transform.position); //"transform" is the transform of the Player } 



Graphique de shader


Position et distance de l'objet


En utilisant le Shader Graph, nous pouvons utiliser les nœuds Position et Distance.



PS Pour que ce système fonctionne avec Sprite Renderers, vous devez ajouter la propriété _MainTex, l'échantillonner et la connecter à albedo. Vous pouvez lire mon précédent tutoriel sur les shaders diffus Sprites (qui utilise le graphique des shaders).



HLSL (surface)


Position de l'objet


En HLSL, nous pouvons ajouter la variable worldPos à notre structure Input pour obtenir les positions des sommets des objets.

 struct Input { float2 uv_MainTex; float3 worldPos; //add this and Unity will set it automatically }; 

Sur la page de documentation Unity, vous pouvez découvrir quels autres paramètres intégrés il est permis d'ajouter à la structure d'entrée.

Appliquer la distance


Nous devons utiliser la distance entre les objets et le joueur comme quantité de dissolution. Pour ce faire, vous pouvez utiliser la fonction de distance intégrée ( documentation Microsoft ).

 void surf (Input IN, inout SurfaceOutputStandard o) { half dissolve_value = tex2D(_DissolveTexture, IN.uv_MainTex).x; float dist = distance(_PlayerPos, IN.worldPos); clip(dissolve_value - dist/ 6f); //"6" is the maximum distance where your object will start showing //Set albedo, alpha, smoothness etc[...] } 

Résultat (3D)



Résultat (2D)



Comme vous pouvez le voir, les objets se dissolvent «localement», nous n'avons pas obtenu un effet uniforme, car nous obtenons la «valeur de dissolution» de la texture échantillonnée en utilisant les UV de chaque objet. (En 2D, c'est moins visible).



3D LocalUV Dissolve Shader sur HLSL


 Shader "Custom/GlobalDissolveSurface" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness("Smoothness", Range(0,1)) = 0.5 _Metallic("Metallic", Range(0,1)) = 0.0 _DissolveTexture("Dissolve texture", 2D) = "white" {} _Radius("Distance", Float) = 1 //distance where we start to reveal the objects } SubShader{ Tags { "RenderType" = "Opaque" } LOD 200 Cull off //material is two sided CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 sampler2D _MainTex; sampler2D _DissolveTexture; //texture where we get the dissolve value struct Input { float2 uv_MainTex; float3 worldPos; //Built-in world position }; half _Glossiness; half _Metallic; fixed4 _Color; float3 _PlayerPos; //"Global Shader Variable", contains the Player Position float _Radius; void surf (Input IN, inout SurfaceOutputStandard o) { half dissolve_value = tex2D(_DissolveTexture, IN.uv_MainTex).x; float dist = distance(_PlayerPos, IN.worldPos); clip(dissolve_value - dist/ _Radius); fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } ENDCG } FallBack "Diffuse" } 

Sprites Diffuse - Local UV Dissolve Shader sur HLSL


 Shader "Custom/GlobalDissolveSprites" { Properties { [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {} _Color("Tint", Color) = (1,1,1,1) [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 [HideInInspector] _RendererColor("RendererColor", Color) = (1,1,1,1) [HideInInspector] _Flip("Flip", Vector) = (1,1,1,1) [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {} [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0 _DissolveTexture("Dissolve texture", 2D) = "white" {} _Radius("Distance", Float) = 1 //distance where we start to reveal the objects } SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" "CanUseSpriteAtlas" = "True" } Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha CGPROGRAM #pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing #pragma multi_compile _ PIXELSNAP_ON #pragma multi_compile _ ETC1_EXTERNAL_ALPHA #include "UnitySprites.cginc" struct Input { float2 uv_MainTex; fixed4 color; float3 worldPos; //Built-in world position }; sampler2D _DissolveTexture; //texture where we get the dissolve value float3 _PlayerPos; //"Global Shader Variable", contains the Player Position float _Radius; void vert(inout appdata_full v, out Input o) { v.vertex = UnityFlipSprite(v.vertex, _Flip); #if defined(PIXELSNAP_ON) v.vertex = UnityPixelSnap(v.vertex); #endif UNITY_INITIALIZE_OUTPUT(Input, o); o.color = v.color * _Color * _RendererColor; } void surf(Input IN, inout SurfaceOutput o) { half dissolve_value = tex2D(_DissolveTexture, IN.uv_MainTex).x; float dist = distance(_PlayerPos, IN.worldPos); clip(dissolve_value - dist / _Radius); fixed4 c = SampleSpriteTexture(IN.uv_MainTex) * IN.color; o.Albedo = c.rgb * ca; o.Alpha = ca; } ENDCG } Fallback "Transparent/VertexLit" } 

PS Pour créer le dernier shader, j'ai copié le shader Unity Sprites-Diffuse standard et ajouté la partie «fondu» décrite plus haut dans cette partie de l'article. Tous les shaders standard peuvent être trouvés ici .



Rendre l'effet homogène


Pour rendre l'effet homogène, nous pouvons utiliser les coordonnées globales (position dans le monde) comme coordonnées UV de la texture de dissolution. Il est également important de définir Wrap = Repeat dans les paramètres de texture de dissolution afin que nous puissions répéter la texture sans le remarquer (assurez-vous que la texture est transparente et se répète bien!)


HLSL (surface)


 half dissolve_value = tex2D(_DissolveTexture, IN.worldPos / 4).x; //I modified the worldPos to reduce the texture size 

Graphique de shader



Résultat (2D)



C'est le résultat: on peut remarquer que la texture de la dissolution est désormais uniforme pour le monde entier.

Ce shader est déjà idéal pour les jeux 2D , mais pour les objets 3D il doit être amélioré .

Le problème avec les objets 3D



Comme vous pouvez le voir, le shader ne fonctionne pas pour les faces "non verticales" et déforme considérablement la texture. C’est pourquoi il est que les coordonnées UV ont besoin de la valeur float2, et si nous passons worldPos, alors il ne reçoit que X et Y.

Si nous éliminons ce problème en appliquant des calculs pour afficher la texture sur toutes les faces, nous arriverons à un nouveau problème: lors de l'assombrissement, les objets se croiseront et ne resteront pas homogènes.

Il sera difficile pour les débutants de comprendre la solution: il faut se débarrasser de la texture, générer du bruit tridimensionnel dans le monde et en tirer la «valeur de dissolution». Dans cet article je ne vous expliquerai pas la génération de bruit 3D, mais vous pouvez trouver un tas de fonctions prêtes à l'emploi!

Voici un exemple de shader de bruit: https://github.com/keijiro/NoiseShader . Vous pouvez également apprendre à générer du bruit ici: https://thebookofshaders.com/11/ et ici: https://catlikecoding.com/unity/tutorials/noise/

Je définirai ma fonction de surface de cette façon (en supposant que vous avez déjà écrit la partie bruit):

 void surf (Input IN, inout SurfaceOutputStandard o) { float dist = distance(_PlayerPos, IN.worldPos); //"abs" because you have to make sure that the noise is between the range [0,1] //you can remove "abs" if your noise function returns a value between [0,1] //also, replace "NOISE_FUNCTION_HERE" with your 3D noise function. half dissolve_value = abs(NOISE_FUNCTION_HERE(IN.worldPos)); if (dist > _Radius) { float clip_value = dissolve_value - ((dist - _Radius) / _Radius); clip(clip_value); if (clip_value < 0.05f) o.Emission = float3(1, 1, 1); } fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } 

Un bref rappel de HLSL: avant d'utiliser / d'appeler une fonction, elle doit être écrite / déclarée.

PS Si vous souhaitez créer un shader à l'aide du graphe Unity Shader, vous devez utiliser des nœuds personnalisés (et générer du bruit en y écrivant du code HLSL). Je parlerai des nœuds personnalisés dans un futur tutoriel.

Résultat (3D)





Ajout de contours


Pour ajouter des contours, vous devez répéter le processus de la partie précédente du didacticiel.




Effet inversé


Et si nous voulons inverser cet effet? (Les objets devraient disparaître si un joueur est à proximité)

Il nous suffit de changer une ligne:

 float dist = _Radius - distance(_PlayerPos, IN.worldPos); 

(Le même processus s'applique au Shader Graph).

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


All Articles