Unity中溶解shader的总结
在实际的游戏开发项目中,美术和策划常常会提出溶解效果的表现需求。例如,子弹飞行时弹道不断消融,角色受到大型炮弹攻击击飞时逐渐消融等。一般情况下,这类消融效果会结合粒子系统来实现,具体是通过给粒子的Render组件添加材质来达成相应表现。
通过总结我在项目中使用的消融shader,以及在网上查找的部分相关shader,我对这些shader进行了基本归类,方便后续思路查找。若其中存在错误,欢迎指出,让我们共同学习进步。
实现溶解效果的基本方法
实现溶解效果的基本方法是使用一张基本纹理贴图和一张无序图。基本纹理贴图用于呈现正常效果,无序图则作为消融的参考值。通常,会让美术制作一张层级图作为消融图,从其RGBA四个通道中任选一个作为溶解的无序通道。
参考shader的实现
1. 基本的实现单次溶解的vert/frag shader
Shader "Esfog/Dissolve"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_NoiseTex ("NoiseTex (R)", 2D) = "white" {}
_DissolveSpeed ("DissolveSpeed (Second)", Float) = 1
_EdgeWidth ("EdgeWidth", Range(0, 0.5)) = 0.1
_EdgeColor ("EdgeColor", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "RenderType" = "Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform sampler2D _NoiseTex;
uniform float _DissolveSpeed;
uniform float _EdgeWidth;
uniform float4 _EdgeColor;
float4 frag(v2f_img i) : COLOR
{
float DissolveFactor = saturate(_Time.y / _DissolveSpeed);
float noiseValue = tex2D(_NoiseTex, i.uv).r;
if (noiseValue <= DissolveFactor)
{
discard;
}
float4 texColor = tex2D(_MainTex, i.uv);
float EdgeFactor = saturate((noiseValue - DissolveFactor) / (_EdgeWidth * DissolveFactor));
float4 BlendColor = texColor * _EdgeColor;
return lerp(texColor, BlendColor, 1 - EdgeFactor);
}
ENDCG
}
}
FallBack Off
}
其基本思路是采样无序图,通过其中某个通道(此处为R通道)的值与当前的溶解系数进行对比。如果当前通道值小于溶解系数,则说明当前片元需要被剔除;若不被剔除,则根据当前值距离消融的比例来设置消融的边缘颜色混合。
2. 根据外部触发的消融vert/frag shader
上述shader在开始触发后会持续消融。但在某些情况下,我们希望通过外部时间来控制消融的触发。可以在shader中提供一个外部参数,通过代码找到该材质并设置其值来触发消融。在上述shader的基础上改进后的代码如下:
Shader "Esfog/Dissolve"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_NoiseTex ("NoiseTex (R)", 2D) = "white" {}
_DissolveSpeed ("DissolveSpeed (Second)", Float) = 1
_EdgeWidth ("EdgeWidth", Range(0, 0.5)) = 0.1
_EdgeColor ("EdgeColor", Color) = (1, 1, 1, 1)
_DissolveStartTime ("DissolveStartTime", float) = 0
}
SubShader
{
Tags { "RenderType" = "Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform sampler2D _NoiseTex;
uniform float _DissolveSpeed;
uniform float _EdgeWidth;
uniform float4 _EdgeColor;
float4 frag(v2f_img i) : COLOR
{
bool isNormal = true;
float c = 1;
float4 texColor = tex2D(_MainTex, i.uv);
if (_DissolveStartTime > 0)
{
float DissolveFactor = saturate((_Time.y - _DissolveStartTime) * _DissolveSpeed);
float noiseValue = tex2D(_NoiseTex, i.uv).r;
if (noiseValue <= DissolveFactor)
{
discard;
}
float EdgeFactor = saturate((noiseValue - DissolveFactor) / (_EdgeWidth * DissolveFactor));
float4 BlendColor = texColor * _EdgeColor;
texColor = lerp(texColor, BlendColor, 1 - EdgeFactor);
}
return texColor;
}
ENDCG
}
}
FallBack Off
}
通过外部设置材质中的_DissolveStartTime
,我们可以控制消融的开始。在求解消融系数时,采用相乘的方式,这样能正确表示消融速度。
3. 用透明通道来实现的消融
前面两种shader都是基于一张无序图来判定是否消融。如果需要实现透明度逐渐消散的效果,直接使用discard
会显得很生硬,无法实现从不透明到半透明再到透明的逐渐消散效果。下面给出一种用透明度实现消融的shader,主要通过两张贴图的采样和某些通道值的对比来控制混合的alpha通道。
Shader "Z/DissolveWithBlend"
{
Properties
{
_Color ("Color&Alpha", Color) = (1, 1, 1, 1)
_MainTex ("MainTex", 2D) = "white" {}
_Mask ("Mask", 2D) = "white" {}
_AlphaFactor ("AlphaFactor", Float) = 0.0
}
SubShader
{
Tags { "Queue" = "Transparent", "IgnoreProjector" = "True", "RenderType" = "Transparent" }
Pass
{
Tags { "LightMode" = "ForwardBase" }
Blend SrcAlpha OneMinusAlpha
Cull Front
ZWrite off
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _Mask;
float4 _Mask_ST;
struct a2v
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
};
v2f vert(a2v i)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, i.vertex);
o.uv0 = TRANSFORM_TEX(i.texcoord, _MainTex);
o.uv1 = TRANSFORM_TEX(i.texcoord, _Mask);
return o;
}
fixed4 frag(v2f i) : Color
{
float4 _mainVar = tex2D(_MainTex, i.uv0);
float4 _maskVar = tex2D(_Mask, i.uv1);
float3 emissive = _Color.rgb * _mainVar.rgb;
return fixed4(emissive, _Color.a * _mainVar.a * step(_maskVar.r, _AlphaFactor));
}
ENDCG
}
}
FallBack "Diffuse"
}
该shader的基本操作是通过设置颜色的alpha通道的透明度,实现用透明度控制的消融效果。不过,该shader对时间的控制需要特效外部的particle组件来完成,代码中未设置对时间的操作。