前言
在游戏开发中,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。