前几天特效那边让改一下一个粒子系统使用的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
}
}
}