目标
上一篇博客《观察Niagara特效系统中的基本概念》中的学习只是针对于最基本的概念。
这一篇的目标是:
对Niagara参数进行进一步的观察。我觉得它是整个Niagara系统的核心,因为我发现其他一切都是围绕它的。
Niagara参数(FNiagaraVariable)
正如官方文档《Niagara关键概念 | Unreal Engine Documentation》所说:
Niagara的目标是:可以直接在编辑器内全权掌控效果,而不必求助于程序员修改C++代码。而为了达成这一目标,所做的设计之一(也是文档中放在第一个的)就是:
数据共享
实现用户全面控制,从数据访问开始。我们希望用户能够使用虚幻引擎的所有部分的数据,以及来自其他应用程序的数据。因此,我们决定向用户公开一切数据。
这里具体就是指Niagara参数这个设计。感觉在Niagara编辑器内进行的操作,本质上都是去设置某些【参数】。
参数设置的顺序
【参数】的数量是很大的,因此需要“按顺序地”、“有组织地”去设置。
顺序
从组
上来看,是从“大”到“小”,即系统>发射器>粒子的顺序,在界面上表示为从上到下:
而在每个组
中,又有不同的阶段
,基本的包括生成与更新。顺序自不必说是先生成后更新,然后是其他的例如粒子的事件。
命名空间
每个【参数】都有命名空间
的描述,界面上显示在每个【参数】的左侧:
可以看出,一个【参数】并不是专属某一个命名空间
,而是可能会所属多个命名空间
,代表其多个方面的“特性”。
所有的命名空间
如下:
UENUM()
enum class ENiagaraParameterScope : uint32
{
/** Parameter that is an input argument into this graph.*/
Input UMETA(DisplayName = "Input"),
/** Parameter that is exposed to the owning component for editing and are read-only when used in the graph*/
User UMETA(DisplayName = "User"),
/** Parameter provided by the engine. These are explicitly defined by the engine codebase and read-only. */
Engine UMETA(DisplayName = "Engine (Generic)", Hidden),
/** Parameter provided by the engine focused on the owning component. These are explicitly defined by the engine codebase and read-only.*/
Owner UMETA(DisplayName = "Engine (Owner)", Hidden),
/** Parameter is an attribute of the owning system payload. It is persistent across frames and initialized in the System Spawn stage of the stack.*/
System UMETA(DisplayName = "System"),
/** Parameter is an attribute of the owning emitter payload. It is persistent across frames and initialized in the Emitter Spawn stage of the stack.*/
Emitter UMETA(DisplayName = "Emitter"),
/** Parameter is an attribute of the owning particle payload. It is persistent across frames and initialized in the Particle Spawn stage of the stack.*/
Particles UMETA(DisplayName = "Particles"),
/** Parameter is initialized in the appropriate spawn stage for the stack. It is persistent from frame to frame. For example, if used consistently in an Emitter stage, this parameter will turn into an emitter attribute. Similarly, if used in a Particle stage, it will turn into a particle attribute.*/
ScriptPersistent UMETA(DisplayName = "Stage (Persistent)", Hidden), //@todo(ng) hiding until autotest verification is made.
/** Parameter is initialized at the start of this stage and can be shared amongst other modules within this stack stage, but is not persistent across runs or from stack stage to stack stage.*/
ScriptTransient UMETA(DisplayName = "Stage (Transient)"),
/** Parameter is initialized at the start of this script and is only used within the context of this script. It is invisible to the parent stage stack.*/
Local UMETA(DisplayName = "Local"), //Convenience markup for ScopeToString functions, only use in conjunction with ENiagaraScriptParameterUsage::Local.
Custom UMETA(Hidden), //Convenience markup for expressing parameters using legacy editor mode to freetype namespace and name.
DISPLAY_ONLY_StaticSwitch UMETA(DisplayName="Static Switch", Hidden), //Only use for display string in SEnumComboBoxes; does not have implementation for classes that interact with ENiagaraParameterScope.
/** Parameter is output to the owning stack stage from this script, but is only meaningful if bound elsewhere in the stage.*/
Output UMETA(DisplayName = "Output"),
// insert new scopes before
None UMETA(Hidden),
Num UMETA(Hidden)
};
目前还不清楚其中每一个的具体含义,但是有三个最重要的且互斥的命名空间
:
- 系统(System )
- 发射器(Emitter )
- 粒子(Particles)
他们对应了各自的组
。一个基本的规则是:一个组
对应命名空间中的【参数】,只能在这个组
里设置;而在一个组
里只能读取“上游”的【参数】。也就是说:
组 | 可写入的命名空间 | 可读取的命名空间 |
---|---|---|
系统 | 系统 | 系统 |
发射器 | 发射器 | 系统、发射器 |
粒子 | 粒子 | 系统、发射器、粒子 |
而有些命名空间
是在任意的组
内都能被读取,例如:
- 引擎(Engine):引擎提供的,显式地在代码中定义的值。
- 用户(User ):是用时根据不同的情况在蓝图或C++中设置的值。
当然,这些在编辑器中都是只读的。
参数设置的逻辑:Niagara脚本(UNiagaraScript)
可以直接在某一组
的某一阶段
对【参数】进行设置:
例如,修改【参数】——粒子Color,将会直接看到粒子的颜色发生变化
这样操作很简单,但是不能应对复杂的特效。因为对于复杂的特效系统,设置【参数】的逻辑也将是复杂的,往往与多个其他的【参数】相关联并且经过复杂的数学计算才能得出结果。于是,Niagara脚本应运而生,它容纳了设置【参数】的逻辑,在其中可以读取/设置当前所有可以操作的参数(用Parameter Map
表示)
有三种脚本:
- 模块(Niagara Module Script)
- 动态输入(Niagara Dynamic Input Script)
- 脚本函数(Niagara Function Script)
他们的本质没有区别:都存放了关于【参数】的计算逻辑。而且他们的编辑方式也没有区别。区别在于组织上,他们发挥作用的方式不一样,而这也可以从他们的“输入”与“输出”上看出来:
脚本类型 | 输入 | 输出 |
---|---|---|
模块 | 当前的 Parameter Map | 修改后的Parameter Map |
动态输入 | 当前的 Parameter Map | 某种数据类型 |
脚本函数 | 某种数据类型 | 某种数据类型 |
下面具体看这三者发挥作用的方式:
模块
“模块”可以在某一组
的某一阶段
中加入:
随后可以右键打开对应的UAsset,查看其中的逻辑。
可以添加这个模块的输入命名空间的【参数】
而这些 输入【参数】 将会显示在Niagara编辑器的界面上:
模块最终一定会修改Parameter Map
中的某几个参数,这是它的目的。
动态输入
在上一步中,“Color模块”的 输入【参数】 都是定值,那如果想让它依照某种逻辑进行变化时该怎么办呢?
当然了,写一个新的“Color模块”并在其中加入自己想要的逻辑,这是完全可以的。但如果加入的逻辑比较少,而且只是针对其中一个【参数】,那么“新的Color模块”势必和“原版Color模块”有重复的逻辑。“减少重复代码” 肯定是所有程序员的共识,而且每次因为小的逻辑就创建一个新的“模块”,也会造成“模块”数目太大难以维护。
因此,Niagara系统中有一个设计叫做动态输入。它表示一个输入【参数】 的值将依照某种逻辑进行更新变化。
例如,这里为Scale Alpha参数指定一个动态输入Cosine(余弦函数)
Normalized Angle(归一化的角度值) 默认是发射器的Age(年龄)
随后便可以看到所有粒子的Alpha都跟着发射器的Age(年龄) 做余弦函数的变化:
脚本函数
脚本函数所扮演的角色则很明确,就像材质函数对于材质的关系一样,脚本函数也提炼了能够复用的逻辑可以在其他Niagara脚本中使用。
在脚本编辑器网格中右键可以选择创建一个脚本函数节点
例如Is Point Inside Sphere计算一个点是否在一个球内,返回bool
而这个计算内部同样也可以有其他的脚本函数节点
参数最终的去向
虽然上面讨论了很多关于【参数】设置的问题,但一个最重要的问题还没有讨论:
这些计算好的【参数】最终的去向是什么?换句话说:【参数】将怎样影响最终渲染出的特效?
答案也很明白:
每个发射器都拥有一个渲染器(Sprite/网格体/条带/光线)。这些渲染器是由C++编写的,在他们的C++代码中,指定了需要哪些“数据”,而这些“数据”又默认绑定到了哪些【参数】上,在Niagara编辑器界面内也可以对“绑定”进行修改:
这样,参考它们就可以了解自己设置哪些【参数】可以对最终的渲染产生实质性的修改了。
其他问题
1. 命名空间
命名空间
有很多,它们具体的含义又各自是什么?
2.与C++有交互的参数
在INiagaraModule::StartupModule()
可以看到创建了很多参数
它们之中很多粒子的【参数】已经观察到被某个渲染器所使用了。但是其他还有很多【参数】,它们是否也在C++中有某种特别的职责吗?或者被C++代码所设置?
3. Niagara脚本编辑器中的节点
除了上面讨论的Parameter Map
、脚本函数
,其实Niagara脚本编辑器中能创建的节点还有很多,它们又各自有什么样的用途呢?