Particle System Overview

From Valve Developer Community
< Zh
Revision as of 04:33, 16 October 2022 by GlrWere (talk | contribs) (新的页面汉化(尚未完成) new created translation for the page, W.I.P., will be done soon)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
English (en)Русский (ru)中文 (zh)Translate (Translate)

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

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

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

示例

Particle effects from Alien Swarm

剖析

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

创建你的第一个粒子系统

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


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

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

性能

最大粒子数量
By default each system is set to 1000 max particles (even though it says 1004 max particles). This much memory is allocated for the system regardless of whether it's used or not. So after setting up your system, you'll want to take a look at the counter to see how many particles you're using and set the max particles to this number. This will help to keep the memory usage to a sane amount.
Threading
Particles are multithreaded by system. So if you have multiple systems, they'll be distributed over as many threads as are available. So on its face, it's good to split complex systems up into multiple systems to take advantage of this feature. That said, there's overhead to each system, so there's a limited benefit to the usefulness of this approach, especially on simpler systems. Splitting up a system that only has a few dozen particles will almost certainly be a net loss. However, if you system has in the thousand+ range of particles, it's worth treating it as multiple lower count particle systems which can be multi-threaded. A system and it's children are always on the same thread because parents and children can pass data to each other and as such need to be be grouped together.
SIMD
Right now, most particle operators, Initializers, etc. work in SIMD. This means on current hardware they're generally doing all math on groups of four particles simultaneously. Theoretically in the future this will scale up to wider numbers on different kinds of hardware. The point here is that if you stick to multiples of four, you'll be making better use of the system. Scaling a system down to the closest multiple of four will make it slightly more optimal. This isn't a huge gain, but it's something to be aware of.
Overdraw
Overdraw is caused by having to redraw the same pixel many times over, due to many layers on top of one another. Particles are a common culprit, and it's relatively easy to run into a worst case scenario of many sprites directly on top of one another taking up the entire screen. There's a few ways to combat this within the particle system.
The starting point to avoid this is general good practices, such as using fewer, smaller, more opaque particles, rather than many very translucent ones. However, depending on the usage case, that's not always possible.
So there are some useful .vmt material parameters which can be helpful. A pair of them are $minfadesize and $maxfadesize. These cause a particle to begin/end to fade based on its percentage of screensize. So for example a material with a $minfadesize of .25 and $maxfadesize of .5 would start to fade if it were 25% of the screen size and be entirely faded (and not render) once it hit 50% of the screensize. In the case of walking through dense particle based smoke this could make the difference between fast framerates and overdraw death.
Another useful parameter is $maxsize. This is a maximum size, in screenspace, that a particle can reach. It is capped at this size. So for example, if you need to see blood for hit registration purposes, you can't have it fade, but you can cap how much of the screen it'll take up, thus reducing the potential overdraw.
Conversely there's a $minsize parameter. This is less of a performance option as a visual one. It can limit the minimum size, in screenspace, of a particle. So to use the example of blood again, you may want it to always be at least some minimum size, so enemies shot a very far way away will still have a visible effect. Setting the minsize will accomplish this.
The two can be used together for other interesting effects. For example, we use dust motes in a few areas which are limited to a very small min-max range. So at a distance you see these sparkling pixel sized motes floating in the air, but they don't grow to be golf ball sized bits of fuzz in the air when you approach them.
Depthblending makes particles look nice and smooth when interpenetrating surfaces. However, it's also more expensive for fillrate. So setting $depthblend 0 in the .vmt can help to improve performance where needed.
One additional useful .vmt parameter for animated texture sheets is $blendframes 0/1. By default animated textures blend between any given two animation frames to increase the apparent animation rate. However, this adds additional overdraw to the scene. Disabling the frameblend will result in less smooth animation, but increased performance.
Sharing data
There are a variety of ways to share data between parent and children systems. Some of the more complex Initializers or operators can minimize their performance impact by writing out their results to a control point, which can then be read by children and use the same data without having to do any of the work.
Collision
In general, collision is an expensive operation. The default mode 0 does traces for each particle every frame. This is expensive and doesn't scale well. However, the Collision constraint allows for a few different collision modes which allow for much faster collisions that trade off accuracy for speed. Collision Mode 3 is the best accuracy/performance tradeoff for dynamic collisions with particles in unpredictable locations/movements.

Creation

Particles must be added to the GAME\particles\particles_manifest.txt file. To make dx8-level fall backs for particle systems, "_dx80" must be appended to the filename, for example "rockettrail.pcf" becomes "rockettrail_dx80.pcf". On DirectX 8 machines, this is loaded instead. This is unlikely to be worth the effort, as almost all hardware in use will support DX9 or higher.

See also