63.碰撞检测相关优化

63.性能优化-CPU-物理-碰撞检测


63.1 知识点

合理利用碰撞层矩阵设置

要减小碰撞检测带来的开销,根本上就要减少碰撞候选对的产生。

候选对 是指在 Broadphase(广义检测/粗检测)阶段得到的一组可能相交的物体对。
候选对越多,在 Narrowphase(狭义检测/精检测)中的工作量越大,CPU 压力越大。

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

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

有时通过碰撞层矩阵难以精确控制碰撞忽略,例如同层物体中只有个别需要忽略:

  • 角色丢出的手榴弹不要和自己身上的 Collider 相撞
  • 近战武器的 Collider 要忽略和角色自身 Collider 的碰撞

若只需控制个别对象之间的碰撞忽略,可使用 Physics 的忽略碰撞方法:

// 参数1:碰撞体A  参数2:碰撞体B  参数3:是否忽略(true=忽略,false=恢复检测)
Physics.IgnoreCollision(boxCollider, sphereCollider, true);

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

Broadphase 负责快速判断哪些物体可能相交(生成候选对),通常使用空间分区结构(动态 BVH、四叉树、八叉树等),其核心是 AABB。

AABB(Axis-Aligned Bounding Box,轴对齐包围盒):
用六个面分别平行于 X、Y、Z 轴的长方体包住物体,粗略表示对象的体积范围。
每个碰撞体都有一个 AABB,Broadphase 利用所有物体的 AABB 计算候选对。

粗检测重建 指:当物体位置、旋转、缩放变化很大,或静态碰撞体被移动时,引擎不得不重新构建空间分区结构以保持 AABB 正确。
原有的 Broadphase 树、格子、排序信息会被废弃,必须重新插入或重排节点,这是非常耗性能的 CPU 操作,尤其在大场景或碰撞器很多时。

何时会触发重建:

  1. 移动静态碰撞体
    直接改 transform.position 会导致引擎强制重建整个 Broadphase。
  2. 瞬移动态碰撞体
    Update 里直接改刚体的 transform,相当于“瞬移”,也会触发重建。
  3. 缩放改变
    修改 Collider 尺寸导致 AABB 改变,节点重插。
  4. 启用/禁用 Collider
    在 Broadphase 树中添加或移除节点。
  5. 大量 Instantiate 或 Destroy
    每次实例化或销毁碰撞体,Broadphase 都要插入或删除节点,大量操作会触发局部甚至全局重建。

开发时应尽量控制这些问题带来的开销。

降低误配对和过度接触

  1. 极大或异常薄长的碰撞体 会扩大 AABB,导致候选暴涨,必要时拆分为多段更紧的碰撞体。
  2. 物理设置中的接触偏移 不要过大,默认值一般够用,过大会导致接触过早产生,候选与接触点数增多。
  3. 背面射线检测射线检测触发器 两个选项,不需要就关闭,可减少无用检测开销。

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

1. 用 NonAlloc 相关 API 替代普通 API

大批量射线和范围检测时,应优先使用 NonAlloc 相关 API,例如:

  • Physics.RaycastNonAlloc
  • Physics.OverlapBoxNonAlloc
  • Physics.BoxCastNonAlloc

NonAlloc 与普通查询的差别本质上是是否产生新的数组分配,直接影响 CPU 性能和 GC。

普通查询:

RaycastHit[] hits = Physics.RaycastAll(ray, 100f);
// 每次调用都会分配新数组,频繁调用会产生大量垃圾,造成 GC 和 CPU 压力

NonAlloc 查询:

RaycastHit[] hitBuffer = new RaycastHit[10];
int hitCount = Physics.RaycastNonAlloc(ray, hitBuffer, 100f);
// 结果写入传入的数组,不分配新内存,不产生垃圾

2. 合理利用参数

调用相关 API 时,合理使用最大距离、层级过滤、忽略触发器等参数,避免全局检测。

合理使用布娃娃系统

布娃娃系统是 Unity 自带功能,可为指定骨骼自动添加刚体、碰撞器、关节,让角色倒地、摔倒、物理模拟,常用于角色死亡时的尸体飞出效果。
创建路径:GameObject -> 3D Object -> Ragdoll...

优化建议:

  1. 减少碰撞器数量
    可在骨盆、胸部、头部和每个肢体各用 1 个碰撞器,共约 7 个,虽会牺牲部分真实性,但能明显降低消耗。
  2. 避免布娃娃之间碰撞
    若允许布娃娃之间碰撞,性能会呈指数级增长。
    可利用碰撞层矩阵,让布娃娃处于同一 Layer,并关闭该 Layer 与自身的碰撞。
  3. 及时禁用或移除不活跃的布娃娃
    布娃娃完成使命后应及时禁用或移除,减少开销。

63.2 知识点代码

Lesson63_性能优化_CPU_物理_碰撞检测.cs

using UnityEngine;

public class Lesson63_性能优化_CPU_物理_碰撞检测 : MonoBehaviour
{
    public BoxCollider boxCollider;

    public SphereCollider sphereCollider;

    void Start()
    {
        #region 知识点一 合理利用碰撞层矩阵设置

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

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

        #endregion

        #region 知识点二 利用Physics公共类API单独忽略物体碰撞

        //有时可能通过 碰撞层矩阵 不太好进行碰撞忽略
        //比如处于某两层中的物体,并不是所有都不需要碰撞检测,而只希望个别忽略
        //举例:
        //1.角色丢出的手榴弹,不要和自己身上的 Collider 撞到
        //2.近战武器的 Collider 要忽略和角色自身的 Collider 的碰撞
        //等等
        //如果只是想控制个别对象之间的碰撞检测忽略
        //我们可以利用物理公共类中的忽略碰撞方法
        //void Physics.IgnoreCollision(Collider collider1, Collider collider2, bool ignore = true)
        //参数1:碰撞体A
        //参数2:碰撞体B
        //参数3:是否忽略碰撞检测,传true就不会对两个对象进行碰撞检测,传false就会恢复检测
        Physics.IgnoreCollision(boxCollider, sphereCollider, false);

        #endregion

        #region 知识点三 避免全局粗检测(Broadphase)重建

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

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

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

        //何时会触发重建
        //1.移动静态碰撞体(只挂碰撞器组件的对象)
        //  如果你直接改 transform.position,会导致引擎强制重建整个粗检测(Broadphase)
        //2.瞬移动态碰撞体(挂载刚体和碰撞器组件的对象)
        //  如果你在 Update 里直接改刚体的 transform,相当于“瞬移”,也会触发重建
        //3.缩放改变
        //  改 Collider 的尺寸,导致AABB改变,节点重插
        //4.启用/禁用 Collider
        //  会在粗检测(Broadphase)树中添加或移除节点
        //5.大量物体被 Instantiate或Destroy
        //  每次实例化、销毁碰撞体,粗检测(Broadphase)都要插入或删除节点
        //  大量操作会触发局部甚至全局重建

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

        #endregion

        #region 知识点四 降低误配对和过度接触

        //1.极大或异常薄长的碰撞体会扩大 AABB,导致候选暴涨
        //  必要时拆分为多段更紧的 碰撞体(Collider)
        //2.物理设置中的 接触偏移 参数不要过大
        //  默认值一般够用,过大会导致接触过早产生,候选与接触点数增多
        //3.物理设置中的 背面射线检测 和 射线检测触发器 两个选项,不需要就关闭了
        //  可以减少无用检测的开销
        //等等

        #endregion

        #region 知识点五 合理使用射线检测和范围检测API

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

        //  普通的查询,比如
        //RaycastHit[] hits = Physics.RaycastAll(ray, 100f);
        //  会返回一个新的数组对象,每次调用都会分配内存,如果频繁调用,会产生大量垃圾,造成GC,从而带来CPU压力
        //  而 NonAlloc 查询,比如
        //RaycastHit[] hits = new RaycastHit[10]; 
        //int count = Physics.RaycastNonAlloc(ray, hits, 100f);
        //  结果会写进你传入的数组 hits 里,不会再分配新内存
        //  这种调用方式不会产生垃圾,从而不会造成GC压力,也就不会给CPU带来压力了

        //2.调用相关API时,合理利用最大距离、层级过滤、忽略触发器等参数
        //  不要全局检测
        //Physics.Raycast

        #endregion

        #region 知识点六 合理使用布娃娃系统

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

        //1.减少布娃娃系统使用的碰撞器数量
        //  可以在骨盆、胸部、头部和每个肢体使用各1个碰撞器
        //  相当于只使用7个碰撞器,虽然会牺牲一些布娃娃的真实性
        //  但是可以大大降低消耗成本
        //2.避免布娃娃之间的碰撞
        //  如果允许布娃娃之间碰撞,性能会呈指数级增长
        //  我们可以利用碰撞层矩阵,让布娃娃处于同一层级后让其不要和自己所在层进行碰撞
        //3.及时禁用或移除不活跃的布娃娃
        //  当布娃娃完成它的使命后,应该及时禁用或者移除它,从而减少开销

        #endregion
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏