Hallo allerseits! Mein Name ist Grigory Dyadichenko und ich bin der Gründer und CTO von Foxsys Studios. Heute sprechen wir über Vertex-Shader. Der Artikel wird die Praxis aus der Sicht von Unity untersuchen, sehr einfache Beispiele sowie viele Links zum Studium von Informationen über Shader in Unity. Wenn Sie gut darin sind, Shader zu schreiben, werden Sie für sich selbst nichts Neues finden. Jeder, der anfangen möchte, Shader in Unity zu schreiben, ist bei cat willkommen.

Ein bisschen Theorie
Zum besseren Verständnis des Shader-Prozesses werfen wir einen Blick auf eine kleine Theorie. Ein Vertex-Shader oder Vertex-Shader ist eine programmierbare Stufe eines Shaders, der mit einzelnen Vertexen arbeitet. Vertexe wiederum speichern verschiedene Attribute, die von diesem Teil des Shaders verarbeitet werden, um konvertierte Attribute am Ausgang zu erhalten.
Beispiele, bei denen Vertex-Shader verwendet werden
Verformung von Objekten - realistische Wellen, Auswirkungen von Regenwellen, Verformung beim Auftreffen einer Kugel - all dies kann mit Vertex-Shadern durchgeführt werden und sieht realistischer aus als dasselbe, das durch Bump-Mapping im Fragmentteil des Shaders erzielt wird. Da dies eine Änderung der Geometrie ist. Level 3.0-Shader zu diesem Thema verfügen über eine Technik namens Dispacement Mapping, da sie jetzt Zugriff auf Texturen im Scheitelpunktteil des Shaders haben.
Animation von Objekten. Spiele sehen lebendiger und interessanter aus, wenn Pflanzen auf einen Charakter oder Bäume reagieren, die sich im Wind wiegen. Hierzu werden auch Vertex-Shader verwendet.
Cartoon Beleuchtung oder stilisiert. In vielen Spielen sieht aus Sicht des Stils nicht die pbr-Beleuchtung viel interessanter aus, sondern die Stilisierung. Gleichzeitig macht es keinen Sinn, irgendetwas im Fragmentteil zu berechnen.
Häuten. Gegenwärtig ist dieses Problem in Spiel-Engines behoben, aber es ist dennoch nützlich, Vertex-Shader zu verstehen, um zu verstehen, wie es funktioniert.
Einfache Beispiele für die Arbeit mit Scheitelpunkten

Ich möchte nicht, dass es passiert, wie in den alten Lektionen über das Zeichnen einer Eule. Gehen wir also schrittweise vor. Erstellen Sie einen Standardoberflächen-Shader. Dies kann mit der rechten Maustaste in der Projektansicht oder im oberen Bereich auf der Registerkarte Assets erfolgen. Erstellen-> Shader-> Standard Surface Shader.
Und wir bekommen so einen Standardrohling.
Surface ShaderShader "Custom/SimpleVertexExtrusionShader"
{
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
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
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;
}
ENDCG
}
FallBack "Diffuse"
}
Wie es funktioniert und im Allgemeinen werden wir es im Artikel nach der Grundübung detailliert analysieren und es während der Implementierung von Shadern teilweise verstehen. Lassen Sie vorerst einige Dinge wie gegeben bleiben. Kurz gesagt, es gibt keine Magie (in Bezug auf die Art und Weise, wie Parameter erfasst werden usw.). Nur für bestimmte Schlüsselwörter generiert das Gerät Code für Sie, um ihn nicht von Grund auf neu zu schreiben. Daher ist dieser Prozess nicht offensichtlich genug. Weitere Informationen zum Surface Shader und seinen Eigenschaften in Unity finden Sie hier.
docs.unity3d.com/Manual/SL-SurfaceShaders.htmlWir werden alles Überflüssige entfernen, damit es nicht ablenkt, da es im gegebenen Moment nicht benötigt wird. Und hol dir so einen kurzen Shader.
Vereinfachter ShaderShader "Custom/SimpleVertexExtrusionShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0
struct Input
{
float4 color : COLOR;
};
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = _Color;
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}

Nur die Farbe auf dem Modell mit Beleuchtung. In diesem Fall ist Unity für die Berechnung der Beleuchtung verantwortlich.
Fügen Sie zunächst den einfachsten Effekt aus den Unity-Beispielen hinzu. Die Extrusion ist normal und an ihrem Beispiel werden wir analysieren, wie es funktioniert.
Fügen Sie dazu den Modifikator
vertex: vert zur
Zeile #pragma surface surf Standard fullforwardshadows hinzu. Wenn wir
inout appdata_full v als Parameter an eine Funktion übergeben, ist diese Funktion im Wesentlichen ein Vertex-Modifikator. Im Kern ist es Teil des Vertex-Shaders, der von der Code-Generierungseinheit erstellt wird, die die vorläufige Verarbeitung von Vertices durchführt.
Fügen Sie außerdem im
Eigenschaftsblock das Feld
_Amount hinzu , das Werte von 0 bis 1 akzeptiert. Um das Feld
_Amount im Shader zu verwenden, müssen Sie es auch dort definieren. In der Funktion werden wir abhängig von
_Amount einfach zur Normalen
wechseln , wobei 0 die Standardscheitelpunktposition (Null-Verschiebung) und 1 die Verschiebung genau zur Normalen ist.
SimpleVertexExtrusionShaderShader "Custom/SimpleVertexExtrusionShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_Amount ("Extrusion Amount", Range(0,1)) = 0.5
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows vertex:vert
#pragma target 3.0
struct Input
{
float4 color : COLOR;
};
fixed4 _Color;
float _Amount;
void vert (inout appdata_full v)
{
v.vertex.xyz += v.normal * _Amount;
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = _Color;
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
Sie können ein wichtiges Merkmal von Shadern feststellen. Obwohl der Shader in jedem Frame ausgeführt wird, wird das während des Shader-Vorgangs erhaltene Ergebnis nicht im Netz gespeichert, sondern nur zum Rendern verwendet. Daher ist es unmöglich, sich auf die Funktionen des Shaders sowie das
Update in Skripten zu beziehen. Sie werden bei jedem Frame angewendet, ohne die Mesh-Daten zu ändern, sondern ändern einfach das Mesh für das weitere Rendern.
Eine der einfachsten Möglichkeiten, eine Animation zu erstellen, besteht darin, die Amplitude mithilfe der Zeit zu ändern. Das Gerät verfügt über integrierte Variablen. Eine vollständige Liste finden Sie hier
docs.unity3d.com/Manual/SL-UnityShaderVariables.html In diesem Fall schreiben wir einen neuen Shader basierend auf unserem vorherigen Shader.
Lassen Sie uns anstelle von
_Amount den
Gleitkommawert _Amplitude festlegen und die integrierte Unity-Variable
_SinTime verwenden .
_SinTime ist der Sinus der Zeit und nimmt daher Werte von -1 bis 1 an. Vergessen Sie jedoch nicht, dass alle in Unit Shader integrierten
Zeitvariablen float4- Vektoren sind. Beispielsweise ist
_SinTime definiert als
(sin (t / 8), sin (t / 4), sin (t / 2), sin (t)) , wobei t die Zeit ist. Daher nehmen wir die z-Komponente, damit die Animation schneller ist. Und wir bekommen:
SimpleVertexExtrusionWithTimeShader "Custom/SimpleVertexExtrusionWithTime"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_Amplitude ("Extrusion Amplitude", float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows vertex:vert
#pragma target 3.0
struct Input
{
float4 color : COLOR;
};
fixed4 _Color;
float _Amplitude;
void vert (inout appdata_full v)
{
v.vertex.xyz += v.normal * _Amplitude * (1 - _SinTime.z);
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = _Color;
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
Das waren also einfache Beispiele. Es ist Zeit, eine Eule zu zeichnen!
Verformung von Gegenständen
Ich habe bereits einen ganzen Artikel über einen Deformationseffekt mit einer detaillierten Analyse der Mathematik des Prozesses und der Logik des Denkens bei der Entwicklung eines solchen Effekts geschrieben.
Habr.com/de/post/435828 Dies wird unsere Eule sein.
Alle Shader im Artikel sind in hlsl geschrieben. Diese Sprache hat eine eigene umfangreiche Dokumentation, die viele vergessen und sich fragen, woher die Hälfte der verkabelten Funktionen stammt, obwohl sie in HLSL-
Dokumenten definiert sind.
Microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl- EigenfunktionenTatsächlich sind Oberflächen-Shader in einer Einheit ein großes und umfangreiches Thema für sich. Außerdem möchten Sie sich nicht immer mit Unity-Beleuchtung anlegen. Manchmal müssen Sie den schnellsten Shader betrügen und schreiben, der nur die richtigen vordefinierten Effekte hat. In Unity können Sie Shader auf niedrigerer Ebene schreiben.
Low-Level-Shader

Nach der guten alten Tradition der Arbeit mit Shadern werden wir im Folgenden das Stanford-Kaninchen quälen.
Im Allgemeinen ist das sogenannte Unity ShaderLab im Wesentlichen eine Visualisierung eines Inspektors mit Feldern in Materialien und einer gewissen Vereinfachung des Schreibens von Shadern.
Nehmen Sie die allgemeine Struktur des Shaderlab-Shaders:
Allgemeine Shader-StrukturShader "MyShaderName"
{
Properties
{
//
}
SubShader // ( )
{
Pass
{
//
}
//
}
//
FallBack "VertexLit" // ,
}
Kompilierungsanweisungen wie
#pragma vertex vert#pragma fragment fragBestimmen Sie, welcher Shader als Vertex- bzw. Fragment-Shader kompiliert werden soll.
Nehmen wir an, wir nehmen eines der häufigsten Beispiele - einen Shader zum Anzeigen der Farbe von Normalen:
SimpleNormalVisualizationShader "Custom/SimpleNormalVisualization"
{
Properties
{
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.color = v.normal * 0.5 + 0.5;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4 (i.color, 1);
}
ENDCG
}
}
FallBack "VertexLit"
}

In diesem Fall schreiben wir im Scheitelpunktteil den konvertierten Normalwert in die Scheitelpunktfarbe und im Pixelteil verwenden wir diese Farbe als Farbe des Modells.
Die
UnityObjectToClipPos- Funktion ist eine Unity-Zusatzfunktion (aus der
UnityCG.cginc- Datei), die die Scheitelpunkte des Objekts in die der Kamera zugeordnete Position übersetzt. Ohne sie wird ein Objekt, wenn es in die Sichtbarkeit der Kamera eintritt (Frustrum), unabhängig von der Position der Transformation in den Koordinaten des Bildschirms gezeichnet. Seit Anfang werden die Positionen der Eckpunkte in den Koordinaten des Objekts dargestellt. Nur Werte relativ zu seinem Drehpunkt.
Dieser Block.
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
Dies ist die Definition der Struktur, die im Scheitelpunktteil verarbeitet und in das erste Fragment übertragen wird. In diesem Fall wird bestimmt, dass zwei Parameter aus dem Netz entnommen werden - die Position des Scheitelpunkts und die Farbe des Scheitelpunkts. Weitere
Informationen darüber, welche Daten in einem Gerät gespeichert werden können, finden Sie unter diesem Link
docs.unity3d.com/Manual/SL-VertexProgramInputs.htmlWichtige Klarstellung. Die Namen der Netzattribute spielen keine Rolle. Nehmen wir an, Sie können im Farbattribut den Vektor der Abweichung von der ursprünglichen Position schreiben (auf diese Weise bewirken sie manchmal einen Effekt, wenn der Charakter davon abweicht, sodass das Gras davon abstößt). Wie dieses Attribut verarbeitet wird, hängt vollständig von Ihrem Shader ab.
Fazit
Vielen Dank für Ihre Aufmerksamkeit! Es ist problematisch, einige komplexe Effekte ohne einen fragmentarischen Teil zu schreiben. Aus diesem Grund werden wir ähnliche Themen in separaten Artikeln behandeln. Ich hoffe, dass in diesem Artikel etwas klarer wurde, wie der Code der Vertex-Shader im Allgemeinen geschrieben ist und wo Sie Informationen zum Lernen finden, da Shader ein sehr tiefes Thema sind.
In zukünftigen Artikeln werden wir die anderen Shader-Typen und die einzelnen Effekte analysieren und versuchen, meine Denklogik beim Erstellen neuer oder komplexer Effekte zu beschreiben.
Es wurde auch ein Repository erstellt, in dem alle Ergebnisse dieser Artikelserie
github.com/Nox7atra/ShaderExamples hinzugefügt
werden.Ich hoffe, dass diese Informationen Anfängern nützlich sind, die sich gerade mit diesem Thema befassen.
Einige nützliche Links (einschließlich Quellen):
www.khronos.org/opengl/wiki/Vertex_Shaderdocs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-referencedocs.unity3d.com/en/current/Manual/SL-Reference.htmldocs.unity3d.com/Manual/GraphicsTutorials.htmlwww.malbred.com/3d-grafika-3d-redaktory/sovremennaya-terminologiya-3d-grafiki/vertex-shader-vershinnyy-sheyder.html3dpapa.ru/genauer-Versetzungs-Workflow