摘要
分离轴定理(SAT)是一种高效的碰撞检测方法,通过判断两个物体在所有方向上的投影(影子)是否重叠来确定是否碰撞。核心思想是:只要存在一个方向使投影不重叠,则物体未碰撞;若所有方向投影均重叠,则发生碰撞。SAT适用于游戏中的凸多边形/多面体检测(如OBB),优势在于可提前终止计算,兼顾效率与通用性。生活中类似“从不同角度观察书本是否重叠”的现象可帮助理解该原理。
一、分离轴定理(SAT)是什么?——“影子重叠”的故事
想象你有两个不规则的物体,比如两块积木。你想知道它们有没有碰到一起。
分离轴定理的核心思想是:
“如果你能找到一个方向(轴),让这两个物体在这个方向上的影子(投影)完全不重叠,那它们就一定没有碰到;如果所有方向上的影子都重叠了,那它们就一定有碰撞。”
形象比喻
- 把两个物体放在阳光下,阳光从不同方向照射。
- 你在每个方向上都看它们的影子。
- 只要有一个方向,两个影子完全分开了,那它们肯定没碰到。
- 如果无论怎么照,影子都重叠,那它们肯定有交集。
关键点
- “轴”可以是任意方向,但对于常见的几何体(如盒子、凸多边形),有一套有限的“关键轴”需要检查。
- 只要找到一个分离轴(影子不重叠),就能断定“不碰撞”。
- 如果所有轴都重叠,才是“碰撞”。
二、SAT在游戏中的实际应用
1. 碰撞检测
在3D游戏中,常见的碰撞体有AABB、OBB、多边形、凸多面体等。
SAT是检测两个凸多边形/多面体是否相交的通用方法,尤其适合OBB(有向包围盒)和凸多边形。
例子:两个OBB的碰撞检测
- 先找出所有可能的分离轴(比如两个盒子的主轴和它们主轴的叉积)。
- 把两个盒子在每个轴上投影,检查投影区间是否重叠。
- 只要有一个轴不重叠,就不用再算了,直接判定“不碰撞”。
- 如果所有轴都重叠,判定“碰撞”。
伪代码流程
for axis in all_possible_axes:
proj1 = project_shape1_on_axis(axis)
proj2 = project_shape2_on_axis(axis)
if not overlap(proj1, proj2):
return False # 有分离轴,不碰撞
return True # 所有轴都重叠,碰撞
2. 游戏中的实际场景
- 角色与障碍物:比如主角和箱子、门等物体的碰撞体都是OBB,SAT可以高效判断是否相交。
- 物理引擎:如Box2D、Bullet、PhysX等,底层都用SAT做凸多边形/多面体的初步碰撞检测。
- 射线检测:判断子弹是否击中目标,也可以用SAT原理。
3. 优点
- 高效:只要找到一个分离轴就能提前结束,适合实时游戏。
- 通用:适用于任意凸多边形/多面体。
三、再举个生活例子
想象你有两本书,你想知道它们有没有叠在一起。你可以从正上方、正侧面、正前方分别看它们的投影(影子)。
- 如果有一个方向看过去,两个影子完全分开,那它们肯定没叠在一起。
- 如果每个方向看过去,影子都重叠,那它们肯定有重叠部分。
四、总结一句话
分离轴定理就是“找影子不重叠的方向”,只要有一个方向影子分开了,物体就没碰到。
我们继续深入,用更具体的例子、动画想象和伪代码,让你彻底掌握SAT的原理和实际用法。
一、二维例子:两个凸多边形的碰撞检测
1. 动画想象
假设有两个不规则的凸多边形(比如两个盾牌),你想知道它们是否碰撞。
- 你把每个多边形的每一条边都当作“分离轴”。
- 把两个多边形都投影到这条轴上,看看它们的投影区间(影子)是否重叠。
- 如果有一条轴上,两个投影区间完全不重叠,说明这两个多边形没有碰撞。
- 如果所有轴上投影都重叠,说明它们碰撞了。
2. 伪代码(二维凸多边形)
def polygons_collide(poly1, poly2):
for edge in poly1.edges + poly2.edges:
axis = edge.normal() # 取边的法线作为分离轴
proj1 = project_polygon(poly1, axis)
proj2 = project_polygon(poly2, axis)
if not overlap(proj1, proj2):
return False # 找到分离轴,不碰撞
return True # 所有轴都重叠,碰撞
投影函数
def project_polygon(polygon, axis):
dots = [vertex.dot(axis) for vertex in polygon.vertices]
return min(dots), max(dots)
重叠判断
def overlap(proj1, proj2):
return not (proj1[1] < proj2[0] or proj2[1] < proj1[0])
二、三维例子:两个OBB的碰撞检测
1. 需要检查哪些轴?
对于两个OBB(有向包围盒),需要检查的分离轴有:
- 盒子A的3个主轴
- 盒子B的3个主轴
- 盒子A和B主轴两两叉积,共9个轴
总共15个轴。
2. 伪代码(简化版)
axes = []
axes += [A.axis0, A.axis1, A.axis2]
axes += [B.axis0, B.axis1, B.axis2]
for i in range(3):
for j in range(3):
axes.append(cross(A.axis[i], B.axis[j]))
for axis in axes:
projA = project_obb(A, axis)
projB = project_obb(B, axis)
if not overlap(projA, projB):
return False # 有分离轴,不碰撞
return True # 所有轴都重叠,碰撞
3. 投影OBB到轴
OBB在某个轴上的投影区间,可以通过中心点在轴上的投影±半长宽高在该轴上的投影之和得到。
def project_obb(obb, axis):
center_proj = obb.center.dot(axis)
r = 0
for i in range(3):
r += obb.half_extents[i] * abs(obb.axes[i].dot(axis))
return (center_proj - r, center_proj + r)
三、实际游戏中的优化
- 只对可能接触的物体用SAT:比如先用AABB快速排除远距离物体,再用SAT精确检测。
- 提前终止:只要找到一个分离轴就立刻返回,节省大量计算。
- 缓存轴和投影:减少重复计算。
四、可视化动画想象
你可以想象这样一个动画:
- 两个多边形/盒子在屏幕上。
- 你拿一根棍子(分离轴),在不同方向上“照射”它们。
- 在每个方向上,两个物体都投下影子。
- 只要有一次,两个影子完全分开,物体就没碰到。
- 如果每次影子都重叠,物体就碰上了。
五、SAT的局限
- 只适用于凸多边形/多面体,不适用于凹形物体(凹形要先分解成多个凸形)。
- 对于复杂物体,分离轴数量会增加,计算量也会增加。
六、总结口诀
“找轴投影,影子分离则不碰,影子都重叠才碰撞。”