前幾天特效那邊讓改一下一個粒子系統使用的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,扭曲值由自定義數據(曲線)進行調整,noise的offset隨生命週期變化。
自定義數據決定扭曲值:
注意這個曲線的時間軸是粒子的生命週期
自定義頂點數據流:
Shader "Effect/Particles/heat_distortion_pc"
{
Properties
{
_NoiseTex ("Noise Texture (RG)", 2D) = "white" {}
_AlphaTex ("Alpha (A)", 2D) = "white" {}
_HeatTime ("Heat Time", range (0,1.5)) = 1
_HeatForce ("Heat Force", range (0,3)) = 0.1
}
SubShader
{
Tags { "Queue"="Transparent-1" "IgnoreProjector"="True" "RenderType"="Transparent+10" "PreviewType"="Plane" }
Blend SrcAlpha OneMinusSrcAlpha
Cull Off Lighting Off ZWrite Off
LOD 200
GrabPass{ "_DistortTexture" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_particles
#include "UnityCG.cginc"
sampler2D _AlphaTex;
float4 _AlphaTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
sampler2D _DistortTexture;
float _HeatForce;
float _HeatTime;
struct appdata
{
float4 vertex : POSITION;
fixed4 color : COLOR;
float4 texcoord : TEXCOORD0; //xy放uv,zw放lifetime和自定義數據
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float4 texcoords : TEXCOORD0; //xy採樣AlphaTex,zw採樣NoiseTex
float4 grabPos : TEXCOORD1;
float2 customData : TEXCOORD2; //x放lifetime,y放自定義數據
};
v2f vert (appdata v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f, o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.color = v.color;
o.texcoords.xy = TRANSFORM_TEX(v.texcoord.xy, _AlphaTex);
o.texcoords.zw = TRANSFORM_TEX(v.texcoord.xy, _NoiseTex);
o.grabPos = ComputeNonStereoScreenPos(o.vertex);
o.customData = v.texcoord.zw;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed heatForce = i.customData.y * _HeatForce;
float alpha = tex2D(_AlphaTex, i.texcoords.xy).a;
half offset1 = tex2D(_NoiseTex, i.texcoords.zw + i.customData.xx * _HeatTime);
half offset2 = tex2D(_NoiseTex, i.texcoords.zw - i.customData.xx * _HeatTime);
i.grabPos.x += (offset1 + offset2 - 1) * heatForce * alpha;
i.grabPos.y += (offset1 + offset2 - 1) * heatForce * alpha;
half4 grabCol = tex2D(_DistortTexture, i.grabPos.xy/i.grabPos.w);
return fixed4(grabCol.rgb, alpha);
}
ENDCG
}
}
}