简介
游戏中经常会看到特效给人眼前一亮的感觉,例如当怪物死亡时逐渐消逝的效果、人物由隐形状态逐渐显形的效果。本篇文章将从Shader的角度去思考如何通过代码去实现这些效果。
原理
- 噪声图:利用噪声图的特性可以产生从随机点开始扩散的消融效果
- 片元裁剪:利用Clip函数裁剪消融片元
基本流程:定义一个阈值(DissolveThreshold)范围在[0,1],对噪声图进行纹理采样,将采样结果与阈值进行比较,如果大于阈值则正常显示,如果小于阈值则裁剪当前片元。当阈值从0递增到1时,消融范围逐步扩大实现逐渐消逝效果。当阈值从1递减到0时,消融范围逐步减少实现逐渐显形效果。
普通消融效果
Shader"MyShader/NormalDissolve"
{
Properties
{
_Diffuse("Diffuse Color",Color) = (1,1,1,1) //主颜色
_MainTex("Main Tex",2D) = "white"{} //主纹理
_DissolveMap("Dissolve Map",2D) = "white"{} //消融噪声
_DissolveThreshold("Dissolve Threshold",Range(0.0,1.0)) = 0.0 //阈值
}
SubShader
{
Tags
{
"RenderType" = "Opaque"
"Queue" = "Geometry"
}
//主Pass负责消融效果
Pass
{
Tags{"LightMode"="ForwardBase"}
//关闭背面剔除同时渲染模型正面与背面(避免消融裸露模型内部)
Cull Off
CGPROGRAM
#include"Lighting.cginc"
#include"AutoLight.cginc"
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _DissolveMap;
float4 _DissolveMap_ST;
float _DissolveThreshold;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvMapTex : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uvMapTex = TRANSFORM_TEX(v.texcoord,_DissolveMap);
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//采样噪声纹理
fixed3 map = tex2D(_DissolveMap,i.uvMapTex).rgb;
//阈值比较进行裁剪
clip(map.r-_DissolveThreshold);
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 albedo = tex2D(_MainTex,i.uvMainTex).rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _Diffuse.rgb*_LightColor0.rgb*(dot(worldNormal,worldLight)*0.5+0.5);
return fixed4(diffuse,1);
}
ENDCG
}
//负责处理阴影(避免以消融的片元产生阴影进而穿帮)
Pass
{
Tags{"LightMode" = "ShadowCaster"}
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
float _DissolveThreshold;
sampler2D _DissolveMap;
float4 _DissolveMap_ST;
struct v2f
{
V2F_SHADOW_CASTER;
float2 uvDissolveMap : TEXCOORD0;
};
v2f vert(appdata_base v)
{
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
o.uvDissolveMap = TRANSFORM_TEX(v.texcoord,_DissolveMap);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 map = tex2D(_DissolveMap,i.uvDissolveMap).rgb;
clip(map.r-_DissolveThreshold);
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
}
FallBack"Diffuse"
}
从运行效果图可以看出消融效果比较突兀,没有过渡的感觉。例如像物体燃烧时逐渐碳化然后消融的效果无法达到。
渐变消融效果
基本思路:定义一个边界宽度来控制渐变的范围,通过smoothstep函数根据噪声图采样结果与阈值的相减值计算渐变系数,根据渐变系数通过插值函数得到渐变色,将渐变色与片元颜色进行混合输出最终表面颜色值。
Shader"MyShader/GradientDissolve"
{
Properties
{
_Diffuse("Diffuse Color",Color) = (1,1,1,1) //主颜色
_MainTex("Main Tex",2D) = "white"{} //主纹理
_DissolveMap("Dissolve Map",2D) = "white"{} //消融噪声
_DissolveThreshold("Dissolve Threshold",Range(0.0,1.0)) = 0.0 //阈值
_DissolveEdge("Dissolve Edge Width",Range(0.0,0.2)) = 0.1 //渐变边界宽度
_DissolveColorA("Dissolve Color A",Color) = (1,1,1,1) //边界颜色(近)
_DissolveColorB("Dissolve Color B",Color) = (1,1,1,1) //边界颜色(远)
}
SubShader
{
Tags
{
"RenderType" = "Opaque"
"Queue" = "Geometry"
}
//主Pass负责消融效果
Pass
{
Tags{"LightMode"="ForwardBase"}
//关闭背面剔除同时渲染模型正面与背面(避免消融裸露模型内部)
Cull Off
CGPROGRAM
#include"Lighting.cginc"
#include"AutoLight.cginc"
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _DissolveMap;
float4 _DissolveMap_ST;
float _DissolveThreshold;
float _DissolveEdge;
fixed4 _DissolveColorA;
fixed4 _DissolveColorB;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvMapTex : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uvMapTex = TRANSFORM_TEX(v.texcoord,_DissolveMap);
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 map = tex2D(_DissolveMap,i.uvMapTex).rgb;
clip(map.r-_DissolveThreshold);
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 albedo = tex2D(_MainTex,i.uvMainTex).rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _Diffuse.rgb*_LightColor0.rgb*(dot(worldNormal,worldLight)*0.5+0.5);
/*
smoothstep(edge0,edge1,x)函数:
当x<=edge0时:返回0
当edge0<=x<=edge1时:返回edge0与edge1间平滑插值
当x>=edge0时:返回1
*/
//计算渐变系数
fixed t = 1-smoothstep(0.0,_DissolveEdge,map.r-_DissolveThreshold);
//计算渐变颜色
fixed3 burnColor = lerp(_DissolveColorA,_DissolveColorB,t);
burnColor = pow(burnColor,5);
//颜色混合
fixed3 finalColor = lerp(ambient + diffuse,burnColor,t*step(0.0001,_DissolveThreshold));
return fixed4(finalColor,1);
}
ENDCG
}
//负责处理阴影
Pass
{
Tags{"LightMode" = "ShadowCaster"}
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
float _DissolveThreshold;
sampler2D _DissolveMap;
float4 _DissolveMap_ST;
struct v2f
{
V2F_SHADOW_CASTER;
float2 uvDissolveMap : TEXCOORD0;
};
v2f vert(appdata_base v)
{
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
o.uvDissolveMap = TRANSFORM_TEX(v.texcoord,_DissolveMap);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 map = tex2D(_DissolveMap,i.uvDissolveMap).rgb;
clip(map.r-_DissolveThreshold);
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
}
FallBack"Diffuse"
}