【Unity】Collision Detectionのパフォーマンスを比較する実験
Sphere Colliderを使って気体分子の運動を作ってみたのですが、シーン内に存在するRigidbodyの数が多くなった時、急激に物理演算の時間が長くなり、フレームレートが驚異の1FPSまで落ちました。
パフォーマンスが落ちた原因として考えられるのが、Rigidbodyの衝突検知モード(Collision Detection)を『ContinuousDynamic』にしていたこと。
そこでオブジェクトの個数の観点から、衝突検知モードの各設定のパフォーマンスを確認してみたいなぁと。
具体的には、時間経過でオブジェクトをどんどんインスタンス化していき、特定のFPSを下回った時の個数をモード毎に比較します。
環境
macOS 10.13 High Sierra
Unity2018.1.0f2
MacbookPro Late 2016(プロセッサ : 2.9 GHz Intel Core i7)を使用してエディタ上で確認します。
衝突検知モードについて
各衝突検知モードの動作については下の記事をご覧あれ。
衝突検知モードは3つ存在します。
Descrete
離散的に衝突を検知します。FixedUpdateが呼ばれたタイミングで衝突を確認するため、そのときにColliderが重なっていないのであれば衝突したと見なされません。
進路上にオブジェクトがいるかどうかを計算しないのでパフォーマンス的には軽いです。
が、今回のケースだと壁をどんどこすり抜けそうな予感。
Continuous
このRigidbodyを持ったオブジェクトの進路上に、Rigidbodyを持っておらず、かつColliderを持つオブジェクトがあれば、FixedUpdateの間であっても連続的に衝突を検知するモード。
今回の例だと、壁との衝突までは検知してくれます。動き回る気体分子同士の衝突までは計算してくれません。
FixedUpdateが呼ばれたタイミングで運よく位置が重なっていれば離散的に衝突が発生するので、全く衝突が発生しないわけじゃないです。
ContinuousDynamic
『Continuous』のケースに加えて、相手がRigidbodyを持っていても、相手のCollision Detectionが『Continuous』または『ContinuousDynamic』であれば連続的に衝突を検知してくれるモード。
気体分子が高速で飛び回っていても、壁との衝突や気体分子同士の衝突を検知してくれます。
ただ計算量はえげつないことに。
実験方法
狭い空間内で気体分子をPrefabからインスタンス化していきます。インスタンス化のペースは0.5秒に1個です。
気体分子はインスタンス化された際ランダムに力を与えられ、空間内を飛び回ります。
この時、気体分子オブジェクトにアタッチされたRigidbodyにおいて、衝突検知モード(Collision Detection)によってパフォーマンスがどの程度変化するのかを確認します。
今回はフレームレートを基準として、以下のフレームレートを下回った時のオブジェクト数を比較。
- 30FPS : 処理時間 0.033s
- 15FPS : 処理時間 0.067s
- 10FPS : 処理時間 0.100s
- 5FPS : 処理時間 0.200s
- 1FPS : 処理時間 1.000s
処理時間の確認ですが、テラシュールブログさんにプロファイラから処理時間を取得する方法が分かりやすく書かれた記事があったので、それを参考にさせてもらいます。
物理演算の処理時間を確認するため、プロファイラから『Physics.Processing』を引っこ抜いて処理時間を確認します。
計測用スクリプト
フレーム内の処理時間を確認するため、以下のスクリプトを作成。
fpsBasesでintの配列を作り、基準となるフレームレートを並べました。その数に応じて、boolの辞書を作っています。
fpsとbool値を対応させておくとforeachで一気に確認できるので楽ちんです。毎フレームforeachが呼ばれるのはご愛嬌。
プロファイラの値を見るために、Recorderへの参照を取っています。物理演算の処理時間を確認するため、『Physics.Processing』のラベルを対象にGetしています。
ラベルに関しては、公式マニュアルの『CPU Usage プロファイラー』にあるPhysicsマーカーの項目を見てもらうといいかも。
Update()の中では、最初にフレームカウントを確認しています。というのは、ゲーム実行を開始した直後は処理が立て込んでいて、オブジェクトが生成されていないのにメッセージが出力されちゃうためです。
メッセージは基準値を下回った初回のみ出力するようにしているので、1フレーム目でメッセージが出力されて、あとは何も出ません、とかだと実験になりませんもんね。ざっくりと10フレームくらい待ってから処理時間を確認開始です。
その後に、前回のフレームの処理時間を取得し、それを秒に変換しています。この処理時間とFPSの各基準値を比較して、基準値を超えていればメッセージを出力します。
メッセージ内ではフレームカウントとオブジェクト数を出力しています。
ここで掲載したのは処理時間のカウント用スクリプトで、オブジェクトの生成用スクリプトは以下の記事に掲載しています。
実験結果
『Discrete』、『Continuous』、『ContinuousDynamic』の順番で結果を記載します。
Discreteの結果
結論から言うと、フレームレートは30FPSまで下がらず、メッセージが表示されませんでした。
パフォーマンス面でアドバンテージがあるだけのことはあります。
……が、狭い空間に閉じ込めた気体分子オブジェクトたちは、簡単に壁をすり抜けて遥か彼方へと旅立ちました。
FixedUpdateが呼ばれたタイミングで壁の厚さの範囲内にいないと衝突扱いにならないので、高速で動く気体分子はすり抜けやすいんです。
Profilerを見ると、処理時間は1.5ms程度、ピークで4.8ms程度でした。他のRenderingやUIの方が大きいくらい。物理演算の処理はやっぱり軽いです。
Continuousの結果
壁との衝突をバッチリ行ってくれるモード。
まずは処理時間が基準値を超えた時のコンソール出力です。最終的に5FPSまでしか到達しませんでした。基準値を越えると一気に処理時間が増加しています。
このまま1FPSを割るところまで行くかとも思いましたが、そこまでは行かずでした。
続いてProfilerを見ると、ピークは271ms程度。
……ピークを観測できたということは、処理時間が増加を続けるのではなく、減少したことを意味します。
画面の更新が鈍くなっても見守っていた結果、なんと処理時間が減ってきたんです。
ゲーム画面を見ると、オブジェクトのカウントは増えているのに、箱の中身が少ないような……。
と思って『Hierarchy』ウィンドウからオブジェクトのTransformを確認すると、Positionにe+10の文字が。10の10乗メートル先ってあんた。どうやら箱の中を飛び出して火星あたりまで飛んでいったようです。
箱の中のオブジェクトが減った結果、進路上にStaticなColliderが存在するオブジェクトが減少。その結果計算量が減り、最終的に処理時間が減少したみたい。
Continuousだと壁との衝突も完璧! なんて思ってましたが、負荷かかり過ぎるとすり抜けてますね。
ContinuousDynamicの結果
今回の本命。
壁との衝突も検知するし、Rigidbodyをアタッチしたオブジェクト同士の衝突も検知します。
コンソールへの出力は以下のようになりました。見事に1FPSまで到達して満足です。
30FPSを下回るまでのオブジェクト数も『Continuous』より少し早いですね。
Profilerでは、以下のようになっていました。ピークは1096msで見事に1FPSを割っています。
177個のオブジェクト毎に、進路上のStaticなColliderと、DynamicなColliderを計算しているため、地獄のような計算量が発生することに。
衝突検知の観点では一番優秀なモードですが、パフォーマンスはトレードオフです。
今回のケースでは狭い箱の中で衝突をさせていることから、過度に計算量が上がっているようにも見えます。進路上に他のオブジェクトがなければ多分計算もそんなに多くならないはず。
障害物の少ない屋外とかならここまで重くはならないと思います。
また、『Continuous』と同じようにしばらく放置してみたら、だんだんと処理時間が減ってきました。
箱の中のオブジェクトが減ってきたんだろうなーとTransformを見てみたら案の定ここではないどこかにいました。
箱の中を見れば、気体分子は13個しかいません。残りの1139個は箱の外に旅立ちました。
1FPSまで下がった後に処理時間は回復したものの、『ContinuousDynamic』の機能は失われたと言ってもいいくらい衝突を検知してませんね。
処理時間が増え始めると一気に不安定になるので、そうなる前にオブジェクトの数を減らす必要があるかもしれません。
実験結果のまとめ
表にまとめると以下のようになります。数字は、フレームレート基準値を下回った時のオブジェクト数です。
フレームレート基準値 | Discrete | Continuous | ContinuousDynamic |
30FPS | – | 195 | 168 |
15FPS | – | 196 | 169 |
10FPS | – | 198 | 169 |
5FPS | – | 225 | 171 |
1FPS | – | – | 177 |
今回のように、特に狭い空間にRigidbodyが密集していると計算量が多くなりますね。
負荷がかかり過ぎると、『Continuous』や『ContinuousDynamic』であっても壁をすり抜けていき、本来の役目を果たせていませんでした。
今回使用した大きさが1のSphereオブジェクトだと、150を超えたら危険水位、くらいの感覚でしょうか。『ContinuousDynamic』を使う場合は、オブジェクト数の管理が大事ですね。
今回のまとめ
衝突検知モードの違いによって、パフォーマンスにどのような影響が出るのか実験を行いました。
『ContinuousDynamic』はマニュアルにも書かれている通りの重い処理なので、Profilerはよく見ておかないといけないかも。
パフォーマンスと衝突検知の正確さはトレードオフ。ゲームプレイ時のストレスを考えるなら、衝突検知の正確さはある程度犠牲にするのもやむなしかなぁ。
ゲーム開発の攻略チャートを作りました!
-
前の記事
【Unity】Sphere Colliderを使って気体分子を作って遊ぶ実験 2018.05.30
-
次の記事
【Unity】物理特性マテリアルを使って摩擦を表現する実験 2018.05.31
コメントを書く