Erstellung eines Voxel-Wasser/Lava-Shaders mit Unity

Blog | Softwareentwicklung | Unity |

In unserem ersten Blogbeitrag aus dem "Tagebuch eines Entwicklers" stellen wir Ihnen die Erstellung eines Voxel-Wasser-Shaders mit der Game-Engine Unity vor.

Unser Entwickler Daniel Fischer erkl├Ąrt Ihnen in einzelnen Arbeitsschritten mit anschaulichen Beispielen seine pers├Ânliche Herangehensweise bei der Umsetzung.

Es geht im Folgenden also darum, wie wir mit einem Shader ein langweiliges Rechteck:

in dieses spannende Ergebnis verwandeln k├Ânnen:

In diesem Blog benutzte Tools

F├╝r das Rendering benutze ich die Game-Engine Unity - Version 2020.3.2f1 - und f├╝r die Erstellung der Shader das visuelle Tool Shadergraph von Unity.

 

Was ist ein Shader? 

Shader sind kurz gesagt Programme, die nur auf einer GPU-Grafikkarte laufen und urspr├╝nglich f├╝r die "Schattierung" von 3D-Szenen benutzt wurden.

Mittlerweile werden sie auch f├╝r komplexe Spezialeffekte, Nachbearbeitungsarbeiten

und sogar Funktionen eingesetzt, die gar nichts mehr mit einer grafischen Darstellung zu tun haben.

Durch die Parallelisierung einer Grafikkarte sind Shader extrem schnell und performant. So lassen sich auch rechenintensive Probleme wie z. B. k├╝nstliche Intelligenz mit Pathfinding auf die Grafikkarte ├╝bertragen.

 

Welche Shader gibt es?

Die zwei h├Ąufigsten Shader sind Fragment-/Pixel- Shader und Vertex-Shader. Des Weiteren gibt es noch Compute-Shader f├╝r rechenintensive Anwendungen, Geometrie-Shader, Tessellation-Shader und Raytracing-Shader.

Ich beziehe mich hier allerdings nur auf Fragment-/Pixel- und Vertex-Shader.

 

Fragment-Shader

Fragment-Shader oder auch Pixel-Shader sind zust├Ąndig f├╝r die Farbe eines "Fragments" bzw. Pixel. Hiermit werden bspw. Texturen auf Objekten dargestellt.

Fragment- bzw. Pixel-Shader laufen f├╝r jeden einzelnen Pixel auf dem Bildschirm. Bei einer HD-Aufl├Âsung von 1920 x 1080 Pixel bedeutet dies exakt 2,073,600 mal.

 

Vertex-Shader 

Vertex-Shader werden benutzt, um die "Vertices" eines 3D-Objekts zu ver├Ąndern.

Gemeint sind hier die einzelnen Eckpunkte, aus denen ein 3D-Objekt besteht (s. gr├╝ne Markierung).

Der Trick hinter dem Welleneffekt

Da wir nun wissen, dass wir mit einem Vertex-Shader die einzelnen Punkte beliebig verschieben k├Ânnen, m├╝ssen wir dies nur noch dem Shader mitteilen.

Mit Shadergraph geht das ganz leicht, indem wir zur aktuellen Position der Vertices einfach etwas hinzuf├╝gen und diesen Wert dann f├╝r das Objekt umwandeln.

Wir haben unser Objekt nun um drei Einheiten nach oben verschoben.
Insgesamt ist dieser Effekt jedoch noch recht unspektakul├Ąr.

F├╝r tats├Ąchliche Wellenbewegungen brauchen wir "Noise"!

Noise, zu Deutsch "Rauschen", ist im Prinzip genau das.

Es gibt eine Funktion, mit der wir solch ein Rauschen mit einem wiedererkennbaren Muster erzeugen k├Ânnen.

Die Eingabewerte hei├čen hier "Scale" (Skalierung) und "Cell Density" (Zellen-Dichte). Wenn wir diese jeweils mit Zahlen f├╝llen, erzeugen wir genau so einen Rauscheffekt.

ÔÇőÔÇőÔÇőÔÇőÔÇőÔÇőÔÇőMehrere Noise-Ebenen ├╝berlagern

Wir k├Ânnen zwar direkt ein einzelnes Noise-Muster f├╝r unsere Wellen benutzen, das Ergebnis ist jedoch immer noch nicht so richtig beeindruckend.

Der Trick liegt darin, die verschiedenen Muster zu ├╝berlagern. F├╝r dieses Beispiel benutzen wir drei Ebenen.

Zwei Gradient-Noise-Muster mit verschiedenen Gr├Â├čen und ein Vornoi-Noise-Muster, das sp├Ąter die wei├čen Kanten von bspw. brechenden Wellen darstellt.

Was sind UVs?

Um fortzufahren, m├╝ssen wir den Begriff "UV" kl├Ąren. "U" und "V" sind die Texturkoordinaten. Diese bestimmen, welcher Teil einer Textur wo auf das 3D-Modell "gemalt", also projiziert, werden soll. U und V haben hier keine sprachliche Bedeutung.

UV-Koordinaten beginnen unten links auf einer Textur mit den Koordinaten "0, 0" und enden oben rechts mit den Koordinaten "1, 1".

 

UVs bewegen f├╝r richtige Wellen

Da Wellen sich bewegen, m├╝ssen wir auch die UVs unserer Noise-Muster bewegen. Das passiert mit "Tiling and Offset" (s. o. Screenshot).

Hier werden die Texturkoordinaten mit einer Zeitangabe und einem Wert multipliziert, um den Bewegungseffekt zu erzeugen.

 

Noise-Muster sind wiedererkennbar

Normalerweise w├╝rden Texturen grob gesagt "verschwinden", sobald wir die UVs zu weit bewegen.

Jedoch haben unsere Noise-Muster den Vorteil, dass sie wiedererkennbar sind und sich ihre UVs beliebig weit bewegen lassen.

 

Nun hei├čt es alles zusammenmischen

In Shadergraph gibt es die Funktion "Blend", mit der wir unsere Noise-Muster vermischen k├Ânnen. Wenn wir unsere beiden Muster eingeben, vermischt die Blend-Funktion diese um dem Wert einer Zahl.

Wir mixen nun zuerst die zwei Gradient-Noise-Muster und vermischen dieses neue Muster dann mit dem Voronoi-Muster.

Nun k├Ânnen wir die Vertices ver├Ąndern

Da wir jetzt ein sch├Ânes bewegtes Wellenmuster haben, k├Ânnen wir damit die Vertices ver├Ąndern.

Hierzu werden die Werte unseres finalen Noise-Musters normalisiert. Das hei├čt lediglich, dass die Werte dort nur noch zwischen "0" und "1" liegen.

Dann k├Ânnen wir dieses Muster f├╝r die Angabe der Wellenh├Âhe mit einer Zahl multiplizieren und zu unserer Position hinzuf├╝gen.

Hier ist es wichtig, dass wir nur den Wert f├╝r die H├Âhe bearbeiten. Sonst bewegt sich unser Objekt nach links und rechts, was unnat├╝rlich wirken w├╝rde.

Daher teilen wir die Position auf, sodass nur der Wert f├╝r die H├Âhe das Noise-Muster bekommt.

Da wir hier direkt mit der "Objekt"-Position arbeiten, m├╝ssen wir diese nicht umwandeln wie zu Beginn des Blogs. Allerdings bedeutet dies, dass wir die Z-Koordinate anstatt die Y-Koordinate f├╝r die H├Âhe nutzen m├╝ssen. Im Screenshot stehen "R, G, B, A" f├╝r die Koordinaten "X, Y, Z, W".

Zwischenstand

Wenn wir alles richtig gemacht haben, sieht unser 3D-Modell jetzt so aus:

Hier ist gut erkennbar, wie die einzelnen Vertices basierend auf unserem Muster nach oben oder unten verschoben werden.

UVs "verpixeln"

Um den Effekt zu erzeugen, dass unser Wasser aus Voxel (3D-Pixeln oder auch W├╝rfeln) besteht, k├Ânnen wir die Texturkoordinaten vor dem ├ťbergang in die Noise-Muster kacheln. .

Alternativ kann dieser Teil auch entfallen, z. B. wenn Sie "normales" Wasser lieber m├Âgen.

Water Resolution hat jeweils eine X- und eine Y- Koordinate. Hier k├Ânnen wir Aufl├Âsungen wie "32 x 32, 64 x 64" oder auch "2048 x 2048" eintragen.

Jetzt fehlt noch Farbe!

Jedes Noise-Muster erh├Ąlt nun seine eigene Farbe.
Farben werden in Shadergraph mit "R, G, B-Werten" von "0" bis "255" angegeben.

Dazu packen wir lediglich den Output jedes einzelnen Noise-Musters in eine "Multiply"-Funktion. Diese multipliziert zwei Eingaben miteinander. Die zweite Eingabe ist dann die jeweilige Farbe f├╝r das Noise-Muster.

Am Ende addieren wir alle Farben zusammen und verbinden den Output mit unserem Fragment-Shader.

Hier das Ergebnis!

Hier das Ergebnis!

Um unser Wasser noch etwas zu optimieren, eignen sich Reflexionen sehr gut.

Reflexionen f├╝r den letzten Feinschliff

Um unser Wasser noch etwas zu optimieren, eignen sich Reflexionen sehr gut.

Normal Maps 

Normal Maps sind Texturen, die die Lichtberechnung beeinflussen. Sie dienen dazu, einem Objekt mehr Details zu verleihen, ohne jedoch die Anzahl an Polygonen zu erh├Âhen.

Polygone sind die Dreiecke zwischen unseren Vertices. Je mehr wir davon haben, desto h├Âher wird die Aufl├Âsung, aber auch der Rechenaufwand.

 

Normal Maps erzeugen mit Shadergraph

Um nun passende Reflexionen zu erzeugen, kopieren wir einfach den gesamten Teil der Noise-Muster-Berechnung ohne das "Verpixeln" und packen den Output in die "Normal From Height"-Funktion.

Der Shader ist fast fertig!

Jetzt schieben wir das Ganze noch in den "Normal"-Input unseres Fragment-Shaders. Dort k├Ânnen wir den Wert "Smoothness" ver├Ąndern. Dieser gibt an, wie viel unsere Oberfl├Ąche reflektieren kann.

Level up! Der Shader ist fertig!

Hier sehen Sie zwei weitere Beispiele, welche Shader-Effekte noch m├Âglich sind. Eine Lava-Variante und einen blubbernden Gift-See.

Wir hoffen, unser Blogbeitrag zur Erstellung eines Voxel-Wasser-Shaders war interessant f├╝r Sie. Vielleicht haben Sie ja auch n├╝tzliche Anregungen f├╝r Ihre eigene Arbeitspraxis gewonnen.

 

Ihr Daniel Fischer

Galerie

Unity-Shader Bild-2: Plane Nachher
Unity-Shader Bild-1: Ebene Vorher
Unity-Shader Bild-2: Plane Nachher
Unity-Shader-Bild-3-Wireframe_closeup_makiert
Unity-Shader-Bild-4-shadergraph_vertex_beispiel
Unity-Shader-Bild-5-noise_examples
Unity-Shader Bild-6 noise layers
Unity-Shader Bild-7 noise blending
Unity-Shader Bild-8 vertexoffset
Unity-Shader Bild-9 water wireframe
Unity-Shader Bild-11 coloring
Unity-Shader Bild-12 water shaded
Unity-Shader Bild-13 normal map
Unity-Shader Bild-14 fragment
Unity-Shader Bild-15 lava shaded
Unity-Shader Bild-16 poison wireframe shaded

Kontaktdaten

Winterrabe - Media

Agentur f├╝r interaktive Medien
Jan Ritzmann
Kaiserstra├če 128
42477 Radevormwald