UnityShader教程 溶解效果Dissolve
溶解效果在游戏中颇为常见。例如,在一些神话或魔法世界的游戏里,某些NPC角色会在剧情需要时逐渐消失;还有些更炫酷的场景,如用火焰喷射器将目标燃尽,这些都能运用溶解效果。本文将着重讲解较为基础的溶解效果的实现方法,不过实现途径并非唯一,本文仅提供其中一种思路。
原理
若要让角色身体逐渐一块块消失,可使角色身体相应部位不进行渲染(本文选择此方式,当然也可将其设为透明)。那么,依据什么来判断身体的哪些部分需要溶解呢?这时就需要一张额外的贴图,或者利用角色纹理贴图的Alpha通道(本文选用前者)。这张贴图和纹理贴图一样,对应着玩家身体的每个位置,如此便能根据贴图上某个指定通道的颜色值,来控制角色各个身体部位是否溶解。
另外,我们会用到一个由CG提供的命令——discard。若该命令出现在片段着色器(fragmentShader)中,意味着立即放弃当前处理的片元。也就是说,当判定当前片元需要溶解时,就使用discard命令。
上图即为用于控制溶解程度的纹理。本文较为简单,仅使用了R通道;若想实现更复杂的效果,也可利用其他通道。在本文中,我们将随着时间推进,不断溶解掉贴图上R通道颜色值较小的区域。美术人员可利用这张贴图控制角色的任意溶解顺序和效果。由于本文素材均为随意选取,且作者不擅长画画,所以这张贴图和角色纹理可能不太搭配,还请见谅。
接下来,我们看看下面这张图(此处应插入参数图链接),提前了解一下本篇Shader在Properties中提供的各个可调节参数:
- Base(RGB):角色纹理贴图,无需过多解释。
- NoiseTex(R):非常重要,用于控制角色溶解样式的贴图,仅利用了R通道。
- DissolveSpeed(Second):整个溶解过程所需的时间,单位为秒。
- EdgeWidth:这是额外添加的一个边缘效果参数。例如,观察纸张化作灰烬时,其周围会先变成黑褐色。这里的
EdgeWidth定义的是这个周围区域的大小,需注意,这个width并非指长度,而是定义一个透明度的间隔区间,即与基准值相差多少可算作边缘处理。 - EdgeColor:与
EdgeWidth类似,是边缘效果的颜色。
实现
以下是具体的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
}
代码解释
- 5 - 9行:前面已经对这些参数进行过解释,此处不再赘述。
- 18行:这里使用了Unity提供的
vert_img顶点着色器。由于我们的需求较为简单,只需进行顶点坐标变换并将uv传递给后续处理,既然有现成的函数,就无需自己编写。它会自动将结果返回到一个v2f_img结构体中。 - 30行:
_Time.y表示从物体开始渲染到当前所经过的时间。Unity为我们提供了几个不同的时间参数,都保存在_Time中,其中_Time.y是标准时间,单位为秒。用它除以_DissolveSpeed,就能得到当前时间在总溶解时间中所占的比例。为确保该比值范围限定在(0, 1)内,最后调用CG提供的saturate()函数来实现这一目的。 - 31行:从溶解贴图中取出R通道的值。
- 32 - 35行:利用刚才计算出的比值,对溶解贴图中R通道的颜色值进行判断。若R通道颜色值小于等于该比值,则通过
discard命令抛弃当前片元,当前片元的处理将立即结束。 - 38行:再次计算一个比值,分子为
noiseValue - DissolveFactor,该值表示溶解贴图上的R通道值与目前的溶解基准值的差值;而(_EdgeWidth * DissolveFactor)可这样理解:_EdgeWidth表示多大的差值可算作边缘,乘以DissolveFactor意味着边缘最大宽度会随时间变化,时间越长,宽度越宽。最后,这两个值相除代表当前片元的边缘程度,值越大表示离被溶解的时间越长,反之则表示很快就要被溶解。 - 39行:计算当前纹理颜色与边缘颜色相乘的值,供后续使用。
- 41行:使用
lerp函数,利用上一步计算出的(1 - EdgeFactor)对原始纹理颜色和混合颜色进行插值。在解释第38行时我们提到,EdgeFactor越小表示离溶解时间越近,即边缘化的颜色成分越重。由于lerp(x, y, a) = x * (1 - a) + y * a,为使边缘化的颜色更重,我们使用1 - EdgeFactor作为因子。
效果展示
使用不同的溶解贴图会产生截然不同的效果。以下展示从网上随便找到的两张贴图及其对应的效果:
使用贴图1
使用贴图2
总体而言,这个Shader的效果好坏,很大程度上取决于美术提供的溶解贴图是否合适。本文素材并非一套,所以效果看起来可能会有些奇怪。
另外,在学习Shader的过程中,你可能会发现别人的源代码中会出现很多pow、lerp等函数,以及对各个参数的加减乘除运算。希望大家不必过于纠结这些地方的意义,有时确实存在一定的数学原理,但很多时候是开发者为了调节效果而编写的经验公式。随着经验的积累,你也会开始使用一些经验公式,只要效果达标且不影响性能即可,毕竟玩家看到的并非源代码。