Anime Aura VFX 技術解説②

概要 この記事について

 UE5 向けの NiagaraSystem アセットである Anime Aura VFX についての技術解説です。
全3回に分けての解説を予定しています(これはボリューム等を見て変更する可能性があります)。

第1回 Anime Aura VFX の原理
第2回 パーティクルの投影(本記事)
第3回 濃度マップ→表現

 今回はパーティクルの位置を用いて2次元の濃度マップを作製するプロセスについての説明です。
以下の知識を必要とする部分があります。読み進める中で詰まる場合は適宜ほかの資料で補うなどしてください。

  • NiagaraSystem 中心の記事ですので、多少触ったことがあるくらいの経験がないと全体的に理解しづらいと思います
  • ベクトルとか射影とかが分かるとHTMLコードが読み取れます

Aura VFX NiagaraSystem 全体図


 Anime Aura VFX には多くの NiagaraSystem が含まれますが、殆ど全てについて構造は共通しています。
左側の Emitter がパーティクル発生、右側が投影とスプライト表示を担います。

パーティクルを発生させる


 こちらはごく一般的な Emitter とあまり変わりません。
パーティクルを直接表示しないため、Renderer が無効になっています(有効化するとパーティクルの振る舞いを確認できます)。
キャラクターにまとわせるエフェクトですので、発生位置に SkeletalMeshLocation を用いています。
 ここでパーティクルの発生量や位置・速度を制御することでオーラの挙動を変えることができます。

パーティクル以外のパーティクル
 このシステムはGPUでの処理がやや複雑なものですので、パーティクルの数を極力抑えたいのが実情です。
 投影処理においては上記のエミッターで発生させたパーティクルの他に、
SkeletalMesh のボーンの位置を「大きいパーティクル」のように扱います。
ボーン由来のパーティクルでオーラの概形を決め、実際のパーティクルで「揺らぎ」を作るような形をとっています。

パーティクルの位置を投影する


 右側です。本システムの特徴からすると、こちらのエミッターが本体と言えるでしょう。
エミッター(放出するもの)と言いながらパーティクルを一切生成しないのが少し変わったところです。
 コメントを付けている部分(DrawArray/DrawBones/DrawParticles)が実際に投影を行っている部分ですが、初期化から順を追って説明していきます。

Emitter Spawn


 エミッター内で使用するパラメータはここで初期化されます。

 Set では上から順に、

  • AttributeReader : 左のパーティクル生成用エミッターからパーティクルの情報を読み取るためのもの
  • ReferenceEmitter : そのエミッター自体の情報を読み取るためのもの。実際のところ LocalSpace かどうかを見ているだけです
  • ResultTexture : 最終結果を書き込むテクスチャ。濃度マップ。これをスプライトのマテリアルに渡すことになります
  • ScalarFieldGrid : 投影したパーティクルの情報を書き込むバッファで Grid2D。これをいかに計算するかが本記事の主題です
  • SpriteSize : スプライトの表示サイズ(ワールドでのサイズ)

のようなものの初期状態を決めています。
 さいごに、NM Set Grid Size では ScalarFieldGrid のサイズを決定します。

投影処理

 このエミッターはパーティクルを一切発生させないため、ParticleSpawn / ParticleUpdate ではほぼ何もせずに SimulationStage の処理に入ります。
 上から順に説明していきます。

Clear Working Buffer

 ScalarFieldGrid のうち、現フレームの情報を書き込む部分を 0 クリアします。
ここで ScalarFieldGrid(Grid2D)について触れておきます。

Grid2D について
 ScalarFieldGrid は Grid2D という型を持ちますが、これは2次元配列状の構造を持ったデータです。
ピクセルのフォーマットを自由に設定できるテクスチャ」のように考えればイメージとしては大体合っている気がします。
実際、NiagaraEditor の中では Grid2D の値をテクスチャのような感じでプレビューが可能です。
プレビューの様子。
 ScalarFieldGrid には
* 1フレーム分のデータを書き込む WorkingBuffer
* フレームを跨いで WorkingBuffer を合成して得る ScalarValue
という二つのスカラー値を保存していますので、二枚の画像を並べたように表示されます。

DrawXXX

 DrawArray / DrawBones / DrawParticle の作用は殆ど同じで、
元となる(仮想的な)パーティクルの位置情報がそれぞれ 「決められたベクトル配列(多分あまり使わない)」「SkeletalMesh から得るボーン位置」「パーティクルの位置」 という違いがあるくらいです。
 これらは、パーティクルの位置をスプライトの平面上へ投影して ScalarFieldGrid への書き込みを行います。
 ここでは DrawBones を取り上げて説明します。

準備


 グラフに入る前にモジュールの設定です。
 Niagara SimulationStage では「何らかのデータ集合に対して繰り返し処理を行う」ことができます。
何らかのデータ集合とはパーティクルであったりそれ以外(テクスチャ や Grid2D)であったりします。
画像中の Iteration Source に「何について繰り返すか」を指定します。
 ここでは Iteration Source として ScalarFieldGrid (Grid2D)を用います。
そうすると、モジュールのグラフ(から生成されるシェーダーコード)が ScalarFieldGrid の各要素(ピクセル的なもの)に対して実行されることになります。

計算

 以下が NM Bones to Scalar Field モジュールのグラフです。

 Niagara Module においてはわりとよくあることかと思いますが、主要な計算はHLSL コードの部分で行われます。
以下がその部分を拡大したものです。

 長いコードではありませんが変数の命名がけっこう適当かつコメントが無いので読みづらいかもしれません。
ですので補助線として変数の意味を幾つか書いておきます:
Num:パーティクル数
LocalTarget:ピクセル(仮)のローカル座標(スプライト中心を基準とした平面上の座標)
Projected:スプライト上に投影された座標(ワールド)
LocalPos:それをローカルに変換したもの
TmpPower:そのパーティクルにより生じる"濃度"

 繰り返しになりますが、ScalarFieldGrid の1ピクセル(仮)につき一度このコードが実行されます。
 内容としては、全てのパーティクル(ここではボーンですが)についてスプライト平面への投影を行いピクセル(仮)との距離から濃度を算出して足し込んでゆくようになっています。
 遠近法も適用され、カメラの近いパーティクルほど大きく描かれることになります。

 以下に概念図を載せます。

 0/1 ではなく、パーティクルが投影される位置がピクセル(仮)に近いほど濃く塗るようにしてグラデーションを作ります。
いわゆる2Dメタボールと同じような考え方ですので、このワードで検索してみると理解が捗るかもしれません。
 このようにして、下の画像の左側のようなマップが作成されます。

Mix

 上記の処理で作成されたマップを、「流れ」を加えつつ直前のマップと合成します。

画像左のマップを少しずつずらしつつ薄めながら足していくことを繰り返して右のマップを得ます。

 処理の内容を図で表すと以下のような感じです。

CopyToRenderTarget

 Grid2D はテクスチャではないのでそのままではマテリアルに渡せません。
ここで ScalarFieldGrid から、統合後のマップを RenderTarget へコピーします。
右下がコピー元(統合されたマップ)、左上が結果の RenderTarget です。

 もののついでに周辺部をフェードアウトさせる処理を行っています。

まとめ

 NiagaraSystem を用いてパーティクル(+SkeletalMesh)から2次元のテクスチャ(濃度マップ)を生成するプロセスについて説明しました。
 Anime Aura VFX には色々な見た目のエフェクトが含まれていますが実はここまでの部分は殆ど共通しており、多少パラメータが異なるくらいです。
出来上がった濃度マップを用いてどのように処理するかによって様々な外観を作り出すことが可能となっています。
次回はそちらについて解説する予定です。