内置时间变量
动画效果往往都是把时间添加到一些变量的计算中,以便在时间变化时画面也可以随之变化。Unity Shader提供了一系列关于时间的内置变量来允许我们方便地在Shader中访问运行时间,实现各种动画效果。表11.1给出了这些内置的时间变量。
时间变量应用
1.纹理动画
纹理动画在游戏中的应用非常广泛。尤其在各种资源都比较局限的移动平台上,我们往往会使用纹理动画来代替复杂的粒子系统等模拟各种动画效果。
(1)序列帧动画
- 原理:最常见的纹理动画之一就是序列帧动画。依次播放一系列关键帧图像,当播放速度达到一定数值时,看起来就是一个连续的动画。
- 优点:灵活性很强,我们不需要进行任何物理计算就可以得到非常细腻的动画效果。
- 缺点:由于序列帧中每张关键帧图像都不一样,要制作一张出色的序列帧纹理所需要的美术工程量也比较大。
要想实现序列帧动画,我们先要提供一张包含了关键帧图像的图像,由于是透明纹理,因此需要勾选该纹理的Alpha Is Transparency属性)。下面的图像包含了8 × 8张关键帧图像,它们的大小相同,而且播放顺序为从左到右、从上到下(在Unity中纹理坐标竖直方向的顺序(从下到上逐渐增大)和序列帧纹理中的顺序(播放顺序是从上到下)是相反的 )。
![]() |
![]() |
我们需要在每个时刻计算该时刻下应该播放的关键帧的位置,并对该关键帧进行纹理采样。
Shader关键代码如下:
Shader "Unity Shaders Book/Chapter 11/Image Sequence Animation" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
//包含了所有关键帧图像的纹理
_MainTex ("Image Sequence", 2D) = "white" {
}
//该图像在水平方向和竖直方向包含的关键帧图像的个数
_HorizontalAmount ("Horizontal Amount", Float) = 4
_VerticalAmount ("Vertical Amount", Float) = 4
//控制序列帧动画的播放速度
_Speed ("Speed", Range(1, 100)) = 30
}
SubShader {
//由于序列帧图像通常是透明纹理,我们需要设置Pass的相关状态,以渲染透明效果
Tags {
"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass {
Tags {
"LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
......
fixed4 frag (v2f i) : SV_Target {
//【1】把_Time.y和速度属性_Speed相乘来得到模拟的时间
//并使用CG的floor函数对结果值取整来得到整数时间time
float time = floor(_Time.y * _Speed);
//【2】每个时刻需要播放的关键帧在纹理中的位置,是该关键帧所在的行列索引数
//使用time除以_HorizontalAmount的结果值的商来作为当前对应的行索引
float row = floor(time / _HorizontalAmount);
//除法结果的余数则是列索引
float column = time - row * _HorizontalAmount;
//【3】使用行列索引值来构建真正的采样坐标
//把原纹理坐标i.uv按行数和列数进行等分,得到每个子图像的纹理坐标范围
//使用当前的行列数对上面的结果进行偏移,得到当前子图像的纹理坐标
half2 uv = float2(i.uv.x /_HorizontalAmount, i.uv.y / _VerticalAmount);
uv.x += column / _HorizontalAmount;
uv.y -= row / _VerticalAmount;//对竖直方向的坐标偏移需要使用减法
fixed4 c = tex2D(_MainTex, uv);
c.rgb *= _Color;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
(2)滚动背景
很多2D游戏都使用了不断滚动的背景来模拟游戏角色在场景中的穿梭,这些背景往往包含了多个层(layers)来模拟一种视差效果。而这些背景的实现往往就是利用了纹理动画。
Shader关键代码:
Properties {
//第一层(较远)和第二层(较近)的背景纹理
_MainTex ("Base Layer (RGB)", 2D) = "white" {
}
_DetailTex (