[UE4]Niagara(SimulationStage)にまつわるTips

![SPH_Demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/bd53becb-677a-2bf7-3cfe-8c2836fc58c1.gif)
現在、こんな感じのSPH(Smoothed Particle Hydrodynamics)を使った表現を、UE4/Niagaraで実装すべく検証中です。
この内容については追々まとめる予定ですが、今回はこの実装に使ったNiagara（特にSimulaionStage）で突き当たった問題やTipsがあるので、いくつかまとめておきます。

確認しているバージョンは、UE4.26です。また、NiagaraのSimulationStageは4.26時点でExperimentalな機能です。ここで書いたことも、バージョンが上がると実装が変わる可能性がありますのでご注意ください。

#とにかく細かく保存
SimulationStageを使っていると顕著ですが、処理やパラメータを調整した結果、ループ数が多いなど重い処理が動く結果になると、エディタが良く落ちます。
Niagaraのプレビュー画面と、エディタ本体画面で同じNiagaraSystemを再生していても挙動が怪しいです。その為、Niagaraのプレビュータブを予め閉じておく事もよくやります。
不幸にも強制終了してしまった場合は、自動保存からの復旧も可能です。しかし、自動保存だとどの時点のデータか判然としない場合が良くあります。落ちてから後悔しても遅いので、とにかく細かく保存するしかありません。

#Niagaraのモジュールパラメータの扱い
![NiagaraModuleParameter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/659e21f6-e43e-3c61-7835-4ac43a04e717.png)
各ステージで使われるNiagaraModule（スクラッチパッド）ですが、その入力パラメータは二種類の設定の仕方があります。

1. マップ取得ノードでは、入力パラメータで定義し、モジュールのパラメータでエミッタパラメータなどを指定する
- マップ取得ノードで直接エミッタパラメータなどを指定する

どちらも結果は同じですが、1.はパラメータを外から確認できますが、モジュールを追加するたびに指定する必要があります。2.の場合モジュールの外から確認が出来ないため、使われているパラメータを確認するにはその都度モジュール（スクラッチパッド）を開く必要があります。
どちらが良いかは好みですが、入力パラメータならMultiply Floatなどの動的入力でカスタマイズできますし、モジュール（スクラッチパッド）を開かなくても使っているパラメータが分かるため、個人的には1.を好んで使っています。

#ParameterMapSetの実行順
![ParameterMapSet_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/865a2897-5972-fe73-fcf0-d687caacca81.png)
こんな感じに、位置の履歴を保存するようなモジュールを作りました。が、これではうまく動かないことがあります。原因は、ParameterMapSetの実行順がピンの並び順ではなく、順不同のようです。
![ParameterMapSet_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/ce10332b-4860-3c71-1c14-890510f7a4e1.png)
そのため上図のように、一時変数（ローカル）に保存してから更新しないと、意図した動きにならないことがあります。

#StaticSwitchに任意のEnumを指定する方法
StaticSwitchノードに自分で作ったEnumを指定したくなることはあると思います。その際、

https://twitter.com/0ct0pus1/status/1361625625274970115

と同じように、ノードをテキストエディタにコピーしてアセット名書き換え、ペーストして入れ替えていました。
このTweetに書かれている通り、いくら何でもそれは無いだろうと思って、ちゃんと調べたところやっぱり方法がありました。
![StaticSwitchEnum.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/b8f3116b-9c65-5042-805a-c04282fa0564.png)
このように、自分で作ったEnumアセットを**プロジェクト設定＞Niagara＞列挙型の追加パラメータ**に増やします。そうすると、StaticSwitchノードの**スタティックスイッチタイプ**に指定したEnumがちゃんと出てきました。
Config識別子は**Niagara**なので、[ProjectFolder]\Config\DefaultNiagara.inなどに

```ini:[ProjectFolder]\Config\DefaultNiagara.ini
[/Script/Niagara.NiagaraSettings]
+AdditionalParameterEnums=/Game/Enums/ETestEnum.ETestEnum
```
といった設定を書いても追加できます。

#SimulationStageにおけるCustomHLSLのコンパイルエラー
SimulationStageに配置したCustomHLSLでコンパイルエラーを起こしたときは、どこのノードで出ているか分かりません。
![NiagaraCompileError.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/eb6fcc1b-4abe-116c-ee81-4ad4076c7443.png)
このように、大抵はエラーがあるのに「成功しました。」とでます。
![NiagaraCompileError2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/920ee8b3-f326-f40e-d7e3-429f1d3c55c4.png)
ただ、スクラッチパッドの場合は、編集後に左上の適用を押した後、数秒間はコンパイルエラーの表示が出ます。スクリーンキャプチャなどを使って、そのエラーを確保すると調査が多少楽になります。
しかし、このエラーメッセージもどこのノードでエラーが起きているかの情報はありません。生成された統合シェーダコード上の行数は出るので、そこからある程度絞り込むことで頑張るしかありません。

なお、これもスクラッチパッドの場合だけで、モジュールでは数秒で消えるエラーも出ません…。その為、スクラッチパッドでしっかり構築してからモジュール化することをお勧めします。あるいは、単純なコンパイルエラー（セミコロン忘れとか、括弧忘れ、↑の例のような定義ミス）なら、オンラインのHLSLコンパイラで多少調査できます。

#CameraQueryのプレビューにおける挙動
![CameraQuery.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/2462350f-1780-44f9-47ac-4c5d1aa81dd7.png)
CameraQueryを使うとこんな警告が出ます。**「CPUエミッターではプレビューできません」**という警告なので、GPUSimなら関係ないや。…と思ったら大間違い。
![CameraQuery2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/6bcbd9ce-7920-2fa0-de3b-4ef25f620c1e.gif)
これはカメラに正対するようなSpriteを描画しているのですが、このようにメインのビューポートの向きによって、プレビューに表示されるSpriteの向きが変わります。という事で、CameraQuery由来のパラメータを使った場合プレビューウィンドウの結果はあまり当てになりません。

#CustomHLSLノードのエディタは貧弱
CustomHLSLノードにシェーダコードを書く事になるのですが、エディタ機能が貧弱です。

- キーワードのハイライトがない
- 改行挿入後のインデント操作をしてくれない
- そもそも、Tabでインデント操作ができない
- Niagaraのエディタウィンドウが狭くて全体を見渡せない

と使いづらい点が多数あります。ちょっとしたコードなら良いのですが、インデントが必要になるコードは、別エディタ（VisualStudioCodeやお好みのエディタ）で編集した後にコピペで適用する事をお勧めします。

#SimulationStageのCustomHLSLはGPU_SIMULATIONで囲う
機能別サンプルにある、Niagara_Advancedを見ていると当たり前のように書かれていますが、SimulationStageでCustomHLSLノードを使う場合は、出力変数の初期化以外は基本的に
　 **#if GPU_SIMULATION　～　#endif**
で囲みます。簡単な算術演算だけならなくても大丈夫なことがありますが、大抵の場合はこれが無いとコンパイルエラーが発生します。

# Grid2Dの中身
Grid2Dは2次元のデータを扱うData Interfaceですが中身はR16Fなどの2次元テクスチャです。その為、設定さえすればそのプレビューをテクスチャビュワーで確認する事が出来ますし、マテリアルからも直接参照することが可能です。

1. 描画ターゲット(TextureRenderTaeget2D)を作成
![Grid2D_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/72a3bcb4-84a3-7647-fa06-fb8ab70599cb.png)
プレビュー用の描画ターゲットを作成します。任意の名前でOKです。
また、画像サイズやフォーマットなど何も設定しなくて大丈夫です。一連の設定が終わったらNiagaraから勝手に変更してくれます。

2. Niagaraのシステムユーザ変数にプレビュー用テクスチャを追加
![Grid2D_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/98dea536-264b-fc3c-8e4a-1088ea735495.png)
Niagaraシステムのユーザ変数として、TextureTargetを追加します。似たようなオブジェクトでRenderTarget2Dがありますが、ここではTextureTargetを指定して作ってください。
その後、パラメータの値として、1.で作った描画ターゲットのアセットを指定します。

3. Grid2Dにユーザ変数を指定
![Grid2D_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/77e70df3-079c-2fab-5c29-0b3adb156de7.png)
使用するGrid2Dの初期化時、RenderTargetUserParamに2.で作ったユーザ変数を指定します。

これで設定は完了です。1.で作ったアセットをテクスチャビュワーで開き、シミュレーションを開始すると、以下のような結果が確認できます。
![Grid2D_PreviewRT.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/6ee86d40-707b-e466-0bc2-165c433c1b6f.png)
各チャンネルのデータは二次元テクスチャが一列に並んだ形で確認できます。データの並び順はVector要素数順→SimulationStageで要素を追加した順になります。
例えば

1. Vector3
2. Float
3. Vector4
4. Vector3

という順で、要素を登録しても、数字順には並ばず3→1→4→2の順に並びます。
テクスチャサイズについては、Vector４なら4枚分、Vectorなら3枚分、floatなら1枚分と無駄なパディングなどはありません。

#Grid2Dの中身をマテリアルで参照する方法
Grid2Dの中身は2次元テクスチャの配列だということが分かったので、それを利用してマテリアルでも参照する事が出来ます。
![Grid2D_Material.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/059fa732-e74f-b8f7-7fd2-8a8b61e44066.png)
上図のようにマテリアルにTextureParameterを用意し、NiagaraからはMaterialParameterBinding経由で、Grid2Dオブジェクトを指定するだけです。
マテリアル内でテクスチャサイズや必要なパラメータの開始位置などを元に、UVの再計算(GetGrid2DValuesの部分)をする必要がありますが、画像のような簡単な設定でGrid2Dに書き出した情報を取得できます。

#CustomHLSL内でView.～～～で参照できる情報
[UE4のResolvedView.hogehoge で取れそうなやつ](https://gist.github.com/shop-0761/f83064b332bb038e1d8f96e1d2149f04)
このページに詳しく並んでいます。マテリアルエディタのCustomノードでは**ResolvedView**という変数ですが、SimulationStageのCustomHLSLでは**View**という変数で同じ内容の物が取得できます。
各座標空間の変換に使える行列など様々な変数があるので、これが使えることを覚えておくと重宝します。また、公式ドキュメントの[座標空間の用語](https://docs.unrealengine.com/4.26/ja/Basics/CoordinateSpace/)も合わせて確認すると、それぞれがどのような座標空間かを把握できるのでお勧めします。

#あとがき
最初はSimulationStage関連のTipsをと思っていたんですが、あれもこれもと書いていくうちに長くなってしまいました。
使っていて感じますが、NiagaraのSimulationStageはGPGPUで何かをやらせるにはかなり強力な機能なので、個人的にも今後の動向に注目しています。

先週はUE5のアーリーアクセスも始まって、Niagaraがどう進化していくのか気になる所ですが、このSimulationStageの機能は今後もフィーチャーされていくんじゃないかと見ています。