AABB(Axis-Aligned Bounding Box)包围盒和OBB(Oriented Bounding Box)有向包围盒
前言
在游戏开发中,AABB(轴对齐包围盒,Axis-Aligned Bounding Box)和 OBB(有向包围盒,Oriented Bounding Box)是两种常用的碰撞检测和物体包围的技术,各自有不同的用途和优缺点。
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碰撞发生!");}}
}
性能优化
当物体发生移动、旋转、缩放后再重新计算。
空间划分优化。通过四叉树或者八叉树将区域划分,只启用组件和计算存在碰撞可能的区域。
大致判断碰撞检测时使用AABB,需要详细计算再使用OBB。