【UE4】フレームレートが50FPSのプロジェクトを120FPSに改善しよう

Pocket
LinkedIn にシェア
LINEで送る

いつもお世話になっている方からこんな依頼が。

Unreal Engine 4 (4.27)でXR向けのコンテンツを作っている。内容的にはこれでいいんだけど、ユーザーがAR体験中に急に重くなったり、ハングアップする。
有識者に問い合わせたら「シェーダーが重い」からだとかなんだとか言われたが、UE4での具体的な対処方法はわからくて困っている。安定して90FPS出るように効率改善してもらえないか。
期限は2日。いける?

という相談でした。

「とりあえず1、2時間でプロジェクトをお預かりしてイケそうか見てみますす。」と回答して、調査開始。

ひとまずプロジェクトを受け取り、起動すると、そんなに複雑なコンテンツではないようです。
私のPCのUE4エディタ上では50FPSしか出ませんでした。
結果を先にいうと、半日で私のPCで120FPSが安定して出るようになり、ヘッドマウントディスプレイを装着しても違和感のある挙動は無くなせました。
何をしたか記録を残しましたので、どこか参考になりましたら。

動画版

やり方

実は基本的なことばかりです。
でも基本的な確認が大切です。

先方のワークフローは、3DSMAXでモデリングとマテリアル設定をし、DataSmithでUE4にインポートしているとのこと。

1.同じマテリアルをマージする

まずコンテンツブラウザをざっと見ると、パッと見で同じようなマテリアルが散見されました。
実際にマテリアルエディタを開いてみると全く同じでした。

ということで、同じものは1つを残してすべて削除します。
削除するときに参照を置換すればOK。

最後に、念の為「フォルダのリダイレクト」を実行して終わり。

[限定] UE4: GPU Profileを参照して、遅い原因を探す
timeline
0:00 開始
0:23 修正前のプロファイル
0:34 遅い原因は3点。(ShadowDepths, Lights, PostProcessing)
0:44 ShadowDepthsの原因、Main2.DirectionalLight_1。
1:02 Lightsの原因、Main2.DirectionalLight_1。
1:40 実験として、Main2レベルのDirectionalLightをOFFにして、再計測。
2:12 ShadowDepths, Lightsが0msに近づいた。
2:37 PostProcessingの原因、FFTBloom。14msも使用する。
3:08 PostProcessingのBloomのMethod=ConvolutionをStandardに変更して、再計測。
3:36 Bloomが殆ど0msになった。
5:32 DirectionalLightなどをStaticに変更。

GPU Profileを参照して、遅い原因を探す
現時点のプロファイラーの結果を見てみましょう
遅い原因は3点だとわかります
ShadowDepths、Lights、PostProcessing
手始めにShadowDepthsの原因を追ってみると
Main2.DirectionalLight_1が何かやらかしていることがわかります
3.57ms
続けてLightsの原因を見ると、これもまたMain2.DirectionalLight_1でした
Directional Lightを見てみると、なんとMovableでした。あーあ
試しにMain2レベルのDirectionalLightをOFFにして再計測
ShadowDepthsとLightsがかなり抑制できました
さてPostProcessingの原因ですが、FFTBloomでした
私はこのブルームの設定値を知らないのですが、こいつだけで12msもかかっています
60FPSのためには全処理を16msで済ませねばならないっていうのに、これだけで12msって!
さて設定を見てみると、編集した形跡がありました
PostProcessingのBloomのMethod=ConvolutionをStandardに変更
初期値にして、見た目が殆ど変わらないことを目視し、
いちど初期値にして再計測してみましょうか
Bloomが殆ど0msになりました
この時点での成果をみてみましょうか
カメラの位置や方向によっては100FPSオーバーになっているようです
あとはライトをひと通り見てみると、殆どMovableでした
これらを全部Staticに変更します
後日聞いてみたところ、「ベイクすると光の跡が汚くなるから」とのこと
それはライトマップ解像度を上げれば解消するので、問題ではありません
ライトマップ解像度の変更は後からやります

2.殆ど同じマテリアルはマテリアルインスタンス化する

[限定] UE4:同じマテリアルを削減する。シェーダー命令数を表示する手順
timeline
0:00 開始
3:05 Material Editorを開いて、Preview windowの[▼]を押して→[統計情報(Ctrl+L)]を押すと、[統計]ウインドウに、シェーダー命令数(Base pass shader)が表示される。

同じマテリアルを削減する。シェーダー命令数を表示する手順
マテリアルを見てみましょう
単純にアセット参照に無駄がないかを見ます
パッと見で気づきましたが、そっくりなマテリアルがたくさんあるように見えます
マテリアルエディタで確認しても、Colorもノードも全く同じでした
ということで整理しましょう
簡単で確実なのは「リファレンスを置換」をします
これで今削除したマテリアルを参照しているアセットは、
置換後のマテリアルを見るようになっています
これを繰り返していきます。一括置換ももちろんできますよ
2行目のグレーのマテリアルも怪しいですよね
次に、似ているマテリアルは統合します
例えば、ノードはBasecolorだけでRGBが異なる。ってノードが散見されますので、
こういうときはRGBAノードをパラメータ化して、マテリアルインスタンスにします
さらに、マテリアルの複雑度を見ながら、見た目がほぼ同じでノードを簡潔にしましょう
Material Editorを開いて、
Preview windowの[▼]を押して→[統計情報(Ctrl+L)]を押すと、
[統計]ウインドウに、シェーダー命令数(Base pass shader)が表示される
今このマテリアルは156ですね
無駄なノードが多いと当然値が大きくなり、
積み重なった結果が右下のコンパイルの数字です
数値はイコールじゃないのですが、関連性はあります
例えばこうやっていくと、156→144まで減ります

[限定] UE4: シェーダー命令数を削減する
timeline
0:00 開始
1:14 修正前は、Base pass shader: 141 instructionsです。
1:35 下に、利用されていないnodeを発見したので、削除。141→138。
2:24 Rougnessに繋がるnodeを見ると、結局roughness=1.0だとわかるので、Reflection_GlosinessとRoughnessを直接繋ぐ。138→133。
4:09 整理していくと、125に減った。

シェーダー命令数を削減する
修正前は、Base pass shader: 141 instructionsです
まず、下部のノード群って結果的にどこにもつながっていませんね
いらないので削除すると
改善しないんです
下に利用されていないnodeを発見したのでさらに削除すると
こいつは141から138に減ります
削減できる判断基準がちょっとわからなかったのですが
とりあえずいらんものは削除しようってことです
で、次はRoughnessにつながってるのを見てみると、
Reflection_GlosinessとRoughnessを直接繋ぐ
4を、ゼロで掛けて(=0)、
1-xして(=1)、
1を5乗したら、1やん! って
ちなみにこのマテリアルはインスタンス化していないので
じゃあこれって最初から定数1を直接繋げばいいじゃない。って話ですよね
こんなんで138が133に改善します。お買い得な発見です

こういうムダの蓄積がビジュアライゼーションのパフォーマンスを台無しにします

整理していくと、125に減りました
かなり乱暴ですが、このマテリアルに関しては112まで減らせる可能性があるってことです

[限定] UE4: ビューポートにシェーダー情報を表示する。不要なシェーダーをコンパイルしないための設定
timeline
0:00 開始
0:10 統計データ→AdvancedShaderCompilingとShadersをONにする
0:50 プロジェクト設定→Rendering→Shader Permulation Reductionを開き、「Support low quality lightmap shader」をOFFにする。理由はモバイル版向けのシェーダーコンパイル設定で、Windows版向けなら不要だから。

不要なシェーダーをコンパイルしないための設定
プロジェクト設定→Rendering→Shader Permulation Reductionを開き

Support low quality lightmap shader」をOFFにする。

これは「モバイル版向けのシェーダーコンパイル設定」で、Windows版向けなら不要だからです
シーンは限られるかもしれませんが、例えば夜のインテリアとかだったら
これらは思い切ってOFFにしてしまっても良いかもしれません
  固定スカイライトをサポート、
  待機フォグをサポート、
  Sky Atmosphereをサポート、
  高さフォグに影響を及ぼすSky Atmosphereをサポート

[限定] UE4: Lightmap resolution sizeを可視化して、適切な大きさに調整する
timeline:
0:00 開始
0:10 最適化ビューモード→ライトマップ密度を開く。
2:50 テーブルの解像度が無駄に大きいので、小さくします。128→64にすると、緑になりました。
4:50 ソファも赤色なので、対応する。

Lightmap resolution sizeを可視化して、適切な大きさに調整する
最適化ビューモード→ライトマップ密度を開く。
序盤で少し触れた「最適そうなライトマップ解像度」を可視化してくれます

赤色はメッシュに対して解像度がでかすぎることを示し、
青色はメッシュに対して解像度が少なすぎるということです
それで「ダウンライトの光があたった壁の光の跡が汚い」とのことですから
壁を見ると、青色でした。解像度が足りないんですね
試しましたが、最終的に2048にしました
光の跡の見た目が納得行く解像感にしました
あくまでも色は目安なので、最終的にはクライアント要求を満たす値にします
今回は可視化した解像度が水色のままでしたが、問題ないです
テーブルの解像度が無駄に大きいので、小さくします。128→64にすると、緑になりました
無理にやる必要はありませんが、真っ赤で数値が大きいメッシュは
再検討の対象基準になるでしょう
ソファも赤色なので、対応する

色の説明:
[解像度が小さすぎる←] blue – light blue – green – orange – red [→解像度が大きすぎる]
pink: selected mesh

補足:
・理想的な状態は、全てが緑色またはオレンジ色の状態です。
・赤色のままでも別に構いませんが、ライティングのビルド時間が無駄に長くなります。
・時間がないときは、青色だけ対処しましょう。緑色になる大きさに値を変更します。
・最低は64px程度を目安に。それでも赤色であれば、放置しても構わない。
・最大は1024または2048px程度だと思います。それでも不足ならば、スタティックメッシュを分割するのも検討するほうがいいかも。

[限定] UE4: シェーダー複雑度を減らす
timeline
0:00 開始
0:10 シェーダー複雑度をONにする。赤色はシェーダー処理が複雑です。FPSが落ちる原因です。壁と窓が赤い。
0:22 壁のマテリアルを見てみると、Translucentだった。このマテリアルは半透明が無く、透過か不透過だけなので、Maskedを選択する。
0:56 壁が緑色になり、シェーダー複雑度が減った。
1:00 植栽も赤色になっている。これも半透明は無いので、Maskedに変更。
1:02 植栽が緑色になった。

補足:
・Translucentは非常に重いので、できるだけやらないこと。
・あまり改善しないこともあるが、5-10fpsほど改善することがある

シェーダー複雑度をONにする。赤色はシェーダー処理が複雑です
FPSが落ちる原因です。壁と窓が赤い。なんで?
壁のマテリアルを見てみると…
Translucentでした。もったいない!
このマテリアルは半透明が無く、透過か不透過だけなので、Maskedを選択するべきです
壁が緑色になり、シェーダー複雑度が減りました
植栽も赤色になっています。これも基本は半透明部分は無いはずなので、
マテリアルエディタで確認してから、Maskedに変更します
植栽は葉の部分をTranslucencyにしている場合があるので注意
植栽が緑色になりました

完了

さて結果はといいますと
before=50-60 FPS.
after=120 FPS.
数値上はいい感じ。非常に安定して120FPSが出ています。

Drawsは75%、Primsは60%弱ほどの削減ができています。

あとは導入テスト。クライアントにヘッドマウントディスプレイを装着して試していただきました。
「同じ品質なのに動作に違和感がなくなった。」とのことでした。
一件落着。

コメントを残す