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