OBB 详解:从基础概念到碰撞检测算法(全伪代码实现)
1. 什么是 OBB?
OBB(Oriented Bounding Box) 即有向包围盒,是计算几何与游戏开发中比 AABB 更精确的碰撞检测体积。
它的核心特征有两点:
- 包围盒:一个完全包裹住物体的最小六面体(2D 下为矩形)。
- 有向:该包围盒可以任意旋转,不平行于世界坐标轴,而是与物体自身的局部坐标系对齐。
正因为"有向",OBB 能更紧密地包裹旋转后的物体,大幅减少 AABB 中常见的"空白区域",但代价是计算复杂度显著提升。
2. OBB 的数据结构
在 3D 空间中,一个 OBB 由 中心点、三个半轴向量 和 三个轴方向 共同定义:
结构 OBB:
向量3 center // 包围盒中心点
向量3 axis_x // 局部 X 轴方向(单位向量)
向量3 axis_y // 局部 Y 轴方向(单位向量)
向量3 axis_z // 局部 Z 轴方向(单位向量)
浮点数 half_x // 沿 axis_x 方向的半边长
浮点数 half_y // 沿 axis_y 方向的半边长
浮点数 half_z // 沿 axis_z 方向的半边长
在 2D 场景中,去掉 Z 轴分量即可:
结构 OBB2D:
向量2 center // 包围盒中心点
向量2 axis_x // 局部 X 轴方向(单位向量)
向量2 axis_y // 局部 Y 轴方向(单位向量)
浮点数 half_x // 沿 axis_x 方向的半边长
浮点数 half_y // 沿 axis_y 方向的半边长
关键约束:
axis_x、axis_y、axis_z两两正交且为单位向量(即构成标准正交基)。- 半边长均为正数。
3. 核心操作与伪代码
3.1 从顶点列表生成 OBB(主成分分析法)
生成物体的 OBB 通常使用主成分分析(PCA) 计算顶点分布的主方向:
函数 生成OBB(顶点列表 points) -> OBB:
如果 points 为空:
返回 空OBB
n = points 的长度
// 步骤1:计算质心(中心点)
center = (0, 0, 0)
对于 每个点 p 属于 points:
center = center + p
center = center / n
// 步骤2:计算协方差矩阵 3x3
cov = 零矩阵(3, 3)
对于 每个点 p 属于 points:
d = p - center
cov[0][0] = cov[0][0] + d.x * d.x
cov[0][1] = cov[0][1] + d.x * d.y
cov[0][2] = cov[0][2] + d.x * d.z
cov[1][1] = cov[1][1] + d.y * d.y
cov[1][2] = cov[1][2] + d.y * d.z
cov[2][2] = cov[2][2] + d.z * d.z
cov = cov / n
// 填充对称元素
cov[1][0] = cov[0][1]
cov[2][0] = cov[0][2]
cov[2][1] = cov[1][2]
// 步骤3:计算协方差矩阵的特征向量和特征值
// (此处调用标准库或数值方法,如幂迭代法)
(eigenvalues, eigenvectors) = 计算特征分解(cov)
// 步骤4:按特征值从大到小排序,取前三个特征向量作为轴
// 特征值最大的方向为 axis_x,次大为 axis_y,最小为 axis_z
按特征值降序排列 eigenvectors
axis_x = eigenvectors[0]
axis_y = eigenvectors[1]
axis_z = eigenvectors[2]
// 步骤5:将所有顶点投影到三个轴上,计算半边长
half_x = 0
half_y = 0
half_z = 0
对于 每个点 p 属于 points:
d = p - center
proj_x = 绝对值(点积(d, axis_x))
proj_y = 绝对值(点积(d, axis_y))
proj_z = 绝对值(点积(d, axis_z))
half_x = 最大值(half_x, proj_x)
half_y = 最大值(half_y, proj_y)
half_z = 最大值(half_z, proj_z)
返回 OBB(center, axis_x, axis_y, axis_z, half_x, half_y, half_z)
时间复杂度:O(n),n 为顶点数量(特征分解部分为常数时间,因为协方差矩阵固定为 3x3)。
3.2 判断点是否在 OBB 内部
将点从世界空间变换到 OBB 的局部空间,然后判断是否在局部 AABB 范围内:
函数 点是否在OBB内(点 p, OBB box) -> 布尔值:
// 将点转换到 OBB 局部坐标系
d = p - box.center
local_x = 点积(d, box.axis_x)
local_y = 点积(d, box.axis_y)
local_z = 点积(d, box.axis_z)
// 在局部坐标系中判断是否在 AABB 范围内
返回 (local_x >= -box.half_x 且 local_x <= box.half_x) 且
(local_y >= -box.half_y 且 local_y <= box.half_y) 且
(local_z >= -box.half_z 且 local_z <= box.half_z)
3.3 两个 OBB 的相交测试(分离轴定理 SAT)
核心原理:两个凸多面体不相交,当且仅当存在一条分离轴,使得两个物体在该轴上的投影不重叠。
对于 OBB,潜在分离轴包括:
- 第一个 OBB 的 3 个轴方向(
axis_x1,axis_y1,axis_z1) - 第二个 OBB 的 3 个轴方向(
axis_x2,axis_y2,axis_z2) - 以上 6 个轴两两叉积得到的 9 个轴(共计 15 条轴)
函数 OBB是否相交(OBB a, OBB b) -> 布尔值:
// 收集所有待测试的轴
axes = []
// 添加 a 的三个轴
axes.追加(a.axis_x)
axes.追加(a.axis_y)
axes.追加(a.axis_z)
// 添加 b 的三个轴
axes.追加(b.axis_x)
axes.追加(b.axis_y)
axes.追加(b.axis_z)
// 添加两两叉积的 9 个轴
对于 每个轴 i 属于 [a.axis_x, a.axis_y, a.axis_z]:
对于 每个轴 j 属于 [b.axis_x, b.axis_y, b.axis_z]:
cross_axis = 叉积(i, j)
如果 cross_axis 不是零向量:
axes.追加(归一化(cross_axis))
// 对每条轴进行投影测试
对于 每个轴 axis 属于 axes:
如果 在轴上投影是否分离(a, b, axis) 为 真:
返回 不相交 // 找到一条分离轴即可判定不相交
返回 相交 // 所有轴上都重叠
单轴投影测试函数:
函数 在轴上投影是否分离(OBB a, OBB b, 向量3 axis) -> 布尔值:
// 计算 a 在 axis 上的投影区间
center_a_proj = 点积(a.center, axis)
// 计算 a 的半边长在 axis 上的投影(半径)
radius_a = a.half_x * 绝对值(点积(a.axis_x, axis)) +
a.half_y * 绝对值(点积(a.axis_y, axis)) +
a.half_z * 绝对值(点积(a.axis_z, axis))
min_a = center_a_proj - radius_a
max_a = center_a_proj + radius_a
// 计算 b 在 axis 上的投影区间
center_b_proj = 点积(b.center, axis)
radius_b = b.half_x * 绝对值(点积(b.axis_x, axis)) +
b.half_y * 绝对值(点积(b.axis_y, axis)) +
b.half_z * 绝对值(点积(b.axis_z, axis))
min_b = center_b_proj - radius_b
max_b = center_b_proj + radius_b
// 判断区间是否分离
返回 (max_a < min_b) 或 (max_b < min_a)
注意:SAT 是 OBB 碰撞检测的标准算法,但需要测试 15 条轴(3D 情况下),2D 情况下只需测试 4 条轴(两个矩形的 2 个轴 + 2 个叉积轴)。
3.4 计算 OBB 的 8 个角点(用于渲染或调试)
函数 OBB转顶点列表(OBB box) -> 顶点列表[8]:
// 计算三个半轴向量
half_vec_x = box.axis_x * box.half_x
half_vec_y = box.axis_y * box.half_y
half_vec_z = box.axis_z * box.half_z
// 8 个角点 = center ± half_vec_x ± half_vec_y ± half_vec_z
p000 = box.center - half_vec_x - half_vec_y - half_vec_z
p100 = box.center + half_vec_x - half_vec_y - half_vec_z
p010 = box.center - half_vec_x + half_vec_y - half_vec_z
p110 = box.center + half_vec_x + half_vec_y - half_vec_z
p001 = box.center - half_vec_x - half_vec_y + half_vec_z
p101 = box.center + half_vec_x - half_vec_y + half_vec_z
p011 = box.center - half_vec_x + half_vec_y + half_vec_z
p111 = box.center + half_vec_x + half_vec_y + half_vec_z
返回 [p000, p100, p010, p110, p001, p101, p011, p111]
3.5 将 OBB 转换为 AABB(用于粗筛)
将 OBB 的所有角点投影到世界坐标轴上,生成包裹它的最小 AABB:
函数 OBB转AABB(OBB box) -> AABB:
vertices = OBB转顶点列表(box)
return 生成AABB(vertices) // 调用 AABB 的生成函数
3.6 OBB 的膨胀与收缩
函数 膨胀OBB(OBB box, 浮点数 margin) -> OBB:
// 在三个半轴方向上同时增加边距
返回 OBB(
center = box.center,
axis_x = box.axis_x,
axis_y = box.axis_y,
axis_z = box.axis_z,
half_x = box.half_x + margin,
half_y = box.half_y + margin,
half_z = box.half_z + margin
)
3.7 旋转 OBB(绕任意轴旋转)
函数 旋转OBB(OBB box, 向量3 axis, 浮点数 angle) -> OBB:
// 构造旋转矩阵
rot_mat = 构造旋转矩阵(axis, angle)
// 旋转三个轴方向
new_axis_x = rot_mat * box.axis_x
new_axis_y = rot_mat * box.axis_y
new_axis_z = rot_mat * box.axis_z
// 中心点也绕同一轴旋转(假设物体绕自身中心旋转)
// 若绕世界原点旋转,则需先平移再旋转再平移回去
new_center = rot_mat * box.center
返回 OBB(new_center, new_axis_x, new_axis_y, new_axis_z,
box.half_x, box.half_y, box.half_z)
4. OBB 与射线求交
4.1 算法原理
将射线变换到 OBB 的局部坐标系中,然后使用 AABB 与射线求交算法(Slab 法):
函数 射线与OBB求交(射线 r, OBB box) -> 布尔值 或 交点信息:
// 步骤1:将射线原点变换到 OBB 局部空间
d = r.origin - box.center
local_origin.x = 点积(d, box.axis_x)
local_origin.y = 点积(d, box.axis_y)
local_origin.z = 点积(d, box.axis_z)
// 步骤2:将射线方向变换到 OBB 局部空间
local_dir.x = 点积(r.direction, box.axis_x)
local_dir.y = 点积(r.direction, box.axis_y)
local_dir.z = 点积(r.direction, box.axis_z)
// 步骤3:构造局部 AABB
local_box.min = (-box.half_x, -box.half_y, -box.half_z)
local_box.max = (box.half_x, box.half_y, box.half_z)
// 步骤4:使用 AABB 射线求交算法
返回 射线与AABB求交(局部射线(local_origin, local_dir), local_box)
5. OBB 与 AABB 的相交测试
混合测试中,将 AABB 视为一种特殊的 OBB(轴为单位向量,半轴为 AABB 的半边长),然后使用 SAT 算法:
函数 AABB与OBB是否相交(AABB aabb, OBB obb) -> 布尔值:
// 将 AABB 转换为 OBB
center_a = (aabb.min + aabb.max) / 2
half_a = (aabb.max - aabb.min) / 2
obb_a = OBB(center_a, (1,0,0), (0,1,0), (0,0,1), half_a.x, half_a.y, half_a.z)
// 调用标准 OBB-OBB 相交测试
返回 OBB是否相交(obb_a, obb)
6. 实际应用中的注意事项
6.1 浮点数精度问题
与 AABB 类似,OBB 的 SAT 测试中对边界情况也需要容差处理:
函数 在轴上投影是否分离带容差(OBB a, OBB b, 向量3 axis, 浮点数 eps) -> 布尔值:
// ... 计算 min_a, max_a, min_b, max_b ...
返回 (max_a < min_b - eps) 或 (max_b < min_a - eps)
6.2 生成 OBB 时的退化情况
当物体近乎线性或平面分布时,协方差矩阵可能产生非常小的特征值,导致某些轴方向不稳定:
函数 生成OBB安全(顶点列表 points) -> OBB:
obb = 生成OBB(points)
// 检查半边长是否过小
如果 obb.half_x < 极小值:
// 使用备用轴(如世界轴)替代
obb.axis_x = (1, 0, 0)
// 重新计算该轴上的半边长
// ...
如果 obb.half_y < 极小值 或 obb.half_z < 极小值:
// 类似处理
返回 obb
6.3 OBB 的适用场景与局限
| 优点 | 缺点 |
|---|---|
| 比 AABB 更紧密地包裹旋转物体 | 生成算法(PCA)计算量较大 |
| 减少碰撞检测的"假阳性" | SAT 相交测试需测试 15 条轴(3D) |
| 适用于刚体物理引擎 | 存储开销是 AABB 的 3 倍以上 |
| 可参与更精确的碰撞响应 | 更新旋转后需重新计算轴方向 |
7. 完整示例:OBB 类的伪代码接口
类 OBB:
// 成员变量
私有 向量3 center
私有 向量3 axis_x
私有 向量3 axis_y
私有 向量3 axis_z
私有 浮点数 half_x
私有 浮点数 half_y
私有 浮点数 half_z
// 构造函数
函数 初始化(向量3 c, 向量3 ax, 向量3 ay, 向量3 az,
浮点数 hx, 浮点数 hy, 浮点数 hz):
// 检查正交性和单位性
如果 不是单位向量(ax) 或 不是单位向量(ay) 或 不是单位向量(az):
抛出异常 "轴必须是单位向量"
如果 绝对值(点积(ax, ay)) > 极小值 或 ...:
抛出异常 "轴必须两两正交"
如果 hx <= 0 或 hy <= 0 或 hz <= 0:
抛出异常 "半边长必须为正"
this.center = c
this.axis_x = ax
this.axis_y = ay
this.axis_z = az
this.half_x = hx
this.half_y = hy
this.half_z = hz
// 查询方法
函数 获取中心() -> 向量3: 返回 this.center
函数 获取轴X() -> 向量3: 返回 this.axis_x
函数 获取轴Y() -> 向量3: 返回 this.axis_y
函数 获取轴Z() -> 向量3: 返回 this.axis_z
函数 获取半边长() -> (浮点数, 浮点数, 浮点数):
返回 (this.half_x, this.half_y, this.half_z)
// 判定方法
函数 包含点(点 p) -> 布尔值:
返回 点是否在OBB内(p, this)
函数 与OBB相交(OBB other) -> 布尔值:
返回 OBB是否相交(this, other)
// 修改方法(返回新实例)
函数 旋转(向量3 axis, 浮点数 angle) -> OBB:
返回 旋转OBB(this, axis, angle)
函数 平移(向量3 offset) -> OBB:
返回 OBB(this.center + offset, this.axis_x, this.axis_y,
this.axis_z, this.half_x, this.half_y, this.half_z)
函数 膨胀(浮点数 margin) -> OBB:
返回 膨胀OBB(this, margin)
// 工具方法
函数 转顶点列表() -> 顶点列表[8]:
返回 OBB转顶点列表(this)
函数 转AABB() -> AABB:
返回 OBB转AABB(this)
8. OBB 与 AABB 的对比总结
| 对比维度 | AABB | OBB |
|---|---|---|
| 数据结构 | 2 个点(6 个浮点数) | 中心 + 3 轴 + 3 半长(15 个浮点数) |
| 存储开销 | 低 | 中高(AABB 的 2.5 倍) |
| 生成算法 | O(n) 简单遍历 | O(n) + 特征分解(3x3 矩阵) |
| 相交测试 | 6 次比较(3D) | SAT:15 条轴测试 |
| 对旋转物体的包裹性 | 差(大量空白) | 好(紧密贴合) |
| 更新旋转成本 | 需从顶点重新生成 | 仅旋转 3 个轴方向 |
| 适用场景 | 静态场景、粗筛 | 刚体物理、精确碰撞 |
9. 总结
OBB 是 AABB 的"旋转增强版",它在保留包围盒简洁性的同时,通过引入局部坐标系实现了对旋转物体的紧密包裹。
其核心价值在于:
- 更精确的碰撞体积——减少了 AABB 中常见的误报(false positive)。
- 成熟的数学工具——SAT 分离轴定理为 OBB 提供了完备的相交测试方案。
- 适合刚体系统——与物体的变换紧密结合,更新成本可控。
尽管 OBB 的存储和计算成本均高于 AABB,但在需要较高碰撞精度的场合(如物理引擎、机器人仿真、CAD 系统),OBB 是不可或缺的基础几何工具。
一句话记住 OBB:中心加三轴,半长定大小,SAT 测相交,十五轴见分晓。
&spm=1001.2101.3001.5002&articleId=163894663&d=1&t=3&u=22f725ca56414ba2a35a7940fe57969b)
7689

被折叠的 条评论
为什么被折叠?



