UE5.8 Niagara 粒子系统基础
小林第一次打开这个新项目的特效文件夹时,发现里面已经没有 Cascade 发射器了。策划案上写着:法师的火球命中敌人后,要炸开一团带火星的火焰,然后留下几缕翻滚的烟雾。项目经理在旁边说,UE5.8 默认用 Niagara,老项目里的 Cascade 资源可以导入,但新效果全得在 Niagara 里做。
他打开 Content Browser,右键 FX > Niagara System,弹出 Asset Browser。那一排模板让小林愣了一下:Fountain、Fire、Smoke、Spark、Explosion……他选了个空模板,决定从头搭一个火球命中效果。这篇文章就是沿着他当天的思路整理出来的。
从 Cascade 到 Niagara
Cascade 是 Unreal Engine 长期使用的粒子编辑器。它的工作方式很直接:把 Spawn、Lifetime、Velocity、Color 这些模块按固定顺序塞进一个发射器里,调参数,看效果。大部分游戏里的火星、烟雾、 blood splatter 都是这么出来的。
Niagara 的设计思路不太一样。它把效果拆成可编程的数据流。每个模块本质上是一段 HLSL 或节点图,负责读取输入、做数学运算、写回参数。技术美术可以直接双击模块看内部逻辑,也可以复制出来改成自己想要的行为。换句话说,Cascade 给的是一把固定规格的积木;Niagara 给的是机床和原材料。
这种区别带来的好处很明显。同样的火星效果,如果策划突然要求火星在命中瞬间根据敌人护甲类型改变颜色,Cascade 里可能需要改材质或者加 Dynamic Parameter;Niagara 里只需要在 Particle Spawn 时读一个 User Parameter,再连到 Color 模块里就行。复用性也更高:一个写好的 Add Velocity in Cone 模块,可以在火焰、喷泉、爆炸里直接用。
代价是学习曲线变陡。新手第一次打开 Niagara System Editor,看到 System、Emitter、Particle Spawn、Particle Update、Event Handler、Render 这些分组,很容易不知道点哪里。这篇文章后面会按四级结构把这张网拆开。
老项目迁移时,Cascade 资源可以通过右键 Convert to Niagara 生成一个近似效果。转换后的 System 通常需要人工清理:Cascade 里的 LOD Distance Check 在 Niagara 里用 Scalability 重新做,材质里的 Dynamic Parameter 命名也可能对不上。小林之前项目里的一团黑烟转换过来后,颜色全乱了,原因是 Cascade 的 Color Over Life 曲线在 Niagara 里被映射到了不同的 Attribute 上。
System、Emitter、Module、Particle 四级结构
Niagara 把效果组织成四个层级。理解它们的职责,是后面所有操作的前提。
architecture-beta
group niagara[UE5.8 Niagara]
service system[System] in niagara
service emitter[Emitter] in niagara
service module[Module] in niagara
service particle[Particle] in niagara
system:B --> T:emitter
emitter:B --> T:module
module:B --> T:particleSystem 是最外层的容器。一个 Niagara System 可以包含多个 Emitter,负责整体生命周期、LOD、Scalability、User Parameter 的声明,以及Emitter 之间的执行顺序。小林做的火球命中效果,最后会是一个 System:里面有一个负责火球本体的 Emitter,一个负责命中瞬间爆开的 Emitter,还有一个负责残留烟雾的 Emitter。
Emitter 是生成粒子的真正单元。每个 Emitter 有自己的 Stack,从上到下依次是 Emitter Spawn、Emitter Update、Particle Spawn、Particle Update、Event Handler、Render。Emitter Spawn 只在 Emitter 被创建时跑一次,用来设置初始值;Emitter Update 每帧跑,控制持续生成粒子的 Spawn Rate;Particle Spawn 在粒子出生时跑一次;Particle Update 每帧更新每个粒子;Render 决定粒子怎么画。
Module 是 Stack 里的最小行为块。Spawn Rate、Add Velocity、Scale Color、Kill Particles 都是模块。模块按顺序执行,后面的模块可以读到前面模块写过的参数。每个模块内部都是 Niagara 节点图,双击就能看到它把哪些输入写到了哪些 Particle Attributes 上。
Particle 是数据记录。它不是看得见的小图片,而是一组属性:Position、Velocity、Color、Size、Lifetime、Age、SpriteRotation 等等。渲染器读取这些属性,把粒子画到屏幕上。CPU 模拟时这些属性存在 CPU 内存里;GPU 模拟时存在 GPU 缓冲区里。
小林当时把这张图记在便签上:System 管整体,Emitter 管生和死,Module 管具体规则,Particle 管每一粒子的数据。之后他再点哪个分组,心里都有个数。
System 还有一个 Timeline 面板,用来快速查看每个 Emitter 的生命周期。你可以拖动时间线观察效果在第几帧进入高潮、第几帧淡出,这对调节奏很方便。Emitter 之间可以设置为顺序执行,也可以并行。小林把爆炸 Emitter 设为在火球 Emitter 结束前的最后几帧开始预热,这样命中瞬间不会有空档。
Scalability 是 System 层面的另一项关键职责。同一个火焰效果在高端 PC、主机和手机上应该呈现不同精度。Niagara 的 Scalability 可以按平台缩放 Spawn Count、Module 复杂度、LOD 距离。小林在 PC 上保留三千个火星,在移动端把 Spawn Rate 压到五百,同时关掉 Curl Noise 模块。
常用模块:Spawn、Velocity、Lifetime、Color、Size、Collision
打开一个 Emitter,最先面对的就是往 Stack 里加模块。下面这几个是小林做火球时反复调整的。
Spawn 模块 决定粒子什么时候出生、出生多少。Instantaneous Burst 适合做爆炸:0.05 秒内喷出 80 颗火星。Spawn Rate 适合做持续火焰:每秒生成 200 个粒子。还可以用 Spawn Per Unit,让粒子沿着运动轨迹按距离生成,火球拖着尾巴飞的时候常用。
Velocity 模块 给粒子初始速度。Add Velocity in Cone 适合爆炸:粒子朝一个圆锥方向飞散。Add Velocity from Point 适合冲击波:粒子从中心向外辐射。小林把火球本体的速度方向设为沿飞行路径,命中爆炸的粒子速度则随机朝半球扩散。他还加了一个 Drag 模块让高速火星慢慢减速。
Lifetime 模块 设置每个粒子活多久。火焰粒子可能活 0.4 到 0.8 秒,烟雾粒子可以活 2 到 4 秒。Lifetime 通常给一个随机范围,这样效果不会显得整齐。
Color 模块 控制颜色随时间变化。小林在 Color Over Life 里让火星从白黄渐变到橙红,再淡出到透明。烟雾则从深灰渐变到浅灰,同时透明度下降。Color 模块经常和 Curve Atlas 或 Dynamic Parameter 配合,把变化交给材质处理。
Size 模块 控制粒子大小。Size Over Life 可以让火星出生很小、中间变大、死亡时收缩成一点。烟雾则通常越来越大,模拟热气扩散。小林在烟雾 Emitter 里用了 Scale Sprite Size by Life,让烟雾在生命周期后半段缓慢膨胀。
Collision 模块 让粒子与环境交互。Hit事件可以让火星撞到地面后反弹或者生成新的效果。CPU 模拟时 Collision 模块支持事件回传;GPU 模拟时也有 GPU Collision,但事件处理能力和平台支持有限。小林给烟雾加了简单的 Scene Depth Collision,让它碰到地面后减速并贴地扩散,而不是直接穿进地板。
CPU Simulation 与 GPU Simulation
Niagara 的模拟可以在 CPU 上跑,也可以在 GPU 上跑。选哪个,取决于效果需求和目标平台。
CPU 模拟适合需要精确事件、游戏逻辑交互、复杂碰撞、数量不多的效果。每个粒子的计算在 CPU 上进行,可以方便地给 Gameplay 发事件。比如火星击中敌人后触发伤害判定,这种情况下 CPU 模拟更可控。
GPU 模拟把粒子计算放到 Compute Shader 里并行执行,能轻松支撑几十万甚至上百万粒子。火焰、烟雾、灰尘、魔法洪流这些大量、规则相对简单的效果,通常首选 GPU。它的限制是不能直接触发 Gameplay 事件,碰撞也主要依赖 Scene Depth 或 SDF,读回 CPU 数据有额外开销。
flowchart TD
A[效果需求分析] --> B{是否需要游戏事件?}
B -->|需要| C[CPU Simulation]
B -->|不需要| D{粒子数量级?}
D -->|少量/复杂碰撞| C
D -->|大量/规则简单| E[GPU Simulation]
C --> F[事件与精确碰撞]
E --> G[高并行吞吐]模拟的核心公式并不复杂。每帧把粒子位置按速度积分一次,再考虑加速度:
速度本身也会变化:
在 Niagara 里, 通常来自 Gravity、Curl Noise、Point Attraction 等模块的叠加。 是帧时间,Niagara 会自动处理固定步长或可变步长。小林在烟雾里加了 的重力分量,但把垂直向上的初速度设得很大,所以烟雾整体还是向上飘。
切换模拟目标很简单:在 Emitter Properties 里把 Sim Target 从 CPU 改成 GPU。但改完之后要检查 Stack 里有没有 GPU 不支持的模块。Niagara 会在编译时标红,告诉你哪些节点需要 CPU 上下文。
实际调优时,Niagara Debugger 和 GPU Profiler 是主要工具。Debugger 可以高亮显示当前选中的 System、查看粒子数量、强制重编译;Profiler 则能看到 GPU Simulation 占用了多少毫秒。小林第一次把火星改成 GPU 后,Profiler 显示时间从 1.2 毫秒降到 0.15 毫秒,但爆炸的 Hit 事件失效了,因为那部分还依赖 CPU Collision。
主机和 PC 的取舍也不一样。PS5 和 Xbox Series X 的 GPU 很强,十万级粒子用 GPU Simulation 通常没问题;Switch 和手机 CPU 偏弱,但 GPU 带宽也紧张,往往需要把粒子数压到很低,甚至用 Stateless Emitters 做预计算效果。UE5.8 的 Platform Preview 让这些平台差异在编辑器里就能看到,不用每次都打包到设备上。
数据接口:User Parameters 与 Particle Attributes
Niagara 里的数据可以粗略分成两类:从外部进来的 User Parameters,和在模拟内部流动的 Particle Attributes。
User Parameters 是 Niagara System 暴露给外部世界的接口。在 System 的 User Parameters 面板里新建一个 Float 叫 FireIntensity、一个 Color 叫 FireTint、一个 Vector3 叫 HitLocation,然后这些值可以在运行时通过 Blueprint、C++ 或 Sequencer 修改。小林把火球的飞行速度、命中位置、火焰颜色都做成 User Parameter,这样策划在关卡里调一次,所有调用这个 Niagara System 的地方都会生效。
Particle Attributes 是模拟过程中每个粒子自己的数据。Position、Velocity、Color、Size、Lifetime 这些是最常见的。模块就是不断读写这些属性。你可以在 Stack 里加一个 Set Parameter 模块,新建一个自定义属性,比如 BurnAmount,然后在 Particle Update 里根据 Age 不断增加它,最后在材质里读取 BurnAmount 控制烧焦程度。
graph LR
UP[User Parameters] --> NS[Niagara System]
NS --> E1[火焰 Emitter]
NS --> E2[烟雾 Emitter]
E1 --> A1[Particle Attributes]
E2 --> A2[Particle Attributes]
A1 --> R1[Sprite Renderer]
A2 --> R2[Sprite Renderer]两者并不是完全隔离的。User Parameters 可以在 Particle Spawn 或 Emitter Update 里读到,变成粒子初始状态的一部分;而 Particle Attributes 也可以在渲染阶段通过 Niagara Mesh Renderer 或 Material 暴露出来。小林常用的套路是:User Parameter 负责外部可控,Particle Attribute 负责内部变化,Renderer 负责最终呈现。
Dynamic Input 是模块参数里常用的快捷方式。一个模块的输入可以是一个常量,也可以是一个表达式。小林把 FireTint 直接拖到 Color 模块的输入框里,Niagara 会自动生成一个 Dynamic Input,引用这个 User Parameter。如果后面想改成按粒子 Age 渐变,只需要把输入类型换成 Curve 就行。
命名规范对团队协作很重要。User Parameter 建议加前缀区分用途,比如 UP_ 表示外部可改,E_ 表示 Emitter 级内部变量。Particle Attribute 的名字最好和材质里的参数一一对应,这样渲染联调时不容易错位。小林的项目里有一份 Niagara 命名表,新来的特效师照着填就不会乱。
一个简单的火焰、烟雾、爆炸效果制作流程
小林当天下午的工作可以分成几步。下面用他做火球命中效果的真实顺序来描述。
sequenceDiagram
actor Artist as 小林
participant CB as Content Browser
participant SE as System Editor
participant ES as Emitter Stack
participant LV as Level
Artist->>CB: 新建 Niagara System
CB-->>Artist: 打开 System Editor
Artist->>SE: 添加 Fireball Emitter
Artist->>ES: 配置 Spawn / Velocity / Color
Artist->>ES: 添加 Explosion Burst Emitter
Artist->>ES: 添加 Smoke Emitter
Artist->>SE: 创建 User Parameters
Artist->>ES: 添加 Collision 模块
Artist->>LV: 拖入关卡并触发
LV-->>Artist: 观察命中爆炸与烟雾第一步,创建 System。 右键 FX > Niagara System,选 Empty。他给这个 System 起名叫 NS_FireballHit。
第二步,添加火球本体 Emitter。 用 Add Emitter from Template 选一个 Empty Emitter,改名 Fireball_Core。在 Particle Spawn 里加 Spawn Rate 每秒 300,Lifetime 0.3 到 0.6 秒,Initial Size 10 到 25,Initial Color 白黄。Particle Update 里加 Add Velocity,让粒子沿飞行方向向后拖尾,形成飞行的焰心。
第三步,添加命中爆炸 Emitter。 这个 Emitter 用 Instantaneous Burst,出生 0 帧时喷发 120 个粒子。Initial Velocity 用 Add Velocity in Sphere,速度范围很大,Lifetime 0.1 到 0.4 秒。Color Over Life 从白到橙红。Size 出生大、死亡快速缩小,模拟爆炸闪光。
第四步,添加残留烟雾 Emitter。 Spawn Rate 每秒 80,Lifetime 2.5 到 4 秒,Initial Size 40 到 80。Velocity 向上并带一点随机水平分量。Color 从深灰半透明开始,到浅灰全透明。小林还在 Particle Update 里加了 Curl Noise,让烟雾边缘有些翻滚。
第五步,暴露 User Parameters。 在 System 里新建 Vector3 HitLocation、Color FireTint、Float ExplosionScale。在命中爆炸的 Emitter 里,把 Spawn Location 绑定到 HitLocation;在 Color 模块里乘以 FireTint;在 Size 模块里乘以 ExplosionScale。这样策划在 Blueprint 里调用 Set Niagara Variable 就能实时控制。
第六步,加碰撞。 烟雾用 Collision Scene Depth,碰撞后把 Velocity 的水平分量保留,垂直分量压到接近 0,模拟贴地扩散。火星用 CPU Collision 并监听 OnHit 事件,命中后生成一个更小的 Spark 子效果。因为事件需要回传 CPU,这个子 Emitter 保持 CPU Sim Target。
第七步,拖进关卡测试。 小林在 Blueprint 里做了一个简单逻辑:火球命中目标时,在命中点 Spawn System at Location,传入 HitLocation 和 FireTint。他在编辑器里反复发射,观察火星数量、烟雾扩散范围、颜色是否太卡通。调了大概二十分钟后,效果已经能交一版。
下面是他当时在 CustomHLSL 模块里写的一段小逻辑,用来根据粒子 Age 和 Lifetime 计算一个燃烧权重:
float NormalizedAge = Age / Lifetime;
float Burn = saturate(NormalizedAge * FireSpeed);
Color.rgb = lerp(BaseColor.rgb, BurnColor.rgb, Burn);这段代码很短,但能说明 Niagara 的核心思路:读属性、算结果、写属性。FireSpeed 是 User Parameter,BaseColor 和 BurnColor 是 Particle Attributes,Color 会被后续渲染器使用。
材质侧也要配合。Niagara 默认会把 Color、Opacity、Size 等属性传给材质,但如果你想用自定义属性,比如前面说的 BurnAmount,需要在材质里加一个 Niagara Vertex Color 或 Dynamic Parameter 节点来读取。小林在火焰材质里用了 SubUV 动画贴图,配合 Random Image Index 让每张火星子图都不一样。
调效果时不要只看编辑器静止画面。小林把相机拉远、拉近、旋转,从多个角度观察烟雾厚度。他还在 Sequence 里录了一段火球飞行动画,用 Take Recorder 反复看慢镜头,确认火星不会突然消失或穿墙。这样做虽然花时间,但放到实际关卡里出问题的概率小很多。
UE5.8 中 Niagara 的新动向
UE5.8 并没有把 Niagara 推倒重来,但围绕它的工作流和配套系统有明显改进。
MegaLights 在 5.8 进入 Production-Ready 状态。对 Niagara 来说,这意味着大量动态光源与粒子自发光同时存在时,光照开销和阴影质量更可控。火球、爆炸这种自带强光的特效,放在复杂室内场景里不再那么容易把帧率拉崩。
Lumen Lite 是 5.8 新增的动态全局光照档位。它用 Irradiance Field 加 Probe Occlusion 降低计算量,适合中低端硬件和掌机模式。烟雾、火焰这些半透明体对环境光的反馈,在 Lumen Lite 下仍然能看出明暗变化,但性能压力比完整 Lumen 小很多。
渲染管线的工具链在 5.8 里更顺畅了。Shader 编译、PSO 预缓存和 Platform Preview 都有优化,GPU Simulation 在反复调试时掉帧和等待时间变少。对小林这种需要反复开关 Niagara Debugger 看 GPU 时间的人来说,这一点比新节点更实用。
Niagara Asset Browser 在 5.8 里支持 Tag Definition,可以给常用 Emitter 和 System 打分类标签。小林给项目里的 Hit Effects、Ambient、Character 三大类建了标签,后面找资源时直接点分类,不用在 Content Browser 里翻几页。
Niagara Fluids 相关的模板和文档在 5.8 里继续扩展。Pyro、Liquids、Smoke 这些基于流体解算的效果,已经不是必须去 Houdini 里做好再导入。初学者可以直接从 Content Examples 或官方模板开始改。小林后来研究烟雾时,就把一个 Niagara Fluids 的 Smoke Template 拆开看了很久。
另外,Niagara 的 Micro Expressions、Events 和数据接口在 5.8 里保持稳定。Micro Expressions 允许把任何内联值转成一段 HLSL 表达式,Events 允许 System 内不同 Emitter 之间通信,Data Interfaces 则可以读取骨骼网格、样条线、音频等外部数据。这些机制都不是 5.8 才出现的,但在 5.8 的示例库和文档里变得更易上手。
最后小林把这套效果保存成 Template,方便同组其他特效师复用。Niagara 的 Template 机制可以把一个 System 或 Emitter 变成可重复创建的资源,避免每次都从零开始。团队内部逐渐形成了一套自己的效果库,做新技能时拖出来改改颜色、速度和碰撞就能交差。
小林收工的时候,火球效果已经能在关卡里正常播放。烟雾还有一点贴地穿模,他记下明天把 Collision 的 Depth Bias 再调小一点。Niagara 的门道很多,但四级结构和几个常用模块搞明白之后,至少不会被界面吓到。剩下的就是在项目里多拆、多试、多问渲染组 GPU 时间了。他还打算周末再把火星子效果单独拆出来,做成一个更通用的命中反馈。