前幾天特效那邊讓改一下一個粒子系統使用的shader,說是shader的一個值希望關聯上粒子的某個值。
我不假思索地就準備寫個腳本掛上去傳個值給shader。等腳本寫好,測試的時候才突然發現,傳值過去後,所有的粒子都是同時改變參數……然後就研究了一下unity粒子系統的自定義shader。
Unity粒子系統的每個粒子在生命週期裏都可以看做一個播放動畫的物體,生命週期結束,動畫就播完了。
如果需要單獨自定義每個粒子在生命週期裏的變化,可以使用腳本改變粒子系統的值,或者自定義shader。不過如果只是普通的更改值,只能使全部粒子發生變化,無法與Lifetime掛鉤,也就是逐粒子進行值的設定。
如果需要自定義一些可以跟隨Lifetime變化的值,有兩種方法:
-
使用Particle System類下的SetCustomParticleData方法和GetCustomParticleData方法來得到並設定當前粒子的數據。
-
第二種方法就是接下來說的,自定義匹配unity粒子系統的shader
Unity內置的粒子shader有兩種,一個Standard Surface,一個Standard Unlit,如果在material裏選擇這兩種shader,會出現這樣的字樣,這就是自定義粒子shader的重點——頂點流(Vertex Streams)
Unity在渲染粒子時,會先根據粒子系統的設置計算一份數據,這就是VertexStreams。在自定義shader里加上預編譯指令#pragma multi_compile_particles ,再在頂點結構體裏聲明對應的數據,就可以使用粒子系統中計算的這份數據了,也就是自定義粒子系統使用的shader。
默認的VertexStreams數據結構爲:粒子的Position,normal,Color,uv1。如果需要自定義頂點流,需要在粒子系統的Renderer頁面勾選Custom Vertex Streams,再根據shader裏所需的數據進行增減。
unity在各個數據後標註了該數據在頂點結構體裏儲存的位置,比如uv1和uv2都存在TEXCOORD0裏,一個在xy分量,一個在zw分量。
粒子系統裏指定的動畫,unity會把動畫的關鍵幀傳給不同的uv,再給出當前時間位置的blend值,即可得到當前時間位置的動畫。
以下是幫特效美術那邊改動的一個粒子系統使用的扭曲shader,粒子系統的Alpha值可以影響扭曲程度。
Shader "heat_distortion_pc"
{
Properties
{
[HideInInspector] _MainTex ("Particle Texture", 2D) = "white" {}
_AlphaTex ("Alpha (A)", 2D) = "white" {}
_NoiseTex ("Noise Texture (RG)", 2D) = "white" {}
_HeatTime ("Heat Time", range (0,1.5)) = 1
_HeatForce ("Heat Force", range (0,1)) = 0.1
}
Category
{
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent+10" "PreviewType"="Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB
Cull Off Lighting Off ZWrite Off
SubShader
{
GrabPass
{
Name "BASE"
Tags { "LightMode" = "Always" }
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma target 2.0
#pragma multi_compile_particles
#include "UnityCG.cginc"
sampler2D _AlphaTex;
float4 _AlphaTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _GrabTexture;
float _HeatForce;
float _HeatTime;
struct appdata_t
{
float4 vertex : POSITION;
fixed4 color : COLOR;
float4 texcoords : TEXCOORD0;
float texcoordBlend : TEXCOORD1;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float4 uvs_partex : TEXCOORD0;
float4 uvs_mytex : TEXCOORD1;
float4 uv_grab : TEXCOORD2;
fixed blend : TEXCOORD3;
UNITY_VERTEX_OUTPUT_STEREO
};
v2f vert (appdata_t v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.color = v.color;
o.uvs_partex.xy = TRANSFORM_TEX(v.texcoords.xy,_MainTex);
o.uvs_partex.zw = TRANSFORM_TEX(v.texcoords.zw,_MainTex);
o.uvs_mytex.xy = TRANSFORM_TEX(v.texcoords.xy,_AlphaTex);
o.uvs_mytex.zw = TRANSFORM_TEX(v.texcoords.xy,_NoiseTex);
#if UNITY_UV_STARTS_AT_TOP
float scale = -1.0;
#else
float scale = 1.0;
#endif
o.uv_grab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
o.uv_grab.zw = o.vertex.zw;
o.blend = v.texcoordBlend;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 colA = tex2D(_MainTex, i.uvs_partex.xy);
fixed4 colB = tex2D(_MainTex, i.uvs_partex.zw);
fixed heatForce = 2.0f * i.color.a * lerp(colA.a, colB.a, i.blend) * _HeatForce;
fixed4 parCol = 2.0f * i.color * lerp(colA, colB, i.blend);
half4 offsetColor1 = tex2D(_NoiseTex, i.uvs_mytex.zw + _Time.xz * _HeatTime);
half4 offsetColor2 = tex2D(_NoiseTex, i.uvs_mytex.zw - _Time.yx * _HeatTime);
i.uv_grab.x += ((offsetColor1.r + offsetColor2.r) - 1) * heatForce;
i.uv_grab.y += ((offsetColor1.g + offsetColor2.g) - 1) * heatForce;
half4 grabCol = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uv_grab));
half4 texCol = tex2D(_AlphaTex, i.uvs_mytex.xy) * parCol;
return texCol * grabCol;
}
ENDCG
}
}
SubShader
{
Blend DstColor Zero
Pass
{
Name "BASE"
SetTexture [_MainTex] { combine texture }
}
}
}
}