9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Unity】SphereColliderを自前の当たり判定に置き換えた場合の処理負荷

Last updated at Posted at 2021-02-04

環境

Unity : Unity2020.2.0f1
CPU : AMD Ryzen 9 3300X 12-Core Processor (3.79GHz 12コア)

はじめに

ColliderとRigidbodyが付いているオブジェクトの衝突判定は、OnCollisionやOnTrigger系のメソッドで取ることができます。

void OnCollisionEnter(Collision col)
{
}

void OnTriggerStay(Collider col)
{
}

ここで疑問が生まれました。
UnityのColliderに頼った場合と当たり判定を自前実装した場合、どちらの方が軽いのだろう?

今回はSphereColliderの処理時間と、自前の当たり判定の処理時間を計測してみました

Unityの物理エンジン = PhysX

Unityの物理演算は PhysX という物理演算エンジンを採用しています。
公式ドキュメントを読むと、PhysXはオブジェクトの衝突時に発生する力や、接触点の座標などの計算を行っているようです。
参考 : https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/AdvancedCollisionDetection.html

検証 : SphereCollider 1万個 vs コリジョン自前実装

SphereColliderを1万置いた場合の処理時間と、自前の当たり判定の処理時間を計測してみます。

Sphere Collider 1万個

SphereColliderが付いた球オブジェクトを1万個置き、処理負荷を計測してみます。
SphereColliderの IsTriggerはONにします。
image.png

球にはOnTriggerStayを実装したMonoBehaviourコンポーネントをアタッチします。

MyObject.cs
using UnityEngine;

public class MyObject : MonoBehaviour
{
    public void OnTriggerStay(Collider other)
    {
    }
}

結果

Updateにて毎フレーム500ms程度の処理負荷が発生しました。
image.png

球の当たり判定 自前実装

次は球の当たり判定をC#で実装してみます。
球を覆うバウンディングボックス(AABB)で当たり判定を行った後、距離による判定を行います。

CollisionScene.cs
using UnityEngine;

public class CollisionScene : MonoBehaviour
{
    [SerializeField] private int TotalNumber = 1000; // 球の合計数
    [SerializeField] private MyObject prefab; // Prefab
    [SerializeField] private MyObject[] instanceArray; 
    [SerializeField] private Vector3[] objectBoundingBoxMin; // 球ごとのバウンディングボックスの最小座標
    [SerializeField] private Vector3[] objectBoundingBoxMax; // 球ごとのバウンディングボックスの最大座標
    [SerializeField] private Vector3[] objectPosition; // 位置
    private int instanceNum; // 配置したオブジェクトの数

    private void Update()
    {
        for (int i = 0; i < instanceNum; i++)
        {
            // 位置のキャッシュ
            objectPosition[i] = instanceArray[i].transform.position;
            
            // Sphereが属するバウンディングボックスを計算
            objectBoundingBoxMin[i] = objectPosition[i] - instanceArray[i].HalfBoundingBoxSize;
            objectBoundingBoxMax[i] = objectPosition[i] + instanceArray[i].HalfBoundingBoxSize;
        }
        
        // 球同士の当たり判定
        for (int i = 0; i < instanceNum; i++)
        {
            for (int j = i + 1; j < instanceNum; j++)
            {
                // まずはバウンディングボックスによる判定を行う
                if (objectBoundingBoxMax[i].x < objectBoundingBoxMin[j].x) continue;
                if (objectBoundingBoxMin[i].x > objectBoundingBoxMax[j].x) continue;
                if (objectBoundingBoxMax[i].y < objectBoundingBoxMin[j].y) continue;
                if (objectBoundingBoxMin[i].y > objectBoundingBoxMax[j].y) continue;
                if (objectBoundingBoxMax[i].z < objectBoundingBoxMin[j].z) continue;
                if (objectBoundingBoxMin[i].z > objectBoundingBoxMax[j].z) continue;
    
                // 距離の計算
                // 補足 : Vector3.sqrMagnitureは内部的にはdoubleキャストされていますが、今回は精度は重要ではないのでfloatのまま距離を計算します
                var distance = LengthSqrt(objectPosition[i] - objectPosition[j]);
                float radius = instanceArray[i].Radius + instanceArray[j].Radius;
                // 球の当たり判定
                if (distance < radius * radius)
                {
                    instanceArray[i].OnHit();
                    instanceArray[j].OnHit();
                }
            }
        }
    }
    
    float LengthSqrt(Vector3 a)
    {
        return a.x * a.x + a.y * a.y + a.z * a.z;
    }

    void Start()
    {
        int n = Mathf.FloorToInt(Mathf.Pow(TotalNumber, 1f / 3f));
        instanceNum = n * n * n;
        instanceArray = new MyObject[instanceNum];
        objectBoundingBoxMin = new Vector3[instanceNum];
        objectBoundingBoxMax = new Vector3[instanceNum];
        objectPosition = new Vector3[instanceNum];

        int i = 0;
        for (int x = 0; x < n; x++)
        {
            for (int y = 0; y < n; y++)
            {
                for (int z = 0; z < n; z++)
                {
                    var instance = Instantiate(prefab);
                    instance.transform.position = new Vector3(x, y, z) - new Vector3(n, n, n);
                    instanceArray[i++] = instance;
                }
            }
        }
        instanceArray[instanceNum - 1].Rigidbody.velocity = new Vector3(-1f, -1f, 0f);
    }
}

結果

処理時間を毎フレーム80ms程度にまで削減できました。
image.png

おまけ : バウンディングボックス(AABB)による当たり判定を省くと重くなる

何も考えずにすべての球同士で距離判定を行うと、かなり重くなります。

処理時間 : 約8000ms
image.png

    private void Update()
    {
        // 球同士の当たり判定
        for (int i = 0; i < instanceNum; i++)
        {
            for (int j = i + 1; j < instanceNum; j++)
            {

                // 距離の計算
                var sqrDistance = LengthSqr(instanceArray[i].transform.position - instanceArray[j].transform.position); 
                
                // 球の当たり判定
                float radius = instanceArray[i].Radius + instanceArray[j].Radius;
                if (sqrDistance < radius * radius)
                {
                    instanceArray[i].OnHit();
                    instanceArray[j].OnHit();
                }
            }
        }
    }

まとめ

処理時間の比較

SphereColliderを1万個おいた場合 : 処理負荷 約530 ms
球の当たり判定を自前実装した場合 : 処理負荷 約80 ms

所感

・OnCollision系、OnTrigger系メソッドは衝突時に力の計算、当たった位置などを計算しているので重い
・衝突判定を自分で実装すると必要な情報だけを計算できるので、軽くできる
(ただし、ちゃんと考えて実装しないと逆に重くなる)

関連

衝突判定
https://ja.wikipedia.org/wiki/%E8%A1%9D%E7%AA%81%E5%88%A4%E5%AE%9A

当たり判定の高速化 – AABB木の使用
http://ftvoid.com/blog/post/364

The PhysX API
https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/3.3.4/Manual/API.html

GameWorks Library > PhysX > PhysX 3.3.4
https://docs.nvidia.com/gameworks/#gameworkslibrary/physx/physx_v3_3.htm%3FTocPath%3DGameWorks%2520Library%7CPhysX%7C_____1

Rigid Body Collision
https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/3.3.4/Manual/RigidBodyCollision.html

Rigid Body Overview
https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/3.3.4/Manual/RigidBodyOverview.html

9
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?