AABB(Axis-Aligned Bounding Box)包围盒和OBB(Oriented Bounding Box)有向包围盒

前言

在游戏开发中,AABB(轴对齐包围盒,Axis-Aligned Bounding Box)和 OBB(有向包围盒,Oriented Bounding Box)是两种常用的碰撞检测和物体包围的技术,各自有不同的用途和优缺点。

1.AABB(Axis-Aligned Bounding Box)包围盒

AABB包围盒概念解释:
AABB是一种最基础也最常用的碰撞检测包围体,它是一个与坐标轴对齐的立方体,用最小和最大的两个点来表示一个完整的包围盒。

主要特点:
1.始终与坐标轴平行
2.不会随物体旋转而旋转
3.计算简单,性能高效
4.内存占用小,仅需存储两个点的坐标

主要应用场景:
1.快速碰撞检测
2.视锥体剔除
3.空间分区
4.物理引擎的粗略检测阶段

原理:
核心逻辑就是存储了minPoint和maxPoint,然后检查不同物体AABB的边在每个轴上的投影是否重叠。

以Unity为例,实现的包围盒部分的代码。

using UnityEngine;

public class AABBBoundingBox : MonoBehaviour
{
    private Vector3 minPoint;
    private Vector3 maxPoint;
    private Vector3 center;
    private Vector3 size;

    private MeshFilter meshFilter;
    private Mesh mesh;
    private Vector3[] originalVertices; // 存储原始顶点数据  

    void Start()
    {
        meshFilter = GetComponent<MeshFilter>();
        if (meshFilter != null)
        {
            mesh = meshFilter.mesh;
            originalVertices = mesh.vertices;
        }

        CalculateAABB();
    }

    void Update()
    {
        // 使用transform.hasChanged检查变换  
        if (meshFilter != null && transform.hasChanged)
        {
            CalculateAABB();
            // 重置hasChanged标记  
            transform.hasChanged = false;
        }
    }

    void CalculateAABB()
    {
        if (mesh == null || originalVertices == null || originalVertices.Length == 0)
            return;

        // 初始化最小点和最大点  
        Vector3 worldVertex = transform.TransformPoint(originalVertices[0]);
        minPoint = maxPoint = worldVertex;

        // 遍历所有顶点,更新最小点和最大点  
        for (int i = 1; i < originalVertices.Length; i++)
        {
            worldVertex = transform.TransformPoint(originalVertices[i]);

            // 更新最小点  
            minPoint.x = Mathf.Min(minPoint.x, worldVertex.x);
            minPoint.y = Mathf.Min(minPoint.y, worldVertex.y);
            minPoint.z = Mathf.Min(minPoint.z, worldVertex.z);

            // 更新最大点  
            maxPoint.x = Mathf.Max(maxPoint.x, worldVertex.x);
            maxPoint.y = Mathf.Max(maxPoint.y, worldVertex.y);
            maxPoint.z = Mathf.Max(maxPoint.z, worldVertex.z);
        }

        // 计算包围盒中心点和大小  
        center = (minPoint + maxPoint) * 0.5f;
        size = maxPoint - minPoint;
    }

    // 考虑缩放的顶点变换  
    private Vector3 TransformVertexWithScale(Vector3 vertex)
    {
        // 先应用缩放  
        Vector3 scaledVertex = new Vector3(
            vertex.x * transform.localScale.x,
            vertex.y * transform.localScale.y,
            vertex.z * transform.localScale.z
        );

        // 然后应用旋转和位移  
        return transform.position + (transform.rotation * scaledVertex);
    }

    // 优化的相交检测  
    public bool IntersectsWithAABB(AABBBoundingBox other)
    {
        bool noOverlap = other.maxPoint.x < minPoint.x ||
                        other.minPoint.x > maxPoint.x ||
                        other.maxPoint.y < minPoint.y ||
                        other.minPoint.y > maxPoint.y ||
                        other.maxPoint.z < minPoint.z ||
                        other.minPoint.z > maxPoint.z;

        return !noOverlap;
    }

    // 在Scene视图中绘制包围盒  
    void OnDrawGizmos()
    {
        if (!Application.isPlaying) return;

        // 正常状态下的颜色  
        Gizmos.color = Color.green;
        Gizmos.DrawWireCube(center, size);

        // 绘制包围盒的顶点  
        Gizmos.color = Color.yellow;
        float pointSize = 0.1f;
        Gizmos.DrawCube(minPoint, Vector3.one * pointSize);
        Gizmos.DrawCube(maxPoint, Vector3.one * pointSize);
    }

    // 用于缓存的属性  
    public Vector3 MinPoint => minPoint;
    public Vector3 MaxPoint => maxPoint;
    public Vector3 Center => center;
    public Vector3 Size => size;

    // 扩展包围盒  
    public void Expand(float amount)
    {
        Vector3 expansion = new Vector3(amount, amount, amount);
        minPoint -= expansion;
        maxPoint += expansion;
        size = maxPoint - minPoint;
        center = (minPoint + maxPoint) * 0.5f;
    }

    // 合并两个AABB  
    public static AABBBoundingBox Merge(AABBBoundingBox a, AABBBoundingBox b)
    {
        GameObject go = new GameObject("MergedAABB");
        AABBBoundingBox merged = go.AddComponent<AABBBoundingBox>();

        merged.minPoint = Vector3.Min(a.minPoint, b.minPoint);
        merged.maxPoint = Vector3.Max(a.maxPoint, b.maxPoint);
        merged.center = (merged.minPoint + merged.maxPoint) * 0.5f;
        merged.size = merged.maxPoint - merged.minPoint;

        return merged;
    }
}

测试代码:

// 检测两个物体的包围盒是否相交  
using UnityEngine;

public class CollisionExample : MonoBehaviour
{
    public GameObject object1;
    public GameObject object2;

    void Update()
    {
        AABBBoundingBox box1 = object1.GetComponent<AABBBoundingBox>();
        AABBBoundingBox box2 = object2.GetComponent<AABBBoundingBox>();

        if (box1.IntersectsWithAABB(box2))
        {
            Debug.Log("碰撞发生!");
        }
    }
}

在这里插入图片描述

2.OBB(Oriented Bounding Box)有向包围盒

OBB相比AABB更加精确,因为它可以随物体旋转,能更好地适应物体的形状,但性能差于AABB。

OBB概念解释:
1.是一个可以任意旋转的长方体包围盒
2.由中心点、三个轴向量和三个半长度定义
3.可以跟随物体旋转,提供更紧凑的包围
4.碰撞检测计算比AABB复杂,但精确度更高

注意事项:
1.OBB的碰撞检测比AABB更复杂,需要更多计算
2.对于简单物体或不需要精确碰撞的情况,考虑使用AABB
3.可以结合AABB进行多层次碰撞检测。

原理:
OBB通过分离轴定理(Separating Axis Theorem, SAT)进行碰撞检测。
分离轴定理的核心思想是:如果两个凸多面体不相交,那么一定存在一个轴,当两个物体投影到这个轴上时,投影不重叠。

OOB包围盒部分代码示例:

using UnityEngine;

public class OBBBoundingBox : MonoBehaviour
{
    private Vector3 center;        // 包围盒中心点  
    private Vector3 extents;       // 包围盒半长度  
    private Vector3[] axes;        // 包围盒的三个轴向量  
    private Vector3[] vertices;    // 8个顶点  
    private Matrix4x4 rotation;    // 旋转矩阵  

    private MeshFilter meshFilter;
    private Mesh mesh;

    void Start()
    {
        meshFilter = GetComponent<MeshFilter>();
        if (meshFilter != null)
        {
            mesh = meshFilter.mesh;
            axes = new Vector3[3];
            vertices = new Vector3[8];
            CalculateOBB();
        }
    }

    void Update()
    {
        // 如果物体发生变换,更新OBB  
        if (transform.hasChanged)
        {
            CalculateOBB();
            transform.hasChanged = false;
        }
    }

    void CalculateOBB()
    {
        if (mesh == null) return;

        // 获取物体的顶点  
        Vector3[] meshVertices = mesh.vertices;
        if (meshVertices.Length == 0) return;

        // 计算协方差矩阵  
        Matrix4x4 covarianceMatrix = CalculateCovarianceMatrix(meshVertices);

        // 计算主轴(使用特征值分解)  
        CalculatePrincipalAxes(covarianceMatrix);

        // 计算包围盒的范围  
        CalculateExtents(meshVertices);

        // 更新顶点  
        UpdateVertices();
    }

    private Matrix4x4 CalculateCovarianceMatrix(Vector3[] vertices)
    {
        // 计算中心点  
        center = Vector3.zero;
        for (int i = 0; i < vertices.Length; i++)
        {
            center += transform.TransformPoint(vertices[i]);
        }
        center /= vertices.Length;

        // 计算协方差矩阵  
        Matrix4x4 covariance = Matrix4x4.zero;
        for (int i = 0; i < vertices.Length; i++)
        {
            Vector3 point = transform.TransformPoint(vertices[i]) - center;
            covariance[0, 0] += point.x * point.x;
            covariance[0, 1] += point.x * point.y;
            covariance[0, 2] += point.x * point.z;
            covariance[1, 1] += point.y * point.y;
            covariance[1, 2] += point.y * point.z;
            covariance[2, 2] += point.z * point.z;
        }

        covariance[1, 0] = covariance[0, 1];
        covariance[2, 0] = covariance[0, 2];
        covariance[2, 1] = covariance[1, 2];

        return covariance;
    }

    private void CalculatePrincipalAxes(Matrix4x4 covarianceMatrix)
    {
        // 使用雅可比旋转法计算特征向量  
        // 这里简化处理,直接使用物体的本地坐标轴  
        axes[0] = transform.right;
        axes[1] = transform.up;
        axes[2] = transform.forward;

        rotation = Matrix4x4.TRS(Vector3.zero, transform.rotation, Vector3.one);
    }

    private void CalculateExtents(Vector3[] meshVertices)
    {
        // 初始化范围  
        extents = Vector3.zero;
        Vector3 min = Vector3.positiveInfinity;
        Vector3 max = Vector3.negativeInfinity;

        // 计算在每个轴向上的投影范围  
        for (int i = 0; i < meshVertices.Length; i++)
        {
            Vector3 point = transform.TransformPoint(meshVertices[i]) - center;
            for (int j = 0; j < 3; j++)
            {
                float projection = Vector3.Dot(point, axes[j]);
                min[j] = Mathf.Min(min[j], projection);
                max[j] = Mathf.Max(max[j], projection);
            }
        }

        // 计算半长度  
        extents = (max - min) * 0.5f;
    }

    private void UpdateVertices()
    {
        // 计算8个顶点的位置  
        for (int i = 0; i < 8; i++)
        {
            Vector3 vertex = new Vector3(
                ((i & 1) == 0 ? -extents.x : extents.x),
                ((i & 2) == 0 ? -extents.y : extents.y),
                ((i & 4) == 0 ? -extents.z : extents.z)
            );

            vertices[i] = center +
                         axes[0] * vertex.x +
                         axes[1] * vertex.y +
                         axes[2] * vertex.z;
        }
    }

    // 分离轴定理(SAT)检测两个OBB是否相交  
    public bool IntersectsWithOBB(OBBBoundingBox other)
    {
        float[,] rotation1 = new float[3, 3];
        float[,] rotation2 = new float[3, 3];
        float[,] rotationMatrix = new float[3, 3];

        // 获取两个OBB的旋转矩阵  
        for (int i = 0; i < 3; i++)
        {
            rotation1[i, 0] = axes[i].x;
            rotation1[i, 1] = axes[i].y;
            rotation1[i, 2] = axes[i].z;

            rotation2[i, 0] = other.axes[i].x;
            rotation2[i, 1] = other.axes[i].y;
            rotation2[i, 2] = other.axes[i].z;
        }

        // 计算旋转矩阵  
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                rotationMatrix[i, j] = 0;
                for (int k = 0; k < 3; k++)
                {
                    rotationMatrix[i, j] += rotation1[i, k] * rotation2[j, k];
                }
            }
        }

        // 计算平移向量  
        Vector3 translation = other.center - center;
        Vector3 translationBox1 = new Vector3(
            Vector3.Dot(translation, axes[0]),
            Vector3.Dot(translation, axes[1]),
            Vector3.Dot(translation, axes[2])
        );

        // 15个分离轴检测  
        return SeparatingAxisTest(translationBox1, extents, other.extents, rotationMatrix);
    }

    private bool SeparatingAxisTest(Vector3 translation, Vector3 extents1, Vector3 extents2, float[,] rotation)
    {
        float[,] absRotation = new float[3, 3];

        // 计算旋转矩阵的绝对值  
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                absRotation[i, j] = Mathf.Abs(rotation[i, j]) + 0.0001f;
            }
        }

        // 测试15个分离轴  
        for (int i = 0; i < 3; i++)
        {
            float ra = extents1[i];
            float rb = extents2[0] * absRotation[i, 0] +
                      extents2[1] * absRotation[i, 1] +
                      extents2[2] * absRotation[i, 2];
            if (Mathf.Abs(translation[i]) > ra + rb) return false;
        }

        for (int i = 0; i < 3; i++)
        {
            float ra = extents1[0] * absRotation[0, i] +
                      extents1[1] * absRotation[1, i] +
                      extents1[2] * absRotation[2, i];
            float rb = extents2[i];
            if (Mathf.Abs(translation[0] * rotation[0, i] +
                         translation[1] * rotation[1, i] +
                         translation[2] * rotation[2, i]) > ra + rb) return false;
        }

        return true;
    }

    void OnDrawGizmos()
    {
        if (!Application.isPlaying || vertices == null) return;

        Gizmos.color = Color.yellow;

        // 绘制12条边  
        int[,] edges = new int[12, 2] {
            {0,1}, {1,3}, {3,2}, {2,0}, // 底面  
            {4,5}, {5,7}, {7,6}, {6,4}, // 顶面  
            {0,4}, {1,5}, {2,6}, {3,7}  // 连接边  
        };

        for (int i = 0; i < 12; i++)
        {
            Gizmos.DrawLine(vertices[edges[i, 0]], vertices[edges[i, 1]]);
        }
    }

    // 属性访问器  
    public Vector3 Center => center;
    public Vector3 Extents => extents;
    public Vector3[] Axes => axes;
    public Vector3[] Vertices => vertices;
}

测试部分代码:

using UnityEngine;

public class OBBTest : MonoBehaviour
{
    public GameObject object1;
    public GameObject object2;

    void Update()
    {
        OBBBoundingBox obb1 = object1.GetComponent<OBBBoundingBox>();
        OBBBoundingBox obb2 = object2.GetComponent<OBBBoundingBox>();

        if (obb1.IntersectsWithOBB(obb2))
        {
            Debug.Log("OBB碰撞发生!");
        }
    }
}

在这里插入图片描述

性能优化

当物体发生移动、旋转、缩放后再重新计算。
空间划分优化。通过四叉树(2D)或者八叉树(3D)将区域划分,只启用组件和计算存在碰撞可能的区域。
大致判断碰撞检测时使用AABB,需要详细计算再使用OBB。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我寄人间雪满头丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值