物理引擎
真实感模拟的物理内核——碰撞检测 (BVH/SAT/GJK) + 刚体动力学 + 约束求解。
概念
物理引擎是游戏中的”物理规则执行者”。它分为两个正交的部分:碰撞检测 (回答”哪些物体碰撞了”) 和动力学模拟 (回答”碰撞后物体如何运动”)。现代物理引擎 (如 PhysX, Bullet) 是”约束求解器的集合”——每一帧通过迭代收敛解决大量数学约束。
核心组件
| 组件 | 职责 | 关键算法 |
|---|---|---|
| 粗筛阶段 (Broad Phase) | 快速排除不可能碰撞的物体对 | BVH、八叉树、扫描剪枝 |
| 精确检测 (Narrow Phase) | 对疑似碰撞对做精确碰撞测试 | SAT (凸多面体)、GJK+EPA (任意凸体) |
| 刚体动力学 | 力 → 加速度 → 速度 → 位置积分 | 欧拉法、Verlet 积分、RK4 |
| 约束求解 | 解决穿透、关节、摩擦力 | Sequential Impulse (投影 Gauss-Seidel) |
| 碰撞响应 | 碰撞后的反弹、摩擦 | 冲量法, 恢复系数 |
物理管线
mermaid
graph TD
FORCE["施加外力<br/>重力, 风力, 玩家输入"] --> INTEGRATE["积分运动<br/>加速度→速度→位置"]
INTEGRATE --> BROAD["粗筛碰撞<br/>BVH 更新, 生成碰撞候选对"]
BROAD --> NARROW["精确碰撞检测<br/>SAT / GJK, 生成接触点"]
NARROW --> SOLVE["约束求解<br/>迭代解: 接触约束+关节约束"]
SOLVE --> POSITION["位置修正<br/>穿透解决, 缝隙闭合"]
POSITION --> READ["同步到渲染<br/>Transform 矩阵更新"]
style BROAD fill:#cfc,stroke:#333
style NARROW fill:#fc9,stroke:#333
style SOLVE fill:#9cf,stroke:#333
碰撞检测
粗筛阶段 (Broad Phase):
暴力 O(n^2) 不可行 (n=1000 → 50 万对)
加速结构: AABB 树 (BVH) 或网格
BVH (Bounding Volume Hierarchy):
每个物体用 AABB (轴对齐包围盒) 包裹
构建二叉树: 叶子是物体, 内部节点是子节点 AABB 的并集
查询: 遍历 BVH 找 AABB 重叠对
时间复杂度: 静态 O(log n), 动态 O(n log n) 重建
精确检测 (Narrow Phase):
SAT (Separating Axis Theorem):
对于两个凸多面体 A 和 B:
寻找一个分离轴, 使得 A 和 B 在该轴上的投影不重叠
候选轴: A 的每个面法线 + B 的每个面法线 + A 每条边 × B 每条边
如果所有候选轴都找不到分离轴 → A 和 B 相交
GJK (Gilbert-Johnson-Keerthi):
用于两个任意凸体的碰撞检测 (包括球、胶囊、凸多面体)
核心思想: 两个凸体的 Minkowski 差包含原点 ≡ 两体相交
Support 函数: 对于给定方向, 返回物体在该方向上的最远点
算法: 在 Minkowski 差形状中构建一个包围原点的单纯形 (simplex)
如果不能包围原点 → 不相交
刚体动力学
牛顿第二定律: F = m * a
积分步骤:
// 欧拉积分 (简单但不够稳定)
velocity += acceleration * dt
position += velocity * dt
// 半隐式欧拉 (Symplectic Euler, 更稳定)
velocity += acceleration * dt
position += velocity * dt
// 先更新速度再用新速度更新位置
// Verlet 积分 (适合粒子、布料):
new_position = 2 * position - previous_position + acceleration * dt^2
velocity = (new_position - previous_position) / (2 * dt)
// 速度是隐式的, 适合约束投影
旋转:
角速度 ω → 四元数 q 的更新:
q_dot = 0.5 * quat(0, ω_x, ω_y, ω_z) * q
q_new = normalize(q + q_dot * dt)
Sequential Impulse 约束求解
约束 = 应该满足但被违反的方程
例如: C(penetration) = 碰撞深度, 目标 C = 0
Sequential Impulse:
FOR 迭代 = 1 TO 最大迭代次数:
FOR 每个约束 cj:
计算该约束的违反程度 (C())
计算约束需要施加的冲量 (λ)
λ = -C() / (JM^{-1}J^T)
施加冲量: velocity += M^{-1} * J^T * λ
钳制冲量 (摩擦锥限制)
这是 Projected Gauss-Seidel 法的物理表述
迭代次数越多 → 结果越精确 → 计算成本越高
典型: 6-20 次迭代