01 - 粒子系统

本文将对游戏引擎中的粒子系统做简单介绍,包括粒子基础、GPU粒子、粒子的应用等内容。

粒子基础

最早的粒子系统在电影《星际迷航》中出现,之后才在3D游戏中普遍应用。

概念

粒子(Particle)是粒子系统里最基础的单位,它通常是一个2D精灵贴图或者是3D模型,并且具有下述属性:

  • 位置;
  • 速度;
  • 大小;
  • 颜色;
  • 持续时间;

粒子的种类

主要有3种粒子:广告牌粒子(Billboard Particle),网格体粒子(Ribbon Particle),条带粒子(Ribbon Particle) 。

广告牌粒子

这种类型的粒子是一个2D贴图,总会面朝摄像机。

网格体粒子

这种类型的粒子是一个3D模型,在生成时可以对它进行随机放缩等操作,体现随机性。

条带粒子

这种类型的粒子由多个粒子控制点组成,会在相邻的粒子控制点之间渲染四边形样式的特效。

使用条带粒子时候需要应用样条曲线插值,否则结果是不平滑的。

生命周期

每个粒子都有它自己的生命周期,从生成到运动到消亡。

粒子的生成

粒子发射器

粒子在粒子发射器中被生成,之后按照特定的规则逻辑进行运动。

多个粒子发射器便组成一个粒子系统。

生成位置

如下图,可以只在一处发射粒子,也能在一片区域、甚至是网格体上发射粒子。

生成模式

也能按需定制粒子的生成模式,例如每帧持续不断地生成粒子,或是在某一时间点全部生成。

粒子的模拟

生成粒子后,就要按照一定的规则来运动模拟了。

粒子可能会受到一些力的作用,例如重力、阻力、风的扰动等。

确定了粒子受哪些力影响后,便能进行逐帧近似计算,在每一帧快速更新该粒子的位置。

此外,有时候还会让粒子和环境互动,这涉及到高效的物理碰撞检测算法。

粒子渲染

透明混合排序

需要按照从远到近的顺序去渲染透明物,否则会在视觉上出错。

粒子系统的排序主要有两种:

  • 全局排序:对系统中所有粒子进行排序,然后从远到近渲染。这样做虽然结果准确,但性能消耗大。
  • 派生排序:先对系统中的粒子发射器进行排序,然后单独对每个发射器中的粒子进行远近排序。但可能存在渲染次序错误的问题 。

半分辨率渲染

如果直接在全分辨率场景中突然渲染大量粒子,就会造成帧数锐减和性能消耗。为了避免这些不必要的消耗,可以将粒子渲染在长宽减半的降采样图片上,然后通过一些滤波算法升采样并混合至原图中。

GPU粒子

GPU擅长处理海量并发的东西,因此很适合用于管理粒子系统。GPU粒子系统解放了CPU,让它更专心地去处理游戏逻辑,而且也容易从GPU中获得深度缓冲。

框架概览

将粒子的生成、模拟和远近排序从CPU中搬到GPU,在计算着色器上执行相关任务。

基本流程

初始化

初始化阶段需要维护如下数据结构:

  • 粒子池:是一个缓冲存储区,在里面存储所有粒子的信息(如颜色,位置等)。
  • 凋亡列表:是一个列表,用于标识当前凋亡的粒子,默认是满着的。
  • 存活列表:是两个列表,用于标识上一帧和当前帧中哪些粒子还活着。

生成粒子

假设当前生成了5个粒子,就要从凋亡列表中取出5个粒子,将其放入存活列表中。同时分配5个计算着色器线程用于粒子的生成。

模拟粒子

在此阶段,计算着色器为当前存活的粒子进行模拟,然后将数据写到我们维护的数据结构中。例如6号粒子凋亡后,需要将其从上一帧的存活列表中取出,塞入凋亡列表中,然后剩下存活的粒子再放入当前帧的存活列表中。

视锥体裁剪

GPU中还容易做视锥体裁剪操作,需要维护的数据结构如下:

  • 一个新的存活列表,用于标识当前摄像机能看到哪些粒子;
  • 一个深度缓冲区,用于标识粒子离摄像机的距离。

排序粒子

接下来会根据深度信息从远到近排序存活列表中的粒子,准备渲染。

在GPU上排序常用并行的归并排序,有两种思路:

  1. 从源数组角度考虑:对于归并前的源数组,每个元素开一个线程,让线程决定这个元素在目标数组的位置。这样做会带来效率降低,因为写操作是内存上不连续的。
  2. 从目标数组考虑:对于归并后的目标数组,每个元素开一个线程,让线程决定该元素应该来自源数组的哪个元素。这样做是内存友好的,效率较高。

渲染并交换存活列表

接下来就能按存活列表中的顺序渲染粒子了,然后用双缓冲思想交换存活列表,供下一帧计算使用。

环境交互

有的粒子还需要和环境交互,如果直接用物理引擎对全部粒子都算一遍碰撞的话,性能会很差。人们常用屏幕空间的深度信息来进行粒子碰撞的快速近似计算。

高级应用

粒子系统的高级应用。

人群模拟

在街道上走的一个个小人都可用粒子来表现,这需要准备一个支持动画的网格体来充当粒子。

此外,为了让粒子小人做不同的动作,需要在粒子系统中实现一个小型的状态机。

有了可动的粒子小人后,接下来需要给它导航,让它们行走在正确的道路上。这需要导航纹理的帮忙,导航纹理通常有两个:

  • 符号距离场纹理,用于标识哪些位置是可通行的,哪些位置是障碍物。
  • 方向纹理,用于标识人群行进的方向。

动态变换

例如有一个网格体粒子A,可通过动态程序生成样条曲线的方式,将A分割成其他小粒子,然后再组装成网格体粒子B。

和环境的交互

例如用粒子模拟鸟群,人走进时鸟群会避开人。

设计思想

基于样式预设

早期引擎粒子系统的设计很简单,只需预先设定好粒子发射器的样式等属性即可。但维护比较麻烦,添加一个新特性需要写新的代码。

基于图结构

现代引擎的粒子系统则基于图结构设计,模块化、逻辑清晰且容易维护。

二者混合

也能将二者的优点结合起来,例如虚幻引擎的Niagara粒子系统。

参考资料

  • GAMES104 (boomingtech.com)

待阅读的资料

  • Programmable VFX with Unreal Engine’s Niagara – GDC 2018
  • The Destiny Particle Architecture – SIGGRAPH 2017
  • Frostbite GPU Emitter Graph System – GDC 2018
  • The inFAMOUS: Second Son Particle System Architecture – GDC 2014 https://www.gdcvault.com/play/1020367/The-inFAMOUS-Second-Son-Particle)
  • Compute-Based GPU Particle Systems – GDC 2014
  • The Visual Effects of inFAMOUS: Second Son – GDC 2014
  • Mergesort - Modern GPU
  • A Faster Radix Sort Implementation – Nvidia