Skip to content
Go back

MH:W | GPU Particle - モンスターハンター:ワールドにおけるGPU Particleの実装

· Updated:

slides

抜粋

方針

  • 可読性は最重要
  • CPUに余裕がないので、Dispatchを少なく
  • GPUの並列性を阻害したくないので、アトミックをなしに

要素

  • エフェクトは任意の数のエミッタを持つ
  • エミッタは任意の数のアイテムを持つ
    • 速度、寿命、座標変換、などの種類がある

構造

  • Emitter Table (CPU→GPU):
    • 有効なエフェクトの配列
    • Emitter Headerへのインデックスを持つ
  • Emitter Header (CPU→GPU):
    • 間接参照のための値の配列
    • EmtBinHead:Emitter Binaryへのオフセット
    • PtBinHead:Particle Binaryへのオフセット
    • PtIdxHead:Particle Index Listへのオフセット
    • PtSize:Particle Binaryでの1要素のサイズ
  • Emitter Binary (CPU→GPU):
    • エミッタごとに、共通データをヘッダに持ち、その下にアイテムの配列を持つ
    • 共通データは、どのアイテムが有効かのビットセット、などを持つ
  • Particle Header:
    • Alive:パーティクルが生存中かのフラグ
    • EmitterID:エミッタの番号
    • Index:パーティクルの番号
    • Depth:深度値
  • Particle Index List:
    • Particle Headerへのインデックスの配列
    • 生存中、消失中、未使用、の順で連続する
  • Particle Binary:
  • Emitter Range:
    • エミッタごとのParticle Headerのスパンを持つ配列
    • 生存と非生存の2つ分
  • Emitter Data:
    • エミッタごとの、発生するパーティクルのParticle Headerへのスパン、生存中の数、を持つ

処理

  • 初期化(GPU):初回のみ一度だけ行う
    • パーティクル総数を0クリアする
    • Particle HeaderをInvalidにする
  • エフェクトの生成(CPU):
    • Emitter Tableに要素を追加して、指定のEmitter Headerの要素を確保
    • Emitter Binaryの末尾に割り当てた領域をEmitter Headerに記録
    • Particle Binaryの末尾に割り当てた領域をEmitter Headerに記録
  • エフェクトの更新(CPU):
    • エミッタを更新
      • 親の移動値の反映、など
    • アイテムを更新
      • パーティクルを出す数の決定、外部パラメータの反映、タイムライン制御、など
    • エミッタ用データの更新
      • Emitter Headerを介して、Emitter Binaryの指定の領域にアイテムを書き込む
  • エフェクトの更新(GPU):
    • Begin Update
      • パーティクルの総数を、前フレームの総数と現フレームの予定数の総和として、指定の最大数でクランプしてから書き出す
    • Fill Unused Index
      • 新規のエミッタが割り当てたParticle Index Listの末尾領域をInvalidで埋める
    • Spawn Particles
      • 新しく発生(spawn)するパーティクル数を決め、Particle Headerの末尾に割り当てる
      • Emitter Rangeをゼロ初期化
    • Initialize Particles
      • Particle Index Listに新しく発生したパーティクルの分を追記
      • Particle Headerにデータを書き込む
      • Dispatch(エミッタ数, 1, 1)
    • Update Particle
      • Particle Headerを辿って、パーティクルをアイテムに沿って動かす
      • 1スレッド、1パーティクル
      • シェーダはuber
    • Bitonic Sort
      • 生存フラグ(1bit)、エミッタID(13bits)、深度値(32bits)をキーとしてParticle Headerをソートする
      • 深度値は半透明の前後関係を正しく描画するために必要
      • Bitonic Sortなのは著者都合
    • Range Particles
      • Emitter Rangeを構築する
      • スレッドごとに要素2つ取り出してEmitterIDや生存フラグを比較する
      • 0スタートと1スタートで2回行う
    • Terminate Particles
      • 消滅したパーティクルのParticle Index Listを無効化する
    • Build Emitter Draw Args
      • 実際のパーティクル数でEmitter Dataを更新
      • Draw Indirect用引数をエミッタごとに構築
    • Build Primitive
      • パーティクルから頂点データを構築する
      • 1スレッド、1パーティクル
      • ビルボードとリボンに対応

感想

  • CPUベースのパーティクルシステムが先に存在していたそうなので、それに合わせるかたちとなった設計もありそう
  • 同じ処理のパーティクルが連続するようにソートするので、更新をuberシェーダにしても許容されている
  • 条件式を定数とする分岐をコンパイラが最適化で除去する、と期待するのは十分アリ
  • エミッタごとの処理の起点がEmitter Tableで、パーティクルごとの処理の起点がParticle Header
  • Particle Headerはソートする必要があるから軽量な間接参照テーブルになっている?
  • 最大数による事前割当やエミッタ毎&パーティクル毎の処理などにより、アトミックを排除している?