Unity_Lesson

Boundsとは?

BoundsはUnityの構造体で、3D空間内のオブジェクトの軸に平行な境界ボックス(AABB: Axis-Aligned Bounding Box)を表します。この境界ボックスはオブジェクトの位置とサイズを基に決定され、主に以下の用途で使用されます。

プログラム例

次のプログラム例では、2つのオブジェクトのBoundsを取得し、それらが交差しているかどうかを判定します。

using UnityEngine;

public class BoundsExample : MonoBehaviour
{
    // シーン内の他のオブジェクトを参照します。
    public GameObject otherObject;

    void Start()
    {
        // このスクリプトがアタッチされたオブジェクトのColliderを取得し、Boundsを取得します。
        Bounds objectBounds = GetComponent<Collider>().bounds;

        // 他のオブジェクトのColliderを取得し、Boundsを取得します。
        Bounds otherBounds = otherObject.GetComponent<Collider>().bounds;

        // 2つのBoundsが交差しているかどうかをチェックします。
        if (objectBounds.Intersects(otherBounds))
        {
            Debug.Log("Objects are intersecting.");
        }
        else
        {
            Debug.Log("Objects are not intersecting.");
        }
    }
}

プログラムの解説

  1. Boundsの取得:
    • Boundsは、オブジェクトのColliderから取得します。Colliderは、オブジェクトの形状や物理的な性質を持つコンポーネントであり、boundsプロパティを使ってそのオブジェクトの境界ボックスを取得できます。
    Bounds objectBounds = GetComponent<Collider>().bounds;
    

    ここでGetComponent<Collider>()は、オブジェクトにアタッチされたColliderを取得し、そのboundsプロパティがBoundsを返します。

  2. Intersectsメソッド:
    • Intersectsメソッドは、2つのBoundsが重なっているかどうかを判定するためのメソッドです。これにより、2つのオブジェクトが空間内で交差しているかどうかを簡単に確認できます。
    if (objectBounds.Intersects(otherBounds))
    {
        Debug.Log("Objects are intersecting.");
    }
    

    この例では、objectBoundsotherBoundsが交差している場合に「Objects are intersecting.」というメッセージをコンソールに出力します。

  3. 可視性判定:
    • Boundsを使ってオブジェクトがカメラの視野内にあるかどうかを判定することもできます。視錐台(Frustum)内にBoundsが存在するかを確認するために、GeometryUtility.CalculateFrustumPlanesGeometryUtility.TestPlanesAABBメソッドを使用します。
    Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
    if (GeometryUtility.TestPlanesAABB(frustumPlanes, objectBounds))
    {
        Debug.Log("Object is within the camera's frustum.");
    }
    else
    {
        Debug.Log("Object is outside the camera's frustum.");
    }
    

    この例では、カメラの視錐台(frustum)を表す平面(Plane)の配列を計算し、それに対してBoundsが含まれているかをチェックしています。

まとめ

これらの機能は、3Dゲームやシミュレーションにおいて、効率的な衝突判定や可視性チェックを行うために非常に重要です。



視錐台

カメラの視錐台内にオブジェクトが含まれているかをチェックし、その結果に基づいて描画するかどうかを決定するプログラムの例を見てみます。


プログラム例

using UnityEngine;

public class FrustumCullingExample : MonoBehaviour
{
    public GameObject[] objectsToRender; // チェック対象のオブジェクトリスト

    void Update()
    {
        // カメラの視錐台を計算
        Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(Camera.main);

        // 全てのオブジェクトに対して処理を行う
        foreach (GameObject obj in objectsToRender)
        {
            // 各オブジェクトのBoundsを取得
            Bounds bounds = obj.GetComponent<Renderer>().bounds;

            // オブジェクトのBoundsが視錐台内にあるかチェック
            if (GeometryUtility.TestPlanesAABB(frustumPlanes, bounds))
            {
                // 視錐台内にある場合、オブジェクトを表示
                obj.SetActive(true);
            }
            else
            {
                // 視錐台外にある場合、オブジェクトを非表示にする
                obj.SetActive(false);
            }
        }
    }
}


解説

  1. objectsToRender配列:
    • このスクリプトは、複数のオブジェクトを視錐台内にあるかどうかチェックするために使用します。objectsToRenderには、チェック対象のオブジェクトを登録しておきます。
    public GameObject[] objectsToRender;
    
  2. GeometryUtility.CalculateFrustumPlanesメソッド:
    • CalculateFrustumPlanesは、カメラの視錐台を構成する6つの平面を計算し、それをPlane型の配列として返します。この視錐台を使ってオブジェクトのBoundsが含まれているかをチェックします。
    Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
    
  3. RendererコンポーネントからBoundsを取得:
    • Rendererコンポーネントのboundsプロパティを使って、オブジェクトのBoundsを取得します。Rendererは、メッシュなどの描画を担当するコンポーネントです。
    Bounds bounds = obj.GetComponent<Renderer>().bounds;
    
  4. GeometryUtility.TestPlanesAABBメソッド:
    • このメソッドは、視錐台内にBoundsがあるかどうかを判定します。もし視錐台内にBoundsがあれば、trueを返します。
    if (GeometryUtility.TestPlanesAABB(frustumPlanes, bounds))
    
  5. オブジェクトの表示/非表示:
    • 視錐台内にオブジェクトがあれば、そのオブジェクトをSetActive(true)で表示し、視錐台外であればSetActive(false)で非表示にします。これにより、視錐台内にあるオブジェクトだけが描画されます。
    obj.SetActive(true);
    

このプログラムの用途

このプログラムは、視錐台カリングと呼ばれる手法の一例です。視錐台カリングは、カメラの視野内にないオブジェクトを描画しないことで、ゲームのパフォーマンスを向上させるために使用されます。特に大規模なシーンや多くのオブジェクトがあるシーンでは、視錐台カリングを活用することで、描画負荷を大幅に減らすことができます。

この例では、オブジェクトの表示・非表示をSetActiveメソッドで制御していますが、実際のゲームでは、他のカリング手法(例えばオクルージョンカリングやLOD(レベルオブディテール))と組み合わせて使用されることもあります。


BoundsとGameObject、視錐台に対する調査の違い

まとめ

このように、BoundsGameObjectは異なる役割を持っており、視錐台カリングではBoundsを利用することで、効率的に描画対象を決定することができます。


(Octreeの構造を使って効率的に空間分割を行い、視錐台内に存在するオブジェクトのみを描画することを目指します。)

以下にその例と解説。

Octreeの基本的な概念

簡単に書くと

この仕組みを使うことで、特に大規模なシーンでの描画パフォーマンスを向上させることが可能です。

プログラム例

using UnityEngine;
using System.Collections.Generic;

public class OctreeNode
{
    public Bounds bounds;
    public List<GameObject> objects;
    public OctreeNode[] children;

    public OctreeNode(Bounds bounds)
    {
        this.bounds = bounds;
        this.objects = new List<GameObject>();
        this.children = null;
    }

    // Octreeの子ノードを作成
    public void Subdivide()
    {
        if (children != null)
            return;

        children = new OctreeNode[8];
        Vector3 size = bounds.size / 2f;
        Vector3 center = bounds.center;

        for (int i = 0; i < 8; i++)
        {
            Vector3 newCenter = center + new Vector3(
                (i & 1) == 0 ? -size.x / 2f : size.x / 2f,
                (i & 2) == 0 ? -size.y / 2f : size.y / 2f,
                (i & 4) == 0 ? -size.z / 2f : size.z / 2f
            );
            children[i] = new OctreeNode(new Bounds(newCenter, size));
        }
    }

    // オブジェクトをOctreeに追加
    public void Insert(GameObject obj)
    {
        if (!bounds.Contains(obj.GetComponent<Renderer>().bounds.min) || !bounds.Contains(obj.GetComponent<Renderer>().bounds.max))
            return;

        if (children == null)
        {
            objects.Add(obj);

            // ノードが満杯になった場合、分割する
            if (objects.Count > 8)
            {
                Subdivide();

                foreach (var existingObj in objects)
                {
                    Insert(existingObj);
                }

                objects.Clear();
            }
        }
        else
        {
            foreach (var child in children)
            {
                child.Insert(obj);
            }
        }
    }

    // 視錐台内のオブジェクトを取得
    public void RetrieveObjectsInFrustum(Plane[] frustumPlanes, List<GameObject> result)
    {
        if (!GeometryUtility.TestPlanesAABB(frustumPlanes, bounds))
            return;

        if (children != null)
        {
            foreach (var child in children)
            {
                child.RetrieveObjectsInFrustum(frustumPlanes, result);
            }
        }
        else
        {
            foreach (var obj in objects)
            {
                if (GeometryUtility.TestPlanesAABB(frustumPlanes, obj.GetComponent<Renderer>().bounds))
                {
                    result.Add(obj);
                }
            }
        }
    }
}

public class OctreeCulling : MonoBehaviour
{
    public GameObject[] objectsToInsert;
    private OctreeNode rootNode;

    void Start()
    {
        // Octreeのルートノードを設定(ここでは空間の全体サイズを指定)
        Bounds sceneBounds = new Bounds(Vector3.zero, new Vector3(100, 100, 100));
        rootNode = new OctreeNode(sceneBounds);

        // オブジェクトをOctreeに挿入
        foreach (var obj in objectsToInsert)
        {
            rootNode.Insert(obj);
        }
    }

    void Update()
    {
        // カメラの視錐台を取得
        Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(Camera.main);

        // 視錐台内に存在するオブジェクトを取得
        List<GameObject> objectsInFrustum = new List<GameObject>();
        rootNode.RetrieveObjectsInFrustum(frustumPlanes, objectsInFrustum);

        // 全てのオブジェクトを非表示にし、視錐台内に存在するオブジェクトのみ表示
        foreach (var obj in objectsToInsert)
        {
            obj.SetActive(false);
        }

        foreach (var obj in objectsInFrustum)
        {
            obj.SetActive(true);
        }
    }
}

解説

  1. OctreeNodeクラス:
    • このクラスは、Octreeの各ノードを表します。各ノードはその領域を表すBoundsと、その領域内に含まれるGameObjectのリストを持っています。また、必要に応じて8つの子ノードを持つことができます。
  2. Subdivideメソッド:
    • このメソッドは、現在のノードを8つの小さなノードに分割します。ノード内のオブジェクトが一定数を超えた場合に、空間を分割して管理します。
  3. Insertメソッド:
    • このメソッドは、オブジェクトをOctreeに挿入します。オブジェクトが現在のノードのBoundsに完全に収まる場合、そのノードに追加されます。ノードが満杯になると分割され、オブジェクトが適切な子ノードに再挿入されます。
  4. RetrieveObjectsInFrustumメソッド:
    • このメソッドは、カメラの視錐台に対してノードのBoundsをチェックし、その範囲内にあるオブジェクトを取得します。再帰的に子ノードをチェックし、最終的に視錐台内のオブジェクトをリストに追加します。
  5. OctreeCullingクラス:
    • OctreeNodeのルートノードを作成し、シーン内のすべてのオブジェクトを挿入します。Updateメソッドで、視錐台内にあるオブジェクトだけを表示する処理を行います。

このプログラムの動作

このように、Octreeを使用することで、大規模な3Dシーンでのオブジェクト管理が効率的になります。また、視錐台カリングと組み合わせることで、無駄な描画を避け、ゲームのパフォーマンスを最適化できます。