UE4のOcclusion Cullingで良く聞かれる質問1: Occlusion Culling自身の処理負荷を減らしたい
この記事は UE4 Advent Calender 2018 その2 の4日目の記事にあたります。
Occlusion Cullingは、沢山のオブジェクトがシーンに存在するときなどに効いてくる、レンダリング関連の最適化手法の一つです。デフォルトでONなので皆さん気づかないうちに使っているのですが、どのような仕組みかご存じない方はまずオフィシャルドキュメントVisibility and Occlusion Cullingを参考にしてみてください。ちょうどUE4.21から、Epic GamesのTim Hobsonさんがドキュメントをアップデートし、Occlusion Cullingも含めたUE4のCullingシステムを非常に詳細に説明してくれてます。ナイスタイミング。以下の説明では上記ドキュメントを読んだという前提で話を進めてまいります。
便利なOcclusion Cullingなのですが、ほぼ毎月、類似の質問を各所からいただきます。なので今回はそれらをまとめてシェアしたいと思います。少々長いので記事は2つに分けさせてください。
- Occlusion Culling自身の処理負荷を減らしたい (本記事)
- Occlusion Cullingによりオブジェクトが1フレーム消失することがある
では早速、Occlusion Cullingの処理負荷の削減についてです。
Occlusion Cullingの処理負荷が気になったら。。。
まずその前に。。。
Occlusion Culling自身のコストを減らす前に、そもそもOcclusion Culllingの前段階で省けるオブジェクトは省いてしまうのが最強です。その省き方について「UNREAL FEST EAST 2018」における株式会社ユークス様のご講演で非常に詳しく説明されています。シーンにオブジェクトが多いゲームではマストな内容なので是非!!!ユークス様貴重な情報共有誠にありがとうございます!!
それでもまだOcclusion Cullingのコストが高い場合、中身を調整する必要が出てくるかもしれません。まずはおさらいとしてOcclusion Cullingの利点と欠点について見ていきます。
Occlusion Cullingの利点
以下の左図の様なシーンを考えます。


Occlusion Cullingはタダではない
しかしこんなOcclusion Cullingにもコストはあります。Occlusion Cullingに投入されるオブジェクトは「他のオブジェクトに遮蔽されているかどうか」のチェックが必要です。このチェックにももちろんコストがかかります。つまり、下図の画面に見えているキューブ達には、余計にそのチェックのコストがかかっているということになります。

試しに、約6500オブジェクト全てがオクルージョンカリングに投入させるようにしてみました。

OcclusionCullingのみの処理負荷を抽出してみると、某プラットフォームでは以下のような処理負荷になりました。
CPU | 6.6ms |
---|---|
GPU | 3.0ms |
Occlusion Queries (投入されたObject) | 6561 Objects (100%) |
前フレームで見えていたStaticオブジェクトは確率的にOcclusion Cullingに投入する
非常に大きな更新がない限り前フレームで見えていたオブジェクトは今のフレームでも見えているだろう。。。というコンセプトのもと、UE4はOcclusion Cullingの処理負荷を軽減するために”前フレームで見えていたStaticオブジェクト”は確率的にオクルージョンカリングに投入します。
SceneVisibility.cpp内部 FetchVisibilityForPrimitives_Range()関数にて。。。
bRunQuery = (FractionMultiplier * Rnd) < GEngine->MaxOcclusionPixelsFraction;
この投入の確率を制御するパラメータがMaxOcclusionPixelsFractionです。Engine.iniにて記載されておりDefaultでは0.1、つまり10%になっています。(※厳密には10%ではないのですが詳細はコードを参照してください。)
このデフォルト設定によりOcclusion Cullingのコストは前に上げた例で以下の様に変化しました。
MaxOcclusionPixelsFraction | 1.0 | 0.1 |
---|---|---|
CPU | 6.6ms | 1.9ms |
GPU | 3.0ms | 0.3ms |
Occlusion Queries (投入されたObject) | 6561 Objects (100%) | 668 Objects (10.18%) |
最後に結論。。。いや注意点。
このブログの結論としましては
結論
もしもOcclusion CullingのCPU/GPU負荷が気になったら。。。MaxOcclusionPixelsFractionも気にしてみましょう!
ということなのですが、いくつか注意点がありますのでシェアします。
注意点: Occlusion Cullingはランダムで発生するので、実際にカリングされるタイミングもラグが発生します

オブジェクトの裏にいっても確率的に投入されるのでしばらくしないとOcclusion Cullingされません。その分の負荷は増えてしまいますが、全オブジェクト毎フレームチェックするよりは良い効果を出す方が遥かに多いかと思います。
注意点: Was Rencently Renderedのタイミングもずれます

Was Recently RenderedはGPUに投入された際に更新されます。つまり、Occlusion Cullingでのカリングが確率的に行われたときに、絵としては別オブジェクトに遮蔽されて見えなくなったフレームと、実際にWasRecentlyRenderedが返すフレームにずれが生じる場合があります。
注意点: この確率的投入はStatic/Stationaryなオブジェクトのみ
このOcclusion Culllingの最適化はbAllowApproximateOcclusionがtrueのオブジェクトのみで行われます。ですがこの値はデフォルトではMovableに対応していません。
PrimitiveSceneProxy.cpp内部
, bAllowApproximateOcclusion(InComponent->Mobility != EComponentMobility::Movable)
Movableなオブジェクトは動き回るので前後関係が頻繁に変わるからという理由からだと思うのですが、見えていても毎フレーム投入されるのでご注意を。。
もしもMovableでもこの仕様でOKな場合、bAllowApproximateOcclusionのチェック式を変更したり、独自で拡張してみて動作を確認して頂ければ。手元で少し試しましたがうまく動作していたと思います。
注意点: MaxOcclusionPixelsFractionの詳細。CVarじゃないこいつ。
- Engne.iniにて記載可能です。エンジンのBaseEngine.ini内部にDefault値0.1と記載されています。
- CVar化されていません!なので動的変更は不可です。 (ですが、行いたい場合は拡張して頂ければ。。実装を見ている限り問題ないはずです。)
- 1.0とすることで、全オブジェクトが毎フレームOcclusion Cullingに投入されるようになります。
- オブジェクトが専有する画面のピクセルの割合がこの値よりも大きいと、さらに低い確率でこのオブジェクトを投入しようとします。(つまり、画面を占める割合が大きいオブジェクトは直ぐにカリングされるではないだろうという算段です。。)
以上です。最後に注意点だらけで嫌な感じになっちゃったかもしれませんが、強力な最適化手法なのでシェアさせていただきました。次は、Rwiさんの「UE4初心者による公式ドキュメントの大事そうな点まとめ+α」書きます!です。
ありがとうございました!