45.2K Views
October 17, 18
スライド概要
講演動画:https://www.youtube.com/watch?v=IlSxh3edvIg
2018年10月14日に行われた「UNREAL FEST EAST 2018」における株式会社ユークス様の講演で使用されたスライドです。
●公式サイト
https://unrealengine.jp/unrealfest/
===
大人気シリーズ""地球防衛軍""の新作である""EARTH DEFENSE FORCE: IRON RAIN""をUnreal Engine 4を使用して開発中です。本セッションでは、大量のキャラクターやオブジェクトが登場するゲームを開発する際に得られた、問題点とその改善方法、制作手法に関するノウハウをお話しさせて頂きます。
Unreal Engineを開発・提供しているエピック ゲームズ ジャパンによる公式アカウントです。 勉強会や配信などで行った講演資料を公開しています。 公式サイトはこちら https://www.unrealengine.com/ja/
Unreal Engine 4 を使って地球を衛る方法 株式会社ユークス テクニカルディレクター 野津 翔太郎
はじめに ディースリー・パブリッシャー様から2019年に発売予定の、 大人気タイトル「地球防衛軍」シリーズの最新作 EARTH DEFENSE FORCE: IRON RAIN (以降、EDF:IR/本作) を開発しています。ナンバリングタイトルとは全く別の新しい世界観で、戦い 続ける兵士たちをリアリティをもって描く、もう一つのEDFとなっています。 https://www.d3p.co.jp/edfir/
はじめに
はじめに ・現在は4.19.2で開発しています ・可能な限りエンジンのアップグレードに追従することを目指しています ・アップグレードに追従しつつカスタムエンジンの保守コストを最低限にする ため、基本的にはエンジン改造しないという方針で進めています。 ・回避できないクラッシュバグや処理負荷がボトルネックになる部分、ワーク フロー的にフィーチャーの達成が困難な場合などなど、どうしても改造が必 要になるケースでのみ対応を行っています
はじめに ・UE4はバージョンアップサイクルがとても速く、色々なフィーチャーの実装 により情報量がかなり膨大になっています ・個人で調べる、頑張るぞ!ではすぐににっちもさっちもいかなくなります ・そのため、いちクリエイター個人の頑張りだけではなく、我々はチームアンリ アルとして知見を相互交換することが非常に重要だと考えています ・私もそんなに頻度は高くありませんが、こういった機会に知見を放出させて 頂いています
はじめに ・先述のように本作は大量のキャラクターやオブジェクトを登場させるために 最適化関連の対応がかなり必要なタイトルです ・とは言え、最適化をしないタイトルというのは当然ありません ・特にUE4において、最適化についてはエンジニア系の職種に限らず、チー ム全体で取り組む意識が必要です
はじめに ・UE4の無償化からはや4年が経ち、色々な情報が年々増えています ・特にここ一年は最適化に関する情報がかなり多く露出してきました ・最初に、EG/EGJ公式の資料をご紹介します
最適化資料 ■パフォーマンスおよびプロファイリング http://api.unrealengine.com/JPN/Engine/Performance/ 言わずと知れた、EGの最適化に関する公式ドキュメントです。 CPUやGPUなど、カテゴリ毎にページが更に分かれており、UE4で最適化 を行うための基礎的な情報がたくさん載っています。 情報は随時更新されているようですので、定期的にチェックしてみて下さい。
最適化資料 ■Unreal Japan Stream https://www.youtube.com/watch?v=tTBdtsnqlTA https://www.youtube.com/watch?v=oZtlHG-s6DI EGJよりほぼ月1でYoutubeに配信される大変ありがたい映像コンテンツ です。上記のURLは「UE4のプロファイリングと最適化のTips!」というこ とでUE4におけるコンテンツの最適化に踏み込んだ大変マニアックな回です。 Part 1、2とありますので、じっくりご覧ください。
最適化資料 ■Unreal Engine 4 のレンダリングフロー総おさらい https://www.slideshare.net/EpicGamesJapan/cedec2016-unrealengine-4 EGJ篠山様のスライドです。2016年のCEDECで発表された内容です。 負荷の原因となる部分を特定するための必須知識が盛りだくさんの神スラ イドです。
最適化資料 ■UE4プロファイリングツール総おさらい(グラフィクス編) https://www.slideshare.net/EpicGamesJapan/cedec2017-ue4 同じくEGJ篠山様のスライドです。2017年のCEDECで発表された内容 です。 プロファイリングの必要性とイロハについて書かれています。特にゲームコン ソールの開発に非常に役立つ神スライドです。
最適化資料 ■ UE4で多数のキャラクターを生かすためのテクニック https://www.slideshare.net/EpicGamesJapan/cedec2018-ue4111105729 EGJ鍬農様のスライドです。2018年のCEDECで発表された内容です。 キャラクターの最適化について、本当にたくさんの情報が詰まった神スライド です。 あと1年早く知りたかった情報が目白押しでした。
最適化資料 ■ UE4アニメーションシステム総おさらい https://www.slideshare.net/EpicGamesJapan/cedec2018-ue4111104578 EGJ澤田様のスライドです。こちらも、 2018年のCEDECで発表された内 容です。システムの総おさらいということですが、最適化に関する情報もふん だんに盛り込まれている神スライドです。 あと1年早く知りたかった情報が目白押しでした。
最適化資料 最後の2つの神スライドで、本講演の後半でキャラクター関連についてお話 しようと思っていた内容がほぼ全て網羅されており、8月末に大きな衝撃を 受けたことは記憶に新しいです。 大量のキャラクターをゲームに登場させる場合は必読のスライドです。
機能要件
機能要件 ・フォトリアリスティックなグラフィクス ・クローズドな箱庭空間でのゲームプレイ ・地面以外の環境オブジェクトは破壊可能 ・大量のエネミーが押し寄せる ・仲間のNPCもそこそこ登場する ・たくさんの武器や乗り物が選べる → ナンバリングタイトルの方向性を概ね踏襲!
機能要件 機能要件に加え、開発初期にディレクターから 「どのシーンを切り取ってもスクショ映えするように画面のビジュアル的な密 度は高く保ちたい!」 という要望があったことで、当初からたくさんのオブジェクトを出す必要があ るという前提で開発してきました。
略語について 下記のように定義させて頂きます。 ・Static Mesh = SM ・Skeletal Mesh = SKM ・Destructible Mesh = DM ・StaticMesh Component = SMC ・SkeletalMesh Component = SKMC ・Destructible Component = DC
プロトタイプ
プロトタイプ ・UE4を初めて使うということで、右も左もわからない状態からのスタート ・まずはWindowsにてUE4の検証をかねたプロトタイプ制作を開始 ・UE4のバージョンは4.11~4.13辺りまでを使用
プロトタイプ ■プロトタイプ要件 ・800m×800m四方のクローズドな都市部モチーフのレベル ・エンバイロンメントの密度感は製品版並にして全て破壊可能 ・エネミーは種別ごとに3種類、武器は2種類実装 ・同時に出現するエネミーは最大15体程度 ・味方NPCはなし ・本作において重要な、ゲームの基本アクションは全て実装
プロトタイプ ■プロトタイプ実装方法検討 ・UE4自体の検証段階で、色々と決めるところから開始 ・C++が使えるらしいけど、ロジックを組むのに便利なBlueprintという ビジュアルスクリプトがあるらしいので試しにそちらを使ってみよう ・建物破壊にはDMというメッシュを粉砕できる機能があるらしいので使って みよう
プロトタイプ ■プロトタイプ実装方法検討 ・建物は今後の制作の効率化を見据え、Modular Assetsで構築しよう
作ってみた
プロトタイプ ■プロトタイプ感想 ・ゲームの基本システムの実装における試行錯誤イテレーションが超早い ・All Blueprintによるゲームロジックの記述は複数人作業に向かない
プロトタイプ ■プロトタイプ感想 ・Modular Assetsは便利だが、配置するのに結構時間がかかる ・その関係もあり、建物の破壊は時間が足りず確認用の1種類しか作れな かったので、レベルにあるほとんどのActorはSM Actorになった ・エディタでレベルを開くのに数分かかるけど、こんなもん?
プロトタイプ ■プロトタイプ感想 ・エディタで動いたので更にWindowsパッケージで起動したところ、FPSは ほぼ60で安定。しかし、建物を破壊した際には45くらいまで落ちてしまう → プロトタイプとしてはまずまず? ・ゲームを開始してからステージがロードされるまでの時間は約50秒 → 少し長いかな?これから改善すれば何とかなる!
実機で動作確認 ■プロトタイプを実機で起動 ・ゲームコンソールへのデプロイにはエンジンビルドが必要なため、同じ期間 中に別途時間を取ってそちらの検証を進め、なんとかパッケージを作成 ・C++を使わずに開発していたこともあり、コンソール向けパッケージ作成に 関しては特にエラーもなく進めることができた。UE4!すごい! →よし!さっそく実機で起動や!
実機で動作確認 ■実機での起動結果 ・CPU処理負荷がめちゃくちゃ高い ・GPU処理負荷もそこそこ高い ・アセットロード時間がかなり長い ・そもそもメモリがパンクしている やばいよやばいよ
実機で動作確認 ■ 具体的には… ・CPU処理負荷 : 120 ~ 450ms ・GPU処理負荷 : 45 ~ 55ms ・ロード時間 : 246秒 ・メモリ使用量 : メモリの食い過ぎでそもそも起動しなかった → コンソールのデバッグメモリを有効にしてなんとか起動
全然成立してまへんがな!
全部やばい ・ゲームはまともに遊べない状態からのスタート ・先に紹介しましたEGJ篠山様のスライドにも繰り返し書かれていますが、 「ターゲットプラットフォームでプロファイルをせずにエディタで開発を続ける、 ダメ、ゼッタイ!」に見事当てはまる、お手本のような悪例を踏んでしまった
実機で動作確認 ・同じタイミングで砂漠調ランドスケープを使った自然物をメインにしたス テージも用意 ・こちらはなるべくコストをかけず、MarketPlaceで購入したアセットで短 時間で作成したもの ・環境破壊オブジェクトは配置せず、代わりにエネミーを最大30体配置 → 比較対象としては分かりやすそうだったので、同じく実機で確認
実機で動作確認 ■砂漠ステージ ・こちらはCPUは50msほどかかるものの、なんとロード時間3秒…。 ・配置してるものは両ステージとも9割方SM Actorだったので、大きく異な る点としてはActorの数そのもの量の差が考えられた
実機で動作確認 ■Actor数くらべ 砂漠ステージ 500 都市ステージ 96,000
実機で起動するまで ■ 96,000に膨れ上がった原因 ・Modular Assetsを採用したことにより、1つの建物辺り600~800の Actorが必要になった ・800m*800m四方のエリアに配置する建物は軽く100棟を超える ・単純計算でも 平均700/建物 * 100 = 70,000 Actors ・当然ながら、レベルに配置されるものは建物だけではない
実機で起動するまで ■ 96,000に膨れ上がった原因 ・何より正直なところ、Actor数の基準値が分からず、96,000という数字 がUE4を使用する上で一般的なものかどうかの判断がついていなかったの が最大の原因(多すぎる、とは思いつつも制作進行にストップをかけられな かった)
Modular Assetsがやばい!
実機で起動するまで ■Modular Assetsのデメリット ・Modular Assetsを採用する=採用しない場合に比べてActorが増え る、という当然の図式 ・また、Modular Assetsはパーツ1つ1つのポリゴン数はそこまで多くない ことから、LODを作成しにくく(=GPUパフォーマンスを稼ぎにくい) ・更に、LODを作成した際にもシルエットが崩れ易いという問題も
実機で起動するまで ■Modular Assetsのデメリット ・破壊可能な建物もDM ActorをModular Assetsにして作成したが、 こちらは話にならなかった ・先述のCPU処理負荷450msというのは建物破壊時の処理負荷だった ・外壁は当然のことながら、密度を上げようと建物内部にまで格子状の DM Actorを詰め込んでいたのも多くなった原因で、建物1棟当たり 2,000ものDM Actorを使用した
実機で起動するまで ■さらば、Modular Assets ・色々な理由を鑑みた結果、Modular Assetsは使用しないことにして、 建物ごとにメッシュを1つ作成するごく普通のスタイルに変更
実機で起動するまで ■ レベルの作り直し ・アーティストにお願いし、Modular AssetsをまとめたSMを建物ごとに 作成後、SM Actorを再配置して貰い、このタイミングでLODも追加 ・まずは最低限のものを出すために、建物破壊はいったん脇において考える
実機で起動するまで ■ レベルの作り直し ・結果、同じ見た目でActorは96,000 → 1,200に ・実機にて起動したところ、負荷が大幅に改善!CPUの処理負荷は30~ 45FPS程度は達成できるくらい下がり、ロード時間も40秒ほどになった ・ちゃんと作ればちゃんと動く → この時点においては、UE4でActorが多いステージは問題が多いと いうことで結論付ける
実機で起動するまで ・ここまででプロトタイプとしての目的、およびターゲットプラットフォームでの 最低限動作は達成できたので、残った課題は整理しつつ次のステップへ ・課題としては主に下記が残る格好になった - 建物破壊の実装方法を練り直し -依然として高いCPU処理負荷 -内容の割に比較的長いロード時間
プロトタイプまとめ
プロトタイプまとめ ・ターゲットプラットフォームでの検証は、開発初期で時間を取って行うべき ・後回しにすればする程原因が折り重なっていき、わけがわからなくなる ・エンジンビルドが必要とはいえ、タイトル側の対応なしにゲームコンソール 向けパッケージの作成が一撃でできるUE4はすごく頼りになる → Third Person Templateをゲームコンソールで起動するところを スタート地点に!
プロトタイプまとめ ・Actorが多いと問題っぽい ・ただし、具体的に何がマズいのかはこの時点ではわからなかった ・色々な最適化を検討する前にActorを削る方針で動いたので、もしかす ると最適化次第で96,000でも善戦できる可能性も…? ・ただ、ゲーム中の動作だけではなくレベルを開いたりレベル上のActorを操 作したりと、エディタ上の全ての挙動においてActorが多いと重くなりがち
プロトタイプまとめ ・Modular Assetsで街を作る場合、Modularのサイズは吟味すべき → 例えば建物のフロア単位でModular化すれば、Actor数も減るし 配置に際しての人的コストも減る ・DMはLODを持てないため、ジオメトリ量が常に最大値で一定になってし まい、GPUの負荷が上がりやすい
プロトタイプまとめ ・ゲームロジックを書く基底クラスは、C++で書いたほうがいい ・逆に、最終的なキャラクター等の末端のクラスはBlueprintで作成しきめ 細やかに制御できれば
製品版のクオリティを目指して
製品版のクオリティを目指して ■ 製品版のクオリティ ・プロトタイプの後は製品版クオリティを意識した工程に移行 ・その中で実装について工夫した点および発生した問題点、その改善案に ついてご紹介
建物破壊
建物破壊 ■破壊の方向性 ・改めて、本作の特徴でもある建物破壊の実装方法について検討 ・プロトタイプの反省を生かし、構成自体はシンプルな方向へ → 建物1棟につきDMのコンポーネントであるDCを1つ持つクラスを作成
建物破壊 ■そもそもDMとは? ・被破壊性メッシュと呼ばれる、メッシュの破壊表現のためのアセット ・4.17以前はエンジン標準機能として実装されていたが、現在はプラグイン 化されているのでPluginsで有効にすることで使用可能に
建物破壊 ■そもそもDMとは? ・コンテンツブラウザからSMを右クリックしてCreate Destructible Meshを実行すると作成できる ・パラメータをたくさん持っており、エディタからも 編集できるが、NVIDIA社の公式ツールもあり、そちらから作成/編集した ものをインポートすることもできる https://developer.nvidia.com/apex-destruction-physxlab-tutorials
建物破壊 ■そもそもDMとは? ・基本的な使い方として、ダメージの閾値を設定し、Apply Damageとい うダメージを与える機能を呼び出すことで、メッシュをバラバラに粉砕できる ・このことをフラクチャすると言う ・破片数が多いDMをレベル上に配置して壊す だけで結構な快感を味わえる
建物破壊 ■破壊の方向性 ・プロトタイプでわかったように、DMはLODが使用できないので、画面内に 建物がたくさん映るとBasePassに投入するジオメトリが増え過ぎて、 GPUがすぐ死ぬ → 普段はLODありのSMとして振る舞わせる必要がある
建物破壊 ■破壊の方向性 ・以上を踏まえ、 BuildingBaseというBlueprintを作成し、基本は図 のような構成に ・ゲーム開始時は全てSMCを可視状態にしておき、 被弾等で破壊フローに移行した後DCを可視状態 に変更する
建物破壊 ■ポイント1 ・ DCに最初からDMを設定しておくとコリジョン周りで色々と問題が起きる → これは、 SMCとDCを全く同じ位置に配置してあるのが原因
建物破壊 ■ポイント1 ・BuildingBaseにプロパティとしてDMを設定しておき、破壊フローに移る まではDCへはDMをあえて設定しないという状態に ・こうすることで、破壊フロー移行時にフラクチャ したDMの破片がはじけ飛ぶ不具合や、コリジ ョンプロファイルを動的に入れ替えるような面倒 な対応を回避できる
建物破壊 ■ポイント1 ・そして、破壊フロー開始時に、設定が空っぽのDMCに対し DMを設定 → ApplyDamageにてフラクチャ まで一息で行う ・更に、同時にSMCは削除してしまうことで、2つのメッシュのコリジョンが徹 頭徹尾干渉しないようにする
破壊 ■ポイント1:問題点:見た目のバリエーションが作りにくい ・この方法による問題点として、DC側でマテリアルの変更を行うことが出来 なくなる ・例えば、形状を使い回して模様が異なる建物などの表現が事前にDCに 対して設定出来ない ・もし事前にやる場合、DM自体をバリエーション分増やす必要があり、メモ リにやさしくない
破壊 ■ポイント1:改善案:見た目のバリエーションが作りにくい ・見た目のバリエーションは当然SM側とセットで行うので、破壊フロー開始 時にDCへDMを設定した後、SMのマテリアルをDC側へ設定し直すことで 解決 ・ただし、マテリアルの設定処理の負荷は決して軽くないので、マテリアル数 によってはかなり注意しなければいけない ・本作では10個くらいまでは問題ないという方針
建物破壊 ■ポイント2:問題点:アセット容量が増えがち ・DMはLOD 0のSMを元に作成することが多いが、そうすると元の多ポリ ゴンを更に分割しようとするので、ポリゴン数および容量が膨れ上がる ・本作でも最初はそのようにしており、ポリゴン数が数万~十数万程の建物 におけるLOD 0のSMをDMに変換した際、DM1つあたり25MBとか 50MBとかザラにあった
建物破壊 ■ポイント2:改善案:アセット容量が増えがち ・ポリゴン数の多さもさることながら、それ以上にメモリを食い過ぎなので、 アーティストと相談しLOD 0ではなく1~2を元にDMを作成してみた → DMの全体が見えるのは破壊フローに突入する一瞬だけであとは破片 となって飛び散るので、見た目的には案外問題ないことがわかった → メモリ容量を8MB~20MBくらいまで抑えることができ、なんとか運用 できそうなレベルになった(それでも相当重いが…)
建物破壊 ■ Destructible Troubleshooting Guide 公式トラブルシューティング。ワークフローや表現方法、各種機能について 詳しく記載されている。DMを使用する場合は必見。 https://wiki.unrealengine.com/Destructible_Troubleshooting_Guide
建物破壊 ■ Tim Hobson氏 ・DMについては、EGのTim Hobson氏がブログで詳しく色々と情報を公 開している ・色々な表現のチュートリアルがあるのでかなりオススメ ・DM以外の機能に関するチュートリアルも豊富に揃っている http://timhobsonue4.snappages.com/home.htm
建物破壊 ■プロップの破壊 ・ちなみに、プロップ破壊も同じようなフローを採用している ・複数のメッシュを保持しておき、破壊時に切り替える ・破壊後にも継続してレベル上に残ったり物理挙動をしたりとパターンは 様々だが、本質的な部分は建物と同じように実装している
処理負荷
処理負荷 ・建物破壊フィーチャーの処理負荷も考慮した実装めどが立ったので、次は 処理負荷のボトルネック探し/潰し ・ステージに立っているだけで30FPSを切るようなステージが多々あった ・NPCやエネミー、ゲームイベントが存在しない状態のプレーンな各ステー ジをウォークスルーするという最低限の状態で、処理負荷的に成立させるた めの対応に入る
Draw Thread
Draw Thread ・ステージウォークスルーでは当然ゲーム側の機能はほぼ動いていない ・その状態でボトルネックとなるのがDraw Threadの負荷ということで、 描画関連のCPU処理に時間がかかっていた
Draw Thread ・都市部モチーフのプロトタイプステージでのActor数は先述の通り 96,000から1,200まで落とした ・そして実際の制作に入り、製品版クオリティの都市部モチーフのステージで 出てきたActor数は12,000~21,000と、決して少なくなかった ・製品版の密度としてはこれ以上落とせない状態だったので、最適化にて解 決するよう進めた
Draw Thread ■ Draw Thread負荷の計測方法 ・Draw Threadの負荷はstat scenerenderingで確認する ・他にも各種数情報が取れる - Draw Call数 - 現在のDecalの描画数 - レベル上のデカール数 - レベル上のライト数 etc...メチャ便利
Draw Thread ■ InitViews ・重くなりがちなのがInitViewsの項目 ・描画関連の前処理を行っている場所
Draw Thread ■ InitViews ・InitViewsはstat initviewsで更に詳細確認可能 ・特にOcclusion Cull項目が重かった ・Occlusion Cullingで描画足切りされる ものは多かったが、その判定処理が重い せいで元も子もない結果に ・最適化前は最大8~9msにも!
Draw Thread - Culling -
Draw Thread ■ Occlusion Culling(遮蔽カリング) ・簡単に言うと「カメラ位置から見えないものを描画しない」という処理 ・本作ではカメラから見て建物の裏にある小さなプロップは概ね見えないの で、Occlusion Cullingされる ・その遮蔽判定の処理負荷をInitViewsにあるOcclusion Cull項目で 確認できる
Draw Thread ■Occlusion Culling ・どうやったらOcclusion Cullingの負荷が下がるのか? ・もっとも簡単なのは、当然Actorの数を減らすこと ・しかし、Actorを減らせばビジュアル密度が下がり要件を達成しにくくなる ・また、「全体的にものをxx個減らす」という作業はタスクとして曖昧すぎる
Draw Thread ■Occlusion Culling ・Occlusion Cullingはプロジェクト設定のRendering > Cullingに ある設定項目でOffにすることは可能 ・もちろん、そんなことをすれば当然GPU負荷が上がってしまうので、ボトル ネックの場所が変わるだけに… (これはタイトルの方針次第なので、 場合によっては効果的なケースも)
Draw Thread ■Occlusion Culling ・ではどうするか? ・Occlusion Cullingの対象物を減らせればもちろん負荷は下がる ・でも、Actorは無暗に減らしたくない ・Occlusion Cull判定に投入する前に「描画しない」ことを確定させれば 判定対象から外すことができる → Distance Cullingがある!
Draw Thread ■ Distance Culling(距離カリング) ・文字通り、距離をトリガーとするカリング ・Mesh Componentの派生クラスに 設定可能 ・Desired Max Draw Distanceと いうパラメータがあり、カメラからの距離が これをこえた場合、描画されなくなる
Draw Thread ■ Distance Culling ・Mesh Componentごとに細かく設定できるのが非常に便利 ・建物はほぼカリングしないが、プロップはかなりカリングしたいというような メッシュの性質を元に設定できる ・当然Actorごとに手作業で設定する必要はあるが、調整次第ではビジュ アルへの影響はほとんど出さずに済む
Draw Thread ■ Distance Culling ・Mesh Componentごとに設定というのが面倒な場合は、Cull Distance VolumeというActorを設置することで、 Mesh Componentのサイズごとにカリングする距離 を自動的に判定することもできる ・サイズで一律に判別することはできない場合 こちらは使えない可能性も
Draw Thread ■ Distance Culling ・本作ではサイズではなく、主にプロップの種類ごとに設定したかったので、 外部パラメータ化してしまいBeginPlay時にMesh Componentごとに 自動的に設定するようにしているためCull Distance Volumeは不採用 ・パラメータ化&自動設定処理により、そこそこの手間で大きな恩恵を受け ることが出来ている
Draw Thread ■ Distance Culling ・遠くのものを足切りで描画しないということで、 Desired Max Draw Distance近辺でメッシュが突然描画されなくなるという問題は残る ・特に見通しが良いステージでは顕著なので、ステージのビジュアル要件次 第ではあまり使えないケースもある ・得られる効果と見た目のバランスが良いので、本作では積極的に使う方 針
Draw Thread - Decal -
Decal ■Decal ・本作ではDecalにてレベルのディティールを付与し、ビジュアル的な密度を 向上している ・特に、坂道やでこぼこな路面になっている箇所に対して手軽に手を加えら れる点が強い(平面だとSMで事足りる)
Decal ■問題点 ・あるステージでは坂道が多くSMでは表現できなかったため、デカールにて ディティールを付けている ・数は7,248で、割と多め ・描画判定のために処理されるものが多くなり、例によってDraw Thread の処理負荷が上がってしまった
Decal ■問題点 ・よし!Distance Cullingで描画自体削ろう! ・しかしDecalはMesh Componentは持たないのでDistance Cullingの項目が無い ・ワラにもすがる思いで詳細パネルから設定を見てみると
Decal ■改善案 ・なんか消えてくれそうなプロパティ Fade Screen Sizeを発見 ・でもFadeってことはブレンド されるっぽいので、描画自体は 常に行われるのでは?
Decal ■改善案 ・まずは内容について ・この項目は、デカールがFadeで 消え始めるサイズを設定できる ・デフォルト設定はスクリーンサイズ の0.01(1%)となっている
Decal ■改善案 ・試しに、7, 248全てのデカールの この値を0.5(50%)のような 大きな数値にして、試してみると…
Decal ■結果 ・Draw Threadの負荷が大幅に改善! ・stat scenerenderingを叩いてみると、閾値以下になった場合は、描 画対象として計上されないことが確認できた ・更に現実的な確認として、全体の設定を0.1(10%)に変更し、再度確 認してみる
Decal ・見た目への影響は最小限で、大幅に負荷を下げることができた! ※ 左列が設定前、右列が設定後 設定前後でデカールの総量(Decals in scene)は変わらず、見えている 範囲のデカール数(Decals in view)は激減した
Decal ・50%のような大きなサイズで消え始めるのは大いにマズいが、デフォルト 値の1%で消えるというのは視覚効果的に閾値としてほぼ意味をなさない と思われる ・逆に、ちゃんと設定することでかなりの量を出せることが分かった ・デカールを多く設置する場合はこの値の調整は必須と言える
Draw Threadまとめ
Draw Threadまとめ ・UE4のデフォルト設定は「全部描画する」が基本なので、たくさんの Actorを出す場合はゲームとしてフォーカスする距離、表現を決め注力する ポイントを明確にし、描画するものをタイトル側で取捨選択する必要がある ・ただし、所感としては「思ったよりもまぁまぁ出せる」 ・最適化の方向次第では密度は相当出そうなので、最初から出さない方 向に考える必要はなさそう
GPU
GPU ・ウォークスルーにおけるボトルネックはDraw Threadに集中しており、そ ちらを改善することでGPUの負荷もそこそこ減ってくれたので、あまり集中的 にGPU向けの最適化は行っていない ・例えばDistance Cullingで描画するメッシュを足切りする → GPUに 投入するメッシュが削減されBasePassの負荷も下がる等
GPU ・しっかり意識したのは、BasePassが膨れ上がらないように建物メッシュに LODがちゃんと設定されているかどうか ・また、画面解像度に関しては r.ScreenPercentage にて下方調整し ているので、これも影響して問題が起きにくくなっている
GPU ■Dynamic Resolution ・また、4.19からはDynamic Resolutionという動的に画面解像度を 変更してくれる機能が入ったため、GPUの最適化についてはかなり優しい 状況になりつつある ■公式ドキュメント:Dynamic Resolution https://docs.unrealengine.com/enus/Engine/Rendering/DynamicResolution
Game Thread
Game Thread ・Draw Threadの問題が概ね解決したところで、各ステージのウォークス ルーで処理負荷が上がり過ぎることは無くなった ・次はゲーム自体の負荷の改善 ・まずは恒常的に走り得る処理の負荷を下げるためにTickやCollisionの 設定を徹底的に見直した
Game Thread - Tick -
Tick ・いわゆる毎フレーム処理なため つい色々な処理を書いてしまいがちだが、 まずはTickに頼らないという前提で考えるのが重要 → 必要な時のみ処理できる、イベントでの実装が好ましい ・EGJの公式Twitterでも4年も 前から言及している
Tick ・やむを得ずTickに処理を書く場合も、必要になったタイミングで最低限の 期間有効にするというルールを策定し徹底すべき ・SetActorTickEnableを使用することで かなりフレキシブルに切り替え可能
Tick ■ 本作での例:プロップ ・プロップは破壊されるまではTickは走らず、ただ描画されているだけ ・破壊された後にTickが有効になり、自分の状態を監視し始める ・Was Recently Renderedにて描画状態を監視し、描画されていな かったら自分を削除する ・それ以降は、プロップ1個分もろともTick処理が完全になくなるという仕 掛け
Tick ■ チューニング ・どうしても使用したいという場合でも、TickインターバルというTickの処理 間隔を設定する項目でできるだけ時間を空ける形での使用を検討する → デフォルト値は0.0(ゲームの更新頻度と同じ)なので、負荷は高い
Tick ■ チューニング ・レベル上でTickがアクティブなものを調べたい時は、dumpticksを使用 するとTickが有効なActorのリストを表示することができるので、これを見 ながら不必要なものは厳密に削減できるはず
Game Thread - Collision -
Collision ■Collision Complexity ・SMのコリジョンカテゴリに、コリジョンの 複雑度という設定項目がある ・コリジョンとして度の形状を使うかを 決めることができる ・SimpleとComplexがある
Simple
Complex
Collision ■Collision Complexity ・見ての通りにはなるが、Complex設定ポリゴンと同じ形をコリジョンとする ので、何かにつけて処理が重くなる ・ただ、フラグ一つで形状にぴったり合ったコリジョンが生成される(ように見 える)ため、アーティストが初期設定としてつけがち ・こちらは先にルールとコリジョン作成のワークフローを決めて、基本的には Simpleで扱いたい
Collision ■Collision Complexity ・注意点1:明示的にComplex設定を使用する場合の注意点として、物 理ボディを持てずPhysics Simulationが使用できないことが挙げられる ・物理挙動をさせたいメッシュについては、Simple設定にする必要がある ので、先ほどの設定ルールにこちらを含めておく必要がある
Collision ■Collision Complexity ・注意点1:Complex設定では物理ボディを持てない関係上、Set Mass Scale等の物理ボディの設定を行う機能を呼び出した場合にクラッ シュしてしまう(check関数に引っかかる) ・本作ではアセット量産の時期にこちらの問題が多発して各所で作業が止 まってしまったことがあった ・Complex、何卒ご注意を
Collision ■Collision Complexity ・注意点2:基本的にはSimpleで設定したいが、メッシュのマテリアルに 設定された物理マテリアルをLine Trace等のトレース系関数のヒット情報 から取得するためには、Complexコリジョンとの判定を取る必要がある ・本作では、弾が着弾したメッシュの物理マテリアルを参照して、材質により 発生させるパーティクルやSFXを変更させる必要があった
Collision ■Collision Complexity ・注意点2:これにはTrace系の関数に用意されているTrace Complex フラグを有効にした上で、実行する必要がある ・全ての弾のトレースでTrace Complexを 有効にすると、毎回ポリゴン単位での判定が 走ることになり、Traceがボトルネックとなる
Collision ■Collision Complexity ・改善案:ボトルネックを解消するために、本作では2段階でトレースを用 いて物理マテリアルを取得するようにした ・具体的には、普段のトレースは全てSimpleを対象にし、何かにぶつかっ た瞬間そこから更にもう一本、Trace Complexをオンにしたトレースを走 らせ、物理マテリアルを取得するようにした
Collision ■Collision Complexity ・改善案:これにより、普段の処理負荷は抑えつつ、着弾先の物理マテリ アルによってエフェクトが変わる、リッチな表現を得ることができた
Collision ■Generate Overlap Events ・コリジョンのオーバーラップイベントを設定するためのフラグで、Mesh Componentにチェックボックスがある ・コンポーネント同士の重なりを検知し イベントを通知してくれるが、これが 有効なコンポーネントが増えると 地味ながら徐々に重くなる
Collision ■Generate Overlap Events ・エンジニア以外の職種であまり触ることが無いはずだが、有効になっている ケースが散見される ・そこで、各メッシュアセットのデフォルト値に違和感を覚えたので改めて調べ てみると…
Collision ■Generate Overlap Events ・SM ActorのSMC OFF ・追加したSMC ON ・SKM ActorのSKMC OFF ・追加したSKMC OFF ・DM ActorのDC ON ・追加したDMC ON 何故かデフォルト値はバラバラ
Collision ■Generate Overlap Events ・SMはデフォルト値がこのような状態なので、意識してOFFにしないと知ら ぬ間に軽微な処理負荷の増加に繋がっている事も ・オーバーラップイベントがONになっているコンポーネントをキャラクターにつ けて90体動かすと、オーバーラップの更新だけで8msの処理負荷になった ケースも過去にあった ・動いているもののオーバーラップイベントをONにするのは要注意
Game Thread - Spawn -
Spawn ・ゲームプレイ中にリアルタイムに発生するSpawnによる処理負荷もバカに ならない ・本作では動的にSpawnするActorとして下記があった - Strike - Particle - Decale - SFX
例) ストームアントとの戦いにおける1トリガーあたりのSpawn A.ライフルの弾(Strike) B.マズルフラッシュ(Particle) C.排莢(Particle) D.弾発射音(SFX) E.弾の着弾エフェクト(Particle) F.ストームアントの被弾エフェクト(Particle) G.弾の被弾音(SFX) H.ストームアントの被弾音(SFX) I.ストームアントの被弾時弾痕(デカール) J.ストームアントの被弾破片エフェクト(Particle) K.ストームアントの返り血(Strike) L.ストームアントの返り血エフェクト(Particle) M.ストームアントの返り血(デカール) N.ストームアントの返り血着地音(SFX) ※ 更にこのタイミングで死亡した場合 O.ストームアントの死亡音声(SFX) P.ストームアントの死亡エフェクト(パーティクル)
おおすぎる
Spawn ・1発弾を撃つだけで色々な種類のActorのSpawnが16回 ・更にこの弾は1秒間に8発放たれる(最もベーシックなライフル) ・ものによっては、更に色んな種類のリアクションがスポーンされる ・これにより恒常的に負荷が3ms~10数msまでかかる状態になっていた ・そこで、Object Poolパターンを適用
Object Pool
Object Pool ■ Object Poolって? ・UE4独自の概念ではなく、色々な界隈のプログラミングに使われるデザイ ンパターンの1つ ・メモリアクセスの速度の関係上、オブジェクトの生成/破棄時のコストはバ カにならない ・最初にたくさんのオブジェクトを生成して保持(Pool)しておき、あとはそれ を使い回すことで生成/破棄コストをまるごと回避するという素晴らしいもの
Object Pool ■ 本作における実装について:準備 ・ObjectPoolManagerという管理するクラスとPoolableObjectとい う管理されるインターフェースクラスを用意 ・ Strike/Particle/DecalそれぞれのクラスにPoolableObjectを(多 重)継承させる ・ゲーム開始時にそれぞれのクラスからゲームに必要な最大数分Actorを 作成し、 ObjectPoolManagerで保持しておく
Object Pool ■ 本作における実装について:実装 ・ObjectPoolManagerにて、下記の関数を実装 - Use() // PoolされたActorの使用権を得る - Unuse() // 使用中Actorの使用権をPoolに戻す
Object Pool ■ 本作における実装について:実装 ・PoolableObjectを継承した各クラスにて、下記の関数をオーバーライ ドしそれぞれで必要な処理を実装 - Start() // 使用開始(Activate) - Stop() // 使用終了(Deactivate)
Object Pool
■ 簡単なイメージ:使用時
void AWeapon::Shot()
{
AStrike* pStrike = Cast<AStrike>(
UObjectPoolManager::Use<AStrike>()
); // Strikeの使用権を1つ確保する
this->AddShot(pStrike); // ゲーム処理
}
Object Pool
■ 簡単なイメージ:使用時
template<class T>
AActor* UObjectPoolManager::Use ()
{
T* pActor = Cast<T>(UseFromPool());
AddUsingList(pActor); // 使用リストに追加
pActor->Start(); // 使用開始
}
Object Pool
■ 簡単なイメージ:使用終了時
void AStrike::Tick()
{
if( !IsHit() ){ return; }
m_pOwnerWeapon->RemoveShot(this); // ゲーム処理
UObjectPoolManager::Unuse(this); // 使用権を戻す
}
Object Pool ■ 簡単なイメージ:使用終了時 void UObjectPoolManager::Unuse(PoolableObject* pObj) { pObj->Stop(); // 使用終了 RemoveFromUsingList(pObj); // 使用リストから外す }
Object Pool ■ ポイント ・PoolableObjectを継承した各クラスでは、エンジン側で勝手に DestroyされないようにあえてSuper::Destroyをコールしない空の Destroyをオーバーライドしておく bool Destroy( bool bNetForce = false, bool bShouldModifyLevel = true) override { return false; } // あえて何もしない
Object Pool
■ ポイント
・破棄が必要なタイミングがあれば、親クラスのDestroyをコールする
{
...
pStrike->AActor::Destroy();
}
・もしくはレベル終了時GCで消される
Object Pool ■ 注意点 ・Spawn/Destroy以外のコストは当然ながら削減されない ・本作で遭遇した処理負荷事例を紹介すると、パーティクルアセットを設定 するAEmitter::SetTemplate()が重いことがわかっている ・こちらについては、Unuseの際にアセット設定のみ残しておき、Useの際 に同じアセット設定のものを優先して使用するアルゴリズムを実装することで かなり軽減できた
Object Pool ■ 注意点 ・また、Spawnの負荷はどうであれActorが増えると別途何かしらの処理 負荷は当然かかるので、好き放題使用する設定にされても非常に困る ・各担当者と話をして何が重要なのかはしっかり決めて、方針を共有するの は大前提となる
Object Pool ■ 採用事例 ・Object PoolはEGのVRゲームRobo Recallでも採用されており、 EGJ岡田様のスライドにて軽く触れられています ■ Robo Recallで使われている最新のVR開発テクニックをご紹介! https://www.slideshare.net/EpicGamesJapan/robo-recallvr ・Robo Recallはソースコードも見れるようになっているので是非参照して 頂ければ
Object Pool ■ 公式機能 ・Spawn重い問題への対策は、4.20から パーティクル限定にはなりますが公式機能 としても用意されているので、今後はこの辺りも エンジン側で面倒を見てくれる可能性も
物理アセット
物理アセット ・物理アセットはUE4において非常に重要 ・物理アセットというと、いかにも物理シミュレーションに使用されそうな名前 だが、ゲームプレイ中の当たり判定コリジョンに使用したり、簡易シャドウの ソースとして使用したり、物理という言葉から直接イメージし辛い他の用途 へもガンガン使用される
物理アセット ・本作では物理アセットのボディ数がボトルネックになっていた ・ゲームプレイのコリジョンとして使用しているので、Line Trace等の判定 対象が多くなることによる問題や、キャラクターが物理シミュレーションへ移 行した際の負荷の問題等々が発生していた
物理アセット ・そこで、用途によりボディ数が異なる物理アセットを複数用意 - 当たり判定用に使用するゲームプレイコリジョン用 - 完全に物理挙動に処理を任せる際のラグドール制御用 - ある程度モーションの動きを含めつつも物理的な挙動をしてほしい 場合の物理アニメーション用
物理アセット ・これらをStrikeが被弾した衝撃で吹き飛んだ時、起き上がった時、死亡し た時というように、キャラを管理するステートが遷移する度に必要に応じて切 り替える仕組みを実装 ・これにより、一番有効な時間が長いゲームプレイ中のコリジョンはボディを 最低限にすることができ、通常時の負荷はかなり下がった
物理アセット ・死亡して物理シミュレーション状態になったエネミーがレベル上に増えてくる と、Game Thraedが130mも負荷を計上する問題が発生 ・物理アセットのボディが多いため、Physicsの計算が増え過ぎ、Tick GroupのDuaringPhysicsがなかなか終わらず、Game Threadがス トールしていた ・しかし、仕様上 死亡後のエネミーをレベル上に長時間残す必要があった ため、死亡後すぐに消すといった対応は取れなかった
物理アセット ・死亡後のエネミーは障害物としてしか扱わないので、障害物として成立す る最低限のボディ数に調整した物理アセットを用意することで、ストールを 回避することができた
物理アセット ■注意点 ・遠くの敵はコリジョン少なくてもいいのでは?ということで試しにLODレベル ごとにディティールの異なる物理アセットを用意し、LODが切り替わる度に 物理アセットも切り替わる処理を書いてみたが、こちらは切り替え処理自体 が重すぎて採用できなかった ・ボディ数が多い物理アセットを使う際は、ワールドへのボディ登録処理がボ トルネックになる可能性が非常に高いので切り替え時は要注意
物理アセット ・物理アセットは容量がそこまで大きくないので、複数持つリスクは比較的 低い ・切り替える頻度にはよるが、タイトル側の用途に応じて複数用意して切り 替える運用はかなりオススメ
Spawned Actor List
Spawned Actor List ・名前は大仰だが、やっていることはシンプルで、SpawnしたActorをタグ 付けした上でリストとしてゲームプレイ中保持しているだけ ・何のためかというと、GetAllActorsOfClassの代わりをやらせたかった
Spawned Actor List ・ただでさえ重いと悪評が高いGetAllActorsOfClassだが、本作では Actor数が多いからか1回コールするだけで8~15msもかかっていたので リアルタイムではコール出来なかった ・しかし、そういった機能が欲しいのは欲しい、特にデザイナーからの意見と してよく上がる
Spawned Actor List ・そこで、味方NPCやエネミーなどグループで取得して処理を施したいもの をSpawn時に登録しておき、そのリストからActorを探す処理を実装 ・これにより、1コール1ms以下の、リアルタイムで使える処理負荷で機能 を実装できた
まとめ
まとめ ・ターゲットプラットフォームでの実行、およびプロファイルは開発の初期から 行わないと、ダメ、ゼッタイ ・タイトルとして何を重要視するか、という点をチームでしっかり定めた上で、 取捨選択を行えるようにしておくと、判断基準が明確になる ・そして、捨てると決めたところは捨てる勇気を持つ
まとめ ・闇雲に減らしたり削ったりせず、何が問題点なのかしっかりプロファイルして、 最適化に挑む → UE4にはそれを手伝う機能が山盛り搭載されている ・しっかりとプロファイルしてボトルネックを潰すことで、大量のActorをUE4 で出すことは十分可能だと言える
まとめ ・辛く長く、そして苦しくなりがちな最適化作業だが、数msもあるような大き めのボトルネックを解消できた時の快感は何ものにも代えがたい
UE4で大量のActorを使っている事例、私も是非聞きたいです。 そういったコンテンツを開発されている方、是非意見交換しましょう!