7 days of WordPress themes, graphics & videos - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Game Development
  2. Programming

Erstellen Sie einen leuchtenden, fließenden Lavastrom mit Bézier-Kurven und -Shadern

Scroll to top
Read Time: 10 mins

German (Deutsch) translation by Federicco Ancie (you can also view the original English article)

Meistens ist die Verwendung herkömmlicher Grafiktechniken der richtige Weg. Manchmal können Experimente und Kreativität auf den grundlegenden Ebenen eines Effekts jedoch den Stil des Spiels verbessern und ihn stärker hervorheben. In diesem Tutorial werde ich Ihnen zeigen, wie Sie einen animierten 2D-Lavastrom mit Bézier-Kurven, benutzerdefinierter strukturierter Geometrie und Vertex-Shadern erstellen.

Hinweis: Obwohl dieses Tutorial mit AS3 und Flash geschrieben wurde, sollten Sie in fast jeder Spieleentwicklungsumgebung dieselben Techniken und Konzepte verwenden können.


Endergebnis Vorschau

Klicken Sie auf das Pluszeichen, um weitere Optionen zu öffnen: Sie können die Dicke und Geschwindigkeit des Flusses anpassen und die Kontrollpunkte und Positionspunkte verschieben.

Kein Blitz? Schauen Sie sich stattdessen das YouTube-Video an:


Installieren

Die obige Demo-Implementierung verwendet AS3 und Flash mit Starling Framework für GPU-beschleunigtes Rendern und die Feathers-Bibliothek für UI-Elemente. In unserer ersten Szene werden wir ein Bodenbild und ein Vordergrundgesteinsbild platzieren. Später werden wir einen Fluss hinzufügen und ihn zwischen diese beiden Schichten einfügen.


Geometrie

Flüsse entstehen durch komplexe natürliche Wechselwirkungsprozesse zwischen einer flüssigen Masse und dem Boden darunter. Es wäre unpraktisch, eine physikalisch korrekte Simulation für ein Spiel durchzuführen. Wir wollen nur die richtige visuelle Darstellung erhalten, und dazu verwenden wir ein vereinfachtes Modell eines Flusses.

Die Modellierung des Flusses als Kurve ist eine der Lösungen, die wir verwenden können, um eine gute Kontrolle zu haben und ein mäanderförmiges Aussehen zu erzielen. Ich habe mich für quadratische Bézier-Kurven entschieden, um die Dinge einfach zu halten.

Bézier-Kurven sind parametrische Kurven, die häufig in der Computergrafik verwendet werden. In quadratischen Bézier-Kurven verläuft die Kurve durch zwei festgelegte Punkte, und ihre Form wird durch den dritten Punkt bestimmt, der üblicherweise als Kontrollpunkt bezeichnet wird.

Wie oben gezeigt, verläuft die Kurve durch die Positionspunkte, während der Kontrollpunkt den Kurs verwaltet, den er nimmt. Wenn Sie beispielsweise den Kontrollpunkt direkt zwischen den Positionspunkten platzieren, wird eine gerade Linie definiert, während andere Werte für den Kontrollpunkt die Kurve "anziehen", um sich diesem Punkt zu nähern.

Dieser Kurventyp wird mit der folgenden mathematischen Formel definiert:

[latex]\Large B(t)=(1 - t)^2 P_0 + (2t - 2t^2) C + t^2 P_1[/latex]

Bei t=0 stehen wir am Anfang unserer Kurve; bei t=1 sind wir am Ende.

Technisch werden wir mehrere Bézier-Kurven verwenden, wobei das Ende der einen der Anfang der anderen ist und eine Kette bildet.

Jetzt müssen wir das Problem lösen, unseren Fluss tatsächlich anzuzeigen. Kurven haben keine Dicke, daher werden wir ein geometrisches Grundelement darum herum bauen.

Zuerst brauchen wir eine Möglichkeit, die Kurve in Liniensegmente umzuwandeln. Dazu nehmen wir unsere Punkte und fügen sie in die mathematische Definition der Kurve ein. Das Schöne daran ist, dass wir leicht einen Parameter hinzufügen können, um die Qualität dieser Operation zu steuern.

Hier ist der Code zum Generieren der Punkte aus der Definition der Kurve:

Und so konvertieren Sie die Kurve in Liniensegmente:

Wir können jetzt eine beliebige Kurve nehmen und sie in eine benutzerdefinierte Anzahl von Liniensegmenten konvertieren - je mehr Segmente, desto höher die Qualität:

Um zur Geometrie zu gelangen, werden zwei neue Kurven basierend auf der ursprünglichen generiert. Ihre Position und Kontrollpunkte werden um einen normalen Vektorversatzwert verschoben, den wir uns als Dicke vorstellen können. Die erste Kurve wird in die negative Richtung bewegt, während die zweite in die positive Richtung bewegt wird.

Wir werden jetzt die zuvor definierte Funktion verwenden, um Liniensegmente aus den Kurven zu erstellen. Das bildet eine Grenze um die ursprüngliche Kurve.

Wie machen wir das im Code? Wir müssen normalen Vektofen für Positions- und Kontrollpunkte berechnen, sie mit dem Versatz multiplizieren und sie zu den ursprünglichen Values addieren. Für die Positionspunkte müssen wir durch Linien gebildete Normalen zu benachbarten Kontrollpunkten interpolieren.

Sie können bereits sehen, dass wir diese Punkte verwenden können, um kleine vierseitige Polygone zu definieren - "Quads". Unsere Implementierung verwendet ein benutzerdefiniertes Starling DisplayObject, das unsere geometrischen Daten direkt an die GPU weiterleitet.

Ein Problem, abhängig von der Implementierung, ist, dass wir Quads nicht direkt senden können. Stattdessen müssen wir Dreiecke senden. Es ist jedoch einfach genug, zwei Dreiecke mit vier Punkten auszuwählen:

Ergebnis:


Texturierung

Ein sauberer geometrischer Stil macht Spaß und ist möglicherweise sogar ein guter Stil für einige experimentelle Spiele. Aber damit unser Fluss wirklich gut aussieht, könnten wir ein paar weitere Details gebrauchen. Die Verwendung einer Textur ist eine gute Idee. Dies führt uns zu dem Problem, es in einer zuvor erstellten benutzerdefinierten Geometrie anzuzeigen.

Wir müssen unseren Eckpunkten zusätzliche Informationen hinzufügen. Positionen allein reichen nicht mehr aus. Jeder Scheitelpunkt kann zusätzliche Parameter nach unseren Wünschen speichern. Um die Texturabbildung zu unterstützen, müssen Texturkoordinaten definiert werden.

Texturkoordinaten befinden sich im Texturraum und ordnen die Pixelwerte des Bildes den Weltpositionen der Scheitelpunkte zu. Für jedes Pixel, das auf dem Bildschirm angezeigt wird, berechnen wir interpolierte Texturkoordinaten und verwenden sie, um Pixelwerte für Positionen in der Textur zu suchen. Die Werte 0 und 1 im Texturraum entsprechen den Texturkanten. Wenn Werte diesen Bereich verlassen, haben wir mehrere Möglichkeiten:

  • Wiederholen - Wiederholen Sie die Textur auf unbestimmte Zeit.
  • Clamp - Schneiden Sie die Textur außerhalb der Grenzen des Intervalls [0, 1] ab.

Diejenigen, die ein wenig über Textur-Mapping wissen, sind sich der möglichen Komplexität der Technik sicher bewusst. Ich habe gute Nachrichten für dich! Diese Art der Darstellung von Flüssen lässt sich leicht auf eine Textur abbilden.

Von den Seiten wird die Texturhöhe in ihrer Gesamtheit abgebildet, während die Länge des Flusses in kleinere Teile des Texturraums unterteilt wird, die entsprechend der Texturbreite dimensioniert sind.

Um es nun im Code zu implementieren:

Jetzt sieht es, wie ein Fluss, aus:


Animation

Unser Fluss sieht jetzt viel mehr wie ein echter aus, mit einer großen Ausnahme: Er steht still!

Okay, wir müssen es animieren. Das erste, woran Sie denken können, ist die Verwendung von Sprite-Sheet-Animationen. Und das mag gut funktionieren, aber um mehr Flexibilität zu erhalten und ein wenig Texturspeicher zu sparen, werden wir etwas Interessanteres tun.

Anstatt die Textur zu ändern, können wir die Art und Weise ändern, wie die Textur der Geometrie zugeordnet wird. Dazu ändern wir die Texturkoordinaten für unsere Scheitelpunkte. Dies funktioniert nur für kachelbare Texturen, bei denen die Zuordnung repeat werden soll.

Eine einfache Möglichkeit, dies zu implementieren, besteht darin, die Texturkoordinaten auf der CPU zu ändern und die Ergebnisse in jedem Frame an die GPU zu senden. Dies ist normalerweise ein guter Weg, um eine Implementierung dieser Art von Technik zu starten, da das Debuggen viel einfacher ist. Wir werden jedoch direkt darauf eingehen, wie wir dies am besten erreichen können: Animieren von Texturkoordinaten mithilfe von Vertex-Shadern.

Aus Erfahrung kann ich sagen, dass Leute manchmal von Shadern eingeschüchtert werden, wahrscheinlich aufgrund ihrer Verbindung zu den fortgeschrittenen grafischen Effekten von Blockbuster-Spielen. Um ehrlich zu sein, das Konzept dahinter ist extrem einfach, und wenn Sie ein Programm schreiben können, können Sie einen Shader schreiben - das ist alles, kleine Programme, die auf der GPU ausgeführt werden. Wir werden einen Vertex-Shader verwenden, um unseren Fluss zu animieren. Es gibt verschiedene andere Arten von Shadern, aber wir können auf sie verzichten.

Wie der Name schon sagt, verarbeiten Vertex-Shader Scheitelpunkte. Sie werden für jeden Scheitelpunkt ausgeführt und verwenden als Eingabe Scheitelpunktattribute: Position, Texturkoordinaten und Farbe.

Unser Ziel ist es, den X-Wert der Texturkoordinate des Flusses zu versetzen, um den Fluss zu simulieren. Wir halten einen Flusszähler und erhöhen ihn in jedem Frame um das Zeitdelta. Wir können einen zusätzlichen Parameter für die Geschwindigkeit der Animation angeben. Der Versatzwert sollte als einheitlicher (konstanter) Wert an den Shader übergeben werden, um dem Shader-Programm mehr Informationen als nur Scheitelpunkte zu liefern. Dieser Wert ist normalerweise ein Vierkomponentenvektor; Wir werden nur die X-Komponente verwenden, um den Wert zu speichern, während wir Y, Z und W auf 0 setzen.

Diese Implementierung verwendet die AGAL-Shader-Sprache. Es kann etwas schwer zu verstehen sein, da es sich um eine Assembler-ähnliche Sprache handelt. Mehr erfahren Sie hier.

Vertex-Shader:

Animation in Aktion:


Warum sollen wir hier aufhören?

Wir sind ziemlich fertig, aber unser Fluss immer noch unnatürlich aussieht. Der einfache Schnitt zwischen dem Hintergrund und dem Fluss ist ein echter Schandfleck. Um dies zu lösen, können Sie eine zusätzliche Schicht des Flusses verwenden, die etwas dicker ist, und eine spezielle Textur, die die Ufer des Flusses überlagert und den hässlichen Übergang abdeckt.

Und da die Demo einen Fluss geschmolzener Lava darstellt, können wir unmöglich ohne ein wenig Glühen auskommen! Erstellen Sie eine weitere Instanz der Flussgeometrie, indem Sie jetzt eine Leuchttextur verwenden, und stellen Sie den Mischmodus auf "add". Fügen Sie für noch mehr Spaß eine flüssige Animation des Glow-Alpha-Werts hinzu.

Letzte Demo:

Natürlich können Sie mit diesem Effekt viel mehr als nur Flüsse machen. Ich habe gesehen, dass es für Geisterpartikeleffekte, Wasserfälle oder sogar zum Animieren von Ketten verwendet wird. Es gibt viel Raum für weitere Verbesserungen. Die endgültige Version von oben in Bezug auf die Leistung kann mit einem Draw-Aufruf erstellt werden, wenn Texturen zu einem Atlas zusammengeführt werden. Lange Flüsse sollten in mehrere Teile geteilt und ausgesondert werden. Eine wichtige Erweiterung wäre die Implementierung des Gabelns von Kurvenknoten, um mehrere Flusspfade zu ermöglichen und wiederum die Bifurkation zu simulieren.

Ich verwende diese Technik in unserem neuesten Spiel und bin sehr zufrieden damit, was wir damit machen können. Wir verwenden es für die Flüsse und Straßen (natürlich ohne Animation). Ich denke nach, einen ähnlichen Effekt für Seen zu verwenden.


Abschluss

Ich hoffe, dass ich Ihnen einige Ideen geben konnte, wie Sie hinter herkömmlichen grafischen Methoden denken können, z. B. die Verwendung von Sprite-Blättern oder Tilesets, um Effekte zu erzielen. Es erfordert etwas mehr Arbeit, ein wenig Mathematik und einige Kenntnisse in der GPU-Programmierung, aber dann erhalten Sie viel mehr Flexibilität.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.