US3S9L2——序列帧动画

分析利用纹理坐标制作序列帧动画的原理

Shader实现序列帧动画的关键点是:UV 坐标原点为左下角,而序列帧图集 “原点” 为左上角
我们需要注意采样开始位置的转换

以下图为例,制作一个序列帧爆炸动画(不需要使用 Unity 的 Sprite Editor 去分割图集):

boom

  • 关键点

    1. UV 坐标范围 0~1,原点为图片左下角
    2. 图集序列帧动画播放顺序为从左到右,从上到下
  • 分析问题

    1. 如何得到当前应该播放哪一帧动画?
    2. 如何将采样规则从 0~1 修改为在指定范围内采样?
  • 问题解决思路

    1. 用内置时间参数 _Time.y​ 参与计算得到具体哪一帧

      时间是不停增长的数值,用它对总帧数取余,便可以循环获取到当前帧数

    2. 利用时间得到当前应该绘制哪一帧后

      我们只需要确认从当前小图片中,采样开始位置,采样范围即可,
      采样开始位置,可以利用当前帧和行列一起计算,采样范围可以将 0~1 范围 缩放转换到 小图范围内

我们按照顺序,从左到右,从上到下的选定不同小图的范围,改变 UV 坐标和采样范围,依次给小图采样
每过一帧就切换到下一个小图采样,通过在大图内依次采样不同的小图的方式,实现序列帧动画效果

image

用 Shader 实现序列帧动画

  1. 新建 Shader,删除无用代码

  2. 声明属性,进行属性映射

    包括主纹理、图集行列、序列帧切换速度

    c
    1
    2
    3
    4
    5
    6
    7
    Properties
    {
    _MainTex ("Texture", 2D) = "white" {} // 序列帧图集纹理
    _Rows("Rows", int) = 8 // 图集的行数
    _Columns("Columns", int) = 8 // 图集的列数
    _Speed("Speed", float) = 1 // 切换动画速度变量
    }
  3. 设置透明 Shader 相关内容

    往往这种序列帧图集图片都会有透明区域,因此需要使用透明混合相关的设置,具体详见:US3S3L5——透明度混合

    设置渲染标签的渲染类型为透明,队列为透明,并忽略投影机
    关闭深度写入,开启混合,需要使用 SrcAlpha OneMinusSrcAlpha​ 混合

    c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    SubShader
    {
    Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent"}

    Pass
    {
    ZWrite Off
    Blend SrcAlpha OneMinusSrcAlpha

    CGPROGRAM
    //...
    ENDCG
    }
  4. 结构体

    只需要顶点坐标和纹理坐标

    c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct v2f
    {
    float2 uv : TEXCOORD0;
    float4 vertex : SV_POSITION;
    };

    sampler2D _MainTex;
    float4 _MainTex_ST;
    float _Rows;
    float _Columns;
    float _Speed;
  5. 顶点着色器

    只需要进行坐标转换和纹理坐标赋值

    c
    1
    2
    3
    4
    5
    6
    7
    v2f vert (appdata_base v)
    {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    return o;
    }
  6. 片元着色器

    1. 利用时间计算帧数
    2. 利用帧数计算当前 uv 采样起始位置(得到小图片 uv 起始位置,注意起始位置要在小图的左下角)
    3. 计算 uv 缩放比例(将 010 \sim 1 转换到 01n0 \sim \frac{1}{n}
    4. 进行 uv 偏移计算(在小图片格子中采样,需要先缩放后偏移)
    5. 采样
    c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    fixed4 frag (v2f i) : SV_Target
    {
    // 利用时间计算当前播放帧数索引
    float frameIndex = floor(_Time.y * _Speed) % (_Rows * _Columns);
    // 计算UV起始坐标
    float2 frameUV = float2(
    // 计算U轴起始位置,其中 / _Columns 是为了让U轴起始位置处于 0~1
    frameIndex % _Columns / _Columns,
    // 计算V轴起始位置,其中+1是为了让起始位置位于格子左下角,/ _Rows是为了让V轴起始位置处于 0~1,
    // (floor(frameIndex / _Columns) + 1) / _Rows 得的是左边上段,因为采样是从左下角开始的,因此需要1-..
    1 - (floor(frameIndex / _Columns) + 1) / _Rows);
    // 计算uv缩放比例
    float2 size = float2(1 / _Columns, 1 / _Rows);
    float2 uv = i.uv * size + frameUV; // 先缩放,是采样范围缩小到0 ~ 1/8,然后加上起始位置

    return tex2D(_MainTex, uv);
    }

显示效果(播放速度 20):

image

完整 Shader 代码如下:

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Shader "TeachShader/Lesson92_SequentialFrameAnimation"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {} // 序列帧图集纹理
_Rows("Rows", int) = 8 // 图集的行数
_Columns("Columns", int) = 8 // 图集的列数
_Speed("Speed", float) = 1 // 切换动画速度变量
}
SubShader
{
Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent"}

Pass
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;
float _Rows;
float _Columns;
float _Speed;

v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}

fixed4 frag (v2f i) : SV_Target
{
// 利用时间计算当前播放帧数索引
float frameIndex = floor(_Time.y * _Speed) % (_Rows * _Columns);
// 计算UV起始坐标
float2 frameUV = float2(
// 计算U轴起始位置,其中 / _Columns 是为了让U轴起始位置处于 0~1
frameIndex % _Columns / _Columns,
// 计算V轴起始位置,其中+1是为了让起始位置位于格子左下角,/ _Rows是为了让V轴起始位置处于 0~1,
// (floor(frameIndex / _Columns) + 1) / _Rows 得的是左边上段,因为采样是从左下角开始的,因此需要1-...
1 - (floor(frameIndex / _Columns) + 1) / _Rows);
// 计算uv缩放比例
float2 size = float2(1 / _Columns, 1 / _Rows);
float2 uv = i.uv * size + frameUV; // 先缩放,是采样范围缩小到0 ~ 1/8,然后加上起始位置

return tex2D(_MainTex, uv);
}
ENDCG
}
}
}