85.性能优化-CPU-粒子系统优化-CPU侧优化思路
85.1 知识点
知识回顾:粒子系统 CPU 侧优化思路
CPU 侧的目标是少算粒子:降低每帧要处理的粒子数,以及每个粒子要计算的内容。
减少粒子数量的并发
CPU 每帧都要模拟所有存活粒子的状态(位置、速度、旋转、颜色、生命周期等)。可通过粒子系统参数控制并发量:
- 缩短 Start Lifetime(开始生命时间):降低同时存活的粒子数。
- 控制发射频率(Emission → Rate over Time):避免单位时间内粒子过多。
- 限制 Max Particles(最大粒子数):防止粒子数无上限增长。
- 使用 LOD 或距离剔除:远处降低发射率甚至关闭发射。
减少高开销模块的使用
每多开一个模块就会多一份计算。以下模块开销较大,应谨慎使用:
1. 碰撞模块(Collision)
粒子碰撞多在 CPU 上计算,代价高,能不用就不用。若必须用,尽量用简单碰撞体,配合碰撞层矩阵优化,并关闭 Send Collision Messages(发送碰撞信息)。
2. 触发器模块(Triggers)
需要逐粒子检测并频繁调用回调。若必须用,应严格限制触发条件,仅在需要时启用。
3. 噪声扰动模块(Noise)
每帧采样噪声函数开销大。若必须用,可降低频率与强度,或用随机值一次性赋值代替每帧采样。
4. 拖尾模块(Trails)
拖尾会带来大量额外顶点,顶点数易呈指数增长。若必须用,可调大 Minimum Vertex Distance(最小顶点距离),并用生命时间限制最大轨迹长度。
5. 子发射器模块(Sub Emitters)
“爆炸里再爆炸”会导致粒子数指数增长,尽量少用或只在关键时机触发,并减少层级。
减少脚本和排序开销
脚本开销
- 少用碰撞/触发回调:在继承
MonoBehaviour的脚本中,可通过OnParticleCollision(开启 Collision 时触发)和OnParticleTrigger(开启 Trigger 时触发)监听粒子的碰撞与触发。这两个回调可能被大量粒子反复调用,不适合高发射率、大规模粒子;若必须用,可结合 Job System 等方案减轻主线程压力。 - **少用
GetParticles/SetParticles**:GetParticles会把当前所有存活粒子数据拷贝到托管数组,SetParticles再把数据写回。每次调用都涉及托管与原生引擎内部数据的拷贝,非常耗性能,不要每帧频繁调用;若必须用,可结合 Job System。
排序开销
粒子多为半透明,需要按从远到近排序再绘制,Unity 在渲染前由 CPU 做基于深度的排序,会带来开销。可:
- 减少需要排序的粒子数量。
- 能用不透明就用不透明,避免排序。
- 降低排序频率/精度,或利用 Renderer 的 Sorting Fudge(排序偏移系数)缓解排序不稳定问题。
利用剔除和对象池
剔除
- Simulation Space(模拟空间):设为 Local 更容易整体被剔除。
- Stop Action(停止动作):表示播放结束后的行为。一次性特效应设为 Disable 或 Destroy,避免播完后仍在后台模拟;若用对象池,可选 Disable。
对象池
频繁使用的粒子(如技能特效、受击特效)应做对象池化,减少 GC 和创建/销毁带来的 CPU 开销。
减少 DrawCall
多个粒子系统尽量共用相同材质或同一贴图图集。网格相同的粒子可开启 GPU Instancing 降低 DrawCall。
85.2 知识点代码
Lesson85_性能优化_CPU_粒子系统优化_CPU侧优化思路.cs
using UnityEngine;
public class Lesson85_性能优化_CPU_粒子系统优化_CPU侧优化思路 : MonoBehaviour
{
ParticleSystem myParticleSystem;
void Start()
{
#region 知识回顾 粒子系统 CPU侧优化思路
//CPU侧
//优化目标 —— 少算粒子
//降低每帧要处理的粒子数
//降低每个粒子要计算的内容
#endregion
#region 知识点一 减少粒子数量的并发
//CPU每帧都要模拟所有存活粒子的状态
//比如 位置、速度、旋转、颜色、生命周期 等等
//我们可以通过修改粒子系统上的一些参数来减少粒子数量的并发量
//1.缩短 开始生命时间(Start Lifetime)
// 可以降低并发粒子数量
//2.控制 发射频率(Emission中的Rate over Time)
// 可以避免过高的发射率,导致单位时间内粒子数过多
//3.限制 最大粒子数 (Max Particles)
// 防止粒子峰值无限增长
//4.使用Lod或距离剔除
// 远处降低发射率甚至关闭
//等等
#endregion
#region 知识点二 减少高开销模块的使用
//粒子系统中有各种各样的模块
//增加模块的使用会增加计算量从而增加开销
//因此对于以下高开销模块,应该谨慎使用
//1.碰撞模块(Collision)
// 粒子碰撞检测通常在 CPU 上进行,代价极高
// 我们应该能不用就不用
// 如果一定要使用,尽量用简单的碰撞器进行检测
// 并且结合碰撞层矩阵进行优化
// 关闭其中的 发送碰撞信息(Send Collision Messages)勾选项
//2.触发器模块(Triggers)
// 触发需要逐粒子检测,频繁的调用回调函数
// 如果一定要使用,要严格限制触发条件,仅在需要时启用
//3.噪声扰动模块(Noise)
// 每帧采样噪声函数开销大
// 如果一定要用,应该降低频率,减少强度
// 或者用随机值一次性赋值,避免每帧采样
//4.拖尾模块(Trails)
// 拖尾效果会产生额外顶点,顶点数会呈指数级上升
// 如果一定要使用
// 可以调大其中的 最小顶点距离(Minimum Vertex Distance)
// 通过生命时间限制 最大轨迹长度
//5.子发射器模块(Sub Emitters)
// 如果利用子发射器在爆炸里再爆炸会产生指数级的粒子数
// 我们应该尽量不使用它,或只在关键效果触发,减少层级
//等等
#endregion
#region 知识点三 减少脚本和排序开销
//脚本开销:
//1.减少粒子 碰撞和触发 回调函数的使用
// 继承MonoBehaviour的脚本中
// 可以通过
// OnParticleCollision(粒子开启Collision模块时会进入回调)
// 和
// OnParticleTrigger(粒子开启Trigger模块时会进入回调)
// 函数进行粒子的碰撞和触发监听
// 这两个函数,单个粒子都可能触发
// 可能造成成千上万次的调用,导致CPU性能开销极大
// 他们非常不适合大规模高发射率的粒子使用
// 如果一定要使用,可以结合Unity的多线程Job System使用
//2.减少粒子组件中 GetParticles和SetParticles方法的使用
// GetParticles 会把当前存活的所有粒子数据(位置、速度、颜色、大小、剩余寿命等)拷贝到一个数组或 List
// SetParticles() 则把你修改后的粒子数据写回去,覆盖粒子系统中的粒子状态
// 每次调用都会进行托管数组和原生引擎内部数据的拷贝,非常耗性能
// 千万不要每帧频繁调用它们,否则甚至会导致CPU卡死
// 如果一定要使用,可以结合Unity的多线程Job System使用
//排序开销:
// 粒子往往都是半透明渲染的
// 半透明物体需要按照 从远到近 的顺序绘制,否则会出现混合错误
// 所以Unity 在渲染粒子前,CPU需要对所有粒子进行 基于深度的排序
// 这个过程就会带来开销
// 我们应该减少排序带来的开销
// 1.减少需要排序的粒子数量,即减少粒子数量
// 2.避免排序,能使用不透明就使用不透明的粒子
// 3.减少排序的频率和精度
// 可以利用 排序偏移系数(Renderer → Sorting Fudge)
// 解决排序不稳定的问题
// 等等
#endregion
#region 知识点四 利用剔除和对象池
//剔除:
// 1.模拟空间参数(Simulation Space)
// 设置为Local空间更容易整体剔除掉
// 2.停止动作参数(Stop Action)
// 表示粒子播放结束时的处理方式
// 一次性特效记得设为 Disable或Destroy,避免播放完还在后台模拟
// 如果使用了对象池,可以选Disable
// 等等
//对象池:
// 频繁会使用的粒子,应该对象池化
// 可以有效的避免GC和CPU创建销毁的成本
#endregion
#region 知识点五 减少DrawCall
//多个粒子系统尽量用相同材质或贴图图集
//对于网格相同的粒子
//可以使用GPU Instancing减小DrawCall
#endregion
}
private void OnParticleCollision(GameObject other)
{
}
private void OnParticleTrigger()
{
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com