WaterSystemのFluidSimulationとFoamについて

[Unreal Engine (UE) Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ue) 12/24の記事です。

昨日はmokoさんの

https://effect.hatenablog.com/entry/ac2021part2

でした。近づいたら徐々に浮き出てくるとか一枚の板でやってるとは思えない、なかなか面白い結果が作れています。記事では砂煙っぽい表現でしたが、濁った水面とかにも応用できそうな気がしますね。
VolmetricFogに頼らない砂嵐などの表現の選択肢として良いかもしれません。

# 概要
4.26から導入されたWaterSystemですが、4.26登場時点ではFluidSimulationが上手く動かなかったり、Foam（白波）が出なかったりと、Experimentalではあるものの散々でした。
この記事では、その修正方法や波の調整の仕方、Foamの出し方など主にFluidSimulationについてまとめています。
なお、確認しているバージョンは4.27.1です。

# WaterSystemのセットアップ
この記事では解説しません。

https://argonauts.hatenablog.jp/entry/2020/11/15/185014

https://unrealengine.hatenablog.com/entry/2020/10/25/130109

などで分かりやすく解説されていますので、これらを参考にセットアップしてください

# WaterPluginの複製
これから説明する内容では、WaterPluginを改変します。
WaterPluginはエンジンに含まれるプラグインですが、ほぼBlueprintとMaterialで構成されている為、直接書き換えて挙動を修正する事が簡単です。
しかし、エンジン内のプラグインを直接書き換えてしまうと、他の環境に持っていくときに難儀しますし、バージョンアップ時に上書きされてしまうかもしれません。

その為、WaterPluginをコピーしてプロジェクトプラグインとして登録する事をお勧めします。やり方は簡単
__C:\Program Files\Epic Games\UE_4.27\Engine\Plugins\Experimental\Water__
を
__＜プロジェクトフォルダ＞\Plugins\Water__
にコピーするだけです。（※＜プロジェクトフォルダ＞は.uprojectがあるフォルダ）

エンジン側とプロジェクト側に同じプラグインが重複して存在するのは大丈夫か？と思ったんですが、プロジェクト側が優先されてエンジン側は参照されなくなるようで、特に問題ありませんでした。

# FluidSimulationが動かない！
ここだけ __4.26__ の話なので、4.26でFluidSimulationが動かなくて困っている人は、折り畳みを開いて確認してみてください。
<details><summary>4.26でFluidSimulationが動かない！</summary><div>
WaterBodyLake(水塊 - 湖)を使って水面をセットアップして、いざ

* /Water/FluidSimulation/Blueprints/Examples/BP_Dynamic_Force_Component
* /Water/FluidSimulation/Blueprints/Examples/BP_Dynamic_Force_SkelMesh
* /Water/FluidSimulation/Blueprints/Examples/BP_Fluid_Impulse_Repeating

などを置いて波を立たせようと思っても、上手く動きません。
__/Water/FluidSimulation/Materials/Simulation/M_Fluid_Sim_01__のマテリアルに１か所エラーがある為、正常に動作していませんでした。
具体的には以下の場所です。
![MaterialError.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/08681298-17e2-5211-e07d-f0ed6b8499ec.png)
マテリアルの上部にFoamSimForcesのコメントで囲われた領域があります。その中にある、TextureSampleのテクスチャ指定が無効になっている為エラーが起きていました。
ここに/Engine/Functions/Engine_MaterialFunctions02/PivotPainter2/Black_1x1_EXR_Textureを設定してください。

これだけで、以下の様にFluidSimulationによる波が出るようになります。
![WaterPlugin.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/5e4d6061-8dcf-0ed5-cfd5-6d1da7693765.gif)
</div></details>

# foam（白波）が出ない！
![SimulateWaveFoam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/af6bb8c5-8ad3-70de-a36a-f0d7d284a108.png)
セットアップ時に置いた __BP_FluidSim_01__ の中にSimulateWaveFoam（上図の赤枠）という項目があったので、これをONにすれば出る！
…と思ったら出ませんでした:persevere:

出すにはここの設定をいくつか変更するのと、マテリアルの修正が必要でした。
まずは設定の変更から。前出の画像のアンダーラインを引いている部分を修正します

* __Water Body__に、配置しているWaterBody（ここではWaterBodyLake）を指定
* __Solver__をRippleSolverからShallowWaterに変更

次にマテリアルですが、
![SampleFluidSimulation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/400ebf18-db65-6778-ec62-80fea72dbd47.png)

__/Water/Materials/WaterSurface/Water_Material__で使われている、__/Water/Materials/Functions/SampleFluidSimulation__（マテリアル関数）の引数__EnableFoam__をTrueにする必要があります。
SampleFluidSimulation内でデフォルトの値をTrueにしてもいいし、Water_Materialで引数に値を設定しても大丈夫です。

ただ、元の状態ではマテリアルパラメータになっていないため、外部から操作する事はできませんので、切り替えたい場合はWater_Materialにマテリアルパラメータを用意する事をお勧めします。

その結果がこちら。
![ShallowFoam.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/6e9da238-1a53-7329-37d1-035dec2a9dac.gif)
なかなかいい感じです:thumbsup:

# RippleSolverでも白波を出したいんだけど？
WaterSystemの波のシミュレーションアルゴリズムには、RippleSolverとShallowWaterがあります。RippleSolverの方が比較的大きな動きの波を出せるので、RippleSolverを使いたい場面があると思います。
しかし、RippleSolverではここまでの対応を行ってもFoamが出ませんでした。調べてみると、マテリアル内で情報としては途中まで作っているのですが、作りかけなのか最終的に出力されていません。
その為、RippleSolverでFoamを出すにはさらに追加の対応がいくつか必要です。

## Foamパラメータのミッシングリンクを整備する
先ほども書いた通り、Foamパラメータは途中までは生成されていますし、Waterマテリアルによる描画もShallowWaterと共通なので動いています。
では、なぜ描画されないかをShallowWaterと比較して調べたところ、ミッシングリンクがありました。
法線バッファへ波の情報を書き出す際、AチャンネルにFoamの情報を格納すると、後段の描画で使ってくれるのですが、それがありませんでした。

そこで、RippleSolverで法線を計算、出力しているマテリアルの __/Water/FluidSimulation/Materials/Simulation/M_RT_Normals__ に2か所の変更をします。
![RippleFoam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/037ac9b5-8422-a8d9-d729-3f64e556c933.png)
### BlendModeの変更
１つ目はBlendModeの変更。元は__Opaque__でしたが、__AlphaComposite(Premultiplied alpha)__に変更してください。
これでオパシティのピンが有効になり、Aチャンネルに直接値が出力できます。

### Foamパラメータの出力
２つ目は前段階で生成されているFoamパラメータをオパシティへ出力します。前段のRenderTarget(Forceバッファ)ではGチャンネルにFoamのパラメータが格納されているので、MidpointのGチャンネルから情報を引っ張ります。
ついでに、UVの端でフェードアウトさせる処理も一緒に組み込みます。これは、ShallowWaterの同じ部分(M_ComputeNormal)の処理を真似ました。

## RippleSolverでもFoamが出た…？
これでFoamが出ました。
![RippleFoam1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/1fae50a5-de84-bacd-e7cc-db5b25d4b22b.gif)
…しかし、ShallowWaterの時と比べても見た目がかなり違いますし、特にFoamがなかなか消えません。その為、Foam（白波）というより、泡を撒いている感じがします。
Foamという言葉からすると正しいのかもしれませんが、コレジャナイ…:dizzy_face:

# RippleSolverをクオリティアップ！
ここからはFoamの出方を含めた、RippleSolverをクオリティアップする方法を紹介します。
クオリティアップの方法なので、紹介する方法が正解。というわけではありません。各自のクオリティアップの参考にしてください。

## 波が立ちすぎる
これは好みの問題ですが、RippleSolverは波が大きく出るのは良いんですが、力を加えた周辺がばねの様にいつまでも揺れて波が立ち続けてしまいます。
また、大きな波を立たせようと強い力を加えると反動で水面が不自然に盛り上がってしまうのも気になっています。
それを防ぐために、ちょっとした修正を加えました。

水位の変動を計算しているのは、RippleSolverの場合 __/Water/FluidSimulation/Materials/Simulation/M_Fluid_Sim_01__ になります。
その最後で変位に上限値を設けて、見えない天井を作ります。
![WaterCeil.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/0f74fc02-306d-d016-7bdd-966eae09596d.png)
ここではClampノードを使用して、下限(Min)は制限したくないので負の適当に大きな値を入れておきます。上限(Max)も見た目調整で10.0としましたが、これは好みに応じて調整してください。

## Force由来のFoamを削除
WaterSystemのDynamicForceやImpulseForceは、それぞれ

* /Water/FluidSimulation/Materials/Forces/M_Fluid_Sim_Force_Component
* /Water/FluidSimulation/Materials/Forces/M_Fluid_Sim_Force_Impulse
* /Water/FluidSimulation/Materials/Forces/M_Fluid_Force_Skelmesh

のマテリアルでForceバッファに描画されています。このGチャンネルにFoamの情報が格納されているのですが、これが力を加えた領域全体にFoamを発生させてる原因でした。
そこで、思い切ってこれらを全て切ります。
![FoamGChannel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/5260548d-77b7-a97b-562d-7eb8c35f5b45.png)

## Foamの生成方法の見直し

もう一つ、Foamの生成方法も見直しました。
Foamのパラメータ生成も変位の計算と同じ、__/Water/FluidSimulation/Materials/Simulation/M_Fluid_Sim_01__で行われています。
![FoamQualityUp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/db5ead76-e91e-571c-e1a9-8da01118750f.png)
元々は下方向への変位が大きい部分にFoamを追加で出すようになっていましたが、Forceバッファ由来のFoamを削除したことで、これだけでは逆に貧相になってしまいました。
そこで、ShallowWaterの処理から部分的に持ってきています。それが上記画像の下部「法線の勾配から係数を作成」の部分です。
本当はShallowWaterのFoamの見た目を再現する為に、全部持ってきたかったんですがRippleSolverではVelocityの情報を持っていないので、部分的な引用にとどまりました。
これを、元からあった変位によるFoam係数と入れ替えます。

## その他の調整と結果
Foamがなかなか消えていかない事も見た目を悪くしている原因だと思ったので、減衰率(FoamDamping)を調整しました。

その結果がこちら
![RippleFoam2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/e0953e53-48d8-1061-bb36-c677e951263d.gif)
まぁ、これでもちょっとおかしい感じもしますが、さっきの泡をばらまいたような結果よりはだいぶマシになっていると思います。

## 調整パラメータについて
最後に、このセクションで触れた部分にある調整パラメータを紹介しておきます。

| 変数名 | 説明 | 最終結果の値 |
|:-|:-|:-|
| LimitHeight | 変位の上限値<br>これがあることで、揺れの継続性が抑えられます | 10.0 |
| FoamDamping  | Foamの減衰率<br>次のフレームでどれぐらい残るか。値が大きいほど、時間経過をしてもFoamが残ります。 | 0.75 |
| ZScale | 法線を作る時に適用する傾きの強度<br>NormalMapを強調する時と同じようなパラメータ。傾きの大きさに比例してFoamが反応するので、値を大きくするとFoamが出やすくなります。 | 10.0 |
| MinZforFoam | Foamが反応する範囲の閾値 | 0.1 |
| FoamZLen | Foamが反応する範囲の幅の調整<br>値が大きいほどFoamのグラデーション幅が広がり、小さいほど狭くなる | 1.5 |

## 今後の課題
先ほど「これでもちょっとおかしい感じもしますが」と書いた通り、まだまだ改善すべきはあります。特にFoamにタイリング感が出てしまっているのはいただけない。

このあたりの見た目がShallowWaterと違う理由ですが、Velocityが無い事が大きいです。
この記事で触れたFoamパラメータを作る部分もそうですが、 __/Water/Materials/Functions/SampleFluidSimulation__ で行われているFoamを作る処理で、FlowMapの流れや調整にもVelocityを参照しています。
残念ながらRippleSolverにはVelocityを出力する処理は無く、この部分は新たに作る必要があります。

# あとがき
Epicも一押しのWaterSystemですが、まだExperimentalという事もあり色々と作りかけの部分が散見されます。
EpicGamesが配信した動画

https://www.youtube.com/watch?v=d5Ft7UaYayM

を見ていると、すごい海が作られていますが、ここまでするには自分で色々と作らないといけなさそうです。
UE5では、こんな海がサクッと作れるようになっている事を期待！

さて、明日はクリスマス。アドベントカレンダーも明日でおしまいです。
オオトリは [@_akoto_](https://qiita.com/_akoto_) さんのFoliageTypeについての記事です。