炎を生き生きと表現する:Ignitementでのリアルタイム流体シミュレーション

SØRB / SOLO DEVELOPERGuest Blog
Apr 23, 2026|4 分
SørbによるIgnitementのゲーム内スクリーンショット。中心点から放射される溶岩の爆発的な波に飲み込まれる環境のアイソメトリックビュー。
このウェブページは、お客様の便宜のために機械翻訳されたものです。翻訳されたコンテンツの正確性や信頼性は保証いたしかねます。翻訳されたコンテンツの正確性について疑問をお持ちの場合は、ウェブページの公式な英語版をご覧ください。

本日のゲスト投稿では、ソロ開発者のSørb氏が、彼の今後のアクションローグライトにおける印象的な炎と溶岩のVFXの背後にある技術的な芸術性について解説します。 Ignitement.

外観で最初に気づくのは Ignitement VFXです。特に炎は、すぐに違いがわかります。単に 外観 がアニメーションしているだけでなく、生き生きとしており、反応的で、世界に深く統合されているように感じられます。

では、内部では何が起こっているのでしょうか?

火のVFXを気にするべき理由

火や流体のようなエフェクトは、ゲームでうまく表現するのが非常に難しいことで知られています。従来のパーティクルシステムは素晴らしい外観になりますが、世界との真のインタラクションが欠けていることがよくあります。一方、フル3Dシミュレーションは、リアルタイムのゲームプレイには通常、コストが高すぎます。

The Tomorrow CorporationのLittle Infernoのスクリーンショット。
リトルインフェルノ | The Tomorrow Corporation

流体シミュレーションをコアのメカニクスとして取り入れたゲームはいくつかある。トモロー・コーポレーションの リトル・インフェルノ(上)では、火の動作が体験の中心となっているが、スティーブ・メイソンの プラズマ・ポング (下)は、反応的で流れるような動きを中心にゲームプレイ全体を構築している。これらの例は、流体ベースのシステムがゲームプレイに直接影響を与えることでどれほど強力なものであるかを示している。

スティーブ・メイソンによるプラズマ・ポングのスクリーンショット。
プラズマ・ポング | スティーブ・メイソン

基本的な考え方

パーティクルシステムに頼る代わりに、 Ignitement は完全に動的かつリアルタイムの流体シミュレーションを使用しています。

見た目は高価そうに見えるかもしれない。

「でも、PCが燃えたり、パフォーマンスが落ちたりしないの?」

少なくとも、そうではない。 ハードウェア.

シミュレーションは完全に2Dで実行されます。 Graphics.Blit、少量のテクスチャ(主に1024×1024と512×512)を更新します。実際には、これはいくつかのポストプロセスエフェクトと比べてコストが同じくらいだ。

フラグメントシェーダーをコンピュートシェーダーではなく採用したのも、意図的な選択でした。これにより、システムはビルトインのテクスチャフィルタリングと補間を活用できるようになり、古いハードウェアや将来的なコンソールターゲットでも互換性を維持できます。

理解しやすいように、システムを3つの部分に分けて説明します。

  • シミュレーション
  • レンダリング
  • ライティング

流体シミュレーションの分解

その中心にあるのは、完全にシェーダーで実装された標準的な流体シミュレーションです。 Graphics.Blit パス。このシミュレーションは、それぞれ異なる物理プロパティを表す複数のテクスチャで動作します。

  • 密度(1024×1024、RGBAハーフ) これはあなたの煙で、空気の厚みと可視性を生み出すすべてです。
  • 速度(512×512、RG半) これは物体の動き方を制御します。何かが流れ、漂い、または渦を巻く場合、その理由はここにあります。
  • 温度(1024×1024、シングルチャンネルハーフ) 異なる領域がどのくらい熱いのかを決定します。
  • リアクション(1024×1024、RGBAハーフ) ここには実際の炎、その強度、広がり、動作が生きています。

この構造を念頭に置いて、流体ソルバーを以下の疑似コードで概説できます。

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
}

反応データは、時々GPUから抜け出してしまいます。ダメージなどのゲームプレイ効果を駆動するために、CPUでダウンサンプリングされ、読み戻されます。だから、炎は単に危険に見えるだけでなく、本当に危険なのだ!

シミュレーション領域自体は、世界に固定されていません。プレイヤーが動くにつれて、カメラに追従して移動する。これにより、実際には比較的狭い範囲しか計算されていないにもかかわらず、無限に続く連続的なシミュレーションの錯覚が生じる。いたるところに煙が立ち込めるが、コストはかからない。

レンダリング

すべてのデータが整ったら、次にやることは、実際にじっと見たいと思えるような外観にすることです。

炎は、高さマップと似たような扱いを受けるリアクションテクスチャから描画されます。フラグメントシェーダーのパララックススタイルのテクニックによって、擬似3Dの外観がもたらされ、真のボリューム表現のコストなしに奥行きが加わります。

煙とフォグは、主に温度情報から生成されます。これらはシェーダーで解釈され、技術的には単にテクスチャが動いているだけなのに、驚くほどボリューム感のある柔らかい形状が生成されます。

燃えさし

そしてもちろん、燃えさしなしでは炎は完全ではない。

これらは、ベロシティフィールドをサンプリングするGPU駆動パーティクルなので、シミュレーションの流れに自然に従います。余分なロジックは必要ありません、ただ流れに身を任せるだけです(文字通り)。

これらのエンバーパーティクルは、カスタムGPU実装(ShurikenもVFXグラフもなし)を使用して更新され、流されます。なので、 ComputeBuffer でパーティクルデータ全体を、 ComputeShader.Dispatch の呼び出しでそれらを更新し、 Graphics.DrawProcedural の呼び出しで画面に描画します。

ライティング

ライトマップの計算

ライティングは、多くの重い作業を行う単純なトリックを使用して処理されます。

リアクションテクスチャはダウンサンプルされ、ぼやけ、動的ライトマップに変換されます。物理的に正確である必要はありませんが、正確である必要もありません。見た目が良ければOKです!

環境にライトを適用する

オブジェクトを描画する際、ライトはカスタムシェーダー内の単一のテクスチャルックアップに依存します。

表面で直接サンプリングする代わりに、法線方向にわずかにずらすことでルックアップを行います:

worldPosition + worldNormal * c

SørbによるIgnitementのライトマップルックアップのイメージ。Made with Unity.
ライトマップルックアップ

このわずかなオフセットが大きな効果をもたらします。ライトが差し込んでいるインプレッションが生まれます 前へ 環境に影響を与え、表面に説得力のある奥行きと方向性を与えます。

すべてが、1つのテクスチャサンプルから。悪くない。

詳細を知りたい場合は、私が使用しているシェーダー機能がこちらです:

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
}

この機能と必要なすべてのユニフォーム変数を.cgincファイルにまとめ、ライトマップを読み取るシェーダーで便利に使用しています。

ライティングを超えたライトマップの拡張

この設定の最も優れた副産物の一つは、ライトマップが単なるライティング用ではないことです。

In Ignitement, UIの一部も実際にそれを使用しています。ノーマルマップ付きのエレメントは、ライトマップをサンプリングして反射を擬似的に表現します。例えば、ヘルスコンテナのガラスは周囲の炎を映し出すので、コンテナが世界の「上に」あるのではなく、世界とつながっているように感じられます。

これにより、より変わったエフェクトも可能になります。

あるエリアでは、環境が「肉の壁」で構成されています(なぜだめでしょう?)。これらはライトマップを使用して、どのくらいの強さで揺れるかを制御します。近くの火が激しいほど、壁が反応し、環境そのものが生きているかのような印象を与え、火事になっていることに特に満足していないように見える。

さらに素晴らしいことに、これはすべて頂点シェーダーで行われているため、この動的に見えるものにしては非常に安価だ。

火災のVFXはゲームプレイにどのような影響を与えるか?

視覚効果は素晴らしいが、優れたVFXシステムだけでは良いゲームにはならない。In Ignitement火はゲームプレイに直接影響を与えます:火に触れた敵はバーニングデバフを受け、時間経過によるダメージを受けます。

これを実現するためには、シミュレーションデータがCPUで利用可能でなければなりません。各フレームで、リアクションテクスチャはダウンサンプルされ、読み戻されます。 AsyncGPUReadback.RequestIntoNativeArray.GPUでオブジェクトごとに高コストのクエリを実行する代わりに、システムはテクスチャを一度読み取り、すべての敵に対してCPUで安価なルックアップを実行します。単純なしきい値を使用して、これは、任意の時点での火の形状に完全に一致する単一の非常に動的なコライダーのように効果的に動作します。

制限とトレードオフ

もちろん、このアプローチは完璧ではありません。

シミュレーションは2Dなので、垂直方向に起こることはすべて、物理的に正しい解決策というよりは近似にすぎません。また、シミュレーション領域をシフトするには、目に見えるシームやポッピングを避けるために少し注意が必要です。

とはいえ、これらのトレードオフは非常に意図的です。これにより、システムは高速でスケーラブル、かつ幅広い互換性を保ちながら、リッチで反応性のある結果を提供し続けます。

調査結果の概要

  • 2D流体シミュレーションは、予想以上に多くのことが可能になります
  • シミュレーション データの再利用こそが、魔法の起こる場所です
  • 「見た目がいい」は「物理的に正しい」に勝ることがよくあります
  • GPUからCPUへの読み出しは、小規模に抑えれば十分に実現可能である
  • うまく設計された1つのシステムで、ビジュアル、ゲームプレイ、UIを同時に駆動できる

共有の流体シミュレーションの上にすべてを構築することで、 Ignitement は、炎、ライト、UI、さらには環境の一部さえもすべて同じ言語を話す一貫した視覚スタイルを実現しています。

その結果、ビジュアルが向上しただけでなく、よりつながった、より反応的で、より生きた世界が生まれた。

そして、それはすべて、いくつかのテクスチャ、いくつかのシェーダー...そしてすべてに炎を放つことから始まる。

流体シミュレーションやサバイバーライク/ローグライクが好きな方は、お気軽に ウィッシュリスト Ignitement Steamで そして Discordに参加する.Unityで制作されたゲームをもっと見る SteamキュレーターページUnity開発者によるその他のストーリーもぜひご覧ください。 Unityブログ そして リソースハブ.