Faire en sorte que le feu semble vivant : Simulation de fluides en temps réel dans Ignitement

Dans cet article invité, le développeur solo Sørb décompose l'art technique derrière les impressionnants effets visuels de feu et de lave dans son prochain roguelite d'action, Ignitement.
La première chose que vous remarquez en regardant Ignitement est les effets visuels. Il y a quelque chose d'immédiatement différent à leur sujet, surtout le feu. Il ne semble pas seulement avoir une animation, il semble vivant, réactif et profondément intégré dans le monde.
Alors, que se passe-t-il sous le capot ?
Pourquoi vous devriez vous intéresser aux effets visuels de feu
Le feu, et les effets fluides en général, sont notoirement difficiles à bien réaliser dans les jeux. Les systèmes de particules traditionnels peuvent avoir un bel aspect, mais ils manquent souvent d'une véritable interaction avec le monde. À l'autre extrémité du spectre, les simulations 3D complètes sont généralement beaucoup trop coûteuses pour un gameplay en temps réel.

Quelques jeux ont exploré la simulation de fluides comme mécanique centrale. Dans Little Infernopar la Tomorrow Corporation (ci-dessus), le comportement du feu est central à l'expérience, tandis que Plasma Pong de Steve Mason (ci-dessous) construit tout son gameplay autour d'un mouvement réactif et fluide. Ces exemples soulignent à quel point les systèmes basés sur des fluides peuvent être puissants lorsqu'ils influencent directement le gameplay.

L'idée centrale
Au lieu de s'appuyer sur des systèmes de particules, Ignitement utilise une simulation de fluide entièrement dynamique et en temps réel.
À première vue, cela peut sembler coûteux.
« Mais cela ne met-il pas simplement votre PC en feu et ne fait-il pas chuter les performances ? »
Du moins pas le matériel.
La simulation est entièrement en 2D et fonctionne via Graphics.Blit, mettant à jour un petit ensemble de textures (principalement 1024×1024 et 512×512). En pratique, cela la rend comparable en coût à quelques effets de post-traitement.
Un autre choix délibéré a été de s'en tenir aux shaders de fragment au lieu des shaders de calcul. Cela permet au système de tirer parti du filtrage de texture et de l'interpolation intégrés, tout en maintenant une compatibilité élevée, même sur du matériel plus ancien ou des cibles potentielles de consoles.
Pour faciliter la compréhension, nous pouvons diviser le système en trois parties :
- Simulation
- Rendu
- Éclairage
Décomposition de la simulation de fluide
Au cœur du système se trouve une simulation de fluide standard entièrement mise en œuvre via des passes Graphics.Blit. La simulation fonctionne sur plusieurs textures, chacune représentant une propriété physique différente :
- Densité (1024×1024, RGBA demi) C'est votre fumée, tout ce qui rend l'air épais et visible.
- Vitesse (512×512, RG demi) Cela contrôle comment les choses se déplacent. Si quelque chose coule, dérive ou tourbillonne, c'est pourquoi.
- Température (1024×1024, canal unique demi) Détermine à quel point différentes régions sont chaudes.
- Réaction (1024×1024, RGBA demi) C'est là que se trouve le feu réel, son intensité, sa propagation et son comportement.
En gardant cette structure à l'esprit, le solveur de fluide peut être esquissé avec ce pseudo-code :
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
}
Les données de réaction échappent même au GPU de temps en temps. Il est sous-échantillonné et lu sur le CPU pour générer des effets de jeu comme les dégâts. Donc oui, le feu n'a pas seulement l'air dangereux, il l'est vraiment !
Le domaine de simulation lui-même n'est pas fixe dans le monde. Au lieu de cela, il suit la caméra et se déplace au fur et à mesure que le joueur avance. Cela crée l'illusion d'une simulation continue et sans fin, même si seule une région relativement petite est réellement calculée à tout moment. De la fumée partout, aucun coût nulle part.
Rendu
Une fois toutes ces données en place, l'étape suivante consiste à les faire ressembler à quelque chose que vous voudriez réellement regarder.
Feu
Le feu est rendu à partir de la texture de réaction, qui est traitée un peu comme une carte de hauteur. Un truc de style parallax dans le shader de fragment lui donne un aspect pseudo-3D, ajoutant de la profondeur sans le coût de véritables volumétriques.
Fumée
La fumée et le brouillard proviennent principalement des informations de température. Celles-ci sont interprétées dans le shader pour produire des formes douces et évolutives qui semblent étonnamment volumétriques pour quelque chose qui est techniquement juste quelques textures déplacées.
Braises
Et bien sûr, aucun feu n'est complet sans braises.
Ce sont des particules pilotées par le GPU qui échantillonnent le champ de vitesse, ce qui signifie qu'elles suivent naturellement le flux de la simulation. Aucune logique supplémentaire n'est nécessaire, elles suivent simplement le courant (littéralement).
Ces particules de braise sont mises à jour et advectées à l'aide d'une implémentation GPU personnalisée (pas de Shuriken, pas de VFX Graph). Il suffit donc d'un ComputeBuffer pour toutes les données de particules, d'un appel à ComputeShader.Dispatch pour les mettre à jour, et d'un appel à Graphics.DrawProcedural pour les rendre à l'écran.
Éclairage
Calcul de la carte de lumière
L'éclairage est géré à l'aide d'un simple truc qui finit par faire beaucoup de travail lourd.
La texture de réaction est sous-échantillonnée et floutée, la transformant en une carte de lumière dynamique. Ce n'est pas physiquement précis, mais cela n'a pas besoin de l'être. Il suffit que cela ait l'air correct !
Application de l'éclairage à l'environnement
Lors du rendu des objets, l'éclairage se résume à une seule recherche de texture dans un shader personnalisé.
Au lieu d'échantillonner directement à la surface, la recherche est légèrement ajustée le long de la normale de surface :
positionMonde + normaleMonde * c

Ce petit décalage a un grand impact. Il crée l'impression que la lumière vient de l'environnement, donnant aux surfaces un sens convaincant de profondeur et de direction.
Tout cela, à partir d'un échantillon de texture. Pas mal.
Si vous voulez connaître les détails, voici la fonction de shader que j'utilise :
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
}
J'ai simplement mis cette fonction et toutes les variables uniformes nécessaires dans un fichier .cginc et je l'utilise commodément dans n'importe quel shader qui souhaite lire à partir de la carte de lumière.
Étendre la carte de lumière au-delà de l'éclairage
Un des plus beaux effets secondaires de cette configuration est que la carte de lumière n'est pas seulement destinée à l'éclairage.
Dans Ignitement, certaines parties de l'interface utilisateur l'utilisent également. Les éléments avec des cartes normales échantillonnent la carte de lumière pour simuler des réflexions. Par exemple, le verre du conteneur de santé capte le feu environnant, lui donnant l'impression d'être connecté au monde au lieu de simplement être posé dessus.
Cela ouvre également la porte à des effets plus inhabituels.
Dans une zone, l'environnement est composé de "murs de chair" (parce que pourquoi pas ?). Ceci utilise la carte de lumière pour contrôler à quel point ils se déplacent. Plus le feu à proximité est intense, plus les murs réagissent, donnant l'impression que l'environnement lui-même est vivant et pas particulièrement content d'être en feu.
Encore mieux, tout cela est fait dans le shader de vertex, ce qui le rend extrêmement économique pour quelque chose qui semble aussi dynamique.
Comment les VFX de feu affectent-ils le gameplay ?
Les visuels sont agréables, mais un bon système de VFX à lui seul ne fait pas un bon jeu. Dans Ignitement, le feu affecte directement le gameplay : tout ennemi le touchant reçoit un debuff de brûlure qui inflige des dégâts au fil du temps.
Pour que cela fonctionne, les données de simulation doivent être disponibles sur le CPU. Chaque image, la texture de réaction est réduite et lue à nouveau en utilisant AsyncGPUReadback.RequestIntoNativeArray. Au lieu de faire des requêtes coûteuses par objet sur le GPU, le système lit la texture une fois et effectue des recherches peu coûteuses sur le CPU pour chaque ennemi. En utilisant un seuil simple, cela se comporte effectivement comme un seul collider hautement dynamique qui correspond parfaitement à la forme du feu à tout moment donné.
Limitations et compromis
Bien sûr, cette approche n'est pas parfaite.
Parce que la simulation est en 2D, tout ce qui se passe verticalement est plus une approximation qu'une solution physiquement correcte. De plus, le déplacement du domaine de simulation nécessite un peu de soin pour éviter des coutures visibles ou des pops.
Cela dit, ces compromis sont très intentionnels. Ils gardent le système rapide, évolutif et largement compatible, tout en fournissant des résultats qui semblent riches et réactifs.
Points clés à retenir
- Les simulations fluides en 2D peuvent aller beaucoup plus loin que vous ne l'imaginez
- La réutilisation des données de simulation est là où se produit beaucoup de magie
- « A l'air juste » l'emporte souvent sur « est physiquement correct »
- Le retour de lecture GPU vers CPU est parfaitement viable lorsqu'il est maintenu petit
- Un système bien conçu peut gérer les visuels, le gameplay et l'interface utilisateur en même temps
En construisant tout sur une simulation fluide partagée, Ignitement se retrouve avec un style visuel cohérent où le feu, l'éclairage, l'interface utilisateur et même des parties de l'environnement parlent tous le même langage.
Le résultat n'est pas seulement de meilleurs visuels, c'est un monde qui semble plus connecté, plus réactif et plus vivant.
Et tout cela commence avec quelques textures, quelques shaders... et en mettant tout le feu.
Si vous aimez la simulation de fluides et les jeux de survie / roguelikes, n'hésitez pas à ajouter à votre liste de souhaits Ignitement sur Steam et rejoindre le Discord. Explorez plus de jeux Made with Unity sur notre page de Curateur Steam, et découvrez plus d'histoires de développeurs Unity sur le Blog Unity et le Hub de Ressources.
