4. 三维碰撞检测:球体与球体、AABB与AABB、OBB与OBB的分离轴定理(SAT)
三维碰撞检测,说白了就是判断两个3D物体有没有「碰到一起」。很多刚入行的朋友觉得这玩意儿玄乎,其实拆开来看,核心就那么几种几何体的组合。今天咱们就聊聊最常用的三种:球体、AABB和OBB。我个人习惯把球体检测当作「开胃菜」,AABB是「家常便饭」,OBB加上SAT才是「硬菜」。
4.1 球体与球体碰撞检测
球体检测是3D碰撞里最简单的。你想想看,两个球撞没撞上,就看球心距离是不是小于半径之和。我在项目中遇到过不少新手,一上来就开平方根,其实没必要——平方比较就够了。
// 球体碰撞检测 —— 避免开平方
bool SphereSphereCollision(const Sphere& a, const Sphere& b) {
Vector3 diff = a.center - b.center;
float distSq = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z;
float radSum = a.radius + b.radius;
return distSq <= radSum * radSum;
}
4.2 AABB与AABB碰撞检测
AABB就是「轴对齐包围盒」,说白了就是每个面都平行于坐标轴的盒子。检测逻辑很直观:三个轴分别看投影区间有没有重叠。只要有一个轴没重叠,就没撞上。
我曾经在调试一个物理引擎时,发现两个AABB明明看着重叠了,检测却说没碰撞。查了半天,原来是其中一个盒子的min和max搞反了——min比max还大。嗯,这种低级错误,写代码时一定要加断言检查。
// AABB碰撞检测 —— 分离轴测试(三个轴)
bool AABBCollision(const AABB& a, const AABB& b) {
// X轴
if (a.max.x < b.min.x || a.min.x > b.max.x) return false;
// Y轴
if (a.max.y < b.min.y || a.min.y > b.max.y) return false;
// Z轴
if (a.max.z < b.min.z || a.min.z > b.max.z) return false;
return true;
}
| 特性 | AABB | OBB |
|---|---|---|
| 更新开销 | 低(只需重算min/max) | 高(需重算三个轴方向) |
| 紧密度 | 较差(旋转物体空隙大) | 好(贴合物体朝向) |
| 碰撞检测复杂度 | O(3) 轴测试 | O(15) 轴测试(SAT) |
| 适用场景 | 静态场景、粗略检测 | 动态物体、精确检测 |
4.3 OBB与OBB的分离轴定理(SAT)
OBB(有向包围盒)比AABB麻烦得多。它跟着物体旋转,所以检测时不能只看世界坐标轴。分离轴定理(SAT)就是干这个的——它说:如果能找到一条轴,让两个凸体在这条轴上的投影不重叠,那它们就没撞上。
对于OBB,我们需要检测15条轴:
- 两个OBB各自的3个面法线(共6条)
- 两个OBB各取一条边做叉积(3×3=9条)
// OBB碰撞检测 —— SAT核心逻辑(伪代码)
bool OBBCollision(const OBB& a, const OBB& b) {
// 收集所有待测试的轴
Vector3 axes[15];
int axisCount = 0;
// 1. 两个OBB的6个面法线
for (int i = 0; i < 3; i++) {
axes[axisCount++] = a.axis[i];
axes[axisCount++] = b.axis[i];
}
// 2. 边叉积(9条)
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
Vector3 cross = Cross(a.axis[i], b.axis[j]);
if (cross.LengthSq() > 1e-6f) { // 跳过零向量
axes[axisCount++] = Normalize(cross);
}
}
}
// 对每条轴做投影测试
for (int i = 0; i < axisCount; i++) {
if (!OverlapOnAxis(a, b, axes[i])) {
return false; // 找到分离轴,无碰撞
}
}
return true; // 所有轴都重叠,碰撞
}
4.4 三种方法的对比与选型
我建议你按这个思路选型:
- 球体: 适合子弹、粒子、角色粗略碰撞。计算量最小。
- AABB: 适合静态场景、地形、建筑物。更新快,但旋转物体会产生「空隙」。
- OBB + SAT: 适合车辆、武器、需要精确朝向的物体。精度最高,但开销也最大。
记得有一次做赛车游戏,用AABB检测车身碰撞,结果车一转弯就「穿模」——因为AABB没法跟随车身旋转。换成OBB后,问题立刻解决。你想想看,一个倾斜的车身,AABB的包围盒比车本身大了将近一倍,这检测能准吗?
4.5 知识结构图
下面这张图梳理了本章的核心逻辑,从最简单的球体到最复杂的OBB-SAT,你可以看到它们之间的递进关系。
4.6 避坑指南
最后分享几个我踩过的坑:
- 浮点精度: 两个OBB刚好相切时,投影重叠可能为0。我习惯加一个很小的epsilon(比如1e-5f)做容差。
- 轴重复: SAT的15条轴里,有些轴方向相同或相反。检测前先去重,能省下不少计算。
- 物体高速运动: 如果物体一帧移动距离超过自身尺寸,SAT会漏检。这时候需要做「连续碰撞检测」(CCD),把运动轨迹扫成体来检测。
我曾经在一个VR项目中,用户快速挥动手柄,结果OBB检测总是漏掉。后来加上CCD,问题才解决。嗯,高速运动的物体,千万别只用静态SAT。