【Unity】Physics.SyncTransforms の特性調査
- 経緯
- 検証環境
- 前知識
- 登場人物
- 検証方法
- 検証結果
- PlayerColliders x1
- PlayerColliders x2
- PlayerColliders x4
- PlayerColliders のみ (Animatorをオフ)
- PlayerColliders + AnimationColliders
- PlayerColliders + PhysicsAnimationColliders
- PlayerColliders + DisabledAnimationColliders
- PlayerColliders + DisabledAnimationColliders + CapsuleCollider
- PlayerColliders + ColliderDisabledAnimationColliders
- PlayerColliders + StaticColliders
- 注意事項
- 判明した特性
- 考察
- 検証プロジェクト
- 雑感
今回は Physics.SyncTransforms
の特性を調査したため共有します。
経緯
- Colliderの物理エンジンへのFlushを 通常のタイミングとは別で実施したい
- Flushを任意のタイミングで実行するには
Physics.SyncTransforms
をコールする必要があった Physics.SyncTransforms
を通常のFlushとは別に実施する場合にパフォーマンスなどが気になったため調査
※Flushとは実際にデータを反映する処理のこと(ここでは物理エンジン用メモリ領域への反映)
検証環境
- Unity 2020.3.4f1
- Mono
- .Net Standard 2.0
- Physics.autoSyncTransforms = false
- Oculus Quest 2 (Android)
- Development Build 有り(Profiler用)
前知識
物理オブジェクトの物理エンジンへのFlush
- ColliderのTransformが物理エンジンに反映されるタイミングはFixedUpdate後 (
Physics.autoSyncTransforms
がfalseの場合)- 厳密にはExecutionOrder でいう "Internal Physics Update" がFlushのタイミングと考えられる
- Flushタイミングは TestPhysicsSyncTiming.cs で確認
Physics.SyncTransforms
- 上記「物理オブジェクトの物理エンジンへのFlush」を任意のタイミングで発動させるメソッド
- 今回の主役
Physics.autoSyncTransforms
- ColliderのTransformが変更された際に即座に物理エンジンに反映するかの設定
- Unity 2019以降はデフォルトでfalse
- つまりデフォルトではFixedUpdate後にFlushが実施されると同義
- ProjectSettingsのPhysicsからも設定可能
Physics.autoSyncTransforms
によるとtrueにするとパフォーマンスの低下の可能性がある
When autoSyncTransforms is set to true, repeatedly changing a Transform and then performing a physics query can cause performance loss.
Physics.autoSyncTransforms = true
にする場合はオーバーヘッドに注意- 物理演算の正確性とパフォーマンスがトレードオフとなる
今回は Physics.autoSyncTransforms = false の場合をメインに調査
Flushの発生条件
- ColliderのTransformが変更された場合
- Dirtyフラグが付き、後述の「Flushの発生タイミング」で物理エンジンに反映
- こちらのUnity Pro Tips によれば移動する物理オブジェクトがない場合はオーバーヘッドはほとんどないとのこと
- つまり移動しないオブジェクトに関しては static かどうかにかかわらず Flush が発生しない
Flushの発生タイミング
Physics.autoSyncTransforms = false
であれば FixedUpdate後 orPhysics.SyncTransforms
コール時点- Dirtyフラグが付いた物理オブジェクトのみ
Physics.autoSyncTransforms = true
であればColliderのTransformが変更された時点
-- ここまでが前知識 --
登場人物
PlayerColliders
- CapsuleCollider + BoxCollider 64個 (4 x 4 x 4) をアニメーション
- LateUpdate(Animation Update後として) にて
Physics.SyncTransforms
を実施し時間を計測 - すべての検証環境で使用
AnimationColliders
- BoxColliderを大量(64000個)に配置してプレイヤー同様にアニメーション
- AnimatorのUpdate Mode は
Normal
- AnimatorのUpdate Mode は
PhysicsAnimationColliders
- AnimationColliders の Update Mode を
Animate Physics
に変更したもの
DisabledAnimationColliders
- AnimationCollidersのコライダー群のルートのGameObject(ColliderRoot)をオフにしたもの
DisabledAnimationColliders + CapsuleCollider
- DisabledAnimationCollidersのColliderRootと同階層に1つのCapsuleColliderを配置したもの
ColliderDisabledAnimationColliders
- ColliderRoot以下のすべてのColliderに対してCollider.enabled = false にしたもの
- ColliderRootオブジェクト自体はアクティブのまま
Static Colliders
- 10000個のstaticのBoxCollider群
検証方法
- 後述する「登場人物」を組み合わせた状態で
Physics.SyncTransforms
をコールして実行時間の変化を計測- コールはLateUpdate時 (Animatorが終了した後として)
検証結果
※おおよその平均
注目したい部分は色付き
検証対象 | 時間 (ms) |
---|---|
PlayerColliders x1 | 0.07 |
PlayerColliders x2 | 0.10 |
PlayerColliders x4 | 0.19 |
PlayerColliders x1 (Animator disabled) | 0.00 |
PlayerColliders + AnimationColliders | 21.01 |
PlayerColliders + PhysicsAnimationColliders | 0.07 |
PlayerColliders + DisabledAnimationColliders | 0.07 |
PlayerColliders + DisabledAnimationColliders + CapsuleCollider | 0.41 |
PlayerColliders + ColliderDisabledAnimationColliders | 0.08 |
PlayerColliders + StaticColliders | 0.07 |
クリックで展開:各調査ごとのProfiler
PlayerColliders x1
PlayerColliders x2
PlayerColliders x4
PlayerColliders のみ (Animatorをオフ)
PlayerColliders + AnimationColliders
PlayerColliders + PhysicsAnimationColliders
PlayerColliders + DisabledAnimationColliders
PlayerColliders + DisabledAnimationColliders + CapsuleCollider
PlayerColliders + ColliderDisabledAnimationColliders
PlayerColliders + StaticColliders
注意事項
- 今回は
Physics.SyncTransforms
に主眼を置くためCPU Usageはあまり考慮しない - XR環境のためCPU Usageに表示されている時間の大部分は
Gfx.WaitForRenderThread
判明した特性
Physics.SyncTransforms
の時間はコール時点のFlushされていないオブジェクト数に依存- FixedUpdate直後から
Physics.SyncTransforms
をコールするまでにTransformが変更されたオブジェクトが対象
- FixedUpdate直後から
- Flush済みのオブジェクトは
Physics.SyncTransforms
の時間には大して影響はしない- つまりTransformが変更されたオブジェクトがなければオーバーヘッドは小さい
- 通常Flushとそれ以外でFlushする対象は分けることは一応可能
- ただし「特定のオブジェクトのみFlushしたい」の様なコントロールは不可
- Unity側に機能が用意されていない(と思われる、ご存じの方がいたら教えてください!)
- ただし「特定のオブジェクトのみFlushしたい」の様なコントロールは不可
- 非アクティブなCollider(コンポーネントのenabled=false含む)はFlushの対象外
- アクティブ化した後のFixedUpdate後 or
Physics.SyncTransforms
で改めてFlush
- アクティブ化した後のFixedUpdate後 or
- 同階層にアクティブと非アクティブ両方のColliderが存在する場合は特殊なFlushとなる可能性あり(詳細は不明)
- "DisabledAnimationColliders + CapsuleCollider" の時間を見るとCapsuleCollider1つだけFlushされるにしては時間がかかっている
考察
Physics.SyncTransforms
時にFlushされるCollider数をうまくコントロールすれば、パフォーマンスへの影響をコントロールできそう- UpdateやFixedUpdateなで移動するColliderが多い場合は実行時間に注意が必要になると思われる
- あまりに多いと Physics.SyncTransforms 時スパイクが発生
検証プロジェクト
- 2022/06/19変更
- Performance Testing Extension for Unity Test Frameworkを使用した検証の自動化のためmainブランチには大幅に変更されています
- このブログ記事当初の検証プロジェクトは tag 1.0.0 をご参照ください
雑感
ちょっと個人的な事情があって検証、ついでにドキュメント化してみました。
Flush対象を明示的に指定できないのはちょっと残念ですね。。。
ただ、物理で動くものとそれ以外で分けるのは一応できそうってとこでしょうか。
また、別の機会に Physics.SyncTransforms
を利用したものを作ってみようと思います。
それでは~