85.粒子系统CPU侧优化思路

85.性能优化-CPU-粒子系统优化-CPU侧优化思路


85.1 知识点

知识回顾:粒子系统 CPU 侧优化思路

CPU 侧的目标是少算粒子:降低每帧要处理的粒子数,以及每个粒子要计算的内容。

减少粒子数量的并发

CPU 每帧都要模拟所有存活粒子的状态(位置、速度、旋转、颜色、生命周期等)。可通过粒子系统参数控制并发量:

  1. 缩短 Start Lifetime(开始生命时间):降低同时存活的粒子数。
  2. 控制发射频率(Emission → Rate over Time):避免单位时间内粒子过多。
  3. 限制 Max Particles(最大粒子数):防止粒子数无上限增长。
  4. 使用 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 做基于深度的排序,会带来开销。可:

  1. 减少需要排序的粒子数量。
  2. 能用不透明就用不透明,避免排序。
  3. 降低排序频率/精度,或利用 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

×

喜欢就点赞,疼爱就打赏