【UnityShader】自定義unity粒子系統使用的shader

前幾天特效那邊讓改一下一個粒子系統使用的shader,說是shader的一個值希望關聯上粒子的某個值。

我不假思索地就準備寫個腳本掛上去傳個值給shader。等腳本寫好,測試的時候才突然發現,傳值過去後,所有的粒子都是同時改變參數……然後就研究了一下unity粒子系統的自定義shader。

Unity粒子系統的每個粒子在生命週期裏都可以看做一個播放動畫的物體,生命週期結束,動畫就播完了。

如果需要單獨自定義每個粒子在生命週期裏的變化,可以使用腳本改變粒子系統的值,或者自定義shader。不過如果只是普通的更改值,只能使全部粒子發生變化,無法與Lifetime掛鉤,也就是逐粒子進行值的設定。

如果需要自定義一些可以跟隨Lifetime變化的值,有兩種方法:

  1. 使用Particle System類下的SetCustomParticleData方法和GetCustomParticleData方法來得到並設定當前粒子的數據。

  2. 第二種方法就是接下來說的,自定義匹配unity粒子系統的shader

Unity內置的粒子shader有兩種,一個Standard Surface,一個Standard Unlit,如果在material裏選擇這兩種shader,會出現這樣的字樣,這就是自定義粒子shader的重點——頂點流(Vertex Streams)

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分量。

Custom Vertex Streams

粒子系統裏指定的動畫,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 
		}
	}   
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章