UPL7-8——碰撞检测相关

合理利用碰撞层矩阵设置

想要减小碰撞检测带来的开销,根本上就是要减少碰撞候选对的产生
所谓的 候选对 就是指,在广义检测 / 粗检测(Broadphase)阶段得到的一组可能相交的物体对
产生的候选对越多,在下一步进行 狭义检测 / 精检测(Narrowphase)的工作量就越大,CPU 压力就越大

因此,我们应该尽量避免无意义的碰撞产生,最有效直接的方式就是利用碰撞层矩阵
让无关层(相互之间不需要发生碰撞检测的层)全部互斥,这样可以大幅减少候选对的产生,从而达到优化性能的目的

利用 Physics 公共类 API 单独忽略物体碰撞

有时可能通过 碰撞层矩阵 不太好进行碰撞忽略,比如处于某两层中的物体,并不是所有都不需要碰撞检测,而只希望个别忽略

举例:

  1. 角色丢出的手榴弹,不要和自己身上的 Collider 撞到
  2. 近战武器的 Collider​ 要忽略和角色自身的 Collider 的碰撞

等等

如果只是想控制个别对象之间的碰撞检测忽略,我们可以利用物理公共类中的忽略碰撞方法

1
2
3
4
class Physics
{
public static void IgnoreCollision(Collider collider1, Collider collider2, bool ignore = true);
}
  • 参数 1:碰撞体A
  • 参数 2:碰撞体B
  • 参数 3:是否忽略碰撞检测,传 true​ 就不会对两个对象进行碰撞检测,传 false 就会恢复检测
1
Physics.IgnoreCollision(a, b, false);

避免全局粗检测(Broadphase)重建

通过之前的学习我们知道,Broadphase(粗检测) 负责快速判断哪些物体 可能相交(生成候选对)
它通常用 空间分区结构(例如动态 BVH、四叉树、八叉树 等),这些结构的核心就是 AABB(Axis-Aligned Bounding Box)
每个 碰撞体(Collider) 都有一个 AABB,它的处理逻辑就是:利用所有物体的 AABB 计算出 候选对(可能相交的对象)

AABB:轴对齐包围盒的简称
原理是用一个长方体,它的六个面分别平行于 X、Y、Z 轴,用来包住某个物体,利用这种规则粗略的表示一个对象的体积范围

image

而粗检测重建指的就是:当物体的 位置、旋转、缩放 变化很大,或者 静态碰撞体(只挂碰撞器组件的对象) 被移动时
引擎不得不重新构建空间分区结构,以保证 AABB 数据正确,这意味着原来的 Broadphase 树、格子、排序信息会被废弃,必须重新插入或重排节点
这是一个 非常消耗性能的 CPU 操作,尤其是大场景或碰撞器很多时,因此我们要尽量避免这种情况的发生

何时会触发重建:

  1. 移动静态碰撞体(只挂碰撞器组件的对象)

    如果你直接改 transform.position,会导致引擎强制重建整个粗检测(Broadphase)

  2. 瞬移动态碰撞体(挂载刚体和碰撞器组件的对象)

    如果你在 Update​ 里直接改刚体的 transform,相当于“瞬移”,也会触发重建

  3. 缩放改变

    Collider 的尺寸,导致 AABB 改变,节点重插

  4. 启用/禁用 Collider

    会在粗检测(Broadphase)树中添加或移除节点

  5. 大量物体被 Instantiate​ 或 Destroy

    每次实例化、销毁碰撞体,粗检测(Broadphase)都要插入或删除节点,大量操作会触发局部甚至全局重建

因此我们应该在开发时尽量控制这些问题带来的开销

降低误配对和过度接触

  1. 极大或异常薄长的碰撞体会扩大 AABB,导致候选暴涨

    必要时拆分为多段更紧的 碰撞体(Collider

  2. 物理设置中的 接触偏移 参数不要过大

    默认值一般够用,过大会导致接触过早产生,候选与接触点数增多

  3. 物理设置中的 背面射线检测(Queries Hit Backfaces) 和 射线检测触发器(Queries Hit Triggers)两个选项,不需要可以关闭

    可以减少无用检测的开销

等等

合理使用射线检测和范围检测 API

  1. NonAlloc(非分配)相关 API 替代普通 API

    在进行大批量射线和范围检测时,应优先使用 NonAlloc​ 相关API,
    Physics.RaycastNonAlloc​,Physics.OverlapBoxNonAlloc​,Physics.BoxCastNonAlloc​ 等
    NonAlloc 查询和普通查询的差别,本质上是 是否会产生新的数组(内存)分配,直接影响 CPU 性能和内存垃圾回收

    普通的查询,比如:

    1
    RaycastHit[] hits = Physics.RaycastAll(ray, 100f);

    Physics.RaycastAll 会返回一个新的数组对象,每次调用都会分配内存,如果频繁调用,会产生大量垃圾,造成 GC,从而带来 CPU 压力

    而 NonAlloc 查询,比如:

    1
    2
    RaycastHit[] hits = new RaycastHit[10];
    int count = Physics.RaycastNonAlloc(ray, hits, 100f);

    结果会写进你传入的数组 hits 里,不会再分配新内存,这种调用方式不会产生垃圾,从而不会造成 GC 压力,也就不会给 CPU 带来压力了

  2. 调用相关API时,合理利用最大距离、层级过滤、忽略触发器等参数

    使用 Physics.Raycast 等 API 时,合理利用最大距离、层级过滤、忽略触发器等参数,尽量杜绝全局检测

合理使用布娃娃系统

布娃娃系统是 Unity 自带的功能,可以为指定的骨骼自动添加刚体、碰撞器、关节组件
让角色可以倒地、摔倒、物理模拟,常用于角色死亡时尸体飞出效果

GameObject ——> 3D Object ——> Ragdoll…

image

  1. 减少布娃娃系统使用的碰撞器数量

    可以在骨盆、胸部、头部和每个肢体使用各 1 个碰撞器
    相当于只使用 7 个碰撞器,虽然会牺牲一些布娃娃的真实性
    但是可以大大降低消耗成本

  2. 避免布娃娃之间的碰撞

    如果允许布娃娃之间碰撞,性能会呈指数级增长
    我们可以利用碰撞层矩阵,让布娃娃处于同一层级后让其不要和自己所在层进行碰撞

  3. 及时禁用或移除不活跃的布娃娃

    当布娃娃完成它的使命后,应该及时禁用或者移除它,从而减少开销