前回フォートナイトの最適化について記事を書きましたが今回はそのPart2です。前回の記事は以下から。
※英語の翻訳には自信がありませんので、それなりにミスがあると思って読んでください。大きなミスがあれば指摘していただけると嬉しいです。
Part2の動画は以下にあります。
Optimizing UE4 for Fortnite: Battle Royale - Part 2
www.youtube.com
オールプラットフォーム対応
フォートナイトはPCからコンソールゲーム、スマートフォンまで幅広く対応しており、それぞれでクロスプレイができるという前代未聞なゲームです。そのために行ったことが沢山あります。
全てのプラットフォームで同じマップを使います。全てのプラットフォームで同じコンテンツを使いながら、スケーラビリティ設定を変えていくアプローチを取りました。このアプローチの利点の1つは、モバイルで実行するための最適化がローエンドのPCと60fpsのコンソールに役立つ点です。
これはPCでHighモード時です。
これはPCでMediumモード時です。
最後にiOSのiPhone 8 Plusで実行した際ゲーム画面です。
そしてこれはPCとコンソールで実行した際のレンダリング設定です。
モバイルではこれだけの機能に絞られる、もしくは変更となりました。
スケーラビリティ設定について
コンソールやモバイルで60fpsを達成するためにスケーラビリティ設定を変えることでフレームレートやメモリーを得るようにしました。
スケーラビリティにはグループ設定が可能です。これらの設定は実行時でも変更することが可能です。各プラットフォームごとに設定をオーバーライドしています。
そしてこれらの設定を起動時にデバイスに適用することが可能なデバイスプロファイルセレクターという機能があり、実行時にデバイスの設定をiniファイルから読み込み設定してくれます。
デバイスプロファイルはベースプロファイルを継承することが可能で、iPhone Xの場合にはベースプロファイルを上書きして、Mobile用設定をこのようにオーバーライドしています。
使用メモリーもスケーラビリティ設定で切り替えています。これらはプラットフォームごとに定義しています。例えばiPhone8は2GBのメモリーを持ちますが、他のモバイル端末よりも高速です。
実際に使えるメモリー使用量は非常に限られており、iOSではGB単位辺り700MB程度のメモリーを許可しています。(2GBであれば1.4GB程度)
Androidでは非常に複雑な状況でデバイスによって、使用可能なメモリー量は行っています。Androidでは正確なメモリー使用可能量を得ることは難しいです。ここでは2GBデバイスの場合には500MBを設定し、3GBデバイスでは1.4GBを設定しました。
メモリーの設定も多く最適化を行いました。ストリーミングレベルの分割、エンジンの文字列を4バイトから2バイトへ、テスクチャーはASTCで圧縮、テクスチャーストリーミングプールの調整、プラットフォームごとのLODを設定、メッシュの低詳細モード、低メモリーデバイスでグラスとフォリッジを読み込まない、マテリアルのハイクオリティとミドルクオリティを外す、ダイナミックポイントライトの無効化、ユニークなマテリアルの削減、オーディオの事前カリング、ダウンサンプリング、圧縮クオリティの調整などなど…
コリジョンにはひとつの大きな最適化がありました。可能であればセクションごとにコリジョンを無効化して、コリジョンにも最初はLODがありませんでしたが、途中からはコリジョンにもLODを追加しました。
オーディオにも大きな最適化があります。デフォルトではオーディオキューの中で多くのバリエーション(ランダム性など)がありましたが、iOSとAndroidでは非常な単純なものへとスイッチされるように"Quality Level"ノードを使って出力しています。
プラットフォームごとに圧縮品質も調整可能です。リアルタイム解凍時の閾値を設定することもできますが、これはメモリーとパフォーマンスのトレードオフとなります。
ターゲットフレームレートについてです。30fpsであれば最もハイクオリティなビジュアルが可能でしょう。しかしながら、デバイスは熱を持つとダウンクロックします。デバイスが熱いときにプレイするのは快適ではありません。
私達がやったことは、60fpsをターゲットにし、30fpsでvsyncにしました。
プロファイリング
QAチームには毎日プレイテストを行ってもらい、プロファイルと計測情報をキャプチャーしてもらいました。ヒッチなどの最適化対象原因を特定し、1分間辺りのヒッチ回数や平均fpsを計測していました。
例えば実際にどこでヒッチが発生しているのかをプロファイリングすると、"OnLevelStreamedIn"コールバック内で多くの負荷がありました。
スレッドについて
スレッドについての話をしましょう。
ほとんどiOSデバイスは一般的に2コアのデバイスです。このグラフはiPhone 6Sでのプロファイルです。スレッドは伝統的なやり方のGameスレッドとRenderスレッドがメインとなり、ひとつだけ汎用的な非同期Taskスレッドがあります。これはほぼストリーミングのみです。
iPhone 8やiPhone Xは2つの高速コアや4つの効率的なコアを持ちます。これらには更に3つのTaskスレッドを追加しました。そこにはパラレルアニメーションや物理シミュレーション、テクスチャーストリーミングやカリングなどが処理されます。またオーディオ用に専用のスレッドも追加しました。
Androidの状況はもう少し複雑です。これはGalaxsy S8の場合ですが、2つの大きいコアと2つの小さいコアがあります。我々はOpenGLコマンドを送信するためのRHIスレッドを追加しました。OpenGLでパラレルレンダリングをサポートし、これは非常に大きな処理となるので、他の処理と重複させて処理されることができます。
CPUレンダリングコストについて
CPUに多く負荷となるのはレンダリングコストです。
シーントラバーサル処理はマップ上のメッシュのトータル数に比例して負荷が上がります。シーンのトータルサイズを削減することでレベルストリーミング時の負荷が大幅に減少しました。
次にメインボトルネックとなるのはドローコールです。コンソールでは3500ドローほどあり、PCでは4500以上であることもあります。モバイルではそれほどの余裕はありません。
iOSとAndroidでの比較です。これは純粋にレンダリングスレッドの合計時間です。ドローコールはあまり差がありませんが、CPUの処理時間は大きく異っています。iOSの場合はMetalドライバーで動作し、AndroidではOpenGLドライバーで動作しています。AndroidのCPU時間は完全に描画コマンドを送信する時間となっています。
ドローコールを減らすために距離カリングを積極的に使い、またプレイヤーの背後や気づかないものはカリングしています。また装飾オブジェクトなどゲームプレイに影響しにくいものはカットしています。オクルージョンカリングや階層付きLODを使用し、ドローコールを減らしています。
ハードウェア処理のオクルーションカリングを全てのプラットフォームで使用していますが、モバイルに最適な作業が必要です。遅延フレームを追加し、オクルージョンはフレームの終わり近くで起きます。Androidには更に追加の遅延フレームを、RHIスレッドに追加しています。
ドライバーのバグがAndroidデバイスには存在しているため、それを回避するために"Virtulize queries"という仕組みを追加し、どこでも必要な時にフレーム上でフラッシュできるようにしました。クエリーされているので、いつでも準備が整った状況で使うことができます。
階層付きLOD(HLOD)を使うことで遠景の描画を最適化しています。UE4のHLODツールだけでなく、Simplygonによる自動生成も使っています。
これはタイルタワーメッシュでHLODがない状態です。
こちらは同じタイルタワーメッシュでHLODだけの状態です。
これまでのHLODツールとは別に"被破壊HLOD"というものを追加しました。これはメッシュ低LOD版をマージし、テクスチャーアトラスを作成し、ドローコールを1つにまとめてレンダリングします。オブジェクトIDを頂点カラーに格納し、破棄されたオブジェクトを隠すために使用します。これについては別のセクションで詳しく解説しています。
最終的にリリースされたものは上記のようなものとなりました。
GPU
GPU最適化についてです。
簡単なスケーラビリティ設定、解像度、シャドウ、フォリッジ、マテリアルクオリティ、異方性フィルター、地形テッセレーションを調整しています。
バックバッファ解像度(UIなど)の調整、3D解像度とは別に特定の端末のためにUIをアップスケーリングしています。iPhone 6Sでは3D空間とは別に3つのスケールを行っています。
草と葉のスケールは距離カリングと密度を調整しています。しかし低メモリーデバイスでは完全にオフとなります。シャドウキャスティングに関しては全てのデバイスで無効化しています。
マテリアルのクオリティは"Quality Switch"で切り替えます。モバイルではLow設定のみ選択します。アーティストはシンプルなノードでかつ、頂点アニメーションシェーダーを削除してものを使います。ピクセルコストのかかるものも削減してもらいます。
動的シャドウは常にGPUとCPUに大きな影響を与えるので、ローレンジでは完全にオフにし、ミドルレンジからサポートしました。残念ながら全ての端末でそれをサポートすることはできませんでした。
ここでUE4.20におけるモバイルの改善点リストを紹介します。これらの改善には我々も非常に興奮しており、みなさんがアンリアルを使って開発する時に使えるように共有します。
コンテンツ作成
ここからはフォートナイトでコンテンツ作成を行った際にチャレンジした最適化についてです。
目標はコンソールと同じ60fpsです。最小スペックでの性能を改善し、モバイル共通での最適化です。ドローコールの削減、ポリゴン数削減、頂点&ピクセルシェーダーコストの削減、メモリーの削減などです。
コンテンツ制作戦略はエンジニアとテクニカルアーティストから提供される信頼されるレポートを使って考えます。それは整合性のあるプロファイリングレポートとビジュアルテストのスクリーンショットなどです。
メモリー使用量をObjListで確認し、メモリーを最大に使用しているものを追跡します。テクスチャー使用量もListTexturesで確認し、フロントエンドやゲームプレイ上で分割してレポートします。
キャラクターの最適化です。マテリアルの結合や未使用セクションの削除を行いました。最終的にキャラクターは、頭、身体、バックパック、武器の4つのセクションになりました。
LODは4つのキャラクターと3つの武器があります。ベイクされたポーズがあるため、顔のボーンはLODから取り除いています。またモバイルではLOD1(15000ポリゴン)を最小設定として利用します。
新しくUE4に追加されたアセットで、"LOD定義設定"というものがあり、これを使ってLODの設定を簡単に調整することが出来ます。特定LOD1から顔のボーンを削除し、LOD2からは指と捻りのボーンを削除して、長いアニメーションを最適化しています。
キャラクターのマテリアルを"Masked"から"Opaque(不透明)"へとスイッチしました。更に頂点カラーマスクを使って、頂点シェーダーの要素を隠し、テクスチャーへとベイクするようにして不透明化することができました。
ポイントライトとスポットライトはプロジェクトで無効化し、シェーダー置換機能で削減を行います。フロント画面では1つのディレクショナルライトをブループリントで制御する形にすることで、複数スポットライトの代わりとしました。
エフェクトは詳細モードを低詳細、中詳細、高詳細など明確にしていました。またモーフターゲットメッシュはエフェクトから削除しました。これらはSignificance設定によって実装されます。またカリングのバウンドは完全に固定されたものを有効化しています。
頂点ごとのディレクショナルからノンディレクショナルライティングに切り替えました。エフェクトのマテリアルは既に利用されているものと結合するスイッチを実装しました。またモバイルでは環境エフェクトは無効化しています。
複数コンポーネントを持つアクターの共通マテリアルを結合しました。マテリアルから使用していない、スタティックスイッチを削除しました。モバイルではFully rough設定をオンにしています。更にポストプロセスマテリアルではピクセルシェーダーによる、アウトラインを無効化しています。
テクスチャーの最適化では、最初に見たテキスチャーは常にストリーミングテクスチャーとして読みまれます。いくつかのUIテクスチャーではミップマップを与えて、プラットフォームによってスケールさせています。大きなテクスチャーはサイズを削減しています。類似テクスチャーは結合させています。
テクニカルアーティストがデバイスプロファイルを使ってプラットフォームごとのテクスチャーグループを設定しています。
距離ビュースケールはプラットフォームによって調整しています。ゲームプレイに必須なものはスケールアップしても消さないようにしています。
プレイヤーに影響しないような積極的にカリングします。モバイル詳細モード時には装飾オブジェクトを無効化しています。また全てのオブジェクトはブループリントとして配置されます。そうすることによって1つ辺りの値を微調整することができました。
背景のLODについてです。モバイルでは積極的に最小LODを利用し、ポリゴン数も削減しました。背景アセットでは3-4のLODを持ちます。また粒のような小さなオブジェクトには特殊な低LODを使用しました。
背景メッシュでは結合可能ないくつかメッシュを結合しました。結合の出来ないユニークなメッシュはドローコールとメモリーとのバランスをとる必要があります。
理想的にはできるだけ多くのオブジェクトを実際に作成し、大きなメッシュとして1ドローコールとして呼び出したいですが、メモリーとドローコールを抑えるという欲求のバランスと取る必要があります。
最大の最適化のひとつとしては、草の最適化が最大のインパクトがありました。まずは頂点シェーダーを最適化し、風に揺れたりプレイヤーが踏んだ時に倒れる挙動がありましたがこれらは数学的に非常に高価でした。モバイルでは頂点シェーダーはオフにしました。
次に草の密度スケールをゲームプレイに影響のない範囲で小さくしました。
プレイヤーが作成する壁にはカスタムのLODが導入されています。また頂点シェーダーによってアニメーションしますが、ハイポリゴンとなるためにリダクション作業を行いました。しかしそれによって頂点アニメーションが正しく動作しなくなりました。
そこでカスタムLODによって、テクスチャーと頂点を使ってアニメーションする代わりに大量のマテリアルを使用して不透明に切り替えています。
嵐の雲はモバイルでは再構築しています。PCではタイルのテッセレーションを使われて実装されていましたが、モバイルでは大きな不透明のドーナッツメッシュにポリゴンのセンターだけマスクしました。
ドローコールの最適化
フォートナイトにおいて最も最大の困難はドローコールの最適化でした。
町のビルは300-400の破壊可能なピースとなっています。また全てのライティングはダイナミックかつ時刻照明によりベイクが不可能です。1つのタイルタワーだけで2000ドローコールにもなるものが計15個もありました。またクロスプレイにより、積極的にカリングしたり、背後を隠すにも難しい状況でした。
そこで先ほども紹介した1ドローコールでいける破壊可能なLODを作る必要がありました。しかし各建物のピースは、シルエットに合った手作りのLODが必要でした。カスタムで編集された法線を使い、可能な限りシェーディングを事前に用意します。
それは最も楽しい仕事ではありませんでした。多くの同僚アーティストが私の元へやってきて、私に皮肉なコメントしましたが、私が代わりに「沢山のエッジを使用してもいいよ」と言うと上手くやってくれました。
これを可能にする破壊可能なHLODのセットアップセットというものを追加します。これは1つのレベルで必要なLOD設定を追加する方法で、ここで"Simplify Mesh"という設定を有効化にして第二のレベルの設定として追加されます。
マージ設定のマテリアルを同じHLOD設定アセットを使用して、マテリアルをテクスチャーアトラスとしてマージします。
全てのピースの頂点カラーをユニークな2Dインデックスとして取得し、ビル用G8フォーマットのレンダーターゲットを作成します。破壊ピースはレンダーターゲットのピースインデックスに黒のテクセルとして書き込み、レンダーターゲットの頂点カラーUVを使って頂点シェーダーでメッシュを折り畳みます。
これは新しいエンジンのコンセプトを見ることができます。建物内部の中に残っている沢山のオブジェクトはポリゴンカウントを膨らませる原因となるので除外しています。
オリジナルのメッシュは70000ポリゴンほどありましたが、HLODによる圧縮で5462ポリゴンになりました。ストリーム前のメッシュは1150ポリゴンと更に圧縮されます。
これは町全体の被破壊HLODを使う前のものです。1800ドローコールと484kポリゴンがありました。
被破壊HLODを使うと800ドローコールにまで落ち、375kポリゴンとなりました。
次は木々ですが、島全体には6000本以上の木があります。以前は標準的なLODを使用していました。ここにImpostors(詐欺師)という機能を追加しました。複数の間隔の異なる角度のレイアウトで、半八面体とフル八面体を使って遠くから見た場合と近くで見た場合に変化があります。
このテクスチャーはフリップブックアニメーションのようにも見えますが、これらの八面体のグリッドをバーチャルメッシュとして頂点シェーダーで扱い、フレームの中心をアトラステクスチャー上から全ての頂点から計算します。
カメラからの視線を正面を捉えてバーチャルグリッドとして使用して、3つのサブフレームをシームレスにブレンドすることで、ビルボードのように呼び出されることになります。
これは実際のソースメッシュと比較したものですが、うまくブレンドが働き、どの面から見てもほとんど同じように見えますが、実際は4分の1程度のポリゴンで処理されています。
HLODにもImpostorsを用意し、遠くから切り替わる際にフェードが入り、自動でシームレスに遷移するようになります。低品質のLODもより改善された品質となりました。これを使うためにはテクスチャーメモリーのまともな塊が必要で、最低でも2048程度(モバイルでは1024程度)は確保する必要があります。
最初のレベルジオメトリーは380,000ポリゴンがありましたが、これを標準のHLODを使うことで12,906まで削減。更にImpostorsを使と6000まで削減できました。
Impostorsを使った際の比較です。使う前と比べて最大で180kポリゴン相当のメモリーが削減されました。
ランドスケープがかなり重くなってきた際の最適化で、PCとコンソールでは地形のマテリアルで8のペイントレイヤー+1のマップがありました。しかし4.19のモバイルレンダーでは地形には3レイヤーしか使えないという制限がありました。これをエンジンを拡張することで4レイヤーにしてもらいましたが、それでもモバイルでは足りませんでした。
そこでとった手段は非常に単純な方法で、出来る限りのレイヤーを1つのテクスチャーとしてまとめました。ブループリントでキャプチャーするツールを作り、レイヤーをカスタムマクロで1つのテクスチャーにします。それをモバイルパスで参照しています。
更にこれらの設定は低品質PCにも適用され、全てのプラットフォームに利益をもたらしています。
これは高品質設定のPCとコンソールでの見え方です。
そしてこちらが低品質設定のPCとモバイルでの見え方となります。
最後にフェイクシャドウについてです。これは古くからあるブロブシャドウのアップデートです。シルエットをマスクした4つの角度からキャプチャーします。そのマスクをコンバートし、ディスタンスフィールドチャンネルでパックしたRGBAテクスチャーを作ります。
そして頂点シェーダーから取り出したfloat4のウェイトに時刻ライトのアングルの値を使います。このウェイトに内積を使ってRGBAテクスチャーへとシンプルに適用します。これでライトの角度によって、頂点シェーダーでメッシュのプロジェクションを調整することができます。
シンプルですが、かなり安いコストでライトの向きの変化を与えることができます。テクスチャルックアップも1つしか持っていません。
Part2終了
というわけでPart1含めて全て終わりましたが、もの凄いボリュームとなりました…
ハッキリいっていって、Epic Gamesの改めて技術の凄さを思い知りました…特にHLODの最適化はとんでもないですね…あとフェイクシャドウも安いコストながらかなり効果的です。一度動画で確認してみることをお勧めします。