[UE5]アニメーション付きSkeletalMeshのStaticMesh化

# 概要
https://www.unrealengine.com/marketplace/ja/product/city-sample

UE5のCityサンプルにAnimToTextureというプラグインがあります。
> どうせアニメーション対応のImposter（書割）でも出力する機能なんだろう

と思ったら、そんなちゃちな物じゃありませんでした。

この記事では、SkeletalMeshをアニメーション付きでStaticMesh化する技術について解説します。
AnimToTextureプラグインは、この技術のワークフローの一部で使われていました。

調査したエンジンバージョンは、 **UE5.0.0** です。

# Cityサンプルでの使われ方
![CitySample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/11c1cf6e-29f8-503d-b35d-f00bb89ba528.png)
まずは、StaticMesh化したSkeletalMeshがどう使われているか見ていきます。街中で歩いているキャラクターのLODは、High→Low→StaticMeshと三段階ありその最低レベルに指定されていました。
（アセット名は、**/Game/Character/Player/Male/Meshes/SM_PlayerMale_Simplified**）
![AnimToTexture.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/01cbce02-5360-1bbb-c750-9dd80c36a89e.gif)
**SM_PlayerMale_Simplified** を開いてみると、上記動画のように人が歩いたり座ったりアニメーションをしていますが、これは **StaticMesh** です。

# アニメーション処理について
この技術で使われているアニメーションは、マテリアルですべて計算されています。
大きく分けて、BoneAnimationとVertexAnimationの二種類用意されていました。

## BoneAnimationについて
実際に計算している処理は **/AnimToTexture/Materials/ML_BoneAnimation** に格納されています。
![ML_BoneAnimation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/72dde5a6-a0a0-9fad-f914-de65f2c1728b.png)
主要部分を抜き出すとこんなマテリアル関数です。Bone毎に変形量の情報があり、最大で4つのInfluenceBoneを参照するなど普通のスキニング計算に近い構造になっています。

### InfluenceBoneとWeightの取得
まず、普通のSkeletalMeshなら頂点データに含まれているInfluenceBoneとWeightをテクスチャから取得します。
これらのデータはこの仕組みではテクスチャに格納されていて、頂点に設定されたUVで参照されています。
その結果、普通のSkeletalMeshと同様に、頂点毎に最大4つ分のInfluenceBoneとWeightが取得できます。

### PositionとRotationのアニメーションテクスチャ
次にAnimToTextureプラグインで出力された2枚のテクスチャを参照します。
このテクスチャは、U軸はBoneIndex、V軸はFrameのB8G8R8A8テクスチャになっています。

### 頂点の変位と法線を計算する
InfluenceBoneと別途求めたFrame情報を基に、PositionとRotationのテクスチャを参照して、頂点の変位をWorldPositionOffset、変形後の法線をNormalとして出力します。

WorldPositionOffsetはそもそも元の頂点からの変位を指定するので、そのまま出力します。
変形後の法線についてはWorldPositionOffsetのように変位を出力する仕組みがありませんので、マテリアルでBlendAngleCorrectedNormalノードなどを使ってNormalMapの結果と合成します。

## VertexAnimationについて
![ML_VertexAnimation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/fb893fe1-b667-520d-220b-2aadf715fbf5.png)
こちらもマテリアル関数を見てみるとこんな感じです。頂点毎のアニメーション処理になるおかげで、BoneAnimationよりはすっきりしています。
変形量は頂点毎の情報としてテクスチャに書き込まれており、Morphなどに近い処理になっています。

### PositionとNormalのアニメーションテクスチャ
AnimToTextureプラグインで出力された2枚のテクスチャを参照します。こちらはPositionとNormalの情報が格納されています。
このテクスチャは、U軸は頂点位置、V軸はFrameのB8G8R8A8テクスチャになっています。

### 頂点の変位と法線を計算する
頂点の変位を求めてWorldPositionOffsetに出力する点はBoneAnimationと同じです。しかし、法線は頂点毎に計算された変形後の法線情報がそのまま入っています。

それ以降の処理は、BoneAnimationと同じでWorldPositionOffsetはそのままマテリアルへ出力します。
法線は、BlendAngleCorrectedNormalノードでNormalMapの結果と合成しています。

## BoneAnimationとVertexAnimationの違い
+ /AnimToTexture/Characters/Mannequin/SM_Mannequin_BoneAnimation
+ /AnimToTexture/Characters/Mannequin/SM_Mannequin_VertexAnimation

AnimToTexture内にBoneAnimationとVertexAnimationを使った、比較できるアセットがあります。正直、見比べてもぱっと見は良く分かりません。
しかし注意深く見ていくと、BoneAnimationのほうが頂点や法線の変形はなめらかに感じます。ただ、BoneAnimationの方が頂点の動きにカクツキが見られる事が多いです。

計算量を考えると、Influence数にもよりますがBoneAnimationの方がコストが高いように思います。

アニメーションを出力したテクスチャの容量を比較すると、BoneAnimationは **56.4KiB** 程度、VertexAnimationは **9.3MiB** 程度でした。同じアニメーション内容ではありませんが、それにしてもVertexAnimationの方がかなり大きいです。

それぞれ、メリットデメリットがあるようなので、使いどころに応じて取捨選択することになりそうです。

# この機能のメリット、デメリット
## ◎アニメーションに関するCPUコスト減
リアルタイムのブレンドはできませんが、ベイクする際にAnimMontageを走らせることは可能なようです。
テクスチャにアニメーション情報がベイクされていますので、動かす際にCPUでアニメーション計算をする必要はなく、フレーム情報の管理だけで済みます。

その結果、人に限らず動きのあるキャラクター大量に出す場合、アニメーション計算のCPUコストがほぼゼロになるのが大きなメリットになります。

## ×精度の問題
アニメーションテクスチャは圧縮設定がVertexDisplacementmapになっているとはいえ、8bitです。
そのため、RotationやNormalはまだしもPositionについては変位(Offset)で管理していても限界がありそうです。

実際に、**SM_PlayerMale_Simplified** を見ていても、アニメーション中に震えていたりします。
大きなメッシュを動かす用途には向いていないかもしれません。

## ×ボーン数、フレーム数の限界
アニメーション情報をテクスチャに格納している都合上、テクスチャサイズの上限による制約はあります。
とはいえ、UnrealEngineでは8Kまで扱えるので、あまり気にする必要はないかもしれません。

# まとめ
デメリットをいくつか挙げましたが、使用場面を限定すれば大したデメリットではなく、アニメーション計算のCPUコストがほぼゼロになるメリットの方がかなり大きいです。

以前から、ゴキブリなどをNiagaraを使ってGPU駆動させる技術はありましたし、Houdiniなどを使って作ったVAT(VertexAnimationTexture)を再生する技術もありました。今回のアニメーション付きStaticMeshの仕組みは、これらの技術が融合して生まれた印象です。

実際に既存のSkeletalMeshを使ってアセットも作ってみたので、その解説も近いうちに記事化したいと思います。