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;
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() {
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;
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);
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;
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).