Box2D物理引擎架构:从BroadPhase到ContactSolver
Box2D作为一款成熟的2D物理引擎,其核心架构围绕碰撞检测与物理响应展开。本文将深入剖析从BroadPhase( broad phase,宽相位)到ContactSolver( contact solver,接触求解器)的完整流程,揭示物理世界中物体交互的底层实现。
物理引擎核心流程概述
Box2D的物理模拟遵循经典的"检测-求解" pipeline(管道),主要包含四个阶段:
- BroadPhase:快速筛选潜在碰撞对,排除明显不相交的物体对
- 碰撞检测:精确计算碰撞点和法向量
- 约束生成:将碰撞信息转化为数学约束
- ContactSolver:求解约束方程组,更新物体运动状态
关键数据结构
物理世界的核心状态由以下模块维护:
- 动态树(src/dynamic_tree.c):空间划分数据结构,加速碰撞检测
- 接触管理器(src/contact.c):管理碰撞对生命周期
- 约束图(src/constraint_graph.c):组织待求解的物理约束
BroadPhase:碰撞检测的第一道防线
BroadPhase(宽相位)阶段的任务是高效找出潜在碰撞的物体对,避免对所有物体对进行昂贵的精确碰撞检测。Box2D采用动态AABB树(Axis-Aligned Bounding Box Tree,轴对齐包围盒树)实现这一功能。
动态AABB树原理
动态AABB树是一种自平衡空间索引结构,每个节点表示一个轴对齐包围盒(AABB):
// 动态树节点结构 [src/dynamic_tree.c#L19-L50]
typedef struct b2TreeNode
{
b2AABB aabb; // 节点包围盒
uint64_t categoryBits; // 碰撞过滤掩码
union {
struct { int32_t child1, child2; }; // 子节点(内部节点)
uint64_t userData; // 用户数据(叶节点)
};
union { int32_t parent; int32_t next; }; // 父节点或自由链表指针
uint16_t height; // 节点高度
uint16_t flags; // 节点状态标志
} b2TreeNode;
树的构建采用表面积启发式算法(Surface Area Heuristic),通过最小化包围盒表面积来优化查询效率。当物体移动时,树会动态调整结构以保持平衡性。
碰撞对检测流程
-
移动标记:跟踪位置发生显著变化的物体
// 标记需要重新检测的物体 [src/broad_phase.h#L74-L82] static inline void b2BufferMove(b2BroadPhase* bp, int queryProxy) { bool alreadyAdded = b2AddKey(&bp->moveSet, queryProxy + 1); if (!alreadyAdded) { b2IntArray_Push(&bp->moveArray, queryProxy); } } -
重叠查询:对移动物体执行AABB重叠测试
-
碰撞对过滤:使用碰撞掩码(categoryBits)过滤无效碰撞对
-
结果缓存:将潜在碰撞对存入
pairSet集合([src/broad_phase.h#L48])
性能优化策略
- 增量更新:仅重新处理移动的物体
- 分层树结构:内部节点自动合并子节点AABB
- SIMD优化:使用SSE指令加速AABB合并计算([src/dynamic_tree.c#L745-L751])
从BroadPhase到Contact:碰撞信息的精确计算
经过BroadPhase筛选的潜在碰撞对,需要进行精确碰撞检测以获取碰撞点、法向量等详细信息。
碰撞检测流水线
-
形状类型路由:根据碰撞体形状类型调用相应的碰撞算法
// 碰撞检测函数注册 [src/contact.c#L202-L220] void b2InitializeContactRegisters(void) { b2AddType(b2CircleManifold, b2_circleShape, b2_circleShape); b2AddType(b2CapsuleAndCircleManifold, b2_capsuleShape, b2_circleShape); // ... 其他形状组合 } -
流形计算:针对不同形状组合使用专用碰撞算法
- 圆-圆碰撞:[src/contact.c#L105-L110]
- 胶囊-圆碰撞:[src/contact.c#L112-L117]
- 多边形-多边形碰撞:[src/contact.c#L140-L145]
-
接触点持久化:匹配连续帧中的碰撞点,保持接触状态连续性
// 接触点匹配 [src/contact.c#L620-L654] for (int i = 0; i < pointCount; ++i) { b2ManifoldPoint* mp2 = contactSim->manifold.points + i; for (int j = 0; j < oldManifold.pointCount; ++j) { if (mp1->id == id2) { mp2->normalImpulse = mp1->normalImpulse; // 脉冲继承,用于热启动 mp2->tangentImpulse = mp1->tangentImpulse; mp2->persisted = true; break; } } }
碰撞信息存储
接触信息被封装在b2Contact和b2ContactSim结构中:
// 接触模拟数据 [src/contact.c#L337-L362]
b2ContactSim* contactSim = b2ContactSimArray_Add(&set->contactSims);
contactSim->contactId = contactId;
contactSim->shapeIdA = shapeIdA;
contactSim->shapeIdB = shapeIdB;
contactSim->cache = b2_emptySimplexCache;
contactSim->manifold = (b2Manifold){ 0 };
contactSim->friction = world->frictionCallback(...);
contactSim->restitution = world->restitutionCallback(...);
b2Manifold结构记录碰撞几何信息,包括法向量和接触点集:
ContactSolver:求解物理约束
ContactSolver(接触求解器)是物理引擎的"心脏",负责计算物体在碰撞时的受力和运动状态变化。
约束表示
每个碰撞接触点会生成一个法向约束和一个切向约束(摩擦力):
// 接触约束结构 [src/contact_solver.h#L22-L38]
typedef struct b2ContactConstraint
{
int indexA; // 物体A索引
int indexB; // 物体B索引
b2ContactConstraintPoint points[2];// 接触点数组
b2Vec2 normal; // 碰撞法线
float invMassA, invMassB; // inverse mass(逆质量)
float invIA, invIB; // inverse inertia(逆转动惯量)
float friction; // 摩擦系数
float restitution; // 恢复系数
float tangentSpeed; // 切向速度
float rollingResistance; // 滚动阻力
float rollingMass; // 滚动质量
float rollingImpulse; // 滚动冲量
b2Softness softness; // 约束柔度
int pointCount; // 接触点数量
} b2ContactConstraint;
约束求解算法
Box2D采用**Sequential Impulse(顺序冲量法)**求解约束系统:
-
热启动:使用上一帧的冲量值初始化求解器
// 热启动接触约束 [src/contact_solver.h#L51] void b2WarmStartContactsTask(int startIndex, int endIndex, b2StepContext* context, int colorIndex); -
迭代求解:多次迭代更新冲量,逐步逼近约束方程的解
// 求解接触约束 [src/contact_solver.h#L52] void b2SolveContactsTask(int startIndex, int endIndex, b2StepContext* context, int colorIndex, bool useBias); -
位置修正:应用位置冲量修正物体位置,防止穿透
并行求解优化
为提升性能,Box2D使用图着色算法将约束划分为独立组,实现并行求解:
// 约束图颜色划分 [src/constraint_graph.c]
b2GraphColor colors[B2_GRAPH_COLOR_COUNT];
每个颜色组的约束可以安全地并行求解,因为组内约束不存在共享物体。这种方法在多核CPU上可显著提升性能。
实战分析:碰撞响应全过程
以下落的箱子撞击地面为例,完整的物理响应流程如下:
-
BroadPhase检测:
- 箱子AABB与地面AABB重叠
- 添加到潜在碰撞对列表([src/broad_phase.h#L35-L36])
-
精确碰撞检测:
- 调用
b2CollidePolygonAndPolygon计算碰撞流形 - 生成2个接触点([src/contact.c#L620-L654])
- 调用
-
约束生成:
- 创建
b2ContactConstraint结构 - 设置摩擦系数0.3,恢复系数0.2
- 创建
-
约束求解:
- 热启动:使用上次接触冲量(0)
- 迭代10次更新冲量
- 应用冲量改变箱子速度(法向反弹+切向摩擦减速)
总结与扩展
Box2D的物理引擎架构通过分层设计实现了高效精确的物理模拟:
- 空间划分(BroadPhase)减少碰撞检测数量级
- 精确碰撞计算提供详细接触信息
- 约束求解将物理定律转化为数值解
开发者可通过调整以下参数优化物理模拟:
b2World构造参数:重力、迭代次数- 形状属性:密度、摩擦系数、恢复系数
- 求解器参数:位置迭代次数、速度迭代次数
深入理解这些核心模块有助于开发更真实的物理交互,解决复杂的碰撞场景和性能瓶颈。更多高级特性可参考官方文档:docs/simulation.md。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



