我们将讨论Niagara的
一个特效在Niagara系统中是由多层Emitter(发射器)堆叠而成的。一个Emitter是控制着行为逻辑相同的一组粒子或一种效果,而Emiter的内部粒子的每一帧具体执行了哪些操作或者它行为逻辑的方式是以Module节点网络的方式来定义的。
在Niagara视觉特效处理系统中,共有四个核心组件:
Niagara系统是一种容器,可以放入你要构建该效果的所有内容。在这个系统中,你可以搭建不同的构建块来实现总体效果。
你可以修改一些系统级的行为,这些修改随后将应用到该效果中所有内容。
系统编辑器中的 时间轴(Timeline) 面板将显示系统中包含的发射器,并且可以用于管理这些发射器。
发射器可用来在Niagara系统中生成粒子。发射器将控制粒子的生成、粒子在生命周期中的遭遇,以及粒子的外观和行为。
发射器位于堆栈中。在该堆栈中有几个组,而在组中可以放置用于实现各个任务的模块。 组如下所示。
发射器生成(Emitter Spawn)
此组将定义在CPU上首次创建发射器时将会发生什么。使用此组可以定义初始设置和默认值。
发射器更新(Emitter Update)
此组将定义CPU上每一帧发生的发射器级模块。如果你希望粒子在每一帧上持续生成,可以使用此组来定义粒子的生成。
粒子生成(Particle Spawn)
当粒子生成时,每个粒子将调用一次此组。此时你可能需要定义粒子的初始化细节,例如粒子的生成位置、粒子的颜色、大小和其他特征。
粒子更新(Particle Update)
每一帧上的每个粒子都会调用此组。你需要在此处定义在粒子生命周期中将会逐帧更改的所有特征。例如,粒子的颜色会随着时间逐渐变化。或者,粒子受到各种力的影响,例如重力、旋度噪点或点吸引。你甚至可能需要让粒子随着时间改变大小。
事件处理器(Event Handler)
在事件处理器组中,你可以在一个或多个用于定义特定数据的发射器中创建"生成"事件。然后,你可以在用于触发某个行为以响应该生成的事件的其他发射器中创建"侦听"事件。
渲染(Render)
最后一个组是渲染组。你可以在此处定义粒子的显示,以及为粒子设置一个或多个渲染器。如果要定义3D模型作为粒子的基础以便在这个基础上应用材质,那么你可能需要使用网格体渲染器。或者,你可能需要使用Sprite渲染器并将粒子定义为2D Sprite。我们提供了很多不同的渲染器来供你选择和试用。
模块是Niagara中效果的基础构建块。你可以将模块添加到组中来形成堆栈。模块按照自上而下的顺序处理。
你可以将模块视为能够执行某些数学运算的容器。你需要将数据传送到模块中,然后在模块中对该数据执行一些数学运算,然后在模块结束时将该数据重新写出。
模块是使用高级着色语言(HLSL)进行构建的,但是可以使用节点在图表中以可视方式进行构建。你可以创建函数(包括输入),或者写入到某个数值或参数贴图中。你甚至可以使用图表中的 CustomHLSL 节点,以内联方式编写HLSL代码。
你可以双击Niagara中发射器内部的模块,以查看在模块内部发生的数学运算。你甚至可以复制和创建自己的模块。例如,双击"添加速度(Add Velocity)"模块来查看其内部,则可以看到数据流。
脚本首先检索输入 - 速度输入和坐标空间。然后获取粒子的当前速度,以及输入的比例因子。然后,输入速度随之进行调整,变换到正确的坐标空间中,并添加到粒子的当前速度上。该工作完成后,新的粒子速度将会写出,以便于堆栈中其他需要速度信息的任何模块都可以检索该速度。
所有模块都使用这种基础方法构建,但是部分模块的内部数学运算更加复杂。
参数 在Niagara模拟中,是一种数据的抽象化。系统会将参数 类型 分配给参数,以定义参数所表示的数据。参数分为四种类型:
点击 加号 图标 (+) 并选择 直接设置新参数或现有参数(Set new or existing parameter directly) ,可以将自定义参数模块添加到发射器。这会将 设置参数(Set Parameter) 模块添加到堆栈。点击 设置参数(Set Parameter) 模块上的 加号 图标 (+) 即可设置现有参数,点击 创建新参数(Create New Parameter) 即可设置新参数。
生命周期
整体的system到Emitter再到各个partice在运行时都有自己的生命周期。
生命周期除了在时间轴上控制,我们创建System和Emitter时引擎会默认在各个层级下添加State生命周期管理模块。
System State控制着整个特效包的存活周期,循环逻辑。
非活动响应(Inactive Response)
此设置确定当发射器进入非活动状态时会发生什么状况。非活动意味着发射器处于休眠状态,且不再能够生成或管理粒子。选项包括:
完成(Complete):粒子完成任务,然后终止发射器。
终止(Kill):立即终止发射器和粒子。
继续(Continue):发射器停用,但在系统关闭前不会消亡。
循环行为(Loop Behavior)
此选项确定发射器的行为。你可以从下述选项中进行选择:
循环时长(Loop Duration)
此参数确定循环持续多长时间。
循环延迟(Loop Delay)
发射器延迟发射的时间。
Life Cycle Mode(生命周期模式)
此设置确定是由发射器本身还是由拥有发射器的系统管理生命周期(循环、存在时间和消亡)。设置包括:
系统(System):当你选择此选项后,所属系统将计算所有生命周期功能。在大多数情况下,让系统计算生命周期可提高优化程度。选择此选项将隐藏其他字段。
自身(Self):当你选择此选项后,发射器本身将计算所有生命周期功能。自身模式下设置与 System State相同。
Partice State
控制粒子年龄的增长和它的死亡
Niagara按粒子的每一帧迭代的次数和顺序的不同拆分出了不同的stage执行阶段。
基础的stage有spawn初始化阶段,即指在出生时更新一次,还有update每帧更新阶段。除了这两个默认的,点击Emitter上方的添加stage按钮还有两个可选的。Smiulation Stage和Event 事件。
Event的更新频率是由具体的事件类型来控制的,比如发生碰撞或粒子死亡触发的事件。
Smiulation Stage是GPU粒子专用的可在每帧的tick(活动)下多次迭代。比如比较复杂的流体结算压力向的求解,TBD粒子的约束求解。每一帧都要多次迭代,才能解出当前状态的一个合理值。
首先粒子的发射大都是在Emitter Update这个stage做的,常见的spawn模块有
粒子的初始化阶段(Partice Spawn)
在粒子的初始化阶段(Partice Spawn),首先给了粒子的生命(Lifetime mode)一个随机值,然后还给了粒子的质量(Mass mode)一个随机值,质量的设置是一个很好的逻辑,因为后期在粒子的update阶段,不同的受力模型都可以通过质量反应到每个粒子的变化上去。
另外因为我们碎块是要instance特定的mesh形状上去,所以我们在初始化时又给了它初始的旋转一个随机。
初始速度的添加:add velocity这个模块提供的功能比较全
Velocity xyz 速度方向
Velocity speed Scale 速度比例 (和Velocity 是倍数的关系)
Velocity Speed 点速度强度
Constrain TO Radius 是否约束点速度的范围半径
启用之后:
radius Falloff near/far 衰减近/远
radius Falloff exponent 半径衰减指数 (0不进行点速度的约束衰减 数值越大约束点速度越大)
Offset 点速度的偏移值
Velocity Speed 速度力
distribution Along Cone axis 是否启用 力沿着锥形轴向分布 为0时全圆锥分布 为1时偏向于中心
speed Falloff Form Cone AXis 速度从圆锥轴下降
Random Seed 是否启用力的随机种子
Cone 属性
Cone Axis 圆锥形的朝向 XYZ
Cone angle 圆锥的角度大小
inner Cone angle 内锥角度大小 (为0时是)
Cone Angle Mode 圆锥角度模式(Degrees直接设置度数、Normalized Angle(0-1)归一化角度、Radians弧度)
粒子的更新阶段(Partice Update)
粒子的更新阶段(Partice Update)是粒子主要动态变化发生的阶段,最常用的模块是各种力的添加。
Niagara中粒子的解算和渲染是完全解耦的,我们可以用一次解算结果驱动多个渲染。这个特性大大优化了粒子系统的性能。
具体演示看视频
为了让每个碎块的形状不重复,我们会选几种不同的mesh形状随机Instance上去。
比如这个案例在DCC中做了四种不同的小碎块模型。
可以添加一个MeshIndex属性来控制当前粒子渲染的是哪个模型。
先把四个不同的mesh在Mesh Renderer中添加上。然后又因为在粒子的整个生命周期内,它选用的Instance模型不应该变化,我们就应该在初始化阶段指定这个属性值。把它直接拖到Partice Spawn上来进行指定。
修改index值可以看到instance的模型产生变化。我们可以添加一个自定义的模块去设定如何逐粒子的随机变化这个index。但更方便的是引擎提供了Dynamic Input这种机制。我们可以在每个模块的参数上,直接添加Input脚本。
比如我这里添加一个引擎提供的Random Int脚本,让每个粒子在0~3之间随机变化,可以看到每个粒子选的形状都不一样。
以上我们可以看出每种Renderer它Instance的渲染实体不同就需要不同的属性进行控制。Niagara通过Material Binding把这些属性传递到渲染系统当中。
Mesh Renderer由三维的向量(vector)来控制Mesh的缩放,也就是Scale Binding。朝向由Mesh Orientation Binding来控制。
而这里烟雾用到的Sprite Renderer用二维的向量(vector)来控制UV方向的大小,也就是Sprite Size Binding。用Sprite Faceing Binding和Sprite Alignment Binding两个向量来控制Sprite的朝向。
值得一提的是和影视领域不同,游戏实时交互环境下一般负担不起物理模型解算的Volume烟雾。因此常会用案例中这样朝向相机的Sprite切片播放预烘焙的烟雾动态序列来实现这种烟雾效果。
引擎提供了SubUV动画设置模块,并在Renderer里做了UV切割传递给材质。下面看一下这个Smoke的材质。除了UV的设置,粒子还通过Material Binding把Particle Color传了过来,用来控制渲染的颜色还有半透明。除了这些默认的Binding,比如速度,大小。引擎还提供了四个四维的向量(vector)来传递自定义的属性。
另外比较常见的Renderer还有目前只用于CPU的Ribbon Renderer,它可以根据粒子上的Ribbon id和Ribbon Link Order把粒子连成Spline,常用来做一些光束效果。
感兴趣可以看内容示例的Ribbon相关的案例。
除了这些基础的Render,我们还可以把灯光或者其它类型的Component甚至破碎系统的Geometry Collection Instance到粒子上。让Niagara可以控制场景中更多复杂的元素。
以上过了基础模块和Render的参数调整,下面再来看看如何在System层级上做参数控制,让各层Emitter元素联动起来,并可以放在场景中,从粒子系统外部做用户的参数控制。下面用第一人称关卡做了个简单的枪械Gameplay
案例具体看视频
在影视特效项目中,比较近的景别下细节上通常会要求小碎块可以因为碰撞或运动轨迹的变化产生翻滚的效果,这里就以这个需求展开如何在Niagara中搭建自定义的Custom Module Script。
通过上面的效果来分析原理,在空中飞行状态下,我们可以由当前速度和前一帧速度叉乘得到,即图示蓝色的轴向
当两帧速度方向接近平行,这里的处理是这种情况下让碎块停止翻转,这样做的考量是速度方向变化不大,说明运动轨迹变化的曲率也很小。从观察体验来说,这种情况下发生翻滚的概率也挺小就直接做滑行处理了。
当碎块与场景发生碰撞,在接触面上发生翻滚时,另一个轴向的判断就容易多了。如图所示,直接用速度方向和碰撞面的法向来做叉乘得到这个旋转的轴向。
原理比较清晰了,下面演示具体看视频。
比起Gameplay的控制在动画领域中,我们更常用到的是在镜头里进行调整,这里介绍如何在Sequencer控制粒子系统。
先把Niagara system actor添加进来还有Component track。Life Cycle track可以把Niagara的生命周期铺到Sequencer的时间轴上,控制它出现的时间段。但这里不能默认前后拖动看效果。
我们可以在Properties中把Age Update Mode改成Desired Age,这样就可以前后拖动看效果了。
另外所有User Parameters也暴露在Sequencer中,这里我们设置的ImpactDirection可以添加到track里给它k帧。可以看到变化。
另外,调试粒子效果很重要的一步就是需要频繁的检查属性Debug。引擎提供了很全面的性能查看,信息展示的工具(Niagara Debugger)。我们可以在这里设置粒子的播放速度。慢速看动态的细节。除了System上的一些信息,还可以添加任意的粒子属性,检查它们的具体值。比如添加了前面Custom Module调整的MeshOrientation属性。可以看到会显示到每一个粒子的位置上。
具体描述看视频
Expose Everything
System
Age,loop,duration
Emitter
Age,LocalSpace,Determinism
Particle
Age,life,mass,position,velocity,orient,size…
需要注意的是这些按层级划分的命名空间,还可以通过Namespace Modifier加入一个子空间。Previous 这个Modifier可以获取粒子前一帧的值。也可以通过Initial 这个Modifier获取初始spawn阶段的粒子属性。
Engine
只读
delta,owner.velocity,owner.LODDistance
Transient
临时的,不需要前后帧accumualte的数据
force,drag…
Mouble
Input,Output,Local
场景:
Static Mesh:Distance Field,Triangle
Skeletal Mesh:骨骼,Triangle
+Vector Field,Spline等控制器
+Actor Component 访问到场景中任意actor的Transform信息。
这些都极大的方便我们的粒子系统和场景中不同的actor做联动,做交互。
渲染:
除了3D的场景,我们也可以获取到渲染管线的信息。
GBuffer,RenderTarget
通过采样GBuffer在粒子系统中添加一些后期效果,采样RenderTarget做一些Gameplay的交互等等。
外部资产:
Houdini Cache Data,Texture
我们可以把Houdini的离线解算效果保存成二进制的json文件,然后通过Houdini Niagara插件提供的接口在引擎粒子系统中重现这个Houdini的解算结果。
物理引擎:
Chaos Sim Data
也有接口,它可以响应Chaos Destruction中cluster约束的断裂或者是Impact碰撞事件。
学习地址
Particle.UniqueID它可以保证每一个粒子都有一个唯一值,但是在帧之间这个值可能由于新粒子的出生,旧粒子的死亡而发生变化。
而Particle.ID是可以保证唯一且在帧之间不变的粒子索引。它是一个专用的数据结构由两个int值构成,使用这个值需要在Emitter Properties勾上Requires Persistent IDs这个选项。
内容示例具体演示看视频
具体参考内容示例的Event实现的案例
传递粒子属性到蓝图(去影响其它模块的功能)
性能代价比较高 ,但在离线领域会有应用
具体看视频内容示例演示
学习地址
内容示例中有展示关卡
性能相关
效果相关
地址