最可靠方法是将一个aabb通过变换矩阵转换到另一个的坐标系下,再用8个顶点求轴对齐包络进行相交检测;直接旋转半长或投影点积易漏判误判。

直接用分离轴定理(SAT)判断AABB相交最可靠
两个AABB在各自坐标系下都是轴对齐的,但若坐标系不同(比如一个在世界系、一个在模型系),不能直接比 min/max。必须把其中一个AABB变换到另一个的坐标系下,再做轴对齐相交检测——这是最直观且不易出错的做法。
常见错误是试图“旋转AABB的中心+缩放半长”,结果得到的是OBB(有向包围盒),误当AABB用,导致漏判或误判;或者直接在原坐标系下用点积投影,没选全分离轴,漏掉某个方向。
- 先确认两个AABB各自的
center和half_extents(即半长宽高),以及它们相对于同一参考系(如世界坐标系)的变换矩阵:transform_A和transform_B - 把B的AABB通过
transform_B * inverse(transform_A)变换到A的局部坐标系中,得到新中心center_B_in_A和新半长half_extents_B_in_A(注意:旋转会导致半长不再沿轴对齐,所以必须用变换后的8个顶点求AABB包络) - 对变换后的8个顶点,在A的坐标系中计算其轴对齐包围盒:
min_xyz = min(vertex_i),max_xyz = max(vertex_i),这才是B在A系下的等效AABB
用变换后顶点求包络比解析推导更安全
有人尝试用旋转矩阵直接作用于 half_extents 向量再加绝对值来估算新半长,例如 abs(R * half_extents),但这只在旋转轴与原始轴对齐时才准确;任意旋转下,这个“轴对齐包络”会严重放大,导致保守但低效(比如明明不相交却被判定为相交)。
实操建议始终走顶点路径:
- 写出B的8个局部顶点:
±half_extents_B.x, ±half_extents_B.y, ±half_extents_B.z - 对每个顶点应用变换:
world_pos = transform_B * Vec4(vertex, 1.0f),再除以w(若含透视,但AABB一般不用) - 将所有
world_pos投影到A的局部坐标系:local_pos = inverse(transform_A) * world_pos - 取这8个
local_pos的x/y/z分量极值,构成B在A系下的等效AABB
示例关键片段(假设使用列主序、右手系):
mat4 invA = inverse(transform_A); vec3 local_min(FLT_MAX), local_max(-FLT_MAX); for (int i = 0; i <h3>避免浮点误差导致的“明明相切却不相交”</h3><p>AABB相交判断本质是6个不等式:<code>A.max.x >= B.min.x && A.min.x 。但当两个盒子恰好共面(如 <code>A.max.x == B.min.x</code>),浮点舍入可能让比较失败。</code></p>
- 不要用严格不等号,统一用带epsilon的包容判断:
A.max.x + eps >= B.min.x - eps取值取决于坐标尺度,常用
1e-5f或std::numeric_limits<float>::epsilon() * 100</float> - 若已知坐标范围较大(如地形坐标达1e6量级),需按比例缩放eps,否则失效
性能敏感时可先做粗略剔除
如果每帧要检测成百上千对AABB,逐对做8点变换太重。可先用中心距离快速排除:
- 计算两中心在世界系下的距离平方:
distance_sq = length(center_world_A - center_world_B) * length(...) - 若该距离大于两盒“最大可能半径和”的平方(即
(half_len_A + half_len_B).length_squared()),则一定不相交 - 注意:这里
half_len_A是A在世界系下的实际空间半长(需用transform_A的缩放分量校正),不是原始half_extents
这个预检能干掉大部分无关对,剩下再走完整顶点变换流程。
真正容易被忽略的是:AABB在非单位缩放或含剪切的变换下,其“轴对齐”只存在于局部空间;一旦坐标系不同,就必须显式变换——没有捷径,也别信“用四元数转一下就行”的模糊说法。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











