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 操作,尤其在大场景或碰撞器很多时。
何时会触发重建:
- 移动静态碰撞体
直接改transform.position会导致引擎强制重建整个 Broadphase。 - 瞬移动态碰撞体
在Update里直接改刚体的transform,相当于“瞬移”,也会触发重建。 - 缩放改变
修改 Collider 尺寸导致 AABB 改变,节点重插。 - 启用/禁用 Collider
在 Broadphase 树中添加或移除节点。 - 大量 Instantiate 或 Destroy
每次实例化或销毁碰撞体,Broadphase 都要插入或删除节点,大量操作会触发局部甚至全局重建。
开发时应尽量控制这些问题带来的开销。
降低误配对和过度接触
- 极大或异常薄长的碰撞体 会扩大 AABB,导致候选暴涨,必要时拆分为多段更紧的碰撞体。
- 物理设置中的接触偏移 不要过大,默认值一般够用,过大会导致接触过早产生,候选与接触点数增多。
- 背面射线检测 和 射线检测触发器 两个选项,不需要就关闭,可减少无用检测开销。
合理使用射线检测和范围检测 API
1. 用 NonAlloc 相关 API 替代普通 API
大批量射线和范围检测时,应优先使用 NonAlloc 相关 API,例如:
Physics.RaycastNonAllocPhysics.OverlapBoxNonAllocPhysics.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 个碰撞器,共约 7 个,虽会牺牲部分真实性,但能明显降低消耗。 - 避免布娃娃之间碰撞
若允许布娃娃之间碰撞,性能会呈指数级增长。
可利用碰撞层矩阵,让布娃娃处于同一 Layer,并关闭该 Layer 与自身的碰撞。 - 及时禁用或移除不活跃的布娃娃
布娃娃完成使命后应及时禁用或移除,减少开销。
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