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