ほげたつブログ

プログラムとアニメーションをかじって生きてる

Optimizing UE4 for Fortnite: Battle Royale - アニメーション関連まとめ

Epic Games が Fortnite を開発する際に最適化した内容について GDC 2018 で語られました。今回はアニメーションに絞って内容をまとめていきます。


www.youtube.com


MODULAR CHARACTERS (6:49~)

f:id:hogetatu:20180324220754p:plain

Fortnite のキャラクターは5つのメッシュで構成されています。まずジオメトリを持たないベーススケルトンがあり、ここにアニメーションを流したり何らかのロジックが適応されます。その上に Head, Body, Back Bling, Weapon が乗るという構成です。各部位はベーススケルトンから Transform 情報の一部をコピーしますが、物理シミュレーションや加算アニメを上乗せすることも可能です。この構成は柔軟性が高く、気軽に骨や物理シミュレーション、アニメーションを追加できるので様々な部位を作ることが可能です。


100人のキャラクターが5つのメッシュを持つので、全部で500ものメッシュがゲーム中に存在することになり、これらを解決することは非常に困難です。このとき Skeletal Mesh LOD を用いて解決する骨の数を減らすのは一般的ですが、アニメーション更新処理の場合だと通常は Worker Thread で処理されるため、最も重要である Game Thread (Main Thread) の負荷を抑えるためにはあまり役に立ちません。


※ ここから私見ですが、じゃあ Skeletal Mesh LOD を使わなくて良いかと聞かれたら答えは NO です。Worker Thread で処理されたとしてもそのタスクが長い時間を要するのであれば結果的に Game Thread を待たせることに繋がります。Worker Thread を処理するコアが無限にあれば問題ないですが、現世代機でも数個、某ハードだとコアが3つしかないため、結果的に Game, Render, Worker で 1 コアずつ使ってしまい並列処理ができません。また、Root Motion を使うと移動量を計算するために Character Movement の更新時にアニメーションの更新処理が走るため、Game Thread で処理されるということもお忘れなく。


f:id:hogetatu:20180324224956p:plain

Unreal Tournament 3 では全てのスケルトンをマージして一つにしましたが、Fortnite ではこれらをやっていません。(補足ですが、UE4 ではマージして同じスケルトンにすることで各種処理を共通化できるという利点があります。)その理由の一つがメモリ使用量の増加を防ぐため、もう一つが前述した柔軟性の高さを維持するためです。キャラクターの異なる部位毎に異なる Animation Blueprint(ロジック)を適用できるのはこの構成の良い点です。

DO WORK ASYNCS (8:21~)

f:id:hogetatu:20180324232846p:plain

まず最初にアニメーションシステムが効率的な方法で動作しているのかを確認する必要があります。UE4 のアニメーションシステムは 3 つのステップから成り立ちます。

  • Update - ゲームの状態から情報を収集する更新フェーズ
  • Evaluate - アニメーションの Decompression とブレンディング等を行う(最終ポーズを確定させる)評価フェーズ
  • Complete - Renderer へのステート送信、アタッチされた物体の Transform 更新、アニメーション通知の実行等を行う最終フェーズ


これらの内、中間に属する Evaluate ステップが Worker Thread で実行されていることをプロファイラを使って確認して下さい。それが適切に行われないバグは修正したので今後のリリースに含まれます。(バグってたんかい)


※ これ Root Motion を使用する前提の話ですかね? Root Motion を使わない場合であれば Evaluate ステップはもちろん、Update ステップも Worker で実行されるはずです。Root Motion を使う場合は前述した通り Character Movement の更新処理実行時に更新を済ませないと移動量が取得できないため、エンジン改造でもしない限りは Game Thread で Update ステップを実行する必要があります。Complete ステップは Transform 更新が発生するので、どうあがいても Game Thread で実行されます。

NATIVIZATION (8:59~)

f:id:hogetatu:20180325000731p:plain

Blueprint は非常に柔軟で、Fortnite でも全面的に使用されていますが、ネイティブコードほど高速ではありません。特に毎フレーム実行されるような処理であれば使用は避けるべきです。そして複雑なアニメーション処理はそのようなケースの一つです。Fortnite でプロファイリングを取ってみたところ、アニメーションのためにゲームプレイ情報収集等を行っていたため、キャラクター毎に 1 ms もかかっていました。そこで柔軟性を保つために多くのパラメータを Blueprint に公開しつつも、大部分のロジックを C++ に移行しました。それにより 0.93 ms もかかっていた更新処理を 0.2 ms まで短縮させることに成功しています。

FAST PATH (10:04~)

f:id:hogetatu:20180325005419p:plain

※説明のためにスライドの順番を前後させています

アニメーション高速化手法の一つに Fast Path という機能があります。Animation Graph 内で何らかのロジックを実行する場合、VMを介するので比較的低速な実行となりますが、アニメーションノード処理とプロパティアクセスに限定される場合は余計なステップを省略することで高速に実行が可能になるという機能です。また、これを補助するための機能としてアニメーション再生ノードの入力プロパティに Scale や Clamp を適応できるようになりました。


f:id:hogetatu:20180325003517p:plain

こちらが Fast Path を考慮していない Animation Graph です。Fast Path が適応されているノードは右上に稲妻のマークが付きます。このケースだと全体の中で一つしか適応されていません。


f:id:hogetatu:20180325005443p:plain

こちらが Fast Path を考慮した Animation Graph です。プロパティアクセスのみに限定され、全てのアニメーションノードに稲妻マークが付いていることが確認できます。

SKIP IT IF YOU CAN (11:04~)

f:id:hogetatu:20180325013856p:plain

しかし最も高速なのはアニメーションさせないことです。Skeletal Mesh Component の設定を切り替えることでオフスクリーン時にアニメーションさせないようにできます。また、他のキャラクターが保持する武器に関してはプレイヤーから一定の距離だけ離れると(=重要度が落ちると)アニメーションしないような設定にしています。更にモバイルでは自身以外のキャラクターが保持する全ての武器をアニメーションさせていません。ただしヘルメットやバックパック等ではこの最適化を行っていません。それらはベーススケルトンから状態をコピーしている都合上、アニメーションの更新を止めるとアタッチが正常に動作しなくなるためです。


f:id:hogetatu:20180325021019p:plain

また、Fortnite では Skeltal Mesh を Static Rendering Path で描画できるようにしました。これはフィールドに落ちている物体に使用しています。これによりアニメーションする武器でも、フィールドに落ちている時はスキニング等の余分なコストを抑えることが可能です。メモリ上に同一メッシュのコピーを持つ必要もありません。

UPDATE RATE OPTIMIZATION (12:05~)

f:id:hogetatu:20180325023248g:plain

※GIFだとわかりにくいので元動画を見て下さい

更新レートを最適化することもパフォーマンス改善に有効です。アニメーションロジックを毎フレーム実行するのではなく、数フレームごとに1度アニメーションさせて補間させることができます。これらは一定の距離だけ離れる等して重要度が落ちると適応されるようにできます。

RIGIDBODY ANIMNODE (13:09~)

f:id:hogetatu:20180325030123p:plain

物理シミュレーションには Anim Dynamics ノードを使っていましたが、Rigid Body ノードに置き換えました。単一ノードで設定でき、Anim Dynamics よりも高速で、衝突の設定も細かく可能です。

BOUNDS CALCULATION (13:39~)

f:id:hogetatu:20180325031414p:plain

固定されたバウンディングボックスは使用していません。アニメーションされたメッシュはバウンディングボックスの範囲外に出てアーティファクトを発生させる(意図していないカリング等)可能性があります。そのためキャラクターの物理アセット形状に合わせてバウンディングボックスの形状を毎フレーム再計算しています。しかし、キャラクター毎に5つのメッシュ全てでバウンディングボックスを再計算しているのではなく、各部位はベーススケルトンのバウンディングボックスを利用しています。(これは Skeletal Mesh Component の Use Parent Bounds プロパティで設定できます。Scene Component 継承であれば利用できます。)

NOTIFIES (14:07~)

f:id:hogetatu:20180325122612p:plain

足音やパーティクル等の発生イベントを実行する Anim Notify は、元々は Blueprint で実装されていました。これは非常に柔軟性がありましたが、多くのキャラクターが表示されるのでパフォーマンスが問題になります。そこで C++ での実装に切り替える際に、既にアニメーションに埋め込まれてしまった Notify は Anim Modifier というエディタスクリプトでリプレース作業を行いました。また、足音等でトレーシングする際には非同期トレースAPIを利用し、Worker Thread で非同期に行われるようにしています。更に、遠方のキャラクターはトレーシングをスキップすることも可能です。

まとめ

個人的には Anim Modifier とか使ったことなかった(何ができるのか知らなかった)ので目に鱗な感じです。アニメーション以外にも多くの最適化手法が紹介されているので、ぜひ元動画を見ることをおすすめします。