Unity性能優化——粒子系統 GPU Instancing

與 CPU 渲染相比,GPU 實例化可帶來巨大的性能提升。如果希望粒子系統渲染__網格__粒子(而不是使用渲染__公告牌__粒子的默認渲染模式),則可使用實例化功能。

爲了能夠對粒子系統使用 GPU 實例化,請執行以下操作:

  • 將粒子系統的渲染器模式設置爲 Mesh

  • 對支持 GPU 實例化的渲染器材質使用一個着色器

  • 在支持 GPU 實例化的平臺上運行項目

要爲粒子系統啓用 GPU 實例化,必須在粒子系統的 Renderer 模塊中啓用 Enable GPU Instancing 複選框。

在 Renderer 模块中启用粒子系统 GPU 实例化的选项在 Renderer 模塊中啓用粒子系統 GPU 實例化的選項

Unity 帶有一個支持 GPU 實例化的內置粒子着色器,但默認的粒子材質不使用該着色器,因此必須更改此設置以使用 GPU 實例化。支持 GPU 實例化的粒子着色器名爲 Particles/Standard Surface。要使用它,必須創建自己的新__材質__,並將材質的着色器設置爲 Particles/Standard Surface。然後,必須將此新材質分配給粒子系統渲染器模塊中的材質字段。

与粒子系统 GPU 实例化兼容的内置着色器與粒子系統 GPU 實例化兼容的內置着色器

如果要爲粒子使用其他不同的着色器,必須使用“#pragma 目標 4.5”或更高版本。有關更多詳細信息,請參閱着色器編譯目標。此要求高於 Unity 中的常規 GPU 實例化,因爲粒子系統將其所有實例數據寫入單個大緩衝區,而不是將實例化分解爲多個繪製調用。

自定義着色器示例

也可以編寫使用 GPU 實例化的自定義着色器。有關更多信息,請參閱以下部分:

表面着色器中的粒子系統 GPU 實例化

以下是使用粒子系統 GPU 實例化的表面着色器的完整運行示例:

Shader "Instanced/ParticleMeshesSurface" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // 基於物理的標準光照模型,並對所有光照類型啓用陰影
        // 並通過實例化支持來生成陰影 pass
        #pragma surface surf Standard nolightmap nometa noforwardadd keepalpha fullforwardshadows addshadow vertex:vert
        // 啓用此着色器的實例化
        #pragma multi_compile_instancing
        #pragma instancing_options procedural:vertInstancingSetup
        #pragma exclude_renderers gles
        #include "UnityStandardParticleInstancing.cginc"
        sampler2D _MainTex;
        struct Input {
            float2 uv_MainTex;
            fixed4 vertexColor;
        };
        fixed4 _Color;
        half _Glossiness;
        half _Metallic;
        void vert (inout appdata_full v, out Input o)
        {
            UNITY_INITIALIZE_OUTPUT(Input, o);
            vertInstancingColor(o.vertexColor);
            vertInstancingUVs(v.texcoord, o.uv_MainTex);
        }

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo 來自顏色着色的紋理
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * IN.vertexColor * _Color;
            o.Albedo = c.rgb;
            // Metallic 和 Smoothness 來自滑動條變量
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

 

上面的示例與常規表面着色器之間存在許多微小差異,正是這些不同之處使得表面着色器可以使用粒子實例化。

首先,必須添加以下兩行以啓用程序實例化 (Procedural Instancing),並指定內置頂點設置函數。此函數位於 UnityStandardParticleInstancing.cginc 中,用於加載每個實例(每個粒子)的位置數據:

 #pragma instancing_options procedural:vertInstancingSetup
                #include "UnityStandardParticleInstancing.cginc"

 

此示例中的其他修改是對頂點函數的修改,此函數增加了兩行來應用每個實例的屬性,具體而言就是粒子顏色和紋理幀動畫紋理座標:

vertInstancingColor(o.vertexColor);
                        vertInstancingUVs(v.texcoord, o.uv_MainTex);

自定義着色器中的粒子系統 GPU 實例化

以下是使用粒子系統 GPU 實例化的自定義着色器的完整運行示例。此自定義着色器添加了標準粒子着色器所不具備的功能:紋理幀動畫的各個幀之間的淡入淡出。

Shader "Instanced/ParticleMeshesCustom"
{
    Properties
    {
        _MainTex("Albedo", 2D) = "white" {}
        [Toggle(_TSANIM_BLENDING)] _TSAnimBlending("Texture Sheet Animation Blending", Int) = 0
    }
    SubShader
    {
        Tags{ "RenderType" = "Opaque" }
        LOD 100
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile __ _TSANIM_BLENDING
            #pragma multi_compile_instancing
            #pragma instancing_options procedural:vertInstancingSetup
            #include "UnityCG.cginc"
            #include "UnityStandardParticleInstancing.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
# ifdef _TSANIM_BLENDING
                float3 texcoord2AndBlend : TEXCOORD1;   
# endif
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 readTexture(sampler2D tex, v2f IN)
            {
                fixed4 color = tex2D(tex, IN.texcoord);
# ifdef _TSANIM_BLENDING
                fixed4 color2 = tex2D(tex, IN.texcoord2AndBlend.xy);
                color = lerp(color, color2, IN.texcoord2AndBlend.z);
# endif
                return color;
            }
            v2f vert(appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                o.color = v.color;
                o.texcoord = v.texcoord;
                vertInstancingColor(o.color);
# ifdef _TSANIM_BLENDING
                vertInstancingUVs(v.texcoord, o.texcoord, o.texcoord2AndBlend);
# else
                vertInstancingUVs(v.texcoord, o.texcoord);
# endif
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            fixed4 frag(v2f i) : SV_Target
            {
                half4 albedo = readTexture(_MainTex, i);
                return i.color * albedo;
            }
            ENDCG
        }
    }
}

 

此示例包含與表面着色器相同的用於加載位置數據的設置代碼:

        #pragma instancing_options procedural:vertInstancingSetup
                #include "UnityStandardParticleInstancing.cginc"

 

對頂點函數的修改也與表面着色器非常相似:

                vertInstancingColor(o.color);
                # ifdef _TSANIM_BLENDING
                                vertInstancingUVs(v.texcoord, o.texcoord, o.texcoord2AndBlend);
                # else
                                vertInstancingUVs(v.texcoord, o.texcoord);
                # endif

 

與上面的第一個示例相比,這裏唯一的區別是紋理幀動畫混合。這意味着,着色器需要一組額外的紋理座標來讀取紋理幀動畫的兩個幀而不是一個幀,然後將它們混合在一起。

最後,片元着色器讀取紋理並計算最終顏色。

粒子系統 GPU 實例化與自定義頂點流

上面的示例僅使用粒子的默認頂點流設置。這包括位置、法線、顏色和一個 UV。但是,通過使用自定義頂點流,可以將其他數據發送到着色器,例如速度、旋轉和大小。

在接下來這一個示例中,着色器用於顯示特殊效果,使速度更快的粒子看起來更亮,更慢的粒子更暗。有一些額外的代碼用於根據速度來提高粒子的亮度(使用速度頂點流)。此外,因爲此着色器假定該效果不使用紋理幀動畫,所以從自定義流結構中將其省略。

以下是完整的着色器:

Shader "Instanced/ParticleMeshesCustomStreams"
{
    Properties
    {
        _MainTex("Albedo", 2D) = "white" {}
    }
    SubShader
    {
        Tags{ "RenderType" = "Opaque" }
        LOD 100
        Pass
        {
            CGPROGRAM
# pragma exclude_renderers gles
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #pragma instancing_options procedural:vertInstancingSetup
            #define UNITY_PARTICLE_INSTANCE_DATA MyParticleInstanceData
            #define UNITY_PARTICLE_INSTANCE_DATA_NO_ANIM_FRAME
            struct MyParticleInstanceData
            {
                float3x4 transform;
                uint color;
                float speed;
            };
            #include "UnityCG.cginc"
            #include "UnityStandardParticleInstancing.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            v2f vert(appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                o.color = v.color;
                o.texcoord = v.texcoord;
                vertInstancingColor(o.color);
                vertInstancingUVs(v.texcoord, o.texcoord);
# if defined(UNITY_PARTICLE_INSTANCING_ENABLED)
                UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID];
                o.color.rgb += data.speed;
# endif
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            fixed4 frag(v2f i) : SV_Target
            {
                half4 albedo = tex2D(_MainTex, i.texcoord);
                return i.color * albedo;
            }
            ENDCG
        }
    }
}

 

此着色器包含 UnityStandardParticleInstancing.cginc(其中含有未使用自定義頂點流時的默認實例化數據佈局)。因此,在使用自定義流時,必須重載該頭部中定義的某些默認值。這些重載必須位於包含 (include) 之前。上面的示例設置了以下自定義重載:

首先,有一行代碼使用 UNITY_PARTICLE_INSTANCE_DATA 宏,告訴 Unity 爲自定義流數據使用名爲“MyParticleInstanceData”的自定義結構:

            #define UNITY_PARTICLE_INSTANCE_DATA MyParticleInstanceData

其次,另一個 define 語句告訴實例化系統在此着色器中不需要動畫幀流 (Anim Frame Stream),因爲此示例中的效果不適用於紋理幀動畫:

            #define UNITY_PARTICLE_INSTANCE_DATA_NO_ANIM_FRAME

第三,聲明自定義流數據的結構:

            struct MyParticleInstanceData
                        {
                            float3x4 transform;
                            uint color;
                            float speed;
                        };

這些重載全部都是在 UnityStandardParticleInstancing.cginc 之前包含進來的,因此着色器不會對這些定義使用自己的默認值。

編寫結構時,變量必須與粒子系統渲染器模塊中檢視面板 (Inspector) 中列出的頂點流相匹配。這意味着必須在渲染器模塊 UI 中選擇要使用的流,然後以相同的順序將它們添加到自定義流數據結構中的變量定義,從而使它們匹配:

渲染器模块 UI 中显示的自定义顶点流,显示了一些实例化的流和一些非实例化的流渲染器模塊 UI 中顯示的自定義頂點流,顯示了一些實例化的流和一些非實例化的流

第一項 (Position) 是必填項,因此無法將其刪除。可使用加號和減號按鈕自由添加/刪除其他條目,從而自定義頂點流數據。

列表中以 INSTANCED 結尾的條目包含實例數據,因此必須將它們包含在粒子實例數據結構中。直接附加到單詞 INSTANCED 後面的數字(例如 INSTANCED0 中的 0 和 INSTANCED1 中的 1)表示變量必須在結構中的初始“transform”變量之後出現的順序。尾部字母(.x、.xy、.xyz 或 .xyzw)表示變量的類型,並映射到着色器代碼中的 float、float2、float3 和 float4 變量類型。

可在粒子實例數據結構中忽略在列表中顯示但結尾沒有單詞 INSTANCED 的任何其他頂點流數據,因爲它不是着色器要處理的實例化數據。此數據屬於源網格,例如 UV、法線和切線。

完成我們的示例的最後一步是將速度應用於頂點着色器內的粒子顏色:

# if defined(UNITY_PARTICLE_INSTANCING_ENABLED)
                UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID];
                o.color.rgb += data.speed;
# endif

必須將所有實例化代碼包裝在 UNITY_PARTICLE_INSTANCING_ENABLED 的檢查中,以便在未使用實例化時進行編譯。

此時,如果要將數據傳遞給片元着色器,可將數據寫入 v2f 結構,就像對任何其他着色器數據的處理一樣。

此示例描述瞭如何修改自定義着色器以便用於自定義頂點流,但可以對錶面着色器應用完全相同的方法來實現相同的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章