视频演示
如果你想先看实际效果和性能对比演示,可以直接看这个视频:
GPU粒子性能有多强?Unity粒子转GPUParticle插件实测演示 ParticleSystem一键转GPU粒子 性能拉满 粒子优化

插件简介
〖Unity GPUParticle插件〗是一个面向 ParticleSystem 的 GPU 粒子烘焙与播放工具。它的核心思路很直接:在编辑器里把 ParticleSystem 预制体离线采样成 GPU 可直接播放的数据,运行时不再走原始 ParticleSystem 的逐帧 CPU 模拟,而是使用 合并 Mesh + Shader 进行 GPU 播放。纯shader实现,不依赖任何运行时脚本。在性能、功能、适用范围之间做到最佳平衡。
把ParticleSystem转成GPU粒子 以Mesh + Material作为渲染粒度,就可以使用Unity万人同屏插件: 的高性能合批渲染接口流畅渲染数十万个粒子特效。
这套方案非常适合已经有大量 ParticleSystem 资源、但又遇到同屏数量大、CPU 开销高、Renderer 太多、移动端/微信小游戏压力大的项目。
为什么会有这个需求?因为 Particle System 虽然已经做了不少优化,但它本质上仍然是一个需要主线程参与计算的组件。粒子的发射、速度、生命周期、嵌套子粒子等,都要消耗 CPU。平时一个两个特效看不出问题,但一旦场景里同时出现很多实例,尤其是爆炸、烟花、持续发射、带子粒子层级的特效,性能压力就会很明显。
GPUParticle 要解决的,就是把这部分运行时 CPU 计算全部拿掉。可以把它理解成:保留 ParticleSystem 的制作体验,把"实时计算粒子"改成"直接播放已经烘焙好的 GPU 粒子结果"。
这套插件的定位不是重新发明一套粒子系统,而是尽量保留原有 ParticleSystem 工作流,让特效同学继续用熟悉的方式制作特效,再把最终播放阶段切换到 GPU。
新手友好、简单易用,不需要任何代码就能正常使用,也不需要改原来的特效制作习惯。生成出来的 GPUParticle Prefab 可以直接按传统 GameObject 方式使用。
主要功能模块
| 模块名称 | 功能 |
|---|---|
| GPUParticle Builder | 把 ParticleSystem Prefab 转换成 GPUParticle 构建配置文件,并负责分析、烘焙、输出资源 |
| Build Report | 构建前分析支持情况、时长、帧数、粒子槽位数、预估内存,并提示不支持模块和语义变化 |
| GPU Playback | 运行时使用 Mesh + Material + DataTex 回放粒子,不再依赖原始 ParticleSystem 逐帧模拟 |
| BaseMap Atlas | 自动合并多个发射器用到的贴图与 UV 偏移,减少材质切换 |
| Batch Build | 支持多选 Builder 后批量刷新分析、批量构建 |
支持的粒子模式
插件支持以下粒子渲染模式:
- Billboard(View)
- Billboard(World)
- Billboard(Local)
- Billboard(Facing)
- Billboard(Velocity)
- Stretch
- Horizontal Billboard
- Vertical Billboard
- Mesh
经过对多个特效插件包,数千个粒子特效分析统计,以上特性能够覆盖90%以上的粒子,可以用最轻的计算覆盖较多的功能需求。这已经覆盖了大多数常见手游特效中会用到的主流粒子表现形式。
插件用法
原粒子 和 GPU粒子效果对比
一. 创建 GPUParticle Builder
在 Unity 资源管理界面中,选中一个带 ParticleSystem 的特效预制体,鼠标右键执行:
Assets/Create/GPUParticle/Create Builder From Selection
工具会自动在资源目录中生成一个 GPUParticleBuilder.asset 配置文件,用于保存该特效的烘焙参数和最近一次构建分析结果。
如果需要手动创建空配置,也可以使用:
Create/GPUParticle/GPUParticle Builder Editor
二. GPUParticle Builder 配置
创建完配置文件后,可以在 Inspector 面板中设置构建参数。当前版本常用字段如下:
| 参数 | 作用 | 说明 |
|---|---|---|
Source Prefab |
源特效预制体 | 要转换的 ParticleSystem Prefab |
Output Dir |
输出目录 | 生成的 Mesh、材质、贴图、Prefab、Metadata 保存位置 |
Sample Rate |
采样率 | 允许值会归一化到 15 / 30 / 60 / 90 / 120 |
Bake Duration |
烘焙时长 | 0 表示自动分析时长 |
Loop |
是否循环 | 循环特效会自动分析 LoopAt |
Precision |
数据精度 | RGBAHalf 或 RGBAFloat |
Slot Limit |
粒子槽位限制 | 0 表示自动按 maxParticles |
Include Children |
是否递归子节点 | 开启后会收集子层级中的所有 ParticleSystem |
Copy Renderer Material |
是否拷贝源材质视觉参数 | 会读取颜色、主贴图、Blend 相关信息 |
Shader |
输出 Shader | 默认使用 GPUParticle/GPUParticle |
这里有几个重点:
1. Sample Rate 不是任意值
插件会把采样率归一化到固定档位:
15 / 30 / 60 / 90 / 120
这样做的好处是资源体积更可控,也更方便统一项目内特效规范。
2. Bake Duration 可以自动计算
如果 Bake Duration = 0,工具会根据:
startDelaydurationstartLifetime- 是否循环
自动推导总时长和循环区间。
3. Precision 直接影响数据体积
RGBAHalf:体积更小,默认推荐RGBAFloat:精度更高,但数据量翻倍
大部分常规特效先用 RGBAHalf 即可,只有明显出现精度误差时再升级到 RGBAFloat。
4. Loop 参数不是简单的开和关
很多循环特效一开始都会有一个"从无到有"的发射阶段。
如果直接把整个生命周期从 0 循环到结束,首尾往往接不上,看起来就会穿帮。
GPUParticle 的循环思路更接近真实特效需求:
- 先保留开场的发射阶段
- 再从更合适的时间点进入循环
这样做的好处是,特效既有正常起势,又能在循环时更自然,不会每次都重新从"刚开始喷发"的状态硬切回来。
三. 构建前分析
点击 Inspector 上的 刷新分析 按钮后,插件会先做一次完整分析。分析结果会写入 LastReport,并在面板中展示:
- Emitter 总数 / 支持数
- Duration
- LoopAt / LoopDuration
- FrameCount
- Total Particle Slots
- Estimated Memory
- 每个 Emitter 的槽位和预估内存
- 错误 / 警告信息
这一步非常实用,因为很多问题在真正构建前就能提前暴露出来,例如:
- 某些发射器渲染模式不支持
maxParticles = 0- 只依赖
Rate over Distance Trails / Collision / Trigger / Lights等模块会退化
四. 开始构建
点击 开始构建 后,工具会执行完整的离线烘焙流程:
- 实例化
Source Prefab - 收集可支持的
ParticleSystem - 固定随机种子,保证烘焙结果稳定
- 按时间逐帧
Simulate - 采样粒子位置、尺寸、旋转、颜色、UV、模式等信息
- 生成合并数据纹理
- 生成合并 Mesh
- 生成材质和贴图图集
- 输出可直接使用的
GPUParticle Prefab
如果一次选中了多个 Builder,Inspector 按钮会自动进入批量模式,支持:
- 批量刷新分析
- 批量重置配置
- 批量构建
五. 构建完成后怎么预览
构建成功后,生成出来的 GPU 粒子对象会非常干净。
它本质上就是一套用于回放的资源组合,核心运行对象通常只需要:
- 一个
MeshFilter - 一个
MeshRenderer
不再依赖原始 ParticleSystem 继续做逐帧模拟,也不需要再挂一堆运行脚本去驱动发射逻辑。
对于展示型、循环型或已经定稿的特效,这种形式非常适合直接放进场景预览和比对效果。
GPUParticle 输出资源
构建成功后,插件会输出以下资源:
| 资源名称 | 作用 |
|---|---|
*_GPUParticle.prefab |
最终运行时使用的 GPU 粒子预制体 |
*_GPUParticle_Mesh.asset |
合并后的回放网格 |
*_GPUParticle_Mat.mat |
回放材质 |
*_GPUParticleData.asset |
核心数据纹理 |
*_GPUParticle_Atlas.png |
合并后的底图图集 |
*_GPUParticleMetadata.asset |
构建摘要、回放元信息、Emitter 列表 |
其中最核心的是三样:
MeshMaterialDataTex
运行时真正起作用的就是它们。
运行时播放原理
GPUParticle 的运行时逻辑并不复杂,但非常高效。
材质里会写入一组时间轴元数据:
_GPUParticleMeta = float4(duration, frameCount, sampleRate, loopAt)
Shader 根据当前时间计算:
- 当前播放是否激活
- 当前应该采样哪一帧
- 如果是循环特效,当前落在哪个循环区间
这里有一个很好理解的点:
loopAt >= 0:表示粒子会在指定阶段进入循环loopAt < 0:表示按非循环方式播放完就结束
然后根据顶点中记录的 slotIndex 去读取 DataTex 中对应槽位的数据,恢复每个粒子的:
- 位置
- 大小
- 颜色
- UV
- 旋转
- 拉伸方向
- 渲染模式
- 混合模式
最后在顶点阶段重建出 Billboard、Stretch 或 Mesh 粒子的最终顶点位置。
简单理解就是:
原始 ParticleSystem 的运行时 CPU 计算,被替换成了 Shader 对 DataTex 的一次按槽位回放。
为什么 GPU 粒子能快这么多
视频里最直观的地方,其实不是参数面板,而是对比切换时那一下帧数变化。
原因并不复杂,主要就两点:
1. 一个特效被压缩成了更少的运行时对象
原始粒子特效往往不是"一个粒子系统就完事了",而是:
- 主粒子
- 多个子粒子
- 多个 Renderer
- 多层嵌套发射
所以一个看起来不大的爆炸、烟花或者火焰,实际运行时可能挂了好几层粒子系统。
转换成 GPUParticle 之后,这些播放数据会被合并到一套回放资源里。
对外看起来就只剩下一个可播放对象,运行负担自然会轻很多。
2. 播放阶段主要交给 GPU
GPUParticle 的核心不是"让 CPU 更努力地优化粒子",而是尽量不让 CPU 再去管这件事。
它把特效播放变成了:
- GPU 读取数据纹理
- GPU 根据时间恢复粒子状态
- GPU 在顶点阶段完成位置和朝向重建
所以同屏数量一上来,这种方案的优势就会非常明显。
DataTex 数据结构
这是插件最核心的部分。
每一帧、每一个粒子槽位,会占用 5 行动态数据,充分利用带宽:
| 行号 | 数据内容 |
|---|---|
| Row 0 | centerOS.xyz + axisOctY |
| Row 1 | size.x、size.y / stretch length、mode + blend + visible flags、size.z |
| Row 2 | packed quaternion xyz + axisOctX |
| Row 3 | uvRect |
| Row 4 | color |
如果是 Mesh 粒子,还会额外追加 2 行静态数据:
| 行号 | 数据内容 |
|---|---|
| Static Row 0 | pivot.xyz + alignmentMode |
| Static Row 1 | mesh bounds size.xyz |
也就是说,这张纹理不是普通贴图,而是一份按时间组织的粒子状态数据库。
网格组织方式
GPUParticle 的网格不是简单把所有粒子都做成同一种四边形,而是分成两套策略:
1. 动态粒子:Billboard / Stretch
动态粒子会生成标准四边形,每个粒子槽位:
- 4 个顶点
- 6 个索引
这些粒子会在每一帧重新按以下顺序打包:
SortingLayerSortingOrder- Hierarchy 顺序
- 当前粒子采样顺序
也就是说,动态粒子槽位是按"当前帧可见结果"动态压缩的。
2. Mesh 粒子
Mesh 模式不会每帧重排,而是为每个发射器分配固定槽位区间,并把源 Mesh 复制多份后拼接进合并网格。
这种方式的好处是:
- Mesh 粒子的拓扑更稳定
- Shader 可直接按固定槽位回放
- 还能读取额外的静态 Pivot / Bounds 数据
代价是:
- 顶点数更高
- 数据纹理会多 2 行静态数据
如果发射的不是普通面片,而是 Sphere、碎片模型、物件模型这类真正的 Mesh 粒子,那么这些模型顶点也需要一起烘焙进去。
这时候它的顶点量就会明显高于普通 Billboard 粒子,所以 Mesh 模式更适合少量、重点表现的模型粒子,而不是无脑堆特别多数量。
贴图与材质合并
插件在构建时会自动读取源发射器材质中的视觉数据,并尽量合并成一个统一的回放材质。
当前已确认会处理的内容包括:
_BaseMap/_MainTex_BaseColor/_Color_TintColor- BlendMode 是否为 Additive
- 原始贴图 Scale / Offset
- Texture Sheet Animation 的当前帧 UV
最终每个粒子的 uvRect 并不是简单保存原贴图坐标,而是:
原始贴图偏移 + 图集偏移 + Texture Sheet 当前帧偏移
三者叠加后的最终结果。
这样多个发射器就可以合并到一个回放材质中,减少 DrawCall 和材质切换。
性能测试 Demo
插件附带了性能演示场景:
Assets/Plugins/eFunStudio/Samples/Scenes/Demo/GPUParticlePerformance.unity
对应脚本:
Assets/Plugins/eFunStudio/Samples/Scripts/GPUParticlePerformanceDemo.cs
默认参数如下:
SpawnCount = 10000PerRowCount = 100PerPadding = 1.2
并提供三种对比模式:
- 原始
ParticleSystem Prefab: fps 6 GPUParticle Prefab: fps 106ECSGraphics GPUParticle: 670
这个 Demo 的价值不是帮你直接下结论,而是给出了一套很标准的对比环境:
- 相同数量
- 相同布局
- 相同资源
- 不同回放方案横向对比
如果你要做插件演示视频,这个场景本身就非常适合作为性能对比素材。
在视频演示里,最直观的一组对比就是"1 万个特效实例"的场景切换:
- 原始
ParticleSystem一旦大量发射起来,帧数很快掉到个位数 - 直接切到
GPUParticle的GameObject渲染形式,帧数可以明显回到 100+ 这一档 - 如果再结合
ECS Graphics、Instancing 或更积极的合批方案,帧数还会继续往上走
视频里还有一个很有代表性的观察:
原始粒子一旦层级复杂,每个特效下面还会跟着几个子粒子一起算、一起渲染;而 GPU 粒子版本会把这些层级尽量合并成一个回放对象,所以切换过去之后,帧数提升会显得特别直接。
如果项目目标是"万人同屏""海量技能展示""场景里同时摆满装饰型特效",这类对比会非常有说服力。
原版效果和 GPU 版会不会百分百复刻
答案是不会,GPUParticle 的价值是高性能,但高性能一定伴随着取舍。
换句话说,它追求的是:
- 尽量保留原有视觉
- 尽量保留原有工作流
- 但不追求把所有运行时动态语义 100% 原封不动搬过去
所以看对比时,正确的判断标准不是"有没有像素级完全一致",而是:
- 主要视觉是否保住了
- 播放节奏是否对
- 是否足够支撑大量实例
- 有没有影响项目实际使用的关键缺失
对大多数展示型特效来说,这种取舍是完全值得的。
注意事项
下面这些点建议重点看一下,都是实际使用时非常容易遇到的边界情况。
1. 只依赖 Rate over Distance 的粒子
如果某个发射器只依赖 Rate over Distance 发射,而没有 Rate over Time / Burst,构建时通常会被烘焙为空。
原因是当前构建过程不会驱动源特效在场景里真实移动。
说得再直白一点就是:
原地爆发出来的粒子可以烘焙,依赖"移动过程中边走边喷"的那部分粒子,当前还不适合直接转换。
2. World / Custom Simulation Space
如果粒子使用:
World Simulation SpaceCustom Simulation Space
它们在构建后会被固化为 clip 根节点下的局部轨迹。
如果源特效运行时还会持续移动,最终语义可能和原始粒子不完全一致。
3. 以下模块会退化或不再具备运行时语义
Trails:暂不支持Collision:只保留离线采样结果,运行时不再响应场景碰撞Trigger:不支持运行时触发事件Lights:运行时不会创建 LightExternal Forces:只固化离线结果Custom Data:暂不支持
这里特别容易误解的是"拖尾"。
有些爆炸特效在爆开的瞬间,看起来像是还保留了一些尾迹,这是因为那部分粒子本身被烘焙进去了;但真正依赖 Trail 模块实时生成、或者依赖物体移动轨迹生成的拖尾,当前仍然不在支持范围里。
4. Noise 会被采样固化
如果特效大量依赖高频 Noise 细节,而采样率又不够高,GPU 回放结果可能出现失真。
这种情况建议优先提高:
Sample Rate- 必要时再提高
Precision
5. Stretch 模式有边界
Stretch 模式暂不完整支持以下项:
Camera Velocity ScaleFreeform Stretching
如果你的原始特效高度依赖这两项,转换后的视觉语义可能和原始效果不完全一致。
6. 随机结果更像"预烘焙动画"
GPU 粒子更接近一段被烘焙好的回放结果,所以某些依赖实时随机的表现,看起来会更稳定。
比如有些爆炸特效,原始粒子每次喷射方向都可能不一样;而转成 GPU 粒子后,如果你看到它每次播放方向都一样,这通常不是坏了,而是因为它更像在播放一段固定的高性能动画。
对展示型特效来说,这一般不是问题。
但如果你非常依赖"每次都不一样"的随机性,就需要额外设计这一层。
7. Mesh 粒子更适合单 Mesh、单材质
这一点在使用时要特别注意:
- 多 Mesh 时会固定使用第一个有效 Mesh
- 多子材质会退化为单材质输出
如果你的 Mesh 粒子本身是复杂多材质结构,不建议直接拿来做 GPUParticle 烘焙。
8. 合并后只能保留一个外部 Renderer 排序输出
最终生成的是一个合并 MeshRenderer,所以对外只能保留:
- 一个
GameObject Layer - 一个
SortingLayer - 一个
SortingOrder
内部粒子虽然仍会按每帧排序写入槽位,但最终对外不再是多个独立 Renderer。
9. 槽位数和纹理尺寸要控制
当粒子槽位数过大、帧数过高时,最终数据纹理尺寸可能超过平台支持的最大纹理尺寸。
构建器会做这一步检查,但实际使用时仍然建议控制:
maxParticlesSample RateBake Duration
实际使用建议
如果你准备在项目里正式使用 GPUParticle,我比较建议按下面的规则落地:
- 优先处理展示型、重复播放型、播完即走的特效。
- 默认使用
RGBAHalf + 30fps起步。 - 对
Noise、快速旋转、复杂 TextureSheet 动画特效,再逐步提高采样率。 - 对
Mesh粒子严格控制maxParticles。 - 对依赖实时碰撞、触发、拖尾的特效,保留原始
ParticleSystem方案。 - 发布前一定用项目自带性能场景做同屏压测,而不是只看单个特效的效果。
总结
〖Unity GPUParticle插件〗的核心价值,不在于"又造了一套新粒子系统",而在于它把 ParticleSystem 变成了一个内容生产工具,把运行时播放工作交给了 GPU。