WARNING
文章作者:Deepseek v4 flash
编辑:我
校对:你
包围盒简介和作用
什么是包围盒?
想象一下:你在玩《超级马里奥》,马里奥和蘑菇怪物撞在一起时,游戏怎么知道它们“碰到”了?
其实,游戏并不会真的去比对着两个角色的每一个像素是否重叠——那样太慢了!它会给每个角色套上一个简单的几何体,比如一个矩形或圆,然后只判断这些简单的形状有没有碰到。
这个“简单的几何体”就叫做 包围盒(Bounding Volume,简称 BV)。
包围盒有什么用?
| 应用场景 | 说明 |
|---|---|
| 🎮 游戏碰撞检测 | 快速判断两个物体是否相撞 |
| 🎬 计算机图形学 | 加速光线追踪、视锥体剔除(只渲染屏幕内物体) |
| 🏗️ 物理仿真 | 粒子系统、刚体碰撞的初步筛选 |
| 🖥️ 3D建模软件 | 选中物体时显示的外框(就是包围盒) |
核心思想:先粗后细。 先用包围盒快速排除“肯定没碰上的”,只有包围盒重叠了,才去做精确的像素级检测。这能省下大量计算时间!
包围球(Bounding Sphere)
包围球就是用一个球体把物体包起来。只要确定球心位置和半径,就定义好了。
. . . . .
. .
. ● . ← 球心 (center)
. /|\ .
. r | .
. .|. . .
↓
半径 r
那么包围球如何构造呢?
- 球心:通常取物体所有顶点的中心点(平均值坐标)
- 半径:物体上离球心最远的顶点到球心的距离
优缺点
| 👍 优点 | 👎 缺点 |
|---|---|
| 碰撞检测极快(只需判断两个球心距离是否小于半径和) | 紧密度较差,对长条形物体会浪费很多空间 |
| 旋转不影响包围球(旋转后球不变) | 容易产生“误判”(包围球碰了,实际物体没碰) |
碰撞检测公式
# 两个包围球是否碰撞?
if distance(center1, center2) < (radius1 + radius2):
print("碰撞!")
else:
print("没碰到")AABB(轴对齐包围盒)简介
AABB = Axis-Aligned Bounding Box(轴对齐包围盒)
它是个矩形(2D)或长方体(3D),并且它的边永远和坐标轴平行(X轴、Y轴、Z轴)。
2D 中的 AABB:
y
↑
| ┌──────────┐
| │ │
| │ ●物体 │
| │ │
| └──────────┘
| min max
└────────────────────→ x
如何表示?
AABB 只需要两个点:
- min:x、y、z 最小的那个角
- max:x、y、z 最大的那个角
在 3D 中:
AABB = {
min: (x_min, y_min, z_min),
max: (x_max, y_max, z_max)
}
如何构造一个物体的 AABB?
遍历物体的所有顶点,分别找出 x、y、z 的最小值和最大值:
def make_aabb(vertices):
xs = [v.x for v in vertices]
ys = [v.y for v in vertices]
zs = [v.z for v in vertices]
return {
"min": (min(xs), min(ys), min(zs)),
"max": (max(xs), max(ys), max(zs))
}优缺点
| 👍 优点 | 👎 缺点 |
|---|---|
| 构造简单、存储小(只需6个数) | 物体旋转后 AABB 会变大(不紧贴物体) |
| 碰撞检测非常快 | 对斜向物体贴合度差 |
⚠️ 重要特性:物体旋转时,AABB 会跟着重新计算(因为轴对齐的性质),所以如果你旋转了一个物体,AABB 就必须更新。
4️⃣ OBB(有向包围盒)简介
OBB = Oriented Bounding Box(有向包围盒 / 定向包围盒)
它也是一个矩形/长方体,但可以不平行于坐标轴,可以跟着物体的方向旋转。
2D 中的 OBB:
y
↑
| ┌──────┐
| / /│
| / ● / │
| / /
| └──────┘
└────────────────→ x
如何表示?
OBB 需要存储:
- 中心点(位置)
- 三个方向轴(局部坐标系的方向)
- 三个半边长(沿每个方向轴的一半长度)
OBB = {
center: (cx, cy, cz), # 中心
axes: [u, v, w], # 三个互相垂直的方向轴
half_extents: (hx, hy, hz) # 半边长
}如何构造?
更复杂,常见方法有:
- 主成分分析(PCA):对物体所有顶点的协方差矩阵做特征值分解,得到主方向
- 基于凸包:先求物体的凸包,再找最小体积的包围盒
优缺点
| 👍 优点 | 👎 缺点 |
|---|---|
| 紧密度最好,贴合物体形状 | 构造复杂,计算量大 |
| 物体旋转后 OBB 只需跟着旋转,不需要重建 | 碰撞检测也更复杂(需要分离轴定理 SAT) |
| 适合细长、倾斜的物体 | 存储需要更多数据 |
三种包围盒的对比
| 特性 | 包围球 | AABB | OBB |
|---|---|---|---|
| 紧密度 | ⭐ | ⭐⭐ | ⭐⭐⭐ |
| 碰撞检测速度 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| 构造难度 | ⭐ | ⭐⭐ | ⭐⭐⭐ |
| 旋转友好度 | ⭐⭐⭐ | ⭐ | ⭐⭐⭐ |
实战:用 AABB 做碰撞检测和求重叠区域
这一节我们用 AABB 来实际做两件事:
- 判断两个盒子是否碰撞
- 如果碰撞了,求出它们重叠的区域
5.1 碰撞检测
两个 AABB 碰撞的条件其实很简单:
在 2D 中:两个矩形在 X 轴和 Y 轴上的投影都重叠时,它们就碰撞了。 在 3D 中:再加上 Z 轴,三个轴上都重叠才碰撞。
核心原理:
对每个轴(x, y, z):
如果 盒子A的最大值 < 盒子B的最小值 → 肯定不碰撞(A在B左边)
或者 盒子B的最大值 < 盒子A的最小值 → 肯定不碰撞(B在A左边)
否则 → 在这个轴上重叠
所有轴都重叠 → 碰撞!
代码实现:
def aabb_collision(A, B):
"""
A 和 B 是 AABB,格式:
{"min": (x_min, y_min, z_min), "max": (x_max, y_max, z_max)}
"""
# 检查 x, y, z 三个轴
for i in range(3): # 0=x, 1=y, 2=z
if A["max"][i] < B["min"][i] or B["max"][i] < A["min"][i]:
return False # 只要有一个轴不重叠,就不碰撞
return True # 三个轴都重叠,碰撞!实际理解一下:
┌─── A ──┐ ┌─── B ──┐
│ │ │ │
│ │ │ │
└────────┘ └────────┘
min_A max_A min_B max_B
← 不重叠!因为 max_A < min_B → 返回 False
┌─── A ──┐
│ │
│ ┌─── B ──┐
│ │ │
└───│────────┘
│ │
└────────┘
← 重叠了!三个轴都检查一下... 重叠 → 返回 True
5.2 求重叠区域
如果两个 AABB 碰撞了,那么它们重叠的部分本身也是一个 AABB!
重叠的 AABB 可以这样算:
def overlap_aabb(A, B):
"""返回 A 和 B 重叠区域的 AABB,如果不重叠则返回 None"""
# 先判断是否碰撞
if not aabb_collision(A, B):
return None # 没碰撞就没有重叠区域
# 重叠区域的 min = 两个 AABB min 的逐分量最大值
overlap_min = (
max(A["min"][0], B["min"][0]), # x
max(A["min"][1], B["min"][1]), # y
max(A["min"][2], B["min"][2]) # z
)
# 重叠区域的 max = 两个 AABB max 的逐分量最小值
overlap_max = (
min(A["max"][0], B["max"][0]), # x
min(A["max"][1], B["max"][1]), # y
min(A["max"][2], B["max"][2]) # z
)
return {"min": overlap_min, "max": overlap_max}图形化理解:
两个 AABB 碰撞:
A: min=(1,1), max=(5,5)
B: min=(3,2), max=(7,6)
坐标图:
y
6 ───────────────
│ │
5 │ ┌───A──┐ │
│ │ │ │
4 │ │ ┌───│──┤── B
│ │ │ │ │ │
3 │ │ │ │ │ │
│ │ │ │ │ │
2 │ └──┼───┘ │ │
│ │ │ │
1 │ └───────┘─│
│ │
0 └───────────────────→ x
0 1 2 3 4 5 6 7
重叠区域:
min = (max(1,3), max(1,2)) = (3, 2)
max = (min(5,7), min(5,6)) = (5, 5)
所以重叠的 AABB 是:min=(3,2), max=(5,5)
5.3 完整示例(Python)
def make_aabb_from_vertices(vertices):
xs = [v[0] for v in vertices]
ys = [v[1] for v in vertices]
zs = [v[2] for v in vertices]
return {
"min": (min(xs), min(ys), min(zs)),
"max": (max(xs), max(ys), max(zs))
}
# 物体1:一个正方体
obj1_verts = [(0,0,0), (2,0,0), (2,2,0), (0,2,0),
(0,0,2), (2,0,2), (2,2,2), (0,2,2)]
# 物体2:另一个正方体,向右偏移
obj2_verts = [(1,1,1), (3,1,1), (3,3,1), (1,3,1),
(1,1,3), (3,1,3), (3,3,3), (1,3,3)]
A = make_aabb_from_vertices(obj1_verts)
B = make_aabb_from_vertices(obj2_verts)
print(f"AABB A: min={A['min']}, max={A['max']}")
print(f"AABB B: min={B['min']}, max={B['max']}")
if aabb_collision(A, B):
print("✅ 碰撞了!")
overlap = overlap_aabb(A, B)
print(f"重叠区域 AABB: min={overlap['min']}, max={overlap['max']}")
else:
print("❌ 没碰到")输出结果:
AABB A: min=(0, 0, 0), max=(2, 2, 2)
AABB B: min=(1, 1, 1), max=(3, 3, 3)
✅ 碰撞了!
重叠区域 AABB: min=(1, 1, 1), max=(2, 2, 2)
6️⃣ 总结
| 内容 | 一句话总结 |
|---|---|
| 包围盒 | 给物体套一个简单的几何体,用来快速判断碰撞 |
| 包围球 | 用球包裹,检测最快但贴合最差 |
| AABB | 边和坐标轴平行,简单高效,旋转后需更新 |
| OBB | 可旋转能贴合物体,最准确但计算最复杂 |
| 实战 | AABB 碰撞 = 三个轴都重叠;重叠区域 = 取 min 的最大值和 max 的最小值 |
日常记忆口诀 🎯
包围球:滚着走,最快但最松 AABB:站得正,简单又实用 OBB:转得动,精准但费功 先粗后细:用包围盒筛一遍,再精细检测
进阶阅读方向
- 📖 分离轴定理(SAT):OBB 之间、任意凸多边形之间的碰撞检测
- 📖 BVH(Bounding Volume Hierarchy):用树形结构组织包围盒,加速大规模场景碰撞
- 📖 GJK 算法:更高级的凸体碰撞检测算法
🧠 记住:包围盒的精髓就是 “先粗筛,后细查”——用简单的几何体快速排除不可能的情况,只在必要时做精确计算。这是计算机图形学和游戏开发中最基础也最重要的优化思想之一!
希望这篇教程能帮你理解包围盒的世界,下次玩游戏的时候,你就能知道屏幕背后那些看不见的 “盒子” 在默默地工作啦!🎮✨