粒子系统的设计
因为需要为我们的 3d engine 添加特效系统的模块,我最近读了一篇文章:Efficient CPU Particle Systems 。文章的作者为很多 MMO / MOBA 游戏设计过粒子系统,其中最有名的是上古卷轴 Online 。所以我认为他的实践很有参考价值。
文章很长,夹杂着设计思路,优化,算法实现,渲染实现。对于我来说,由于过去我做过好几版粒子系统,所以读起来不太费力,很多细节可以直接略过,我今天写一篇 blog 把我认为文章中对我最有参考价值的部分列出来。
首先,我们是否需要做一个基于 gpu 的粒子系统?文章的结论是,各有利弊。如果单从性能角度去考虑,GPU 未必更好,尤其是对于 MMO 类发射器很多,每个发射器发射的粒子片并没有多到上千片的情况, GPU 粒子的优势并不明显。
我觉得,对于移动平台来说,把计算负担加在 CPU 或 GPU 上对性能和功耗的考虑也是一种权衡。基于 GPU 的粒子系统会增加一些开发复杂度,但并不算太复杂(bgfx 就在 examples 里给出了一个 gpu 粒子的范例);但 CPU 带来的灵活度的确能带来不少好处。另外,粒子系统的核心计算环节是天生能用多核并行计算的,且计算规模可裁剪(动态砍掉部分效果并不影响业务逻辑),我们不必过于考虑性能问题。
另外,我也读过一些相关的讨论,部分效果,尤其是粒子需要和场景做交互时,可能更适合放在 GPU 中计算。不过大多能找到一些替代方案,不用太担忧。
所以,我认可文章作者的方向,也打算先只给我们的引擎设计一个基于 CPU 的粒子系统。
作者花了很大篇幅谈内存管理。其实这部分并没有太多新的东西,几乎所有商用级别的引擎,粒子系统都很重视这块。用固定内存区维护固定上限数量的粒子片,固定数量的发射器,等等。这有几个明显的好处:
对大规模的计算,内存 cache 友好。基于 CPU 的粒子系统在合理的内存布局设计时,即使是同样的算法,可以带来成倍的计算性能提升。
限制粒子片的物理上限,可以极大的减少运行期的误用。不会因为不小心使用让内存爆掉。对业务逻辑也没有什么影响。最多是粒子发射器无法发射新的粒子,画面效果出错,但不影响其它部分。
内存管理部分作者给出了很多具体代码,我个人认为有少量动态数组部分还可以改进,去掉动态特性。尤其是时间线管理的部分应该做成可共享的固定模板。因为很多数据结构只需要在编辑期才有动态性需求,运行期都是固定的。
我倾向于在运行期用一个 C 模块和简单的 C 数据结构去管理粒子及发射器;而在编辑期,则用 lua 结构管理发射器会更具弹性。我们只需要编写一个 lua 结构到 C 结构的列集函数,在每个编辑修改操作后转换一下即可。
文章中还混杂了多线程管理和渲染流程的具体实现。这对我们的引擎设计是多余的。我想把线程管理和渲染流程从粒子系统模块中剥离出去,只设计一个纯计算模块。
文章中把粒子特效系统的原型设计分为两个层面,其一是基本粒子系统,作者称之为 bread and butter 。即仅仅用简单的多个发射器发射矩形粒子片就可以实现出来的效果:火焰、烟雾、雨、尘埃、雪、火花。
其二是为具体效果定制出来的粒子系统。所以涉及动态构建网格的效果都属于这部分,而每种特定效果都是需要特定编程的。例如:光束、刀光拖尾、闪电、更复杂的天气,等等。
后一种效果文章没有谈,只集中讨论了基本粒子系统的设计。我也想在第一步只在我们的引擎中实现基本部分。
这部分其实只涉及粒子发射器的定义,发射器按预先设定好的参数喷射出粒子,每片粒子在发射出的那一刻,就决定了所有的初始参数。之后,粒子系统每帧按这些参数更新粒子就可以了。在粒子片被构造出来的那一刻开始,整个粒子生命周期的状态变化过程就已经决定了,甚至没有任何随机量。所有随机环节都是在发射器发射时决定的。这样非常适合并行计算。我们可以用多线程,用 SIMD 优化。
btw, 在很多年前,我们第一次设计 3d 粒子系统时犯过过度设计的错误。当时我们实现了一个发射器去发射发射器的特性。现在看来是多余的,增加了许多无谓的复杂度。简单算法支撑的粒子系统一样可以做出丰富的效果。
这篇文章提出了另一个关键点是时间线的基础数据结构。他称为 AnimatedValue 。很多简单的粒子系统对一些参数,例如颜色参量,只提供了 StartColor 和 EndColor 两个控制量明显是不够用的(比如著名的 cocos2d 自带的粒子系统模块)。我们需要用一个时间线,提供任意个关键帧,用一条曲线去描述参数的变化。
另外,就是需要给发射器提供足够多的控制参数。这部分怎么多都不会浪费。
特效设计师总会充分利用这些参数,反复调整,设计出你一开始都想象不出来的效果。我所理解的特效设计师的工作就是在拿到新的特效引擎后,调调这个,改改那个,看看会得到什么结果,反复组合让这些参数之间产生化学反应。引擎设计者和特效师都无法一开始预知能做出什么,所以很难预先提出需求:“我需要这么一个参数”。我们只需要提供更多更多的参数可以调节。
好在这篇文章的作者已经实现过好多商用的粒子系统,他给出的参数列表让我们有个参考标准。他给出了发射器的数据结构定义 ,我个人觉得这是这篇文章对我价值最大的东西。不过我有一点不太赞同的地方:我觉得应该把发射器固有的模板数据和动态变化的数据字段分开,前者应该由多个实例共享。例如在场景中如果有 10 个相同的火把,我认为它们的发射器模板是同一个才对,随粒子系统更新而变化的量才应该分开。而目前作者把它们全部放在了 ParticleEmitter 类中。
最后,作者还对编辑器的设计提了点建议:虽然一个表格式数据输入界面和一个组件节点连线式的图形编辑界面本质上没啥区别;但是美术的思考模式决定了他们就喜欢后一种。所以我们应该尽可能地做成那样让美术更好的理解粒子系统如何工作。
Comments
Posted by: 风行 | (10) February 22, 2019 04:28 PM
Posted by: ddaction | (9) February 18, 2019 11:43 AM
Posted by: 啦啦啦 | (8) January 17, 2019 11:39 PM
Posted by: 技术人成长 | (7) January 17, 2019 12:46 PM
Posted by: Tempest(Yuqiao)Zhang | (6) January 15, 2019 08:30 PM
Posted by: Tempest(Yuqiao)Zhang | (5) January 15, 2019 08:20 PM
Posted by: Tempest(Yuqiao)Zhang | (4) January 15, 2019 08:16 PM
Posted by: Cloud | (3) January 15, 2019 06:08 PM
Posted by: Tempest(Yuqiao)Zhang | (2) January 15, 2019 03:50 PM
Posted by: Tempest(Yuqiao)Zhang | (1) January 13, 2019 02:45 PM