2017年1月2日 星期一

Unity粒子系統小技巧 - 剔除

作者:KARL JONES 原文
潤稿:Kelvin Lo


我們上一篇發佈過關於粒子系統中使用不同曲線模型的效能的文章。今天我們將研究一下粒子系統的剔除(Culling)。

說來話長,重點就是:

  • 只有在系統行為可以被預測時才能使用剔除。 
  • 開啟一個模組不僅會影響這個模組的開銷,而且可能由於從程式化的模組切換為非程式化的模式而影響到整個系統的開銷。 
  • 通過腳本改變的值將不會被剔除。 
  • 使用自訂的剔除可以提供效能上的優勢,但只有開發人員才能判斷是否適合。需要考慮特效類型,玩家是否會注意到在它們不可見時動畫停止了,以及是否可以預測影響的區域? 

程式化模式

每個粒子系統都有兩種執行模式,程式化和非程式化。

程式化模式下可以知道粒子系統在任意時間的狀態(無論過去還是未來),但是非程式化系統中這些都是不可預測的。這代表可以將程式化系統在任何時刻快轉(或回播)到任意位置。

當粒子系統處於所有相機的範圍之外時,它將被剔除。這種情況出現時,程式化系統會停止更新。當粒子系統重新出現時它將快轉到正確時間。非程式化的系統則無法這樣做,即使是不可見時它也必須持續更新系統,因為它是不可預測的。

舉個例子,下面的系統是可預測的。它位於本地坐標系中,所以粒子系統的位移無關緊要,粒子系統不受例如碰撞、觸發器或者風力之類的外力影響。這代表著可以計算出粒子系統生存週期的邊界(黃色邊框),並且可以安全地將它在不可見的時候剔除。


通過將粒子系統改為世界坐標系,可以將它變為不可預測的。粒子產生後需要對當下位置進行採樣。粒子所在位置是不可預測的,也不知道它之前和之後要如何執行。因此即便粒子系統本身已經不可見,系統也必須持續更新來保證重新可見時粒子位置是正確的。

將系統改為世界坐標系,粒子系統將不可預測。如果粒子不可見時不持續更新,那麼系統就無法知道粒子的位置。


是什麼打破了程式化模式,如何知道已經打破?


當粒子系統不再支援程式化模式時,檢視面板會顯示一個小圖示。將滑鼠移動到這個圖示上將提示出不再支援程式化模式的原因,並且該粒子系統將不能被剔除。也可以通過查看粒子系統的包圍盒來判斷是否是程式化模式,如果範圍只包含粒子並且不斷改變表明當前沒有使用程式化模式。


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



ModulePropertyWhat breaks it?
Simulation SpaceWorld space
MainGravity modifierUsing curves
EmissionRate over distanceAny non zero value
External forcesenabledtrue
Clamp velocityenabledtrue
Rotation by speedenabledtrue
Collisionenabledtrue
Triggerenabledtrue
Sub Emittersenabledtrue
Noiseenabledtrue
Trailsenabledtrue
Rotation by lifetimeAngular Velocityif using a curve and the curve does not support procedural*
Velocity over lifetimeX, Y, ZIf using a curve and the curve does not support procedural*
Force over lifetimeX, Y, ZIf using a curve and the curve does not support procedural*
Force over lifetimeRandomiseenabled

*一個曲線如果多於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
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個雨系統的性能


自訂剔除並非適用於所有特效。左側的系統使用了自定剔除,可以發現與右側的無剔除版本並不同步。這也說明了為什麼非程式化系統在不可見時也必須要持續更新。

沒有留言:

張貼留言

關於我自己

我的相片
Unity台灣官方部落格 請上Facebook搜尋Unity Taiwan取得Unity中文的最新資訊