原文链接:《UE4:Niagara的组成》 | 作者:Feilon
Edition 1.00. Base on UE Ver.4.26
概述
为了能之后逐模块逐功能地讲解,我们这里还是费口舌系统地补充一下 Niagara 的一些基本概念,以便今后不再费力气解释很多基本的内容。
Niagara 的组成概述
我们回过头来看一下创建粒子系统时,除了 Niagara 系统以外的其他内容,这些内容就是 Niagara 粒子系统的所有可拆分的组件。我们逐个来看一下它们都有什么作用。
Niagara Emitter
我们对这个内容应该比较熟悉,一个粒子系统可以由多个粒子发射器组成,而我们在外面创建的粒子发射器资产,则是能够在多个粒子系统中进行复用的。创建一个粒子发射器时,我们也可以分别选择三种模式:
- 使用一个已有模板
- 继承一个已有模板
- 使用一个已有发射器
一个 Emitter 是否为模板,可以通过 Emiter Properties 中的 Asset Options 中进行设置,这个我们之前也稍微提到过。设计思想上看,模板并不应该是我们会直接使用的实际粒子系统,而只是一个框架。但是目前的实际应用上似乎没有这个区分,我们在设计时可以稍微注意一下。如图:
这里再次强调的是勾选 Expose to Library 才会被粒子系统索引到,创建 Emitter 时的后两个选项可以索引到非 Library 的 Emitter 。
继承关系意味着对父 Emitter 的修改会同时修改子 Emitter,而子 Emitter 无法删除从父 Emitter 中继承得到的内容或调整它们的顺序,直接使用则是单纯的复制粘贴过程,二者不会产生任何依赖。
继承关系的出现意味着粒子系统引入了面向对象的程序思想,如果需要设计一些有层级关系、部分功能共用的粒子结构或功能,继承关系能节省很大的工作量,也让我们的设计显得更加有逻辑。
Niagara Module Script
下一个层级是 Niagara Module Script,它是我们 Emitter 下的一个基本执行单元,根据情况可以被放在任何一个执行部分中。双击创建后的资产打开,我们在中间能看到一个蓝图编辑器一样的界面,也是我们几乎所有逻辑的编辑位置。在关注这部分之前我们先来看左边的详情设置页:
我们忽略一些诸如 是否废弃 等各种描述性的设置。首先是第一个设置:
这个设置决定了我们的 Module Script 能够被 Emitter 的哪个执行过程中使用。最上边的三个选项决定了当前 Script 的类型,剩下两个类型我们接下来会依次讲解,在这里改变类型并不会真的改变我们资源的类型,以及它们在各个区域的可见性及使用,我们的资源类型是在创建类型时就决定好了的。
Provided Dependencies 与 Required Dependencies 是我们对依赖关系的配置,一般情况下用不到,除非想要设计一些框架内的基础组件。依赖关系在我们的粒子系统中是可以被进一步拆分的,即我们需要手动配置它们的依赖属性,而不仅仅是指定某模块依赖于某模块。
具体来说,模块之间的依赖关系可能是网状的,一个模块满足某个条件时才能被正常使用,但是能够给予这个条件的模块可能不止有一个。同时一个模块也可能给予多个让其它模块依赖的条件。因此我们的模块依赖关系使用纯粹的 FName 进行配置,Provided Dependencies 只给定一系列的该模块提供给其他模块的可被依赖的内容名称即可,Required Dependencies 则还需要对每个依赖额外配置依赖模块间的执行顺序、是否位于同一个执行逻辑内等。
Conversion Utility 的功能暂时不明白,大概是其它模块的内容拷贝到该模块时的转换逻辑?有了解的可以给予指点。
Library Visibility 设置为 Exposed 才能被 Emitter 正常找到并添加。若设置为 Unexposed 则需要在 Emitter 中设置查找非 Library 的内容才能找到。若设置为 Hidden 则会在任何情况下都不能被索引到,但是已经被索引到的地方也不会因此报错,通常适用于一些非强制性废除的模块。
额外值得一提的是 Highlights 内容,点击加号我们能给当前模块设置一个颜色标记,通常这些标记用来反映我们会对哪些状态进行修改等:
这些标记会显示在 Emmiter 中我们使用该模块的最前方,如图 Particle Spawn 中的 Initialize Particle 就是对这些粒子的属性进行了修改:
当然你不做这些标记也不会影响使用。
然后我们再来看中间的核心工作区,即蓝图编辑区。
这个蓝图编辑器与一般蓝图的基本使用与语义是差不多的,比如白色的流程线,细的变量线等。我们忽略那些有共同操作的部分,只讲那些粒子系统特殊的地方。
首先我们看 InputMap 与 OutputModule ,这个中间整个是使用一条白色的类似蓝图流程线的粗线贯穿的,然而它与蓝图真正意义上的流程线不同,实际上也是一个变量传递的线,传递的变量叫做 Parameter Map,即所有当前可访问变量的总表,因此也是可以连出多条线,但是只能连入一条线。我们可以定义多个 Parameter Map 输入以及多个 Parameter Map 输出,同时 Module Script 也能给定多个非一般类型的输入变量。这些输入与输出引脚的追加可以通过左边栏详情下面的输入输出栏进行:
其中 Common 是追加 Parameter Map 引脚,Data Interface 是追加特殊数据引脚,Output 是无法追加数据引脚的。追加后蓝图区域如下图所示:
需要注意的是这种多输入输出的情况大部分都不会编译通过。怎样才能编译通过我们本章最后再说。
然后我们再关注两个默认被创建了的特殊节点:Map Get 与 Map Set。不同于一般的蓝图,我们获取与设置粒子系统内任何变量都必须通过这两个节点来完成。这两个节点可以被创建很多个,即我们可以通过多个 Map Set 的连接来指定某些有依赖关系的变量的赋值顺序,同时我们的 Map Get 不同于 Attribute Reader,始终获取的是当前最新的变量值。当然不是任何变量都能通过这两个节点来获取与设置的,这跟变量的命名空间有关,我们在变量的章节会详细讲述。
我们可以通过这两个节点上面的加号来添加需要获取或设置的变量,又或者,我们可以通过左边栏的 Parameter 列表中添加我们要使用的变量,然后将其拖到这两个节点上来使用:
这两种添加方式的区别是,在节点上只能找到我们粒子系统默认赋予 Emitter 、Particle 等的默认变量,以及创建新的模块输入。而在左边栏我们则还可以直接给 Emitter、Particle 等创建具有新的名称的变量,然后给 Map Get 或 Map Set 使用。使用了该模块的 Emitter ,会自动添加这些我们在模块中添加的变量,即自动补足这种对某些变量的依赖,非常方便,但是也容易产生错误的组合,需要引起注意。
而在我们这个模块内,在节点上添加的结果会直接同步给左边栏,左边栏上显示的是所有节点使用变量的并集,但左边栏添加的内容则需要拖入某个节点后使用,也可以不使用。
我们还需要关注此处的 Module Inputs 跟我们刚刚的红色输入节点的不同。这些被创建的输入变量会在 Emitter 中选中该模块时显示。我们可以在此处为我们定义的这些输入变量赋值。红色输入节点定义的参数则不会出现在此处,而只能在我们从另一个 Module 以脚本节点的方式调用当前节点时,才会作为输入引脚出现,如图:
Map Get 的输入引脚是 ParameterMap,输出引脚则是我们添加的各种类型的数据,而 Map Set 的输入引脚也是 ParameterMap,同时也会输出 ParameterMap。从Map Set 的输出连接 Map Get,获得的是 Map Set 之后的值,这不难理解,但 注意 从红色 Input 节点连出来的 Map Get 获得的也会是当前执行路线的最新值,这是因为我们的 ParameterMap 不会在执行过程中进行多份复制,而是在用指针索引到每个具体变量,因此每次通过指针找到该变量获取的都是最新值,测试可以通过以下简单蓝图进行:
虽然我们一直在对指针操作,但只有最终连入 Output 的 ParameterMap 会被当做最终的输出结果。我们可以认为是编译过程以流程线的规则砍掉了未连接到输出节点的所有执行。
粒子系统无论是实际粒子的运算,还是 SimulationStage 对其他数据类型的操作,本质上都是数据集的批量操作。上一个 Module 计算得到的 ParameterMap 传入当前模块,经过计算再输出给下一个模块是最正常的逻辑。然而如果我们制定了两个输入或输出的参数集的位置,此时我们给定的执行逻辑是模糊的。对粒子系统来讲任意时刻只有且只需要一套参数集,输入的双参数集分支会破坏这种唯一性。
那么我们应该如何理解这种多输入与输出节点的逻辑呐?首先我们先看多输出的情况。
首先我们的输出引脚是存在顺序的。最开始,位于节点上方的输出引脚的路线会被执行。之后,我们得到的输出会作为新一轮执行的输入,再执行下一个输出引脚的路线,依此类推。这比较类似于蓝图里的 Sequence,我们可以通过以下简单蓝图进行测试:
多输入的情况就比较神奇了,本人至今没找到多输入节点全部在流程中使用且编译通过的情况。逻辑上,输出节点只能有一条线连入,但是双输出意味着出现了两条流程线,即使其中一条只使用 Map Get,只要其中的变量被另一条逻辑路线使用,就会编译报错,毕竟此时无法解释来源的 ParameterMap 是什么。而双输入直接连接双输出也是不可行的,因为双输出已经被定义为重复执行,此时 “区别于第一个输入节点的另一个” ParameterMap 也是无法解释来源的。
假如我们不把额外的 Input 节点连入逻辑中,那么是可以编辑通过的。但我们在 Emitter 中添加这样的 Module 会导致 Emitter 无法编译通过。我们只能在另一个 Module 中通过节点的方式使用这样的 Module ,如图:
需要注意所有的引脚都必须被连接,否则还是编译不通过。
如果有人知道怎么用多个 Input 节点,请联系我让我知道,我来进行更正。
关于 Module 的介绍基本上就到这里了,我们接着介绍其他内容。
Niagara Function Script
Function 与 Module 是很类似的,毕竟使用了同样的详情页设置,甚至编辑器内筛选内容都会一起出现(确定不是 Bug ?)。
创建一个 Function Script 后,我们点开编辑页面,可以看到中间给出的默认节点:
这个默认节点就决定了 Module 与 Function 的不同。
首先必须明确的是,Function 不能在 Emitter 中被直接添加作为执行逻辑的一环,只能作为其他 Module 或 Function 中的一个函数节点使用。也正因如此,我们可以为输入与输出添加诸如 float、Vector 等基本数据类型。我们能以 Module 中类似的方式添加更多的输入,甚至加入 ParameterMap 的输入与输出节点,并使用 MapGet与 MapSet。ParameterMap 的执行逻辑与 Module 类似,我们此处不再赘述。
默认情况下,我们的函数没有 ParameterMap 节点,这意味着我们的函数必须有输出并且连接到某个带有 ParameterMap 型的节点上作为输入,才能让我们的函数执行。一般来说,我们会希望我们的函数的输出值被其他地方用到,因此总会连接给某个输入的引脚。但是也可能我们仅仅是在函数中进行了某些特殊数据结构的赋值,甚至可能不需要有输出值,此时添加 ParameterMap 节点就是必要的了。添加了这些引脚的函数在使用时的形式如下所示:
我们也能在此处添加 ModuleInput 类型的变量,但是由于无法以 Module 的形式添加到 Emitter 中,且 ModuleInput 不会反映到节点上,我们找不到能为 Input 赋值的位置,因此只能得到默认的初始值:
即使我们在其他 Module 使用到了该函数,甚至是使用到了其他带有 Module Input 的Module,这些节点内含的 Module Input 都不会显示到 Emitter 的设置页上。
最后值得一提的是,Function 的输出节点必须有任意一条引线连接上去,即使上面显示了默认的输出值。
Niagara Dynamic Input Script
这也是另一个与 Module、Function 同类型同详情页的组成成分,但是有完全不同的用法,我们直接来看打开编辑页面后的默认节点:
这个默认的连接反映了,Dynamic Input 的输入可选类型与 Module 相同,输出可选类型与 Function 相同,但是不能包含 ParameterMap。
Dynamic Input 的使用需要结合 Module Input 来进行。我们知道 Module Input 需要在 Emitter 的界面上选中 Module 后指定,这个指定可以有很多不同的形式:
- 一是我们直接书写默认值上去,
- 二是我们拖到某个同类型的变量到上面去进行关联赋值,
- 三是我们书写一行 HLSL 的计算式来赋值,
- 四是我们使用 Dynamic Input 来赋值。
这也就意味着,我们的输出只需要一个具体的数据,不需要 ParameterMap,而我们的输入能利用我们能利用的任何内容。
由于使用到了 Emitter 中的 Module 设置页,我们再 Dynamic Input 中添加的 Module Input 会在使用该 Dynamic Input 的值下面显示并进行进一步的值的设置。但是通过红色输入节点添加的输入数据无法显示在设置页上,同时我们无法在其他 Module 或 Function 中以函数节点的方式使用 Dynamic Input 。
我们点击红色的 Input 节点看一下他的详情页,注意 Can Auto Bind 选项。选中后,按照该选项的提升,我们应该是能通过名称直接将该变量关联到粒子系统的 System 或 Emitter 同名变量上。这个操作对 Module 与 Function 也是适用的,但是本人没有尝试过这种关联是否好使,毕竟你可以直接通过 ParameterMap 拿到这些参数,因此很少用到,有兴趣的可以尝试一下。
逻辑上讲,Dynamic Input 需要关联输出给某个具体的输入值上,因此输出只能有一个类型,并且必须严格匹配才能索引到,不能隐性进行类型转换。但是实际上你可以添加多个输出参数。添加多个输出类型后,你就不能在变量设置中选择 Dynamic Input 时索引到这个 Dynamic Input 。然而当我们合理设置了 Dynamic Input 后,再添加 Output 的数量,不会影响到 Emitter 的编译。当然直接删掉这个输出类型还是会报错的。
Niagara Parameter Collection
Niagara Parameter Collection(NPC)与 Material Parameter Colloction(MPC)类似,是一种全局的数据参数集。甚至于,我们可以直接在详情页设置继承于某个 MPC:
添加与设置变量没什么好说的,我们此处略过这些内容,来看看怎么在粒子系统中使用这一组件。
最简单的使用方法就是在指定 ModuleInput 的值时,我们能直接在下拉菜单中的 Link Inputs-NPC 中找到所有存在的 NPC 的值。如果我们想在 Script 中使用,则需要在左侧 Parameter 页面中,点击右上角的小眼睛查看高级信息才能看到添加的入口,此处我们也能添加任何 NPC 中存在的值,之后拖到 Script 的 MapGet 节点上使用。
NPC 的值的改变除了手动设置外,也可以通过蓝图进行配置,因此也是一个与逻辑层进行通信的方便的工具。
最后,同 MPC 有 Instance 意义,NPC也有 Instance ,对应某个具体的全局设置,若不同粒子系统共享同一套参数,但是需要不同的数值,应该使用 Instance 类进行配置。
粒子系统中使用 NPC 的参数是不会区分 Instance 的,我们需要在粒子系统的层级上指定我们要使用的 Instance ,在 System Properties 中进行如下设置:
Niagara Effect Type
这是最后一个组件了,我们废话不多说,直接进入详情页:
可以看到这就是一组配置粒子系统的 Scalability 层级的参数集。假如我们的粒子系统有很多,且为了适应不同的性能的平台需要进行统一的不同的配置,就可以使用这个组件来完成统一的设置与修改。
该组件的使用通过粒子系统中的 System Properties 的设置来完成:
里面的个中含义我们暂且略过,因为 Scalability 涉及的内容稍微多,我们会在 Scalability 的章节中单独讲解。
结语
Niagara 虽然是一个前端界面型的工具,但其组成与配置已经很偏向编程思想了,这意味着只要我们的设计合理,我们在使用 Niagara 时能省去很多重复的作业,这个设计比起之前的 Skeleton、Animation 甚至是 Material 都理想得多。
本来想一口气把基础部分讲完,结果组成就讲了这么多,下一篇是变量与 HLSL 部分,这些基础讲完才能开始讲解 Grid、AttributeReader 这些更加综合性的内容。