SPARK CREATIVE Tech Blog

https://spark-group.jp/

流体シミュレーションを実装してみたい その3(燃焼モデル編)

はじめに

こんにちは。株式会社スパーククリエイティブCTOの広本です。
普段は SPARK GEARの開発をしたり、UEでエンジンの改造をしたり、Unityでレンダラーや揺れもの物理を作ったりしています。


前回までは、ナビエ・ストークス方程式を解き、マコーマック法でディテールを維持するところまで解説しました。
しかし、それだけでは「煙が漂う」だけで、「爆発」にはなりません。
爆発とは、物理的には「急激な化学反応による熱エネルギーの放出と、それに伴う気体の体積膨張」です。

今回は、単なるパーティクルエフェクトでは到達できない、密度・燃料・温度・酸素の4要素を連成させた、物理ベースの燃焼モデルの実装詳細を解説します。

1. 燃焼の方程式:4つのステート変数

リアルな爆発挙動をシミュレートするために、各グリッドセル(Voxel)には速度場 \mathbf{u} と圧力 p に加え、以下のスカラー場を持たせます。

  1. Fuel (F): 未燃焼ガスの残量。0になると燃え尽きる。
  2. Oxygen (O): 空気中の酸素濃度。燃焼に不可欠。
  3. Temperature (T): 場の温度。発火点を超えると燃焼トリガーとなり、浮力と発光色の源となる。
  4. Density (D): 燃焼の結果生成される煤(煙)。

これらはすべて移流方程式 \frac{\partial \phi}{\partial t} + (\mathbf{u} \cdot \nabla)\phi = 0 によって運ばれますが、それとは別に反応項(Reaction Step)で相互作用します。

2. 化学反応の離散化

燃焼プロセスは、以下の条件が揃ったセルで発生します。

 T > T_{ignition} \quad \land \quad F > 0 \quad \land \quad O > 0

このとき、1タイムステップあたりに燃焼する燃料の量は、反応速度係数を用いて以下のようにモデル化しました。

 \Delta F_{burn} = k_{burn} \cdot F \cdot O \cdot \Delta t

※雰囲気だけのなんちゃってモデルですが、リアルタイム用途では「燃料と酸素の積」で近似するのが一般的で計算負荷も軽微です。

この反応量に基づき、各ステートを更新します。

 F^{new} = F - \Delta F_{burn}
 O^{new} = O - \Delta F_{burn} \cdot k_{oxidize}
 T^{new} = T + \Delta F_{burn} \cdot k_{heat} - k_{cool} \cdot T \cdot \Delta t
 D^{new} = D + \Delta F_{burn} \cdot k_{smoke}

ここで重要なのは、「燃料が減り、熱と煙が生成される」という保存則のような振る舞いをコードに落とし込むことです。

3. Compute Shaderによる実装

コンピュートシェーダでの実装例です。
実際のコードとは違いますが雰囲気は一緒です。
RWTexture3D を使用し、各セル並列で反応計算を行います。

// 定数バッファ
float _DeltaTime;
float _IgnitionTemp; // 発火温度
float _BurnRate;     // 燃焼速度係数
float _HeatGen;      // 発熱係数
float _SmokeGen;     // 発煙係数
float _CoolingRate;  // 冷却係数
float _ExpansionRate;// 膨張係数

// ステートテクスチャ (Read/Write)
RWTexture3D<float> _FuelTex;
RWTexture3D<float> _OxygenTex;
RWTexture3D<float> _TempTex;
RWTexture3D<float> _DensityTex;

[numthreads(8, 8, 8)]
void UpdateReaction(uint3 id : SV_DispatchThreadID)
{
    // 1. ステートのロード
    float fuel = _FuelTex[id];
    float oxygen = _OxygenTex[id];
    float temp = _TempTex[id];
    float density = _DensityTex[id];

    // 2. 燃焼判定
    // 温度が発火点を超え、かつ燃料と酸素がある場合のみ反応
    if (temp > _IgnitionTemp && fuel > 0.001 && oxygen > 0.001)
    {
        // 燃焼量の計算
        float burnAmount = _BurnRate * fuel * oxygen * _DeltaTime;

        // ステート更新
        fuel -= burnAmount;
        oxygen -= burnAmount; // 簡易的に1:1で消費と仮定
        temp += burnAmount * _HeatGen;
        density += burnAmount * _SmokeGen;
    }

    // 3. 冷却(自然減衰)
    temp = max(0, temp - temp * _CoolingRate * _DeltaTime);

    // 4. 書き込み
    _FuelTex[id] = fuel;
    _OxygenTex[id] = oxygen;
    _TempTex[id] = temp;
    _DensityTex[id] = density;
}

というわけで燃焼モデルを実装してみました。
アニメーションとしては爆発っぽい動きが作れたのではないかと思います。

4. レンダリング:黒体放射 (Black Body Radiation)

シミュレーション結果の可視化においても、アーティスティックなグラデーションマップを使うのではなく、物理ベースのアプローチを採用します。
計算された Temperature(ケルビン想定の値にマッピング)を、プランクの法則に基づく黒体放射の色に変換します。


あくまでゲームなどでリアルタイムに使うためのものなので厳密に物理法則に則っている必要はないかと思います。
それっぽい色が付けばいいだけなので今回は黒体放射のパラメーターを参考にしたランプカーブを用意して色付けをしています。

これにより、中心部の高温地帯は白飛びするほど明るく、冷えてきた周辺部は赤黒い煙になるといったリッチな表現が、パラメータ調整なしで自動的に生成されます。

というわけで実装した結果です。

まとめ

今回の「爆発用燃焼モデル」のポイントは以下の2点です。

4要素の連成: 燃料・酸素・温度・密度の相互作用をReactionステップで計算する。  
温度ベースの描画: 色付けを黒体放射に委ねることで、ダイナミックレンジの広い表現を得る。

次回は最適化に関しての記事になる予定です。

©2025 SPARKCREATIVE Inc.