Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

睡眠和岛屿管理

在任何一个典型的游戏场景中,绝大多数的物理对象在绝大多数时间里都是静止的。例如,一座建筑、一堆静止的箱子、地面上的碎石。如果物理引擎对这些静止的物体持续进行完整的物理计算(包括力计算、积分、碰撞检测),将会造成巨大的性能浪费。睡眠 (Sleeping) 机制正是为了解决这个问题而设计的:它允许引擎识别出那些已经停止运动的物体,并将它们暂时“冻结”,从昂贵的物理更新循环中移除。

为了有效地管理睡眠状态,现代物理引擎引入了岛屿 (Islands) 的概念。一个岛屿是一组相互接触或通过关节连接的、非睡眠状态的物体。岛屿管理使得引擎能够以组为单位来处理物体的睡眠和唤醒,极大地提高了效率和鲁棒性。

睡眠机制

进入睡眠

一个刚体要进入睡眠状态,必须满足一定的条件,即它在一段时间内几乎是静止的。这通常通过监控物体的动能或速度来实现。

  • 睡眠条件: 物体的线速度和角速度都低于一个特定的阈值,并且这种状态持续了一小段时间(例如,0.5秒)。

  • 实现方式:

    1. 为每个非睡眠的物体维护一个“运动计时器”或累积的运动量。
    2. 在每个物理步骤结束时,检查物体的线速度 和角速度 的大小。
    3. 如果 (使用平方可以避免开方运算)都低于某个阈值 sleep_threshold,则增加该物体的“静止计时器”。
    4. 如果速度高于阈值,则重置计时器。
    5. 当静止计时器超过一个预设的“睡眠延迟”时,将该物体标记为睡眠状态。
class RigidBody {
    // ...
    bool isAwake = true;
    float motion = 0.0f;
    const float sleepEpsilon = 0.01f;

    void updateSleepState(float dt) {
        if (!isAwake) return;

        // 计算当前运动量(动能的近似)
        float currentMotion = linearVelocity.squaredMagnitude() + angularVelocity.squaredMagnitude();

        // 用一个平滑的过滤器来跟踪运动趋势
        float bias = 0.98f;
        motion = bias * motion + (1.0f - bias) * currentMotion;

        if (motion > sleepEpsilon) {
            // 保持清醒
            motion = 2.0f * sleepEpsilon; // 防止立即睡着
        } else {
            // 接近睡眠
            if (motion < sleepEpsilon * 0.5f) {
                isAwake = false;
            }
        }
    }
};

唤醒

一个睡眠中的物体在以下情况下需要被唤醒:

  • 受到外力作用: 玩家对它施加了一个力或冲量。
  • 被其他物体碰撞: 一个处于活动状态的物体撞击了它。
  • 属性改变: 它的位置、质量等属性被代码直接修改。
  • 级联唤醒: 与它接触的某个睡眠中的物体被唤醒了。

唤醒过程很简单:只需将物体的 isAwake 标志设置为 true。关键在于如何有效地处理级联唤醒,这正是岛屿算法的用武之地。

岛屿管理

想象一个场景:一堆箱子堆叠在一起,全部处于睡眠状态。如果此时你用一个球撞击了最底部的箱子,那么这个箱子应该被唤醒。但事情不止于此,它上面的所有箱子也应该被唤醒,因为它们的支撑物开始移动了。这种连锁反应就是级联唤醒

如果逐个处理这种关系,将会非常低效和复杂。岛屿算法通过将相互关联的物体组合成“岛屿”来优雅地解决这个问题。

1. 什么是岛屿?

  • 一个岛屿是一组通过接触 (Contacts)关节 (Joints) 相互连接的、非睡眠的刚体。
  • 睡眠中的物体和静态物体(如地面)不属于任何岛屿,它们可以被看作是岛屿之间的“海洋”。

2. 岛屿的构建

在每个物理模拟步骤的开始,引擎会动态地重新构建岛屿。

算法流程:

  1. 初始化所有非静态物体为“未访问”。
  2. 遍历所有非静态、非睡眠的物体。
  3. 如果一个物体 B 未被访问: a. 创建一个新的空岛屿 I。 b. 启动一个图遍历(如深度优先搜索DFS或广度优先搜索BFS),起始点为 B。 c. 将 B 加入岛屿 I 并标记为“已访问”。 d. 将 B 的所有接触点和关节连接的邻居物体放入一个待处理队列。 e. 从队列中取出一个物体 N,如果 N 是非静态、非睡眠且未被访问的,则重复步骤 c 和 d。 f. 遍历结束后,岛屿 I 就构建完成了。

这个过程会将整个场景中的动态物体划分为若干个独立的岛屿。

3. 利用岛屿进行睡眠和唤醒

岛屿算法的威力在于,它可以将睡眠决策从单个物体提升到整个岛屿的层面。

  • 整个岛屿一起睡: 引擎可以计算整个岛屿的总运动量。如果一个岛屿中的所有物体在一段时间内都保持低速运动,那么整个岛屿可以同时进入睡眠状态。这避免了由于微小抖动导致堆叠物体之间频繁唤醒和睡眠的问题。

  • 高效的级联唤醒: 当一个睡眠中的物体 S 被一个活动的物体 A 碰撞时:

  1. 唤醒物体 S
  2. 找到物体 A 所在的岛屿 I_A
  3. 将物体 S 以及与 S 接触的所有其他睡眠中的物体,全部合并到岛屿 I_A 中。这个合并过程可以通过再次运行图遍历来完成。

这样,一次碰撞就可以通过岛屿的合并,自然而高效地完成所有相关的级联唤醒,而无需手动追踪复杂的依赖关系。

总结

睡眠和岛屿管理是现代物理引擎不可或缺的性能优化手段。它基于一个简单的观察:游戏世界中的大部分物体在大部分时间里都是静止的。通过睡眠机制,引擎可以避免对这些静止物体进行不必要的计算。而岛屿算法则为管理睡眠状态提供了强大的框架,它将相互作用的物体分组,使得引擎可以对整组物体进行统一的睡眠决策,并能极其高效地处理复杂的级联唤醒。掌握这一技术,是将一个简单的物理模拟器提升为高性能、工业级物理引擎的关键一步。