Dissolution Shaders und World Exploration

Teil 1: Dissolution Shader


Der Auflösungs-Shader liefert einen schönen Effekt, außerdem ist er leicht zu erstellen und zu verstehen. Heute werden wir es in Unity Shader Graph schaffen und auch auf HLSL schreiben.

Hier ist ein Beispiel dafür, was wir erstellen werden:



Wie funktioniert es?


Um einen Überblendungs- Shader zu erstellen, müssen wir mit dem AlphaClipThreshold- Wert im Shader „Shader Graph“ arbeiten oder die HLSL-Funktion namens clip verwenden .

Im Wesentlichen weisen wir den Shader an, das Pixel nicht basierend auf der übergebenen Textur und dem übergebenen Wert zu rendern . Wir müssen Folgendes wissen: Die weißen Teile lösen sich schneller auf .

Wir werden die folgende Textur verwenden:


Sie können Ihre eigenen erstellen - gerade Linien, Dreiecke, aber alles! Denken Sie daran, dass sich die weißen Teile schneller auflösen .

Ich habe diese Textur in Photoshop mit dem Wolkenfilter erstellt.

Auch wenn Sie nur am Shader-Diagramm interessiert sind und nichts über HLSL wissen, empfehle ich dennoch, diesen Teil zu lesen, da es hilfreich ist, zu verstehen, wie das Unity-Shader-Diagramm im Inneren funktioniert.



Hlsl


In HLSL verwenden wir die Funktion clip (x) . Die Clip (x) -Funktion verwirft alle Pixel mit einem Wert kleiner als Null . Wenn wir also clip (-1) aufrufen, werden wir sicher sein, dass der Shader dieses Pixel niemals rendern wird. Weitere Informationen zu Clips finden Sie in Microsoft Docs .

Die Eigenschaften


Der Shader benötigt zwei Eigenschaften, " Textur auflösen" und " Betrag" (die den gesamten Ausführungsprozess angeben). Wie bei anderen Eigenschaften und Variablen können Sie sie beliebig aufrufen.

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

Stellen Sie sicher, dass Sie nach CGPROGRAM SubShader Folgendes hinzufügen (mit anderen Worten, deklarieren Sie Variablen):

 sampler2D _DissolveTexture; half _Amount; 

Vergessen Sie auch nicht. dass ihre Namen mit den Namen im Abschnitt Eigenschaften übereinstimmen müssen.

Funktion


Wir beginnen die Oberflächen- oder Fragmentfunktion , indem wir die Textur der Auflösung abtasten und den Wert von Rot erhalten . PS Unsere Textur wird in Graustufen gespeichert, dh die Werte von R , G und B sind gleich, und Sie können eine beliebige auswählen . Zum Beispiel ist Weiß (1,1,1) , Schwarz ist (0,0,0) .

In meinem Beispiel verwende ich einen Surface Shader:

 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. //[...] } 

Und alle! Wir können diesen Prozess auf jeden vorhandenen Shader anwenden und ihn in einen Auflösungs-Shader verwandeln!

Hier ist der Standard-Surface-Shader der Unity-Engine, der in einen zweiseitigen Auflösungs-Shader umgewandelt wurde:

 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" } 



Shader-Diagramm


Wenn wir diesen Effekt mithilfe des Unity Shader-Diagramms erstellen müssen, müssen wir den AlphaClipThreshold- Wert verwenden (der anders funktioniert als Clip (x) von HLSL). In diesem Beispiel habe ich einen PBR-Shader erstellt.

Die AlphaClipThreshold- Funktion weist den Shader an, alle Pixel zu verwerfen, deren Wert unter dem Alpha- Wert liegt. Wenn es beispielsweise 0,3 f ist und unser Alpha-Wert 0,2 f beträgt, rendert der Shader dieses Pixel nicht. Die AlphaClipThreshold- Funktion finden Sie in der Unity-Dokumentation : PBR- Masterknoten und unbeleuchteter Masterknoten .

Hier ist unser fertiger Shader:


Wir probieren die Auflösungstextur aus, erhalten den roten Wert und addieren ihn dann zum Betragswert (eine Eigenschaft, die ich hinzugefügt habe, um den gesamten Ausführungsprozess anzuzeigen. Ein Wert von 1 bedeutet vollständige Auflösung) und verbinden ihn mit AlphaClipThreshold . Fertig!

Wenn Sie es auf einen vorhandenen Shader anwenden möchten, kopieren Sie einfach die Knotenverbindungen nach AlphaClipThreshold (verpassen Sie nicht die erforderlichen Eigenschaften!). Sie können es auch zweiseitig machen und ein noch schöneres Ergebnis erzielen!



Konturauflösungs-Shader


Und wenn Sie versuchen, Konturen hinzuzufügen? Lass es uns tun!


Wir können nicht mit bereits aufgelösten Pixeln arbeiten, da sie nach dem Löschen für immer verschwinden . Stattdessen können wir mit „fast aufgelösten“ Werten arbeiten!

In HLSL ist dies sehr einfach. Fügen Sie nach der Berechnung des Clips nur ein paar Codezeilen hinzu:

 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. //[...] } 

Fertig!

Bei der Arbeit mit Shader Graph unterscheidet sich die Logik geringfügig. Hier ist der fertige Shader:




Mit einem einfachen Auflösungs-Shader können wir sehr coole Effekte erzielen . Sie können mit verschiedenen Texturen und Werten experimentieren und sich etwas anderes einfallen lassen!

Teil 2: World Exploration Shader


Mit einem World Exploration Shader (oder einem „ World Dissolution Shader oder Global Dissolution “) können wir alle Objekte in der Szene basierend auf ihrer Entfernung zur Position gleichermaßen ausblenden. Jetzt erstellen wir einen solchen Shader im Unity Shader-Diagramm und schreiben ihn in HLSL .

Hier ist ein Beispiel dafür, was wir erstellen werden:




Abstand als Parameter


Angenommen, wir müssen ein Objekt in einer Szene auflösen, wenn es zu weit vom Player entfernt ist . Wir haben bereits den Parameter _Amount angekündigt, der das Verschwinden / Auflösen des Objekts steuert. Daher müssen wir ihn durch den Abstand zwischen dem Objekt und dem Player ersetzen.

Dazu müssen wir die Positionen von Spieler und Objekt einnehmen.

Spielerposition


Der Vorgang ist sowohl für Unity Shader Graph als auch für HLSL ähnlich: Wir müssen die Position des Spielers im Code übertragen.

 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 } 



Shader-Diagramm


Objektposition und Entfernung dazu


Mit dem Shader-Diagramm können wir die Knoten Position und Abstand verwenden.



PS Damit dieses System mit Sprite-Renderern funktioniert, müssen Sie die Eigenschaft _MainTex hinzufügen, testen und mit Albedo verbinden. Sie können mein vorheriges Sprites- Tutorial für diffuse Shader lesen (das Shader-Diagramme verwendet).



HLSL (Oberfläche)


Objektposition


In HLSL können wir die Variable worldPos zu unserer Eingabestruktur hinzufügen, um die Positionen der Scheitelpunkte von Objekten abzurufen .

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

Auf der Unity-Dokumentationsseite erfahren Sie, welche anderen integrierten Parameter Sie der Eingabestruktur hinzufügen können.

Abstand anwenden


Wir müssen den Abstand zwischen den Objekten und dem Spieler als Auflösungsbetrag verwenden. Dazu können Sie die integrierte Distanzfunktion ( Microsoft-Dokumentation ) verwenden.

 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[...] } 

Ergebnis (3D)



Ergebnis (2D)



Wie Sie sehen können, lösen sich die Objekte „lokal“ auf. Wir haben keinen einheitlichen Effekt erzielt, da wir den „Auflösungswert“ aus der Textur erhalten, die mit der UV-Strahlung jedes Objekts abgetastet wurde. (In 2D ist dies weniger auffällig).



3D LocalUV Shader auf HLSL auflösen


 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 - LocalUV Dissolve Shader auf 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 Um den letzten Shader zu erstellen, habe ich den Standard-Unity Sprites-Diffuse-Shader kopiert und den zuvor in diesem Teil des Artikels beschriebenen Teil „Auflösen“ hinzugefügt. Alle Standard-Shader finden Sie hier .



Den Effekt homogen machen


Um den Effekt homogen zu machen, können wir globale Koordinaten (Position in der Welt) als UV-Koordinaten der Auflösungstextur verwenden. Es ist auch wichtig, Wrap = Repeat in den Auflösungs-Texturparametern einzustellen, damit wir die Textur wiederholen können, ohne es zu bemerken (stellen Sie sicher, dass die Textur nahtlos ist und sich gut wiederholt!).


HLSL (Oberfläche)


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

Shader-Diagramm



Ergebnis (2D)



Dies ist das Ergebnis: Wir können feststellen, dass die Auflösungstextur nun für die ganze Welt einheitlich ist.

Dieser Shader ist bereits ideal für 2D-Spiele , muss jedoch für 3D-Objekte verbessert werden .

Das Problem mit 3D-Objekten



Wie Sie sehen können, funktioniert der Shader nicht für "nicht vertikale" Flächen und verzerrt die Textur erheblich. Deshalb stellt sich heraus. dass die UV-Koordinaten den Wert float2 benötigen, und wenn wir worldPos übergeben, erhält es nur X und Y.

Wenn wir dieses Problem beheben, indem wir Berechnungen anwenden, um die Textur auf allen Flächen anzuzeigen, werden wir zu einem neuen Problem kommen: Beim Abdunkeln überschneiden sich Objekte und bleiben nicht homogen.

Für Anfänger wird es schwierig sein, die Lösung zu verstehen: Es ist notwendig, die Textur loszuwerden, dreidimensionales Rauschen in der Welt zu erzeugen und den „Wert der Auflösung“ daraus zu ziehen. In diesem Beitrag werde ich die Erzeugung von 3D-Rauschen nicht erklären, aber Sie können eine Reihe von Funktionen finden, die sofort einsatzbereit sind!

Hier ist ein Beispiel für einen Noise Shader: https://github.com/keijiro/NoiseShader . Sie können auch lernen, wie Sie Lärm erzeugen: https://thebookofshaders.com/11/ und hier: https://catlikecoding.com/unity/tutorials/noise/

Ich werde meine Oberflächenfunktion folgendermaßen einstellen (vorausgesetzt, Sie haben den Rauschteil bereits geschrieben):

 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; } 

Eine kurze Erinnerung an HLSL: Bevor eine Funktion verwendet / aufgerufen wird, muss sie geschrieben / deklariert werden.

PS Wenn Sie einen Shader mithilfe des Unity Shader-Diagramms erstellen möchten, müssen Sie benutzerdefinierte Knoten verwenden (und durch Schreiben von HLSL-Code Rauschen erzeugen). Ich werde in einem zukünftigen Tutorial über benutzerdefinierte Knoten sprechen.

Ergebnis (3D)





Konturen hinzufügen


Um Konturen hinzuzufügen, müssen Sie den Vorgang aus dem vorherigen Teil des Lernprogramms wiederholen.




Umgekehrter Effekt


Und wenn wir diesen Effekt umkehren wollen? (Objekte sollten verschwinden, wenn sich ein Spieler in der Nähe befindet.)

Es reicht uns, eine Zeile zu ändern:

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

(Der gleiche Vorgang gilt für Shader Graph).

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


All Articles