1. Unity Shader中的时间变量
Shader控制这物体的显示,当向Shader中引入时间变量后,就可以让物体的显示效果随时间发生变化,以实现动画效果。
Unity中常见的时间变量如下表:
变量 | 类型 | 描述 |
---|---|---|
_Time | float4 | (t/20, t, 2t, 3t),其中 t 为自该场景加载开始所经过的时间 |
_SinTime | float4 | (t/8, t/4, t/4, t),其中 t 为时间的正弦值 |
_CosTime | float4 | (t/8, t/4, t/4, t),其中 t 为时间的余弦值 |
unity_DeltaTime | float4 | (dt, 1/dt, smoothDt, 1/smoothDt),其中 dt 为时间增量 |
通过Shader实现的动画效果,最常见的可以分为两种形式:
- 纹理动画——通过时间改变纹理采样的位置
- 顶点动画——通过时间改变顶点的位置
2. 纹理动画
2.1 序列帧动画
序列帧动画是常见的一类纹理动画。将一个动画效果中的关键帧图像按顺序保存到一张纹理上,在游戏中根据时间推移,计算当前应该显示关键帧中的哪一帧,并计算其在纹理上的位置,修改采样坐标后进行采样,即可达到动画显示的效果。
另外,由于序列帧动画,特别是特效动画,往往包含半透效果,因此这类动画需要按照透明度混合的方式进行渲染。
下面的例子展示了一个简单的序列帧动画。
使用的序列帧纹理如下:
测试Shader如下:
Shader "MyShader/Chapter_11/Chapter_11_Boom_Shader"
{
Properties
{
_MainTex("MainTex", 2D) = "white"{}
_NumInRow("NumInRow", Int) = 3
_NumInColumn("NumInColumn", Int) = 5
_Speed("Speed", float) = 10
}
SubShader
{
Tags{"Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "true"}
Pass
{
Tags{"LightMode" = "ForwardBase"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
struct a2v
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _NumInRow;
fixed _NumInColumn;
fixed _Speed;
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//计算当前应该显示的关键帧(第几行第几列)
float _num = floor(_Time.y * _Speed);
float _row = floor(_num / _NumInRow);
float _column = _num - _row * _NumInRow;
//原始的uv是针对整张纹理的采样坐标,由于我们只需要显示其中的一个关键帧,因此需要将uv转换为针对这一帧图像的坐标
//整张纹理被等分成了 _NumInColumn 行、_NumInRow 列,因此只需要与总行数和总列数相除,即可将原始uv缩放到单帧图像的uv
//i.uv = float2(i.uv.x / _NumInRow, i.uv.y / _NumInColumn);
//针对第_row行、_column列的关键帧图像计算偏移
//同样因为整张纹理被等分成了 _NumInColumn 行、_NumInRow 列,且纹理坐标的范围为[0,1]
//因此行中每一个图像的偏移为 1/_NumInRow,列中每一个图像的偏移为 -1/_NumInColumn(图像是从上向下排列的)
//i.uv += float2(_column / _NumInRow, - _row / _NumInColumn);
//对上面的计算过程整理合并,可以减少除法的次数
i.uv += float2(_column, -_row);
i.uv.x /= _NumInRow;
i.uv.y /= _NumInColumn;
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
}
效果如下:
2.2 滚动背景
无限滚动背景的原理是以一张在某方向上可以与自身进行拼接的图片作为纹理,在每个时刻下计算当前时刻在该方向上的UV偏移,并进行采样显示。
下面的例子通过两张图片(一张背景图,一张前景图)的纹理动画实现分层滚动背景的效果。
例子中用到了 frac 方法,其作用是返回参数的小数部分,从而做到循环采样。
Shader如下:
Shader "MyShader/Chapter_11/Chapter_11_BackGround_Shader"
{
Properties
{
_BackTex("BackTex", 2D) = "white"{}
_FrontTex("FrontTex", 2D) = "white"{}
_Speed_1("Speed1", float) = 0.2
_Speed_2("Speed2", float) = 0.3
}
SubShader
{
Tags{"Queue" = "BackGround" "RenderType" = "Opaque" "IgnoreProjector" = "true"}
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
struct a2v
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
};
sampler2D _BackTex;
float4 _BackTex_ST;
sampler2D _FrontTex;
float4 _FrontTex_ST;
half _Speed_1;
half _Speed_2;
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _BackTex) + frac(float2(_Speed_1, 0 ) * _Time.y);
o.uv.zw = TRANSFORM_TEX(v.uv, _FrontTex) + frac(float2(_Speed_2, 0 ) * _Time.y);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 _backColor = tex2D(_BackTex, i.uv.xy);
fixed4 _frontColor = tex2D(_FrontTex, i.uv.zw);
fixed3 _finalColor = lerp(_backColor.rgb, _frontColor.rgb, _frontColor.a);
return fixed4(_finalColor, 1);
}
ENDCG
}
}
}
效果如下: