【UE5】WaterSystemのメッシュ生成フローについて

# 概要
UE4の後半で追加されたWaterSystemですが、UE5.1辺りでまた大きく実装が変わりました。この記事では、現在のWaterSystemがどうメッシュを生成しているかを解説します。また、後半には調整するうえで引っ掛かりやすい点の紹介などもまとめています。

この解説を元に、意図したメッシュが出ない場合にどこを確認/調整したらいいのかの一助になれば幸いです。

なお、この記事は **UE5.3** で調査して書かれています。

# フロー概略
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/3ff0a8eb-1f7b-cdcc-48d9-3af38155b3c7.png)
上図のようなWaterBodyRiverを例にまずは概略から説明します。

WaterBodyRiverの場合、情報の起点はSplineComponentになり、以下のようなデータを経由して最後は、表示されているWaterZoneMeshが生成されています。

:::note
SplineMeshComponent → コリジョンメッシュ
↓
WaterBodyInfoMesh
↓ 
WaterInfoTexture
↓ 
WaterMesh(WaterZone)
:::

# SplineMeshComponent → コリジョンメッシュ
まずはコリジョン生成から紹介します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/92dd2314-2d0b-6388-d7ba-46bd14bdf1d1.png)
上図はViewModeをプレイヤーコリジョンに変更した結果です。分かりやすくするため、WaterZoneMeshも非表示にしています。

このように、スプラインセグメント（スプラインポイントの間）当たり4枚の三角形で構成されています。これはスプラインがどんな曲率を持っていても変わりません。
<table><tr>
<td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/b7a1437b-bc49-55c7-610e-421a35f6ed37.png"/></td>
<td>左図のように、中央部を大きく盛り上げてもスプラインセグメントごとのコリジョンメッシュが分割されることはありません。

その結果、スプラインの曲線と乖離することがあり、<strong>見た目とコリジョンの相違</strong>につながる場合があります。

セグメント当たりのポリゴン数が決まっているので、これを回避するにはスプラインポイントを増やすしかありません。

</td>
</tr><tr>
<td width>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/cee3e282-e023-ffae-38fc-611bfb4e6a43.png"/>
</td>
<td width>これは、コリジョンメッシュが<br>
<strong>Engine/Plugins/Experimental/Water/Content/Meshes/RiverMesh.uasset</strong><br>
というアセットに依存しているためです。このアセットには左図のようにコリジョンが設定されていて、それが使われています。

なお、このアセットはエンジンプラグイン内にあり、またプラグインのソースコードから直指定されていますので、エンジン改造をしない限り変更はできません。

（プラグインなので、プロジェクトプラグインにコピーして改造するという裏技は使えます）</td>
</tr></table>

# SplineMeshComponent → WaterBodyInfoMesh
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/951a6f34-de56-147a-23fe-e82dda65da2c.png)
まず、WaterBodyInfoMeshとは何か。最終的に表示されるメッシュではなく、Waterの基本形状（波などの影響はない状態）を定義した中間データです。

出来るだけSplineMeshComponentの形状に沿うようにポリゴン分割されたメッシュが作られます。

:::note
ポリゴン分割の処理は、
**USplineComponent::DivideSplineIntoPolylineRecursiveWithDistances()**
で行われています。

ざっとアルゴリズムを書くと、
1. スプラインセグメント間の線形補間での中間点（点A）の座標を求める
1. スプラインセグメント間のスプライン補間での中間点（点B）の座標を求める
1. 点Aと点Bの距離が一定(WaterBodyRiverの場合は5.0)以上なら、分割する

という処理を繰り返します。
:::
この処理により、スプライン形状に即したメッシュが生成され、次のWaterInfoTextureへの描画に使われます。

## WaterBodyInfoMeshのデバッグ表示について
コンソールコマンドで **r.Water.WaterInfo.ShowSceneProxies 1** と入力することで確認できるようになります。前項で貼った図では、さらに **表示フラグ＞詳細設定＞メッシュエッジ** を有効にしてWireframeも表示しています。（ライティングされていないメッシュは形状が分かりずらいので…）

:::note
**r.Water.WaterInfo.ShowSceneProxies** はUE5.3からの機能です。

UE5.2では **r.Water.VisualizeWaterInfoSceneProxies** と少し違うコンソールコマンドで実装されていて、選択されたWaterBodyのWireframeだけが表示されるようになっています。
:::

# WaterBodyInfoMesh → WaterInfoTexture
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/5f752b6f-f66c-74e7-0f93-b80285ae59c9.png)
次はWaterInfoTextureへの描画です。WaterBodyInfoMesh をRenderTextureに描画する際、Velocityや水深に関する情報など、WaterSystemにかかわる情報をまとめてテクスチャに描画します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/b485c3a3-a7f2-dc42-4a3a-3f981cf5e534.png)
ここで重要になるのがWaterZoneの設定で、RenderTargetResolutionとZoneExtentです。
<table><tr>
<td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/ae5ea416-bc0b-b850-203e-1e9e473cecc5.png"/></td>
<td>WaterZoneの位置からZoneExtentの範囲を、RenderTargetResolutionで指定したテクスチャに描画しますので、ZoneExtentの外側にWaterBodyがはみ出していると正常に描画されません。</td>
</tr><tr>
<td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/4fc31fb8-4fb2-61dd-c8a8-20a53580ab33.png"/></td>
<td>広大なZoneExtentに対してRenderTargetResolutionが十分でないと、このようにWaterが地形に対してはみ出したりします。</td>
</tr></table>
以前は、WaterZoneがワールドに一個しか置けなかったため、広大なワールドを扱う場合はメモリか解像度かのトレードオフが発生していました。

https://www.docswell.com/s/EpicGamesJapan/53P8EK-UE5_1_World%20Buliding_Core#p41

しかし、UE5.1からはWaterZoneが複数置けるようになっていますので、必要な範囲に必要な解像度を割り当てることがしやすくなっています。さすがに、WaterZoneとWaterBodyを一対一で割り当てるのは無駄ですが、エリア単位に区切ってWaterZoneを配置していくとメモリも処理負荷も軽減できそうです。

## WaterInfoTextureの確認方法
WaterInfoTextureはWaterZoneの詳細パネルにあり、一時的なテクスチャですがダブルクリックすることでテクスチャビュワーで確認が可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/403ce98c-baa8-5a69-b5f9-be26b648a235.png)
なお、各チャンネルには
* R　→　Velocity.x
* G　→　Velocity.y
* B　→　正規化された地面の高さ
* A　→　正規化された水面の高さ

が格納されています。

# WaterInfoTexture → WaterMesh(WaterZone)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/c9b8b026-8942-ceff-251f-da7757d44ed7.png)
生成したWaterInfoTextureを元に、表示に使うWaterMeshを生成します。設定の仕方は

https://docs.unrealengine.com/5.3/ja/water-meshing-system-and-surface-rendering-in-unreal-engine/#%E3%82%A6%E3%82%A9%E3%83%BC%E3%82%BF%E3%83%BC%E3%83%A1%E3%83%83%E3%82%B7%E3%83%A5%E3%81%AE%E8%A9%B3%E7%B4%B0%E5%BA%A6%E3%81%AE%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%A8%E3%83%86%E3%83%83%E3%82%BB%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3

公式ドキュメントにまとまっているのでそちらを参照してください。

## 調整方法の例
WaterMeshはテッセレーションで分割されるので、元の形状が少々分かりづらく調整がややこしく感じます。そこで、以下の対応をして形状を確認しやすくしていました。
* __r.Water.WaterMesh.ShowWireframe 1__ でWaterMeshをWireFrame表示にする
* WaterZone>レンダリング>TessellationFactorを１にする
    * Tessellationによる分割を無効にする
* Landscapeを非表示にする
    * Landscapeに埋もれている部分が見えないので

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/2bd2cac3-f33a-3526-b099-1812cbb2c1c3.png)
この結果が上図になります。この状態で基本となるTileSizeを調整し、その後ZoneExtentを更新するとExtent in Tilesが自動計算されます。

# コリジョンと表示メッシュ(WaterMesh)が一致しない場合
開発をしていると、WaterSystemのコリジョンと表示メッシュが一致しないという問題に遭遇することがあります。前段までの説明の通り、コリジョンと表示メッシュの間には中間データがいくつも挟まっており、問題の切り分けをしないとどのパラメータを調整すべきか見えてきません。

次の項からはよくある差が出るポイントと、対処方法を紹介します。

## SplineMeshComponentとコリジョンメッシュの差
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/1a5a063c-7483-3e77-5683-ae6f983d4961.png)
[SplineMeshComponent → コリジョンメッシュ](##splinemeshcomponent--コリジョンメッシュ)の項目でも紹介しましたが、コリジョンメッシュはセグメント当たり4枚の三角形で構成されています。（正確には厚みもありますが…）

そのため、スプラインの設定によっては上図のようにスプラインとコリジョンの間に大きな差が生まれることがあります。

### 調整方法
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/5fb65b1b-b555-76fc-66dc-a7b641c91fdd.png)
スプラインセグメント当たりのポリゴン数が決まっているので、スプラインポイントを増やしてやれば差を少なくすることができます。ただ、接線をいじっている場合は、増やしたスプラインポイント含めて接線の再設定をしないと同じ形状にはなりませんし、ちょっと形状を調整したい場合でもスプラインポイントが少ない状態より手間が増えます。

### WaterBodyInfoMeshとWaterMeshの差
| 解像度が足りている状態 | 解像度が不足している状態 |
|:-:|:-:|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/743891e8-e7a8-2a0d-ae9f-a23d598e306b.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/37f20546-d4b1-865f-3998-7ccbca8400a2.png) |

[WaterBodyInfoMesh → WaterInfoTexture](#waterbodyinfomesh--waterinfotexture) の項で触れましたが、WaterInfoTextureの解像度が足りないとWaterBodyInfoMeshとWaterInfoTextureの差が大きくなります。上図はちょっと極端な例ですが本来はLandscapeの溝に沿って水面が置かれるはずが、Landscapeの下に潜ってしまったり、溝からはみ出したりしてしまっています。

### 調整方法

これは単純に解像度調整をするしかありません。デフォルトでは、100.0(=1m)あたり1pixelの解像度になっているのでそれが基準になると思います。

WaterZoneの中心からZoneExtentのサイズの範囲を扱いますので、関連するWaterBodyがあるエリアの中心にWaterZoneを置いて、ZoneExtentの大きさを必要最小限にすることで適切な解像度を得やすくなります。

また、複数のWaterZoneを配置できるようになっていますので、無理に広い範囲をカバーする必要はなく、WaterZoneは適宜分割することをお勧めします。

# あとがき
今回はWaterBodyRiverをベースに説明を書きましたが、他のWaterBodyLakeやWaterBodyOceanも似たような手順でメッシュを生成しています。
WaterBodyInfoMeshの存在は意外と気づかないので、そこに気づくと意外と調整もはかどるのではないかと思います。

ただ気を付けてほしいのは、Waterプラグインはまだ **Experimental** であることです。この記事も最初UE5.2で書こうと思って資料を用意していたんですが、「やっぱり最新のUE5.3にしとくか」と思って確認したら、WaterBodyInfoMeshのデバッグ描画がごっそり変わっていました。

次のUE5.4でも、また大きく変わっているかもしれません。ただ、着実に使いやすくなっていることは確かで、そろそろベータぐらいにはなってくれないかなと思ってます。