Unreal Engine 5.6のNiagara LWCタイル更新モードの実装を確認する

# 概要
UE5.6で、Niagara LWC Tile Update Modeが実装されました。World Space EmitterをLWCのタイルを超えて持ち運んでもシミュレーションを継続できるようになります。

https://dev.epicgames.com/documentation/ja-jp/unreal-engine/unreal-engine-5-6-release-notes#niagaralwc%E3%82%BF%E3%82%A4%E3%83%AB%E6%9B%B4%E6%96%B0%E3%83%A2%E3%83%BC%E3%83%89

プロジェクト設定でONにしないと有効になりません。基本良いことしかないように見えるので、なぜ無条件にONにならず、プロジェクト設定が必要なのか気になるところです。よって、実装を調べてみました。

結論から言うと、必要であればとりあえずONでOKと考えます。ONにすることによるデメリットは特になさそうであるためです。

World Space EmitterやNiagaraにおけるLWCの扱いについては、以前記事にしたのでこちらもよろしければご覧ください。

https://qiita.com/yorung/items/7c715262f5639cb0b173

# 使い方

プロジェクト設定で、 
Plugins > Niagara > Large World Coordinate Tile Update Mode
を、Rebase に設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66160/9ee33ab8-a91c-4b96-9097-563c066c3390.png)

エディタの再起動の必要はなく、即反映されます。

# 意図的にタイル更新を発生させる方法

一度Large World Coordinate Tile Update ModeをResetに戻し、タイル更新の瞬間に既存のパーティクルが消去されることを視覚的に確認してみます。

![Untitled video - Made with Clipchamp (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66160/adc29165-7d9b-4256-8879-8f390f6ec260.gif)

一瞬パーティクルが途切れるように見えます。タイル更新が発生したタイミングです。

プレイヤーキャラにNiagara Particle System Componentをつけてあります。Player Startはほぼワールド原点です。BugItGoコマンドでX座標3145700に移動し、わずかに前進します。その瞬間エミッタでワールド原点付近の初期位置と現在位置で更新基準である2タイル差を検出し、タイル更新が発生します。

また、再度Large World Coordinate Tile Update ModeをRebaseに変更し、同様の操作でパーティクルが途切れないことを確認しました。

BugItGoコマンドは以下を参考にしてください。

https://qiita.com/yorung/items/da7d12cbd6f3bd24e2a7


# 実装の確認

World Space Emitterがスポーン位置から一定数のタイルをまたいだと判断されたら、エミッターの所属タイルが更新されます。

所属タイルの更新は、以下UpdateLWCTile関数で行われます。

```C++
void FNiagaraSystemInstance::UpdateLWCTile(const FVector3f& InLWCTile, FNiagaraDataSet& EmitterDataSet)
{
	const ENiagaraLwcTileUpdateMode TileUpdateMode = System->GetLargeWorldCoordinateTileUpdateMode();
	const FVector3f TileDifference(LWCTile - InLWCTile);

	// Should we reset the simulation
	bool bResetSimulation = false;
	switch (TileUpdateMode)
	{
        ...
```

:::note info
FNiagaraSystemInstance::UpdateLWCTileにブレークポイントを置くと、所属タイル更新が発生する瞬間を捉えることができます。
:::

タイルのサイズは以下のように定義されています。

```C++
inline constexpr double UE_LWC_RENDER_TILE_SIZE = 2097152.0;
```

ある座標の所属タイルを求める関数は以下のように定義されています。

```C++
FVector3f TLargeWorldRenderScalar<TScalar>::GetTileFor(FVector InPosition)
```

GetTileForの実装によると、二番目のタイルはUE_LWC_RENDER_TILE_SIZEの半分の座標値(約10km)から開始、三番目のタイルはUE_LWC_RENDER_TILE_SIZEの1.5個分(約31km)の座標値から開始になります。

エミッターの所属タイルを更新すべきかどうかは以下の関数で判定されます。

```C++
bool UFXSystemComponent::RequiresLWCTileRecache(const FVector3f CurrentTile, const FVector CurrentLocation)
```
デフォルトでは、エミッターの所属タイルと現在位置のタイルの差が2タイル以上になったときにtrueが返ります。この値はコンソール変数 fx.LWCTileRecache で定義されており、変更可能です。

Large World Coordinate Tile Update ModeがRebaseに設定されている場合、FNiagaraSystemInstance::UpdateLWCTile関数はパーティクルのPosition型を移動タイル分オフセットします。デフォルト設定では通常UE_LWC_RENDER_TILE_SIZEの2個分である約42kmだけPosition型の値がオフセットされるはずです。

尚、Local Space Emitterの場合はタイルに属するわけではないため、Position型のオフセットは実行されません。（RebaseEmittersという関数内で分岐しています）

エミッターがGPUで実行されている場合は、Position型のオフセットはコンピュートシェーダーで行われます。(NiagaraLWCHelper.usf)

# パフォーマンス

Large World Coordinate Tile Update ModeをRebaseに変更することによるパフォーマンスへの影響は非常に限定的と考えられます。

まず、World Space Emitterが２つ先タイルまで移動しない限りは実行結果に一切差異がなく、それまで何か追加のコードが実行されることもありません。

エミッタのタイル更新が発生するケース自体、非常に稀と考えられます。その為には、最短でも約21kmをWorld Space Emitterが焚きっぱなしのままプレイヤーと一緒に移動しなければいけないからです。

その際確かにPosition型のオフセット作業が追加で実行されることにはなりますが、その負荷は気にする程ではないのではという気はします。

# まとめ

プロジェクト設定に新機能をONにするかどうかの項目が追加される場合、その理由は機能のONにより何らかの副作用が存在し、その副作用「パフォーマンスが低下する場合」、「一部互換性がなくなる場合」などに分類できるかと考えます。

Large World Coordinate Tile Update Modeの場合、どちらでもないか、敢えてどちらかに分類すると後者(VFXをタイル更新に対応させなければいけない)に見えるので、必要と思われればRebaseに設定してしまってOKかと思いました。とくにデメリットはなさそうであるためです。ただし、対象のVFXはPosition型がタイル更新により外部から値が変更される前提で設計し、意図的にタイル更新を発生させてテストするなどの対応は必要になりそうです。