環境
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にします。
球にはOnTriggerStayを実装したMonoBehaviourコンポーネントをアタッチします。
using UnityEngine;
public class MyObject : MonoBehaviour
{
public void OnTriggerStay(Collider other)
{
}
}

結果
Updateにて毎フレーム500ms程度の処理負荷が発生しました。
球の当たり判定 自前実装
次は球の当たり判定をC#で実装してみます。
球を覆うバウンディングボックス(AABB)で当たり判定を行った後、距離による判定を行います。
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);
}
}
結果
おまけ : バウンディングボックス(AABB)による当たり判定を省くと重くなる
何も考えずにすべての球同士で距離判定を行うと、かなり重くなります。
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