物理引擎

真实感模拟的物理内核——碰撞检测 (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 次迭代