粒子系统概览

From Valve Developer Community
< Zh
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
English (en)Русский (ru)中文 (zh)Translate (Translate)

粒子系统粒子系统是使用大量小物体的组合来模拟一些伪3D视觉效果的系统,例如云、射线束。从求生之路起,起源引擎还加入了全屏的后处理特效。

粒子系统通常包含了不止一类的嵌套子系统,每种都是由控制粒子系统行为的各类组件组成。因此,粒子系统得以生成极其简洁或复杂的效果。

粒子是使用粒子编辑器(en)制作的。你必须把粒子编辑器创建的所有 PCF 文件添加到 <游戏目录>\particles\ 文件夹中的 particles_manifest.txt(en) 清单文件,游戏引擎才能识别到它们。

示例

Particle effects from Alien Swarm

剖析

内存字段
粒子系统的设计是用较少的内存占用换取更多运算资源。为了达到这一目的,粒子系统中的每个粒子只使用了少量字段来描述,例如位置、持续时间、颜色、旋转等。基于这些属性字段,粒子系统的组件得以得以控制粒子系统,计算每个粒子在每一帧的状态。
挂点(粒子)(en)
挂点是粒子系统的基础外部输入机制,每个粒子系统最多有64个挂点。挂点包含一个位置、朝向、和一个它们允许引用的实体对象。默认情况下,挂点0表示的是粒子系统的原点和朝向。
如果把挂点和一个实体对象关联起来,它就可以被绑定到该对象,或该对象的附着点上。同理,粒子系统中每个额外的挂点都能设置它的位置、朝向和绑定的实体。这样一来,军团要塞中 Medic 的治疗射线束就能够从治疗枪射向被治疗的队友。
这也让单个的粒子系统能够在多个点或对象间生效,例如制作出蔓延的火焰的效果。
同时,粒子系统的各种元素都可以获取挂点信息,如此一来,粒子系统可以同时受到多个动态元素的影响。挂点在默认情况下表示空间中的一些位置点,同时,它们还能存储其他附加信息。因此,我们可以通过外部代码或实体来向粒子系统输入一些通用信息,并通过内部的处理转换成系统自身的属性信息。
基础属性
在基础状态下,一个粒子系统不包含任何组件,只有一些基础属性。这些属性是所有系统的通用属性,包括影响整个系统的属性、每个粒子中不变且无法适配其他接口的属性,这些组成了一个系统。
组件
除了基础属性之外,粒子系统还可以包含各类可集成的组件。通常来说,每一类组件的添加数量没有上限,即使同时集成了多个相同组件也没有问题,只要能够满足你的需求。这些组件可以在每个粒子的属性字段上起作用,以便设置或修改它们。
渲染器(en)
渲染器用于指定粒子的绘制方式。包括精灵贴图(Sprite,一种始终面朝玩家相机的贴图/实体类型)、绳索、条带等等。如果你想的话,每个粒子都能以不同的方式被多次绘制。
发射器(en)
发射器用于指定在什么时间段内生成多少数量的粒子。如上所述,一个粒子系统可以包含多个发射器,分别指定不同的发射类型,来构成粒子系统的整体图形,
初始化器(en)
初始化器用于设置每个已生成粒子的初始状态,初始化它们的属性字段。例如,一个粒子的初始空间位置、颜色、尺寸和透明度。初始化器只会在每个粒子生成时设置它们的初始状态,之后将无法对粒子产生影响。
操作器(en)
一旦粒子生成时的初始状态由初始化器设置完毕,操作器便会接管粒子,并在每一帧根据设定的函数更新粒子的状态,直到粒子消失。
作用力(en)
作用力是操作器的一类变体,可以影响粒子的运动状态。
约束器(en)
约束器用于约束粒子的运动,例如使用碰撞约束或是基于控制点的距离约束。
子系统(en)
简而言之,子系统就是嵌套到你的粒子系统中的其他粒子系统。部分数据,例如挂点信息,可以向下传递到子系统中。并且子系统可以相互嵌套很多层。

创建我们的第一个粒子系统

通常来说,我们最后还是会直接使用现成的粒子系统,复制几份,简单地修改一下来满足作图的需要。然而,创建一个新的粒子系统并不难,只要点击“创建”按钮,再设置它的名称就完事了。


这样一来,你就创建了一个空空如也的粒子系统。没有包含任何组件的话,它什么事情也做不了。以下将要介绍的是一些必须添加的基础组件。下面我们开工吧。

  1. 添加一个 Animated Sprite Renderer(动态精灵贴图渲染器) 以实现渲染功能。
  2. 我们需要确定粒子创建的位置,因此找到初始化器,并添加一个 Position Within Sphere Initializer(球体初始化器)。将它们的初始最小、最大速度都设置为64。如果没有设置初始位置,粒子将无法正确地生成。
  3. 添加一个Continuous Emitter(连续发射器)。现在你应该会得到一个白色的球体。实际上,现在你正在发射巨量的拥有初始纹理的粒子。
  4. 我们会注意到,即使你赋予了它们一个初始速度,这些粒子并没有移动。同时巨量的粒子还在堆叠中。这是因为没有操作器来控制它们。因此,即便我们设置了它们的初始位置和速度,在之后的状态更新中它还是什么也做不了,除了渲染这些粒子。因此我们需要两个操作器:
    1. 一个是 Movement Basic(基础运动) 操作器。它允许粒子运动。
    2. 另一个是 Lifespan Decay(结束生命周期) 操作器。它能够在粒子的持续时间结束时移除它们。
  5. 现在你的粒子应该会运动并在一秒后消失。粒子的持续时间默认条件下为一秒。
  6. 粒子的持续时间是在生成时初始化的,因此如果要修改它们的持续时间,我们需要添加 Lifetime Random(随机持续时间) 始化器。几乎所有的初始化器都允许设置随机范围的数值。因此如果我们希望每个粒子出现2到4秒时间,分别设置该初始化器的最小、最大值为2和4就行了。
  7. 接下来修改粒子的纹理,让它不再是白色球体。找到基础属性,点击material(材质)打开材质浏览器,并选择一个合适的粒子材质。
  8. 最后,添加一个 Fade Out Random 操作器。它的作用是让粒子在其持续时间的某个时间范围内渐变消失,默认是粒子最后25%的持续时间中。需要注意的是,由于每个粒子的持续时间在2到4秒不等,而粒子渐隐的时间是按比例的后25%,因此其时间也是不固定的。(也就是最后的0.5到1秒时间不等)

性能

最大粒子数量
默认情况下,每个粒子系统支持的最大粒子数是1000(尽管它的说明写的是最大1004个)。不论使用与否,游戏都会为它分配这么大的内存。因此在创建粒子系统后,最好检查一下粒子计数器,看看你使用了多少粒子,然后把最大粒子数设置成这个数。这样做可以避免你的粒子系统占用了丧心病狂的内存大小。
多线程
粒子系统会多线程地处理粒子。因此如果有多个粒子系统,它们会被尽可能多地分配到不同的线程中。因此表面上来看,把一个复杂的粒子系统拆分成多个系统能提高整体性能。尽管如此,每增加一个系统都会增加一份开销,因此,拆分粒子系统所获得的性能提升较为有限。尤其是对于简单的系统而言,拆分一个只有为数不多粒子的粒子系统,基本可以确定是负优化行为。不过,如果你的粒子系统包含了上千个粒子,不妨试试将它拆分成几个,以利用多线程提升性能。对于同一个粒子系统来说,它自身与它嵌套的子系统只能运行在同一个线程上,这样方便它们相互传递数据。
单指令多数据流(SIMD)
目前,大多数粒子操作器、初始化器等等,都是以单指令多数据流(SIMD)方式工作的。意思是在现有硬件上,它们实际上在同时对一组4个粒子数据进行数学运算。理论上在未来的不同类型的硬件上,可以支持更多数量粒子的同时运算。重点在于,如果你保证粒子数是4的倍数,就能很好地利用这个机制。将粒子系统的数量削减到最近的4的倍数,就能获得少量的性能提升。尽管只有一星半点的提升,但知道这件事没什么坏处。
过度绘制
过渡绘制是同一个像素点被多次反复绘制造成的,原因是有许多图层,而显卡需要一层一层绘制图像。粒子通常是过度绘制的罪魁祸首,并且很容易就陷入最坏的情况,即大量精灵贴图互相堆叠,占满了整个屏幕。粒子系统自身提供了一些对抗过度绘制的方法。
最基础的做法就是遵循通用的解决方法,例如使用数量更少、尺寸更小、不透明的粒子,而不是许多半透明的粒子。然而,考虑到粒子系统的运用场景,这种做法不太现实。
下面介绍一些有帮助的 .vmt 材质设置参数。其中一对属性是 $minfadesize 和 $maxfadesize(字面意思是最小消失尺寸和最大消失尺寸)。这些属性会让粒子根据自身屏幕占比的大小逐渐消失。例如某个材质的 $minfadesize 为 0.25,$maxfadesize 为 0.5,它将会在占据 25% 屏幕大小时开始消失,一旦占据 50% 屏幕大小时完全消失(并不再渲染)。在步行穿过粒子特效实现的烟雾时,上述参数的设置与否,足以决定你的机器是上帧率天堂还是下过度绘制地狱。
另一个有帮助的参数是 $maxsize(字面意思是最大尺寸)。它决定了单个粒子在屏幕空间上绘制的最大尺寸。粒子最大只能保持在这个尺寸,无法超出。例如,你需要血液来实现命中反馈效果时,显然不希望它过近时消失(使用$maxfadesize参数)。但你可以设置它的最大屏幕占比,以此减少潜在的过度绘制问题。
顺带一提,$maxsize 还有一个与之相反的参数 $minsize。它与性能提升关系不大,主要是视觉上的考虑。它可以限制粒子在屏幕上的最小尺寸,不会比该值更小。还是以出血的命中反馈来举例,我们希望它在屏幕上的显示不会小到看不见,这样在命中远距离的敌人时依旧能获得视觉反馈。设置 $minsize 可以达到这一目的
上述两个参数也可以用于实现其它有意思的效果。例如,在使用尘埃粒子时我们会将它的尺寸限制在一个非常小的范围。这样一来,在远处你看到的是空气中漂浮的像素级大小闪烁的尘埃颗粒,而凑近看它们也不会变得跟网球一样大,产生糟糕的视觉效果。
深度混合(depth blending)可以在粒子表面互相重叠时依旧细致且平滑。然而,它同时也是填充率(和过度绘制正相关的一个性能指标)杀手。因此如果有需要,可以在 .vmt 中设置 $depthblend 0 来提升性能。
除此之外,对于动态纹理来说一个有用的 .vmt 参数是 $blendframes 0/1(字面意思是混合帧)。默认情况下动态纹理会将两个动画帧混合插帧来提高动画帧率。然而,这样也会提升场景的绘制次数。禁用帧混合会降低动画的流畅度,但能提高性能。
数据共享
有多种方式在主系统和子系统之间共享数据。在一些复杂的初始化器和操作器中,我们可以将某些通用参数、运算结果保存到挂点上,子系统就可以直接访问并使用,而不用重复计算。
碰撞
通常来说,碰撞计算是个开销极大的操作。默认的模式0会在每一帧都检查一遍所有粒子。这样做代价很高,也限制了粒子数量。然而,Collision(碰撞) 约束允许使用其它的碰撞模式,用碰撞精度换取运行速度。Collision Mode 3 对于难以预测粒子位置、运动的粒子系统来说,是精度与性能之间最佳平衡点。

制作

制作好的粒子必须被添加到 GAME\particles\particles_manifest.txt 文件中。关于制作 DX8 图形接口的备选粒子系统,文件名中必须附加 "_dx80",例如 “rockettrail.pcf”就要改成 “rockettrail_dx80.pcf”。在最高仅支持 DirectX 8 的机器上,将会加载该文件作为替代。不过这样做的必要性不大,毕竟绝大多数人的机器都支持 DX9 或更高。

另见