Unity-粒子系統(tǒng) GPU 實(shí)例化
與 CPU 渲染相比,GPU 實(shí)例化可帶來巨大的性能提升。如果希望粒子系統(tǒng)渲染__網(wǎng)格__粒子(而不是使用渲染__公告牌__粒子的默認(rèn)渲染模式),則可使用實(shí)例化功能。
為了能夠?qū)αW酉到y(tǒng)使用 GPU 實(shí)例化,請(qǐng)執(zhí)行以下操作:
將粒子系統(tǒng)的渲染器模式設(shè)置為?Mesh
對(duì)支持 GPU 實(shí)例化的渲染器材質(zhì)使用一個(gè)著色器
在支持 GPU 實(shí)例化的平臺(tái)上運(yùn)行項(xiàng)目
要為粒子系統(tǒng)啟用 GPU 實(shí)例化,必須在粒子系統(tǒng)的?Renderer?模塊中啟用?Enable GPU Instancing?復(fù)選框。

Unity 帶有一個(gè)支持 GPU 實(shí)例化的內(nèi)置粒子著色器,但默認(rèn)的粒子材質(zhì)不使用該著色器,因此必須更改此設(shè)置以使用 GPU 實(shí)例化。支持 GPU 實(shí)例化的粒子著色器名為?Particles/Standard Surface。要使用它,必須創(chuàng)建自己的新__材質(zhì)__,并將材質(zhì)的著色器設(shè)置為?Particles/Standard Surface。然后,必須將此新材質(zhì)分配給粒子系統(tǒng)渲染器模塊中的材質(zhì)字段。

如果要為粒子使用其他不同的著色器,必須使用“#pragma 目標(biāo) 4.5”或更高版本。有關(guān)更多詳細(xì)信息,請(qǐng)參閱著色器編譯目標(biāo)。此要求高于 Unity 中的常規(guī) GPU 實(shí)例化,因?yàn)榱W酉到y(tǒng)將其所有實(shí)例數(shù)據(jù)寫入單個(gè)大緩沖區(qū),而不是將實(shí)例化分解為多個(gè)繪制調(diào)用。
自定義著色器示例
也可以編寫使用 GPU 實(shí)例化的自定義著色器。有關(guān)更多信息,請(qǐng)參閱以下部分:
表面著色器中的粒子系統(tǒng) GPU 實(shí)例化
自定義著色器中的粒子系統(tǒng) GPU 實(shí)例化
自定義粒子系統(tǒng)使用的實(shí)例數(shù)據(jù)(與自定義頂點(diǎn)流一起使用)
表面著色器中的粒子系統(tǒng) GPU 實(shí)例化
以下是使用粒子系統(tǒng) GPU 實(shí)例化的表面著色器的完整運(yùn)行示例:
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 ? ? ?
? ?// 基于物理的標(biāo)準(zhǔn)光照模型,并對(duì)所有光照類型啟用陰影 ?
? ?// 并通過實(shí)例化支持來生成陰影 pass ? ?
? ?#pragma surface surf Standard nolightmap nometa noforwardadd keepalpha fullforwardshadows addshadow vertex:vert ? ? ?
? ?// 啟用此著色器的實(shí)例化 ? ?
? ?#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 來自滑動(dòng)條變量 ? ? ?
? ? ?o.Metallic = _Metallic; ? ?
? ? ?o.Smoothness = _Glossiness; ? ? ?
? ? ?o.Alpha = c.a; ? ?
? ?} ? ? ?
? ?ENDCG
?} ?
?FallBack "Diffuse"
}
上面的示例與常規(guī)表面著色器之間存在許多微小差異,正是這些不同之處使得表面著色器可以使用粒子實(shí)例化。
首先,必須添加以下兩行以啟用程序?qū)嵗?(Procedural Instancing),并指定內(nèi)置頂點(diǎn)設(shè)置函數(shù)。此函數(shù)位于 UnityStandardParticleInstancing.cginc 中,用于加載每個(gè)實(shí)例(每個(gè)粒子)的位置數(shù)據(jù):
? ? ? ?
#pragma instancing_options procedural:vertInstancingSetup ? ? ? ?
#include "UnityStandardParticleInstancing.cginc"
此示例中的其他修改是對(duì)頂點(diǎn)函數(shù)的修改,此函數(shù)增加了兩行來應(yīng)用每個(gè)實(shí)例的屬性,具體而言就是粒子顏色和紋理幀動(dòng)畫紋理坐標(biāo):
vertInstancingColor(o.vertexColor); ? ? ? ? ? ? ? ? ? ? ? vertInstancingUVs(v.texcoord, o.uv_MainTex);
自定義著色器中的粒子系統(tǒng) GPU 實(shí)例化
以下是使用粒子系統(tǒng) GPU 實(shí)例化的自定義著色器的完整運(yùn)行示例。此自定義著色器添加了標(biāo)準(zhǔn)粒子著色器所不具備的功能:紋理幀動(dòng)畫的各個(gè)幀之間的淡入淡出。
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 ? ?
? ?} ?
?}
}
此示例包含與表面著色器相同的用于加載位置數(shù)據(jù)的設(shè)置代碼:? ? ?
#pragma instancing_options procedural:vertInstancingSetup ? ? ? ?
#include "UnityStandardParticleInstancing.cginc"
對(duì)頂點(diǎn)函數(shù)的修改也與表面著色器非常相似:
? ? ? ? ? ? ? ?
vertInstancingColor(o.color); ? ? ? ? ?
# ifdef _TSANIM_BLENDING ? ?
vertInstancingUVs(v.texcoord, o.texcoord, o.texcoord2AndBlend); ? ?
# else ? ? ? ? ? ?
?vertInstancingUVs(v.texcoord, o.texcoord); ? ? ?
# endif
與上面的第一個(gè)示例相比,這里唯一的區(qū)別是紋理幀動(dòng)畫混合。這意味著,著色器需要一組額外的紋理坐標(biāo)來讀取紋理幀動(dòng)畫的兩個(gè)幀而不是一個(gè)幀,然后將它們混合在一起。
最后,片元著色器讀取紋理并計(jì)算最終顏色。
粒子系統(tǒng) GPU 實(shí)例化與自定義頂點(diǎn)流
上面的示例僅使用粒子的默認(rèn)頂點(diǎn)流設(shè)置。這包括位置、法線、顏色和一個(gè) UV。但是,通過使用自定義頂點(diǎn)流,可以將其他數(shù)據(jù)發(fā)送到著色器,例如速度、旋轉(zhuǎn)和大小。
在接下來這一個(gè)示例中,著色器用于顯示特殊效果,使速度更快的粒子看起來更亮,更慢的粒子更暗。有一些額外的代碼用于根據(jù)速度來提高粒子的亮度(使用速度頂點(diǎn)流)。此外,因?yàn)榇酥骷俣ㄔ撔Ч皇褂眉y理幀動(dòng)畫,所以從自定義流結(jié)構(gòu)中將其省略。
以下是完整的著色器:
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
(其中含有未使用自定義頂點(diǎn)流時(shí)的默認(rèn)實(shí)例化數(shù)據(jù)布局)。因此,在使用自定義流時(shí),必須重載該頭部中定義的某些默認(rèn)值。這些重載必須位于包含 (include)?之前。上面的示例設(shè)置了以下自定義重載:
首先,有一行代碼使用 UNITY_PARTICLE_INSTANCE_DATA 宏,告訴 Unity 為自定義流數(shù)據(jù)使用名為“MyParticleInstanceData”的自定義結(jié)構(gòu):
#define UNITY_PARTICLE_INSTANCE_DATA MyParticleInstanceData
其次,另一個(gè) define 語句告訴實(shí)例化系統(tǒng)在此著色器中不需要?jiǎng)赢嫀?(Anim Frame Stream),因?yàn)榇耸纠械男Ч贿m用于紋理幀動(dòng)畫:
?#define UNITY_PARTICLE_INSTANCE_DATA_NO_ANIM_FRAME
第三,聲明自定義流數(shù)據(jù)的結(jié)構(gòu):
struct MyParticleInstanceData ? ? ?
{ ? ? ? ? ? ?
?float3x4 transform; ?
?uint color; ? ? ? ?
?float speed; ? ?
};
這些重載全部都是在?UnityStandardParticleInstancing.cginc
?之前包含進(jìn)來的,因此著色器不會(huì)對(duì)這些定義使用自己的默認(rèn)值。
編寫結(jié)構(gòu)時(shí),變量必須與粒子系統(tǒng)渲染器模塊中檢視面板 (Inspector) 中列出的頂點(diǎn)流相匹配。這意味著必須在渲染器模塊 UI 中選擇要使用的流,然后以相同的順序?qū)⑺鼈兲砑拥阶远x流數(shù)據(jù)結(jié)構(gòu)中的變量定義,從而使它們匹配:

第一項(xiàng) (Position) 是必填項(xiàng),因此無法將其刪除??墒褂眉犹?hào)和減號(hào)按鈕自由添加/刪除其他條目,從而自定義頂點(diǎn)流數(shù)據(jù)。
列表中以?INSTANCED?結(jié)尾的條目包含實(shí)例數(shù)據(jù),因此必須將它們包含在粒子實(shí)例數(shù)據(jù)結(jié)構(gòu)中。直接附加到單詞?INSTANCED?后面的數(shù)字(例如?INSTANCED0?中的 0 和?INSTANCED1?中的 1)表示變量必須在結(jié)構(gòu)中的初始“transform”變量之后出現(xiàn)的順序。尾部字母(.x、.xy、.xyz 或 .xyzw)表示變量的類型,并映射到著色器代碼中的 float、float2、float3 和 float4 變量類型。
可在粒子實(shí)例數(shù)據(jù)結(jié)構(gòu)中忽略在列表中顯示但結(jié)尾沒有單詞?INSTANCED?的任何其他頂點(diǎn)流數(shù)據(jù),因?yàn)樗皇侵饕幚淼膶?shí)例化數(shù)據(jù)。此數(shù)據(jù)屬于源網(wǎng)格,例如 UV、法線和切線。
完成我們的示例的最后一步是將速度應(yīng)用于頂點(diǎn)著色器內(nèi)的粒子顏色:
# if defined(UNITY_PARTICLE_INSTANCING_ENABLED) ?
UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID]; ?
o.color.rgb += data.speed;
# endif
必須將所有實(shí)例化代碼包裝在 UNITY_PARTICLE_INSTANCING_ENABLED 的檢查中,以便在未使用實(shí)例化時(shí)進(jìn)行編譯。
此時(shí),如果要將數(shù)據(jù)傳遞給片元著色器,可將數(shù)據(jù)寫入 v2f 結(jié)構(gòu),就像對(duì)任何其他著色器數(shù)據(jù)的處理一樣。
此示例描述了如何修改自定義著色器以便用于自定義頂點(diǎn)流,但可以對(duì)表面著色器應(yīng)用完全相同的方法來實(shí)現(xiàn)相同的功能。