Unity小技巧:粒子系统性能-剔除

发表于2017-05-07
评论1 3.5k浏览
因为有粒子系统的存在,可以利用个性化的粒子模块配合粒子曲线编辑器让用户更容易做出各种缤纷复杂的粒子效果。但本篇文章的主要目的是粒子系统的剔除,想了解的可以看一看。
  • 只有在系统行为可以被预测时才能使用剔除。
  • 开启一个模块不仅会影响这个模块的开销,而且可能由于从程序化的模块切换为非程序化的模式而影响到整个系统的开销。
  • 通过脚本改变的值将不会被剔除。
  • 使用自定义的剔除可以提供性能上的优势,但只有开发人员才能判断是否适合。需要考虑特效类型,玩家是否会注意到在它们不可见时动画停止了,以及是否可以预测影响的区域?


程序化模式
每个粒子系统都有两种执行模式,程序化和非程序化。

程序化模式下可以知道粒子系统在任意时间的状态(无论过去还是将来),但是非程序化系统中这些都是不可预测的。这意味着可以将程序化系统在任何时刻快进(或回退)到任意位置。

当粒子系统处于所有相机的范围之外时,它将被剔除。这种情况出现时,程序化系统会停止更新。当粒子系统重新可见时它将快进到新的时刻。非程序化的系统则无法这样做,即使是不可见时它也必须持续更新系统,因为它是不可预测的。

举个例子,下面的系统是可预测的。它位于本地坐标系中,所以粒子系统的位移无关紧要,粒子系统不受例如碰撞、触发器或者风之类的外力影响。这意味着可以计算出粒子系统生存周期的边界(黄色边框),并且可以安全地将其在不可见的时候剔除。



通过将粒子系统改为世界坐标系,可以将其变为不可预测的。粒子生成后需要对当前位置进行采样。粒子所在位置是不可预测的,也不知道它之前和之后处于何地。因此即便粒子系统本身已经不可见,系统也必须持续更新来保证重新可见时粒子位置是正确的。


将系统改为世界坐标系,粒子系统将不可预测。如果粒子不可见时不持续更新,那么系统就无法知道粒子的位置。


是什么打破了程序化模式,如何知道已经打破?

当粒子系统不再支持程序化模式时,检视面板会显示一个小图标。将鼠标移动到这个图标上将提示出不再支持程序化模式的原因,并且该粒子系统将不能被剔除。也可以通过查看粒子系统的包围盒来判断是否是程序化模式,如果包围盒只包含粒子并且不断改变表明当前没有使用程序化模式。


下面是一些打破程序化模式的一些情况。

 
*一个曲线如果多于8个分段就无法支持程序化模式。如果曲线不是从0.0开始,或者不是以1.0结束,则一个分段指一串键值再加一个键值。


在播放器中使程序化模式无效

程序化模式基于确切知道系统在不受外部影响的情况下在任意时刻的状态。如果通过脚本或播放模式下在编辑器中改变了某个值,那么之前的假设将不成立,程序化模式就会失效。也就是说即使一个系统已经使用了程序化安全设置,程序化模式也不可能再有效,粒子系统也不会被剔除。

通过脚本改变值或发射粒子都会使得程序化模式无效,这点可以通过检查场景中系统的边框来判断。如果边框持续变化,那么程序化模式就不再有效。

某些时候可以通过使用粒子系统的内置功能而非脚本来改变属性,以避免程序化模式失效。

在已停止的粒子系统上调用播放会重置系统并使得程序化模式重新生效。

性能示例
程序化系统和非程序化系统有着明显的性能差异。当粒子系统不在屏幕范围内时尤为显著。在一个包含了120个默认系统的场景中,每个粒子系统产生1000个粒子,下面显示的是本地坐标系(程序化)和世界坐标系(非程序化)之间的的性能差异。左侧显示的是没发生剔除时,右侧是发生剔除时。 

 
蓝色区域表示粒子系统的消耗


自定义剔除
下图显示的是简单的2D雨效果,它使用了碰撞模块(打破了程序化模式)。

使用了碰撞模块后的系统无法预测。碰撞体可能发生移动,或者它们自身的属性也可能随时间变化。这意味着无法预测粒子未来所在的位置,因此粒子系统在屏幕之外时也需要持续更新。

 
简单的雨效果使用了碰撞模块来产生飞溅的效果。黄色框表示边界。


可以看到碰撞发生在某个特定区域中,并且在整个粒子生命周期中不会移动该区域,但粒子系统并不知道这些。

这样的效果在不可见时不更新也没关系,所以可以通过自定义剔除提高性能。

可以在Unity的剔除系统中嵌入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
using UnityEngine;
   
public class CustomParticleCulling : MonoBehaviour
{
    public float cullingRadius = 10;
    public ParticleSystem target;
   
    private CullingGroup m_CullingGroup;
   
    void Start ()
    {
        m_CullingGroup = new CullingGroup();
        m_CullingGroup.targetCamera = Camera.main;
        m_CullingGroup.SetBoundingSpheres(new BoundingSphere[] { new BoundingSphere(transform.position, cullingRadius) });
        m_CullingGroup.SetBoundingSphereCount(1);
        m_CullingGroup.onStateChanged += OnStateChanged;
    }
   
    void OnStateChanged(CullingGroupEvent sphere)
    {
        if (sphere.isVisible)
        {
           // We could simulate forward a little here to hide that the system was not updated off-screen.
           //这里可以将其前移一点来掩盖系统不在屏幕时不工作。
           target.Play(true);
        }
        else
        {
           target.Pause();
        }
    }
   
    void OnDestroy()
    {
        if(m_CullingGroup != null)
           m_CullingGroup.Dispose();
    }
   
    void OnDrawGizmos()
    {
        // Draw gizmos to show the culling sphere.
        //画出显示筛除剔除球形的小图标
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, cullingRadius);
    }
}


 
自定义的边界球体包括了雨效果的影响区域。


 
100个雨系统的性能



自定义剔除并非适用于所有特效。左侧的系统使用了自定剔除,可以发现与右侧的无剔除版本并不同步。这也说明了为什么非程序化系统在不可见时也必须要持续更新。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引