不能直接用AABB的min/max比较,因为OBB旋转后边不与坐标轴对齐,轴向投影重叠不等于实际相交;需用分离轴定理(SAT),测试9个关键轴(3+3+3叉积),并在box1局部坐标系下计算投影,注意坐标系变换、叉积归一化和浮点误差。

为什么不能直接用AABB的min/max比较
因为OBB(oriented bounding box)是旋转过的长方体,它的边不与坐标轴对齐。直接拿min/max在世界坐标下比,会漏判或误判——比如两个斜着相交的盒子,轴向投影完全重叠,但实际没碰;或者一个盒子完全在另一个的“斜角空隙”里,轴向投影却有重叠。
本质是:OBB碰撞检测必须考虑其朝向,核心思路是分离轴定理(SAT)。只要能找到一个轴,让两个OBB在该轴上的投影不重叠,就一定没碰撞。
怎么用分离轴定理快速实现OBB碰撞
OBB有3个本地坐标轴(通常存为单位向量u、v、w),每个OBB贡献3个轴;再加上两两叉积产生的9个交叉轴(u1×u2、u1×v2…),共15个轴要测试。但实际只需检查15个中的9个:3个来自box1,3个来自box2,还有3个来自u1×u2、u1×v2、u1×w2(其余叉积轴要么重复要么退化)。
- 先将box2的中心、半长宽高、3个轴都变换到box1的局部坐标系下(用box1的旋转逆矩阵),这样只需在box1坐标系里投影
- 对每个候选轴
axis,计算box1在该轴上的投影半长:extent1 = half_size.x * abs(dot(u, axis)) + half_size.y * abs(dot(v, axis)) + half_size.z * abs(dot(w, axis)) - 同样算box2的投影半长
extent2,再算两中心在该轴上的距离abs(dot(center_diff, axis)) - 若
abs(dot(center_diff, axis)) > extent1 + extent2,说明该轴能分离,立即返回false
所有9个轴都过不了分离测试,才返回true。
立即学习“C++免费学习笔记(深入)”;
实际编码时最容易错的三个地方
第一是坐标系混淆:两个OBB的旋转矩阵如果都是相对于世界坐标系定义的,做变换时必须用box1.R.transpose()(不是inverse(),除非非正交)把box2转到box1空间;第二是叉积轴归一化:即使输入轴是单位向量,u1×v2长度也不一定是1,必须normalize()后再用,否则投影计算会错;第三是浮点误差:比较时建议加小偏移,比如if (distance > extent1 + extent2 + 1e-6f),否则刚接触的OBB可能因精度被判定为不碰。
示例关键片段:
vec3 center_diff = box2.center - box1.center; mat3 R1T = transpose(box1.rotation); // 假设rotation是3x3正交矩阵 vec3 center2_in_1 = R1T * center_diff; vec3 u2_in_1 = R1T * box2.u; vec3 v2_in_1 = R1T * box2.v; vec3 w2_in_1 = R1T * box2.w; <p>// 测试box1的3个轴 if (separated(center2_in_1, box1.half_size, box2.half_size, u2_in_1, v2_in_1, w2_in_1, vec3(1,0,0))) return false; // …同理测(0,1,0)、(0,0,1) // 测试交叉轴,如u1×u2_in_1 → 需normalize后再传入separated()
要不要用GJK或更高级算法
对于OBB-OBB这种特定形状,SAT是最快最稳的选择。GJK虽然通用,但对OBB反而多出迭代和支撑点计算开销,且更容易受初始方向或退化情况影响;而像Bullet或PhysX这类引擎内部对OBB碰撞也优先走优化过的SAT路径。除非你要同时处理凸包、胶囊、球等混合类型,否则没必要引入GJK。
真正要注意的是:如果OBB的旋转矩阵严重失真(比如缩放混入或数值漂移),transpose()不再等于inverse(),此时必须先正交化(如Gram-Schmidt)再用——这点常被忽略,导致碰撞结果随时间漂移。


















