Feuer zum Leben erwecken: Echtzeit-Flüssigkeitssimulation in Ignitement

In dem heutigen Gastbeitrag erklärt der Solo-Entwickler Sørb die technische Kunstfertigkeit hinter den beeindruckenden Feuer- und LavavFX in seinem bevorstehenden Action-Roguelite, Ignitement.
Das Erste, was man beim Betrachten von Ignitement merkt, sind die VFX. Es gibt etwas, das sofort anders ist, besonders das Feuer. Es geht nicht nur darum sehen animiert, fühlt es sich lebendig, reaktiv und tief in die Welt integriert an.
Was steckt also dahinter?
Warum Sie sich um Fire VFX kümmern sollten
Feuer und flüssigkeitsähnliche Effekte sind generell notorisch schwer in Spielen korrekt umzusetzen. Traditionelle Partikelsysteme können zwar gut aussehen, aber ihnen fehlt oft die echte Interaktion mit der Welt. Am anderen Ende des Spektrums sind vollständige 3D-Simulationen in der Regel viel zu teuer für Echtzeit-Gameplay.

Nur wenige Spiele haben die Flüssigkeitssimulation als zentrale Spielmechanik untersucht. In Little Infernovon der Tomorrow Corporation (oben) steht das Verhalten des Feuers im Mittelpunkt des Erlebnisses, während Steve Masons Plasma Pong (unten) sein gesamtes Gameplay auf reaktive, fließende Bewegungen aufbaut. Diese Beispiele zeigen, wie leistungsfähig auf Flüssigkeiten basierende Systeme sein können, wenn sie das Gameplay direkt beeinflussen.

Die Grundidee
Anstatt sich auf Partikelsysteme zu verlassen, Ignitement verwendet eine vollständig dynamische, Echtzeit-Flüssigkeitssimulation.
Auf den ersten Blick mag das teuer klingen.
„Aber setzt das nicht einfach Ihren PC in Brand und verschlingt die Leistung?“
Zumindest nicht die Hardware.
Die Simulation ist vollständig in 2D und läuft über Graphics.Blit, wobei eine kleine Anzahl von Texturen aktualisiert wird (hauptsächlich 1024×1024 und 512×512). In der Praxis macht dies die Kosten mit einigen Nachbearbeitungseffekten vergleichbar.
Eine weitere bewusste Entscheidung war es, bei Fragment-Shaders statt bei Compute-Shaders zu bleiben. Dies ermöglicht es dem System, die Vorteile der integrierten Texturfilterung und -interpolation zu nutzen und gleichzeitig die Kompatibilität hoch zu halten, auch auf älteren Hardware- oder potenziellen Konsolenzielen.
Um die Sache leichter nachvollziehbar zu machen, können wir das System in drei Teile unterteilen:
- Simulation
- Rendering
- Beleuchtung
Auflösung der Flüssigkeitssimulation
Im Kern ist das System eine Standard-Flüssigkeitssimulation, die vollständig über Unity implementiert wurde. Graphics.Blit Pässe. Die Simulation arbeitet mit mehreren Texturen, von denen jede eine andere physikalische Eigenschaft darstellt:
- Dichte (1024×1024, RGBA halb) Dies ist dein Rauch, alles, was die Luft dick und sichtbar werden lässt.
- Geschwindigkeit (512×512, RG halb) Dies steuert, wie sich die Dinge bewegen. Wenn etwas fließt, treibt oder wirbelt, dann ist das der Grund.
- Temperatur (1024×1024, einkanalig halb) Bestimmt, wie heiß verschiedene Regionen sind.
- Reaktion (1024×1024, RGBA halb) Hier lebt das eigentliche Feuer, seine Intensität, Ausbreitung und sein Verhalten.
Wenn man diese Struktur im Hinterkopf behält, kann der Flüssigkeitslösungsalgorithmus mit diesem Pseudocode skizziert werden:
void Simulate(float dt)
{
//feed data to simulation
RenderEmittersAndObstaclesToTexture();
ApplyDiffusion(dt); //fluid spreading
ApplyAdvection(dt); //moving data along the velocity field
ApplyExtinguishmentImpulse(dt); //fire producing smoke (reaction>density)
ComputeVorticityConfinement(dt); //increase swirlyness of fluid
//calculate divergence free velocity field
ComputeDivergence();
ComputePressure();
ComputeProjection();
AdvectParticles(dt); //move particles along velocity field
}
Die Reaktionsdaten entkommen sogar gelegentlich der GPU. Es wird downsampelt und auf der CPU zurückgelesen, um Spieleffekte wie Beschädigungen zu erzeugen. Also ja, das Feuer sieht nicht nur gefährlich aus, es ist es auch!
Das Simulationsgebiet selbst ist nicht in der Welt festgelegt. Stattdessen folgt es der Kamera und verschiebt sich, wenn sich der Spieler bewegt. Dies erzeugt die Illusion einer endlosen, kontinuierlichen Simulation, obwohl zu jedem gegebenen Zeitpunkt nur ein relativ kleiner Bereich tatsächlich berechnet wird. Überall Rauch, nirgendwo Kosten.
Rendering
Sobald alle Daten vorhanden sind, besteht der nächste Schritt darin, es so aussehen zu lassen, dass man tatsächlich hinzuschauen möchte.
Feuer
Feuer wird aus der Reaktionstextur gerendert, die ein bisschen wie eine Höhenkarte behandelt wird. Ein Trick im Stil von Parallaxen im Fragment-Shader verleiht ihm ein Pseudo-3D-Aussehen und fügt Tiefe hinzu, ohne die Kosten für echte Volumeninformationen.
Rauch
Rauch und Nebel entstehen hauptsächlich aus den Temperaturinformationen. Diese werden im Shader interpretiert, um weiche, sich entwickelnde Formen zu erzeugen, die überraschend volumenhaft wirken, obwohl es sich technisch gesehen nur um ein paar Texturen handelt, die herumgeschoben werden.
Funken
Und natürlich ist kein Feuer ohne Funken vollständig.
Dies sind GPU-gesteuerte Partikel, die das Geschwindigkeitsfeld abtasten, was bedeutet, dass sie dem Fluss der Simulation auf natürliche Weise folgen. Keine zusätzliche Logik erforderlich, sie gehen einfach mit dem Fluss (wörtlich).
Diese Glutpartikel werden mithilfe einer benutzerdefinierten GPU-Implementierung aktualisiert und advektiert (kein Shuriken, kein VFX-Graph). Also nur ein ComputeBuffer für alle Partikel-Daten, ein ComputeShader.Dispatch Aufruf, um sie zu aktualisieren, und ein Graphics.DrawProcedural Aufruf, um sie auf dem Bildschirm zu rendern.
Beleuchtung
Berechnung der Lichtkarte
Die Beleuchtung wird mit einem einfachen Trick gehandhabt, der letztendlich viel Schwerstarbeit leistet.
Die Reaktionstextur wird downsampelt und verschmiert, wodurch sie in eine dynamische Lichtkarte umgewandelt wird. Sie ist nicht physikalisch korrekt, aber das muss sie auch nicht sein. Sie muss nur richtig aussehen!
Anwendung von Beleuchtung auf die Umgebung
Beim Rendern von Objekten kommt die Beleuchtung auf einen einzigen Texturspeicheraufruf in einem benutzerdefinierten Shader hinaus.
Anstatt direkt an der Oberfläche zu sampeln, wird der Aufruf leicht entlang der Oberflächennormale verschoben:
worldPosition + worldNormal * c

Dieser winzige Versatz bewirkt viel. Es entsteht der Eindruck, dass Licht kommt von die Umgebung, wodurch Oberflächen einen überzeugenden Eindruck von Tiefe und Richtungsorientierung vermitteln.
All das aus einer Texturprobe. Nicht schlecht.
Wenn Sie die Details wissen möchten, hier ist die Shader-Funktion, die ich verwende:
void Simulate(float dt)
{
//feed data to simulation
RenderEmittersAndObstaclesToTexture();
ApplyDiffusion(dt); //fluid spreading
ApplyAdvection(dt); //moving data along the velocity field
ApplyExtinguishmentImpulse(dt); //fire producing smoke (reaction>density)
ComputeVorticityConfinement(dt); //increase swirlyness of fluid
//calculate divergence free velocity field
ComputeDivergence();
ComputePressure();
ComputeProjection();
AdvectParticles(dt); //move particles along velocity field
}
Ich habe diese Funktion und alle benötigten Uniform-Variablen in eine .cginc-Datei eingefügt und benutze sie bequem in jedem Shader, der von der Lichtkarte lesen möchte.
Erweiterung der Lichtkarte über die Beleuchtung hinaus
Eine der besten Nebeneffekte dieser Einrichtung ist, dass die Lichtkarte nicht nur für die Beleuchtung gedacht ist.
In Ignitement, verwenden Teile der Benutzeroberfläche es ebenfalls. Elemente mit normalen Karten verwenden die Lichtkarte, um Reflexionen zu simulieren. Zum Beispiel nimmt das Glas des Gesundheitsbehälters das umgebende Feuer auf, wodurch es sich mit der Welt verbunden anfühlt, anstatt nur darauf zu sitzen.
Dies eröffnet auch die Tür für ungewöhnlichere Effekte.
In einem Bereich besteht die Umgebung aus „Fleischwänden“ (warum auch nicht?). Diese nutzen die Lichtkarte, um zu steuern, wie stark sie sich bewegen. Je intensiver das nahegelegene Feuer ist, desto stärker reagieren die Wände und erwecken den Eindruck, dass die Umgebung selbst lebendig ist und nicht besonders glücklich darüber ist, in Flammen zu stehen.
Noch besser ist, dass dies alles im Vertex-Shader geschieht, was es für etwas, das so dynamisch aussieht, extrem kostengünstig macht.
Wie wirken sich Feuer-VFX auf das Gameplay aus?
Die Visuals sind schön, aber ein gutes VFX-System allein macht noch kein gutes Spiel. In Ignitement, wirkt sich Feuer direkt auf das Gameplay aus: Jeder Feind, der es berührt, erhält einen brennbaren Debuff, der Schaden über die Zeit verteilt.
Damit dies funktioniert, müssen die Simulationsdaten auf der CPU verfügbar sein. Bei jedem Frame wird die Reaktionstextur mit Downsampling verkleinert und zurückgelesen unter Verwendung von AsyncGPUReadback.RequestIntoNativeArray. Anstatt teure Abfragen pro Objekt auf der GPU durchzuführen, liest das System die Textur einmal und führt für jeden Feind kostengünstige Nachschläge auf der CPU durch. Mit einem einfachen Schwellenwert verhält sich dies effektiv wie ein einzelner, hochdynamischer Collider, der die Form des Feuers zu jedem gegebenen Zeitpunkt perfekt abbildet.
Einschränkungen und Kompromisse
Natürlich ist dieser Ansatz nicht perfekt.
Da die Simulation in 2D erfolgt, ist alles, was vertikal geschieht, eher eine Näherung als eine physikalisch korrekte Lösung. Auch das Verschieben des Simulationsbereichs erfordert etwas Sorgfalt, um sichtbare Nähte oder Popping zu vermeiden.
Das heißt, diese Kompromisse sind sehr bewusst gewählt. Sie halten das System schnell, skalierbar und weit kompatibel, während sie dennoch Ergebnisse liefern, die reich und reaktiv wirken.
Wichtige Erkenntnisse
- 2D-Flüssigkeitssimulationen können viel weiter gehen, als Sie vielleicht erwarten
- Die Wiederverwendung von Simulationsdaten ist der Ort, an dem viel Magie passiert
- „Sieht richtig aus“ schlägt oft „ist physikalisch korrekt“
- Die GPU-zu-CPU-Readback ist durchaus machbar, wenn sie klein gehalten wird
- Ein gut gestaltetes System kann Grafik, Gameplay und Benutzeroberfläche gleichzeitig vorantreiben.
Indem alles auf einer gemeinsamen Flüssigkeitssimulation aufgebaut wird, erreicht Ignitement einen kohärenten visuellen Stil, bei dem Feuer, Beleuchtung, Benutzeroberfläche und sogar Teile der Umgebung dieselbe Sprache sprechen.
Das Ergebnis sind nicht nur bessere Grafiken, sondern eine Welt, die sich stärker verbunden, reaktiver und lebendiger anfühlt.
Und das alles beginnt mit ein paar Texturen, ein paar Shadern... und allem in Brand zu setzen.
Wenn Sie Flüssigkeitssimulation und Überlebenserlebnisse/Roguelikes mögen, dann zögern Sie nicht, Wunschliste Ignitement auf Steam und Trete dem Discord bei. Entdecken Sie weitere mit Unity erstellte Spiele auf unserer Steam Curator-Seiteund lesen Sie weitere Geschichten von Unity-Entwicklern im Unity-Blog und im Ressourcen-Hub.
