Visual Effect Graph初步探索

作者:沈琰
本篇难度:★★★☆☆
前言
不久前Unity官方发布了新版的粒子特效编辑组件:Visual Effect Graph的演示视频,其中酷炫的效果非常吸引眼球:

我按捺不住浓浓的好奇心,初步了解了一下。现在把一些自己使用过程中的心得和遇到的问题一并分享给大家,希望能给感兴趣的小伙伴一些参考。
安装
想要使用Visual Effect Graph(以下简称VEG),Unity的版本不能低于2018.3,并且目前VEG仅支持HDRP(High-Definition Render Pipeline,高清晰渲染管线),所以除了VEG的安装包外还需要另外三个HDRP相关的安装包作为其支持:

这里可以选择在PackageManager中直接安装。如果网速不给力,也可以直接下载官方GitHub上的安装包,然后手动导入。
传送门:https://github.com/Unity-Technologies/ScriptableRenderPipeline/tree/release/2018.3

下载下来后,在PackageManage中点击添加按钮,然后在路径中找到安装包文件夹内的json格式文件,点击导入。


经过一段时间的等待导入后,准备工作就基本完成了。
新建
现在开始创建我们的第一个Visual Effect特效。如果之前的安装包导入正确,那么在Hierarchy面板上直接可以点击右键创建一个Visual Effect对象。

然后在Project面板上新建一个VEG,拖入Visual Effect面板上的Asset Template中,点击Edit,就可以开始进行编辑。

VEG的工作流程
进入VEG的编辑界面,首先映入眼帘的是由四个大方块组成的一个默认的流程模板:

VEG的编辑方式类似于ShaderForge,由各个Block之间进行连线操作完成编辑,全程不需要编写任何代码。
而每个流程(在VEG中称为context)包涵多个不同的Block,的整个VEG的工作流程被抽象为4个部分:
Spawn
负责生成粒子,右键点击添加的Block都是与粒子生成相关。类比之前的粒子系统,这个流程就是模拟的发射器模块。
Initialize
初始化模块,负责初始化粒子的属性,如初始速度,生命周期等。
Update
每帧对粒子的参数进行更新,比如重力、移动速度,坐标等。
OutPut
主要负责粒子的渲染,如颜色、形状等。
每个context所连接的并非唯一,比如一个Spawn可以连接多个Initialize,朝多个不同的方向发射粒子。
总得来说VEG使用起来还是比较容易,其设计相当符合人的直觉,如果之前就对ParticleSystem的操作比较熟悉的同学很快就能上手。
每个context能附加的Block非常多,可以以此实现相当酷炫复杂的效果。
不过也正因为如此,以文章的篇幅没法每个Block都详细介绍,所以下面就以一些个人觉得比较重要的功能结合实际的例子简单说明一下VEG的使用方法。
重点与实例
1.Vector Field与Signed Distance Field
个人觉得除了渲染计算方式不同以外,VEG区别于ParticleSystem最大的特点,是新特性Signed Distance Field(有向距离场)和Vector Field(矢量场)的加入。具体的应用如同开头演示视频里的截图,可以让粒子填充出一个具体的形状,或者是让粒子沿着矢量场的方向运动:

不过这里有一个问题,目前Unity还没有内置的有向距离场和矢量场的编辑工具,已知的外部编辑软件编辑出来的资源似乎也没办法直接转换成Unity能够识别的格式。
未来Unity可能会添加编辑工具或者插件,但就目前来说暂时很难通过自制获取此类资源,不过这并不妨碍去了解使用方式。
打开编辑界面,先用一个简单的球状发射器发射一些粒子,然后在Update中新建一个block“Vector Field Force“。在Vector Field字段中会有一张默认的矢量场图片,我们来观察一下默认矢量场是个什么形状:


似乎就是一个均匀的涡流状矢量场,看不出什么名堂。
再如法炮制一下,新建一个block“Comform to Signed Distance Field”,观察默认的有向距离场形状。这个倒是可以看出来是一个挺明显的神灯(茶壶)形状。

当以后如果有了较为方便的编辑工具后,就可以用自定义的资源实现一些酷炫的效果。
不过对于子现在就等不及,已经饥渴难耐的同学也不是完全没办法,参考这里: https://zhuanlan.zhihu.com/p/49301381
2.Point Cache bake tool(点阵缓存烘培器)
虽然有向距离场和矢量场暂时没办法自定义编辑,但是Unity内置了另外一个小工具:Point Cache bake tool:

它的作用是把一张Textrue或者一个Mesh的信息,烘焙成一张点阵图导入VEG中使用,可导入的信息包括颜色、法线、UV信息等等。
具体有什么用呢?举个例子。
在网上随便下载一张图片,导入到Unity里用,刚才的工具进行烘培,得到了这么一个资源文件:

然后就可以在VEG里使用了。
首先在VEG编辑界面的空白区域新建一个点阵缓存节点,并把刚才烘培好的资源拖进去:

然后让粒子生成的位置和颜色应用这张点阵图的数据:

最后让粒子一段时间后自动消散,大概是这么一个效果:

同理,不止是图片,Mesh信息也能如此运用到VEG中:

3.VFX Binder脚本,参数与事件的绑定方式
编辑VEG时,可以在BlackBoard上新建参数,选中Exposed框,并暴露到Insceptor面板上。然后通过调整参数就可以修改Visual Effect的某些属性,比如颜色,大小等等。

但这仅能在编辑模式中调整,有没有办法在游戏运行的时候根据游戏内的逻辑来实时动态改变Visual Effect呢?
既然我都这么问了那自然是有的,在VEG的安装包里内置了一些VFX Binder脚本的模板:

这些脚本的作用就是将外部参数与VEG界面里的参数绑定,可以看到能绑定的数据大致能分为两类:参数(Parameter)和事件(Event)。
参数绑定
参数就是类似坐标,方向,速度之类的信息。只要API支持的数据格式,即便预制模板脚本内没有,也可以依葫芦画瓢仿照模板去编写脚本。支持的类型如下图:

举个例子,现在要让Visual Effect生成的粒子去填充一个球体,但让这个球体在游戏运行时遵循物理引擎运动。
新建一个Shpere并添加刚体,删除renderer组件,也就是说创建一个只有物理碰撞却看不见的隐形球体,然后用Cube在场景中搭建一个框作为球体的托盘。

在VEG编辑界面新建一个Sphere类型的参数,复选暴露到Inspector中。
新建一个Visual Effect,并添加VFX Sphere Binder脚本。将刚才创建的Sphere拖入脚本的Target栏中,并保证Parameter的名字与暴露出的参数名相同。

如此一来场景内Sphere的数据就与VEG内的参数绑定了,就可以拿着这个参数作为填充球的数据了。

再去写个简单的脚本让底座左右摆动,运行效果如下:

事件绑定
不仅参数可以通过脚本传递绑定,粒子的触发事件也能。在VEG里事件作为一个单独的Context,不能附加任何Block并且只能与Spawn相连,作用是管理Spawn的发射开关。

通过C#脚本绑定传递事件并非只是传递了事件本身,也可以附带一些参数。以内置的VFX Mouse Event Binder脚本为例。
通过阅读代码,可以看脚本中把鼠标点击的坐标以EventAttribute作为载体传递到了Visual Effect对象里:
[Tooltip("Computes intersection in world space and sets it to the position EventAttribute")]
public bool RaycastMousePosition = false;
protected override void SetEventAttribute(object[] parameters)
{
if (RaycastMousePosition)
{
Camera c = Camera.main;
RaycastHit hit;
Ray r = c.ScreenPointToRay(Input.mousePosition);
if (GetComponent<Collider>().Raycast(r, out hit, float.MaxValue))
{
eventAttribute.SetVector3(position, hit.point);
}
}
}
这个时候问题又来了,事件附带的参数该如何获取和使用?
一开始以为直接从Event连接的Spawn中创建Block指定坐标就行,但试来试去都没有效果,最后又去仔细了的看了下官方文档,最后找到了方法:

简单来说就是通过Get Attribute这个Block,根据Location的不同,可以获取当前每个粒子模拟的参数,或者是由EventAttribute附带传递进来的的参数,并且Location为Source的Attribute只能用在Initializes中的Block上:

知道怎么用以后就可以试试做个鼠标点击发射粒子的效果。
再新建一个Visual Effect,然后新建一个Plane作为脚本载体,把新建的Visual Effect拖入脚本中:

然后在VEG里新建一个Event,注意名字要与脚本上的EventName一一对应,然后连接到Spawn上:

最后获取鼠标点击的坐标,连接到Initializes里的Set Position上:

最后运行效果如下:

这样相当于是可以通过鼠标点击控制粒子的发射位置。内置脚本里还有一个获取碰撞事件和碰撞点的例子,如果有需要也可以参照这个格式自定义脚本。
更为详细的说明参考官方文档:https://github.com/Unity-Technologies/ScriptableRenderPipeline/wiki/VFX-Attributes-Properties-and-Settings
4.VEG动画效果
动画效果本质上就是一个或多个随时间变化的参数作用在物体上,所以实现动画效果的关键在于获取时间变化的量。
在之前版本的ParticleSystem中,似乎没有能单独获取游戏运行时间的方法。粒子的动画效果是通过类似Color Over LifeTime之类的模块,根据粒子的声明周期实现一个随时间变化的效果。
这个思路是符合ParticleSystem的设计思路的,因为在每个粒子初始化时必须指定它的LifeTime。
但是在VEG中,粒子的运行周期被抽象化了,在Initializes中可以没有Set LifeTime这个模块,也就是说粒子是永远存在的。在这个时候再想实现动画效果就得获取一个跟粒子生命周期无关的时间变量。
VEG中获取时间的节点有三种:

其中Total Time(Per-Particle)与TotalTime的区别是:前者在获取时间变量时会根据每个粒子在当前帧个下一帧的不同时间进行单独计算。假如使用时间变量让发射器周期移动,在速度较快的情况下,后者会在移动路径上显示一个离散的好似残影的效果。而如果是前者作为参数,会因为单独计算每个粒子的时间的缘故,让路径轨迹得到一个类似插值计算的平均化的轨迹。
直接说有些抽象,用一个实际例子来说明一下。
新建一个Visual Effect,实现一个让三个发射器互相旋转纠缠的伪·三体运动,用不同的时间节点作为输入参数对比。


这样对比一看,区别就很明显了。
至于最后一个Periodic Total Time,是获取一个周期时间变化。Periodic 是指的时间周期,单位是秒。Range是指在时间周期内参数的变化范围。上图所代表的意思就是获取以5秒为周期,0到1之间的变化参数。
结束
即便VEG功能如此强大,还是不能完全替代粒子系统。由于计算方式的不同(VGE由GPU计算,ParticleSystem由CPU计算),对于与运行中的游戏场景进行实时物理交互这一块,VEG无能为力,所以在后面的版本中这两个系统暂时还会共存。

另外对于一个全新的组件,个人感觉最快的学习方式,是照着文档和实例弄懂其运行流程,官方在GitHub上有一个实例工程文件,传送门:
结束
即便VEG功能如此强大,还是不能完全替代粒子系统。由于计算方式的不同(VGE由GPU计算,ParticleSystem由CPU计算),对于与运行中的游戏场景进行实时物理交互这一块,VEG无能为力,所以在后面的版本中这两个系统暂时还会共存。
另外对于一个全新的组件,个人感觉最快的学习方式,是照着文档和实例弄懂其运行流程,官方在GitHub上有一个实例工程文件,传送门:https://github.com/Unity-Technologies/VisualEffectGraph-Samples

里面有三个酷炫的粒子效果,其实现方式基本涵盖了VEG大部分的功能,并且编辑界面还有一些关键功能的注释,配合文档食用就能很快上手,做出自己的酷炫效果。



感谢观看到此, 本期工程地址:http://link.zhihu.com/?target=https%3A//github.com/tank1018702/unity-006
想系统学习游戏开发的童鞋,欢迎访问 http://levelpp.com/
游戏开发搅基QQ群:869551769
微信公众号:皮皮关