c# 粒子系统
在上一篇 文章中 ,我写了关于使用粒子系统中可用的各种曲线模式时的性能注意事项。 这次,我们将研究粒子系统的剔除。 (In a previous post, I wrote about the performance considerations when using the various curve modes available in the particle system. This time, we will take a look into particle system culling.)
TL; DR (TL;DR)
- Culling is only possible when a system has predictable behaviour. 仅当系统具有可预测的行为时才有可能进行剔除。
- Turning on a single module will not only add to that module’s overhead, but may increase the overall systems impact due to switching from procedural to non-procedural mode. 打开单个模块不仅会增加该模块的开销,而且由于从过程模式切换到非过程模式,可能会增加整个系统的影响。
- Changing values via script will prevent culling. 通过脚本更改值将防止选择。
- Using custom culling can provide a performance benefit, but only you the developer can decide if and when it is appropriate. Take into consideration the type of effect, if the player may notice it isn’t animating when invisible, and if it is possible to predict the area it will affect? 使用自定义剔除可以提供性能优势,但是只有您自己,开发人员才能决定是否以及何时适当。 考虑一下效果的类型,如果玩家可能会注意到它在不可见时没有动画,并且是否有可能预测会影响的区域?
程序模式 (Procedural Mode)
Internally, each particle system has 2 modes of operating, procedural and non-procedural.
在内部,每个粒子系统都有2种操作模式,即过程性和非过程性。
In procedural mode it is possible to know the state of a particle system for any point in time (past and future) whereas a non-procedural system is unpredictable. This means that it is possible to quickly fast forward (and rewind) a procedural system to any point in time.
在过程模式下,可以在任何时间点(过去和将来)知道粒子系统的状态,而非过程系统是不可预测的。 这意味着可以将流程系统快速快进(或倒带)到任何时间点。
When a system goes out of the view of any camera, it becomes culled. When this occurs, a procedural system will halt updating. It will efficiently fast forward to the new point in time when the system becomes visible again. A non-procedural system cannot do this, it must continue updating the system even when invisible due to its unpredictable nature.
当系统超出任何摄像机的视野时,它将被淘汰。 发生这种情况时,过程系统将停止更新。 当系统再次变得可见时,它将有效地前进到新的时间点。 非过程系统无法做到这一点,即使由于不可预测的性质而看不见,它也必须继续更新系统。
For example, the following system is predictable.It’s in local space, so the movement of the particle system’s transform does not matter; the particles are not influenced by any external forces such as collisions, triggers and wind. This means we are able to calculate the total bounds that particles will exist within during the lifetime of the system (yellow box) and can safely cull the system when not visible.
例如,以下系统是可预测的。它在局部空间中,因此粒子系统变换的运动无关紧要; 粒子不受任何外力(例如碰撞,触发和风)的影响。 这意味着我们能够计算出在系统生命周期(黄色框)内粒子将存在的总边界,并且可以在不可见的情况下安全剔除该系统。
By changing the particle system to world space, it becomes unpredictable. When a particle is spawned it will need to sample the position of the transform at that moment. The position of the transform is unpredictable, its history and future are unknown.,Therefore, the system must continue to update itself even when invisible in order for the particles to be in the correct positions when it becomes visible again.
通过将粒子系统更改为世界空间,它将变得不可预测。 生成粒子时,需要在该时刻采样转换的位置。 变换的位置是不可预测的,它的历史和未来是未知的。因此,即使在不可见的情况下,系统也必须继续更新自身,以使粒子在再次可见时处于正确的位置。
By changing the system to world space it becomes unpredictable. There is no way for the system to know the location of the particles without continuing to update itself whilst invisible.
通过将系统更改为世界空间,它将变得不可预测。 系统无法在不可见的情况下继续更新自身而无法知道粒子的位置。
什么破坏了程序模式,我怎么知道我已经破坏了它? (What breaks procedural mode, how do I know I have broken it?)
When a particle system doesn’t support procedural mode, a small icon is displayed in the Inspector. Mousing over this icon will provide a tooltip that lists the reasons why the system no longer supports procedural mode and cannot be culled. It’s also possible to see that non-procedural mode is in use by looking at the bounding bound of the particle system, a continually changing bounds that only encapsulate the particles are a sign that procedural mode isn’t being used.
当粒子系统不支持程序模式时,将在检查器中显示一个小图标。 将鼠标悬停在此图标上将提供一个工具提示,其中列出了系统不再支持程序模式且无法删除的原因。 通过查看粒子系统的边界范围,还可以看到正在使用非过程模式,这种不断变化的边界仅封装了粒子,这表明未使用过程模式。
The following are examples of conditions that break support for procedural mode.
以下是破坏对过程模式的支持的条件示例。
| Module | Property | What breaks it? |
| Simulation Space | World space | |
| Main | Gravity modifier | Using curves |
| Emission | Rate over distance | Any non zero value |
| External forces | enabled | true |
| Clamp velocity | enabled | true |
| Rotation by speed | enabled | true |
| Collision | enabled | true |
| Trigger | enabled | true |
| Sub Emitters | enabled | true |
| Noise | enabled | true |
| Trails | enabled | true |
| Rotation by lifetime | Angular Velocity | if using a curve and the curve does not support procedural* |
| Velocity over lifetime | X, Y, Z | If using a curve and the curve does not support procedural* |
| Force over lifetime | X, Y, Z | If using a curve and the curve does not support procedural* |
| Force over lifetime | Randomise | enabled |
| 模组 | 属性 | 什么破了? |
| 仿真空间 | 世界空间 | |
| 主要 | 重力修正 | 使用曲线 |
| 排放 | 远距离速率 | 任何非零值 |
| 外力 | 已启用 | 真正 |
| 夹紧速度 | 已启用 | 真正 |
| 转速旋转 | 已启用 | 真正 |
| 碰撞 | 已启用 | 真正 |
| 触发 | 已启用 | 真正 |
| 子发射器 | 已启用 | 真正 |
| 噪声 | 已启用 | 真正 |
| 步道 | 已启用 | 真正 |
| 终生旋转 | 角速度 | 如果使用曲线并且该曲线不支持程序* |
| 整个生命周期的速度 | X,Y,Z | 如果使用曲线而该曲线不支持程序* |
| 终生强制 | X,Y,Z | 如果使用曲线而该曲线不支持程序* |
| 终生强制 | 随机化 | 已启用 |
*A curve can not support procedural mode if it has more than 8 segments. A segment is the number of keys plus an additional key if the curve does not start at 0.0 and another if does not end at 1.0.
*如果曲线有8个以上的段,则它不支持程序模式。 一个段是键的数量加上一个附加键(如果曲线不是从0.0开始,则为另一个,如果不是以1.0结尾)。
播放器中的程序模式无效 (Invalidating procedural mode in the player)
Procedural mode is based on knowing exactly how the system will behave at a specified point in time with no external influences. If a value is changed via script or in the editor during play mode then those assumptions can’t be made and procedural mode is invalidated. This means that even though a system is using all procedurally safe settings, it’s no longer possible to use procedural mode and the system will not be culled anymore.
程序模式基于准确了解系统在指定时间点的行为而不受外部影响的基础。 如果在播放模式期间通过脚本或在编辑器中更改了值,则无法做出这些假设,并且过程模式无效。 这意味着,即使系统正在使用所有过程安全设置,也将无法再使用过程模式,并且系统也将不再被淘汰。
Changing a value or Emitting via script will invalidate procedural mode, which you can notice by examining the bounds of the system in the scene. If the bounds are continuously changing then the procedural mode is no longer being used.
更改值或通过脚本发射将使过程模式无效,您可以通过检查场景中系统的边界来注意到这一点。 如果范围在不断变化,那么将不再使用过程模式。
Sometimes this can be avoided by using the particle system’s built in features to change properties, instead of using a script.
有时可以通过使用粒子系统的内置功能来更改属性,而不是使用脚本来避免这种情况。
Calling Play on a system that has been stopped will reset the system and re-validate procedural mode.
在已停止的系统上调用Play将重置系统并重新验证程序模式。
性能实例 (Performance example)
The performance difference between a procedural and a non-procedural system can be significant. This difference is most noticeable when a system is offscreen. In a scene containing 120 default systems, each simulating 1000 particles, the following performance difference is shown between local space (procedural) and world space (non-procedural). The left area shows the performance when not culled, and the right shows when culled.
程序系统和非程序系统之间的性能差异可能很大。 当系统处于屏幕外时,这种差异最明显。 在包含120个默认系统(每个系统模拟1000个粒子)的场景中,在局部空间(过程性)和世界空间(非过程性)之间显示了以下性能差异。 左侧区域显示未剔除时的性能,右侧区域显示在剔除时的性能。
The blue area represents the work performed by the particle system.
蓝色区域表示粒子系统执行的功。
自定义剔除 (Custom Culling)
The following example shows a simple 2D rain effect that uses the collision module (breaking procedural mode).
下面的示例显示了一个简单的2D雨水效果,该效果使用了碰撞模块(中断过程模式)。
By using the collision module the system is now unpredictable; colliders could be moved or have their properties changed throughout the life of the system. This means that predicting where particles will be in the future is impossible, and therefore the system must continue to update whilst culled.
通过使用碰撞模块,系统现在变得不可预测。 在整个系统生命周期中,对撞机可能会移动或属性发生变化。 这意味着不可能预测粒子将来的位置,因此系统必须在剔除的同时继续更新。
A simple rain effect using the collision module to create a splash effect. The yellow boxes represent the bounding areas.
使用碰撞模块创建飞溅效果的简单下雨效果。 黄色框代表边界区域。
We can see that the collision effect is localised within an area and we will not be moving the transform throughout the life; the particle system has no way to know this though.
我们可以看到碰撞效果位于一个区域内,并且在整个生命中我们都不会移动变换。 粒子系统虽然无法知道这一点。
It is safe for an effect like this to not be updated whilst invisible; it could benefit from custom culling.
这样的效果在不可见时不被更新是安全的; 它可以受益于自定义筛选。
The CullingGroup can be used to integrate into Unity’s culling system, using it we can create a culling area using bounding spheres. When the spheres go in and out of visibility a notification is sent; we can use this to pause the particle system when it is not visible and resume it when it becomes visible again. One downside is that off-screen particles will appear motionless, which can be noticeable in some effects. It’s possible to hide this issue by simulating the system forward a little so as to give the illusion that the system was still active whilst not visible.
该 CullingGroup 可用于集成到统一的系统中剔除,使用它我们可以创建一个使用包围球一个拣出区域。 当球体进入和离开能见度时,会发送通知; 我们可以使用它在看不见粒子系统时暂停它,并在它再次变得可见时恢复它。 不利的一面是,屏幕外的粒子将显示为静止不动,这在某些效果上很明显。 可以通过 稍微向前 模拟 系统 来隐藏此问题, 以使您产生一个幻想,即系统仍处于活动状态,但不可见。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
using UnityEngine;
public class CustomParticleCulling : MonoBehaviour
{
public float cullingRadius = 10;
public ParticleSystem target;
CullingGroup m_CullingGroup;
Renderer[] m_ParticleRenderers;
void OnEnable()
{
if(m_ParticleRenderers == null)
m_ParticleRenderers = target.GetComponentsInChildren<Renderer>();
if (m_CullingGroup == null)
{
m_CullingGroup = new CullingGroup();
m_CullingGroup.targetCamera = Camera.main;
m_CullingGroup.SetBoundingSpheres(new[] { new BoundingSphere(transform.position, cullingRadius) });
m_CullingGroup.SetBoundingSphereCount(1);
m_CullingGroup.onStateChanged += OnStateChanged;
// We need to start in a culled state
Cull(m_CullingGroup.IsVisible(0));
}
m_CullingGroup.enabled = true;
}
void OnDisable()
{
if(m_CullingGroup != null)
m_CullingGroup.enabled = false;
target.Play(true);
SetRenderers(true);
}
void OnDestroy()
{
if (m_CullingGroup != null)
m_CullingGroup.Dispose();
}
void OnStateChanged(CullingGroupEvent sphere)
{
Cull(sphere.isVisible);
}
void Cull(bool visible)
{
if(visible)
{
// We could simulate forward a little here to hide that the system was not updated off-screen.
target.Play(true);
SetRenderers(true);
}
else
{
target.Pause(true);
SetRenderers(false);
}
}
void SetRenderers(bool enable)
{
// We also need to disable the renderer to prevent drawing the particles, such as when occlusion occurs.
foreach (var particleRenderer in m_ParticleRenderers)
{
particleRenderer.enabled = enable;
}
}
void OnDrawGizmos()
{
if (enabled)
{
// Draw gizmos to show the culling sphere.
Color col = Color.yellow;
if (m_CullingGroup != null && !m_CullingGroup.IsVisible(0))
col = Color.gray;
Gizmos.color = col;
Gizmos.DrawWireSphere(transform.position, cullingRadius);
}
}
}
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
using UnityEngine ;
public class CustomParticleCulling : MonoBehaviour
{
public float cullingRadius = 10 ;
public ParticleSystem target ;
CullingGroup m_CullingGroup ;
Renderer [ ] m_ParticleRenderers ;
void OnEnable ( )
{
if ( m_ParticleRenderers == null )
m_ParticleRenderers = target . GetComponentsInChildren < Renderer > ( ) ;
if ( m_CullingGroup == null )
{
m_CullingGroup = new CullingGroup ( ) ;
m_CullingGroup . targetCamera = Camera . main ;
m_CullingGroup . SetBoundingSpheres ( new [ ] { new BoundingSphere ( transform . position , cullingRadius ) } ) ;
m_CullingGroup . SetBoundingSphereCount ( 1 ) ;
m_CullingGroup . onStateChanged += OnStateChanged ;
// We need to start in a culled state
Cull ( m_CullingGroup . IsVisible ( 0 ) ) ;
}
m_CullingGroup . enabled = true ;
}
void OnDisable ( )
{
if ( m_CullingGroup != null )
m_CullingGroup . enabled = false ;
target . Play ( true ) ;
SetRenderers ( true ) ;
}
void OnDestroy ( )
{
if ( m_CullingGroup != null )
m_CullingGroup . Dispose ( ) ;
}
void OnStateChanged ( CullingGroupEvent sphere )
{
Cull ( sphere . isVisible ) ;
}
void Cull ( bool visible )
{
if ( visible )
{
// We could simulate forward a little here to hide that the system was not updated off-screen.
target . Play ( true ) ;
SetRenderers ( true ) ;
}
else
{
target . Pause ( true ) ;
SetRenderers ( false ) ;
}
}
void SetRenderers ( bool enable )
{
// We also need to disable the renderer to prevent drawing the particles, such as when occlusion occurs.
foreach ( var particleRenderer in m_ParticleRenderers )
{
particleRenderer . enabled = enable ;
}
}
void OnDrawGizmos ( )
{
if ( enabled )
{
// Draw gizmos to show the culling sphere.
Color col = Color . yellow ;
if ( m_CullingGroup != null && ! m_CullingGroup . IsVisible ( 0 ) )
col = Color . gray ;
Gizmos . color = col ;
Gizmos . DrawWireSphere ( transform . position , cullingRadius ) ;
}
}
}
|
A custom bounding sphere encapsulates the area the rain effect will influence.
自定义边界球封装了雨水效果将影响的区域。
Performance when using 100 rain systems.
使用100个雨水系统时的性能。
Not all effects are suited to custom culling. The system on the left is custom culled and can be seen to clearly go out of sync whilst the system on the right isn’t culled. This illustrates why non-procedural systems must be updated when not visible.
并非所有效果都适合自定义剔除。 左侧的系统是自定义剔除的,可以看到它明显不同步,而右侧的系统没有剔除。 这说明了为什么非过程系统在不可见时必须进行更新。
翻译自: https://blogs.unity3d.com/2016/12/20/unitytips-particlesystem-performance-culling/
c# 粒子系统