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