Let's Enjoy Unreal Engine

Unreal Engineを使って遊んでみましょう

UE4 GDC2018 フォートナイトの最適化について Part1

GDC2018の中でEpic Gamesの講演がいくつか公開されている中でも特に有意義なものを記事化してみよう、ということで今回大人気バトルロイヤルゲーム、『フォートナイトの最適化』についての講演、2本分についてを解説してみます。今回はPart1で。

※英語の翻訳には自信がありませんので、それなりにミスがあると思って読んでください。大きなミスがあれば指摘していただけると嬉しいです。

Optimizing UE4 for Fortnite: Battle Royale - Part 1
www.youtube.com


Significance Manager(重要性の管理)

フォートナイトでまず始めに解説されていた最適化がSignificance Manager(重要性の管理)です。



f:id:alwei:20180323182953p:plain

このスライドのように青が自分で最も重要な扱いとなり、赤が最も至近距離で重要な対象、それ以外は距離と視野角に合わせて重要度をどんどん下げていきます。

この重要性の高いものを優先して、描画や動作などを処理するようにしていきます。


f:id:alwei:20180323183424p:plain

コンソール機とモバイルでは処理負荷に大きく差があるため、実際に割り当てられるバケットサイズと呼ばれる優先されるべき対象の定義数も僅かながら違いがあります。

モジュラーキャラクターとアニメーション

フォートナイトは様々な衣装を来たキャラクターが登場しますので、基本構造は同じでもパーツ単位で切り替えられるようにしなくてはなりません。


f:id:alwei:20180323183819p:plain

ベーススケルトンにはジオメトリーを含まず、ここにアニメーションが適用されます。ベーススケルトン以外には頭、身体、バッグ、武器で計5つのメッシュを含み、これらにトランスフォーム情報をコピーすることで上手く動作するようになります。


f:id:alwei:20180323185305p:plain

アンリアルトーナメントの時にも同じようなものがありましたが、アンリアルトーナメントでは全てをマージしたベースメッシュにしていました。ただし、このやり方はメモリー使用量の上昇やコスチュームの切り替え機能や部位実装時の柔軟さ(骨の追加など)が失われるため、フォートナイトでは採用されないことに。


f:id:alwei:20180323185556p:plain

しかしこの方法ではアニメーション負荷の上昇なども発生するので、アニメーション自体を並列処理にし、非同期に更新させるようにしました。更新処理のうち"Eval(評価ステップ)"がただしく更新されているかをプロファイラーで確認しましょう。この更新がいくつか適切に行われないバグは既に修正済みです。


f:id:alwei:20180323185730p:plain

そしてアニメーションのイベントグラフのブループリントをネイティブのC++へと変換することで、Tick処理の負荷を一気に下げることが出来ました。


f:id:alwei:20180323190439p:plain

アニメーションの最適化にはFast Pathという機能を使って、変数へのアクセスをブループリントVMを介さずに直接更新するように行わせることが可能です。Fast Pathを使うにはブループリントのロジック上で変数の編集を行わないようにする必要があり、変数へと直接アクセスするようにしなくてはなりません。

このFast Pathについては公式ドキュメントにも詳しい解説がありますので、以下を参考にしてください。

Unreal Engine | アニメーションの最適化


f:id:alwei:20180323190958p:plain

アニメーションが処理される場合、実際には処理しなくてもいい場合があります。特に武器が持つアニメーションなどは距離に応じてオフにするべきです。フォートナイトではスクリーン上に描画されていなくて、Significanceが低いものは処理しないようになっています。またモバイルに関してはプレイヤー自身が保持している武器以外は一括で無効化しているそうです。


f:id:alwei:20180323191646p:plain

拾うことが可能な武器は通常スケルタルメッシュですが、フォートナイトではこれをスケルタルメッシュではなく、スタティックメッシュのレンダリングパスとして処理できるようにしました。これによりメモリー上に別途コピーする必要もありません。このため、あくまでもスケルタルメッシュとして利用しつつ、スタティックメッシュなのでより軽量にレンダリングできるようになっています。


f:id:alwei:20180323192104p:plain

次はUpdate Rate Optimization(URO)、つまり更新頻度の最適化です。これはSignificance Managerにより、重要性が高い順に頻度が上がるようになっており、重要性が低くなると動きがカクカクし、ブレンドなどの処理も行われなくなります。これによりアニメーション更新処理の負荷を大きく落とすことができます。実際にどの程度違うのかは動画で確認してみるとわかりやすいです。


f:id:alwei:20180323192659p:plain

物理に関しては最初は"AnimDynamics"を利用していましたが、途中からはUE4で新しく追加された軽量物理である"RigidBody"に全てスイッチされました。


f:id:alwei:20180323193059p:plain

アニメーションのバウンディングは固定されたものを使わずに、物理アセットが持つバウンディングを使って処理します。これにより複雑なアニメーションを行う場合でも、適時バウンディング外となり、アニメーション負荷を下げることが可能となります。


f:id:alwei:20180323193520p:plain

通知イベントは主にブループリントで実装されていましたが、動作速度の都合上C++へと変換しましたが、既存の通知イベントを置き換える際にAnimModifierの機能を使って一括で置換しました。更に非同期トレースAPIによって通知自体の負荷も下げつつ、Significanceが低い場合には通知自体がスキップされることになります。


f:id:alwei:20180323194456p:plain

移動可能なコンポーネントはそれだけで負荷となり、極力抑えておく必要があります。"Use Auto Manage Attachment"オプションはチェックを外し、実際に動くまでは一切の処理を行わないようにしておく必要があります。


f:id:alwei:20180323194941p:plain

オーバーラップイベントは非常に低速なものですが、このスライドのトマトの頭のように常に回転している場合、オーバーラップが常に発生していることになります。これは無駄な負荷です。オーバーラップイベントはデフォルトでオンになっています。


f:id:alwei:20180323195235p:plain

実際にオーバーラップが必要かどうかは、常に親が子が監視した状態でスキップさせるようにします。オーバーラップが必要な場合でも常にオンにはせず、必要に応じてオンにするようにすることで負荷が下がります。


f:id:alwei:20180323195726p:plain

次に大量のコンテンツの変更が生じる時、UE4はエディタースクリプトの機能を使うことで、簡単にアセットの中の内容を変更することが可能です。今後はブループリントだけではなく、Pythonもサポートすることになるので、より大くの資産が活用可能となる予定です。

キャラクターの移動

ここからはキャラクターの移動についてです。大量にキャラクターが出るゲームは常に負荷が高くなりがちですが、そのための最適化技術も色々とあります。


f:id:alwei:20180323200102p:plain

デフォルトの移動は複数のカプセルが処理されます。しかしこれは非常に重いものとなるので、そのまま使うわけにはいきません。クライアントの移動位置を事前に予測し、Significanceが低いプレイヤーは代わりに補間を使って移動を行います。これにより地面との当たり判定部分を抑制します。ちなみにモバイル上では自分以外の全てのプレイヤーが補間を使って移動します。


f:id:alwei:20180323201744p:plain

クライアントは事前に移動予測地点をサーバーに送信し、それをクライアントが受け取った際には予測位置から移動地点を補間します。ここの移動は毎フレーム行うわけではなく、低フレームレートで処理され、最小限の情報でうまく同期をとっていきます。

スポーン処理

キャラクターの移動の次によく重いと言われるのは出現処理、つまりスポーンです。フォートナイトでは多様なオブジェクトがスポーンされるので、大変重たい部分です。


f:id:alwei:20180323202754p:plain

フォートナイトでは建築&破壊可能な床、壁、階段や大量のプレイヤー、銃弾など大量のオブジェクトがスポーンされています。


f:id:alwei:20180323203607p:plain

コンポーネントはブループリントで作るよりもC++で作ったコンポーネントの方が速かったり、登録されているコンポーネントの設定を変更したり、マテリアルインスタンスのパラメーターコピーを行ったりしています。また2度使われるものはFast Pathを追加し、エンジン側の最適化によって高速化されています。これはエンジン側にも反映されます。


f:id:alwei:20180323204133p:plain

パーティクルのプーリングを行い、一度使ったエフェクトはスポーンすることなく、再度プールされたエフェクトを利用するようになります。Spawn Emitter関数にはプーリングメソッドオプションも追加されています。

Tick処理

特に重たいTick処理についてです。


f:id:alwei:20180323204535p:plain

Slate HUDは最大で2.6msほど使っており、これらを最適化で1msほど削減。まずは低速なブループリントを削除。次"invalidation panels"ウィジェットでキャッシュし、更新を最小限に。更に低速なウィジェットを最適化。しかし、まだまだSlateの最適化は足りていない状況で更なる具体案を探しています。


f:id:alwei:20180323204905p:plain

アクターのTickを計測する際にはコンソールコマンド"dumpticks"を使うことで実際に動作しているTickについてを計測することができます。


f:id:alwei:20180323205100p:plain

時間システムはマテリアルパラメーターコレクションで管理し、実際に必要な多くのコマンドをシステムが最適化し、まとめたものとして送信したものをコマンドとして毎フレーム発行します。


f:id:alwei:20180323205729p:plain

オーディオもサウンドキューを削減しするために事前評価カリング処理を行い、動作するオーディオ数を削減します。またプラットフォームごとに最大発音数を限定し、モバイルではより多くの制限があります。更にリバーブやイコライザといったエフェクトは使用していません。


f:id:alwei:20180323210238p:plain

テクスチャーストリーミングはほとんどの場合オフになっていますが、プラットフォーム固有の場合は別の働きをしますが、ほとんど見た目の違いがわからないものとなっており、これらは非常にレスポンスもよくゲームの中では全面的に使われています。

レベルストリーミング

次にレベルストリーミングについてです。フォートナイトでは特に大きな機能追加がありました。


f:id:alwei:20180323210958p:plain

マップを全体を表示し、その部分だけをロードする変更をしました。ダイナミックに周辺だけをロードしたりなどの大きな変更でメモリー使用量の削減や、描画、ゲームプレイの最適化が行われました。


f:id:alwei:20180323211629p:plain

ロードするデータの最適化を行い、IO用のワーカースレッドを立てて、デシリアライズを非同期ロードスレッドに移動させました。更にポストロード処理として、スカイダイビング時には地面は必要なく、各フレーム5ミリ秒ずつ読み込みを行います。また地面に着いた後も3ミリ秒ずつ読み込みを行います。

そしてマテリイアルインスタンスのロードが遅いことに気づいたので、マテリアル上での最適化もいくつか行いました。


f:id:alwei:20180323212613p:plain

レベルストリーミングにコンポーネントの登録が負荷になるので、スタティックメッシュアクターをインスタンスメッシュアクターとしてマージできるツールを作りました。これは非常に有用なものでエンジンの次のバージョン(4.20)で実際に追加されます。

サーバーパフォーマンス

f:id:alwei:20180323215013p:plain

サーバーは大量の処理が必要となります。レプリケートされるアクターは最大で5万、最大100接続、400のストリームレベルが20HzのTick rateで動作します。


f:id:alwei:20180323215805p:plain

ネットワーク上で関連性のあるものをノードグラフ化し、アクターリストのフィルターを作ります。これは空間グリッドだったり、ストリーミングレベルだったり、全ての関連性のあるものから作り出します。必要な情報を迅速に集めてクライアントに送信します。これはサーバーCPUの大幅な改善にもなります。


f:id:alwei:20180323220259p:plain

サーバーはDedicatedサーバーとして動作します。サーバーは以前までは各アクターごとに状態を送信していましたが、今はそれをデータの塊にしたものを直列化して送信したものを共有することになります。これによりクライアント上から共有できる情報が増えてよりキャッシュ的な使い方ができるようになります。


f:id:alwei:20180323221021p:plain

クライアントからサーバーへ移動用のRPCを送信する際、クライアントは高いFPSで動作しますが、全て送信すると負荷がとてもかかるので、なるべく送信しすぎないようにする必要があります。

サーバーは受信データが少なければ少ないほどに早く処理できますが、重要な送信は即時送る必要があります(ジャンプや屈むなど)。必要な情報とそうでない情報は累積したものをマージしてまとめて送ります。


f:id:alwei:20180323221534p:plain

マージを行う際、真っ直ぐに走っている時は左のように時間が遅くなってもマージができますが、右のように曲ってしまうと時間が遅くなると、間が消失し移動する向きも変わってしまいます。このため、サーバーは余裕がある時によりマージ作業を行っておく必要があります。


f:id:alwei:20180323222356p:plain

フォートナイトはDedicatedサーバーとして動作しているので、アニメーションは必要ありませんでした。プレイヤーコリジョンシェイプを無効化し、コスチューム用コンポーネントや見た目にしか影響しないものは全てデタッチしました。実際に使用するコリジョンシェイプは別に用意しました。

コンソール上で60fpsの実現

f:id:alwei:20180323223241p:plain

なぜ60fpsなのか?低入力遅延になりよりスムーズになる。PS4 ProやXB1Xなどで60fpsオプションモードを得ることができるようになる。PCプレイヤーはビジュアルかフレームレートのどちらを重視するべきかも選択できます。


f:id:alwei:20180323223740p:plain

最初の時点で30fpsのゲームを60fpsで動作させるためにはいくつかの課題があります。GPUは例えば解像度を動的に変化させたり、スケーリング設定を見直します。CPUについては先ほども話した通りです。


f:id:alwei:20180323224233p:plain

そこでデバイスプロファイルを使って様々な設定を見直します。この例はXboxOne用に設定したものです。これは60fpsで動作させるために設定されています。


f:id:alwei:20180323224445p:plain

こちらは30fps時のもの。


f:id:alwei:20180323224601p:plain

そしてこちらが60fps時のものです。解像度が落ちてトラックの影が消えたりディスタンスフィールドAOが消えたりしていますが、ほとんどのシーンではそのままです。

プロファイリングについて

f:id:alwei:20180323225125p:plain

フォートナイトではQAチームに実際に100プレイヤープレイテストを行ってもらい、パフォーマンスマトリクスをさくせいしており、どこでヒッチが発生しているのかをプロファイリングしています。最終的に実機上で動作させたものをグラフ化し、解析を行います。


f:id:alwei:20180323225426p:plain

具体的に何フレーム落ちているのか?ヒッチが発生している時間やヒストグラムを作成し、動的解像度の変更も導入し、ヒストグラム化して、閾値を作ってからそれを実際にShipping版としてリリースしています。

GPU最適化

フォートナイトには様々なGPU最適化が行われています。


f:id:alwei:20180323225928p:plain

これはXB1でのゲーム画面ですが、実際にGPU上でスパイクが発生した際には、一時的に解像度が落ちており、GPUの負荷が安定するまでの間は下がった状態になります。少しずつ変化を起こしているためほとんどの場合はそれに気づきません。


f:id:alwei:20180323230213p:plain

Temporalアンチエイリアシングではアップサンプリングという機能を使って動的に変化が起きます。これは2014にSIGGRAPHで発表されたものです。UBIソフトアクティビジョンでも同様の技術をプレゼンしています。


f:id:alwei:20180323230644p:plain

ミップマップのバイアスがかかっていない状態だとほとんどボヤけて見えませんが、TempralAAアップサンプリングでそのバイアスをいくつか取り戻すことができるようになります。


f:id:alwei:20180323231133p:plain

最終的にフォートナイトが60fpsで動作する各プラットフォームでのTAAU解像度と動的解像度の変更範囲は以下のようになりました。Xbox One Xは最終的に真の4K解像度レンダリングできるように現在も調整中です。


f:id:alwei:20180323231843p:plain

フォートナイトでは完全なZ prepassを利用して、様々利点がありました。EarlyZPassOnlyMaterialMaskingを有効化し、アルファテストピクセルシェーダーの計算コストをスキップしたり、非同期コンピュートSSAOが使えるようになります。


f:id:alwei:20180323232736p:plain

動的シャドウにおいてレイトレースディスタンスフィールドシャドウ(RTDF)はカスケードシャドウマップ(CSMs)よりも素晴らしい影を落とすことができるので、可能であれば利用したいですが、RTDFはCPU負荷が低くてもGPU負荷はそれなりにかかります。60fpsモード時では利用しないようにしています。


f:id:alwei:20180323233336p:plain

RTDFシャドウは60fpsでは動作しませんが、遠距離においても素晴らしい影が落とすことができています。


f:id:alwei:20180323233523p:plain

RTDFシャドウには低クオリティバージョンがUE4.20で追加されます。これまでと比べるとサブサーフェイススキャッタリングが無効化されたりしますが、これまでXboxOne上で3.25msだったものあ1.57msまで高速化されました。更に最適化した低クオリティ版であれば1.15msまで下がります。


f:id:alwei:20180323233858p:plain

ディスタンスフィールドアンビエントオクルージョンはXboxOneとPS4の60fpsモードでは無効化されています。Xbox上では3msもコストがかかってしまいました。またスクリーンスペースアンビエントオクルージョンやフォートナイトのビジュアルとも相性が悪く、屋内のインテリアのライティングでいくつか問題が起きてしまったので、私達もDFAOを最適化できないか現在も改善しようとしています。

CPU最適化

最後にCPU上でのゲームスレッドのコストの最適化についてです。今度は主にレンダリング面での最適化です。

f:id:alwei:20180323235022p:plain

階層付きLODはドローコールの削減に非常に役立っています。シンプルなメッシュを作成し、複数のマテリアルを結合させることもできます。またリダクションにはUE4のツールだけでなく、Simplygonプロキシメッシュツールも利用しています。


f:id:alwei:20180323235542p:plain

パラレルレンダリングはしばしばボトルネックになります。InitVeiwで視錐台カリングやInitialフレームセットアップがレンダリング前の作業として必要です。具体的にどのメッシュがPrepassを合格して、レンダリング用のワーカースレッドに投入されているのかが重要です。


※ここから最後にXboxPS4のCPU描画最適化の話がありますが、あまりにも専門的すぎる内容なので割愛します…

Part1終了

これでPart1が終了したのですが、正直これだけでお腹いっぱいという感じです…

Part2ではモバイルなどの最適化の話がメインとなるようです。

Part2
unrealengine.hatenablog.com