两张纹理可以混合出非常多的花样,例如纹理动画、多重纹理混合、纹理扰动、纹理流动。
今天玉兔给大家介绍,如何使用 Shader 实现纹理动画效果。
下图是纹理动画的常见效果之一 —— 图集的逐帧播放,我们来看看如何使用 Shader 实现该效果。

一、图集
首先,我们需要准备一张图集。图集是由帧动画一个个小格子组成的图片,做游戏的小伙伴对它应该非常熟悉了。
图集的制作需要使用专用的工具,把多张图片合成一张大图,这样渲染时可以减少游戏的内存占用,提高运行效率。

图集生成常用工具推荐:TexturePacker、Zwoptex 等。
二、实现原理
要让图集进行动画播放,关键是找到在某个时间节点的时候,对应显示的是哪个格子的图片。
找到对应的格子后,只需要从整张图集的大图片中取出这个格子,并把它显示出来。
这就是它的实现原理,我们看看用 Shader 怎么实现。
三、Shader 编写
1. properties 设置
我们需要用到两个参数:
-
二维向量 cells:用来表示纹理图集分割后的行列数,例如上图是4行4列。 -
帧率 fps:用来表示一秒钟内显示多少帧图像。
写成代码的形式如下:
properties: &props
……
cells: { value: [1, 1] }
fps: { value: 1 }
……
CCProgram unlit-fs %{
……
uniform Constant {
vec2 cells;
float fps;
};
2. FS 计算
第一步:判断显示图像
首先,我们先通过时间和帧率来决定当前应该显示哪个格子的图像,写成代码的形式如下:
float index = floor(cc_time.x * fps);
用时间 cc_time.x 乘以帧率 fps,可以得到一个随时间不断增加的值。用 floor() 函数向下取整,可以得到一个整数的帧索引 index,它用来表示当前应该显示的小格子的序号。
比如 fps = 5,那么在第二秒的时候,index = 2 * 5 = 10, 那么就应该显示第10张格子的图像。
第二步:判断图像位置
先上代码:
float row = cells.x;
float column = cells.y;
vec2 offset = vec2(mod(index,column)/column, mod(floor(index/column),row)/row);
我们需要先确定整张图集分成的行数和列数,用 cell.x 表示行,cells.y 表示列。
然后来计算当前帧在纹理中的 UV 偏移量,这需要先确定当前帧格子所在的行和列。
我们先来计算当前帧在水平方向上的偏移量,对应需要计算格子位于哪一列(即代码中的 offset.x)。
计算行和列,一般都会用到模除函数 mod()。
mod(index, column) 表示用索引(index)模除列数(column),得到的余数就是格子所在的列数,表示水平方向上的偏移量。

由于整张图集的 UV 是在 0~1 范围内的,因此我们需要把计算出来的列数标准化到 0~1 之间,只需要用模除(mod(index,column))除以列数即可(column)。它用来表示当前帧所在的格子,在 UV 坐标中水平偏移的位置。

然后我们来计算格子位于哪一行,用来表示它在竖直方向上的偏移量(即代码中的 offset.y)。
用 mod(floor(index/column),row),就可以计算出 index 对应的行位置。
同样,把计算出来的行数也标准化到 0~1 之间,只需要用模除(mod(floor(index/column),row))除以行数(row)即可。它用来表示当前帧所在的格子,在UV坐标中竖直偏移的位置。

最后写成二维向量 offset,表示当前帧图像在纹理中的 UV 偏移量。
vec2 offset = vec2(mod(index,column)/column, mod(floor(index/column),row)/row);
第三步:计算新 UV 坐标
计算新 UV 坐标的代码如下:
vec2 newUV = v_uv / cells.yx + offset;
由于图集有多行多列,每个格子纹理只占整个纹理的一小部分,因此我们需要把 UV 坐标映射到某一个格子纹理中,保证采样的时候只采样这一个格子里的纹理。
这里用 v_uv / cells.yx,把 UV 坐标缩小到单个格子纹理的范围内。
由于列数 cells.y 表示 UV 在水平方向上的偏移,行数 cells.x 表示 UV 在竖直方向上的偏移,所以代码在进行纹理缩放时,除以的是 cells.yx,而不是 cells.xy。
第四步:纹理采样
用新计算的 UV 坐标对纹理进行采样,就可以实现最终的效果。
vec4 col = mainColor * texture(mainTexture, newUV);
第五步:材质调整
最后在材质中调整参数。
根据使用图集纹理的行列数,更改 cells 的值,也可以调整帧率,更改动画的切换速度。
