• Unity通过偏移UV播放序列帧动画


      大家好,我是阿赵。
      在Unity引擎里面用shader播放序列图,估计很多人都有用到了,我自己而已写过好几个版本。这里大概介绍一下。

    一、原理

      先说目的,我现在有一张这样的图片:
    在这里插入图片描述

      这张图片上面,有9个格子,可以理解成是一个动画的9个序列帧,接下来,通过写一个简单的Shader,按照顺序逐个的显示出来,形成一个循环的动画:
    在这里插入图片描述

    ASE里面直接就有这样一个播放序列帧动画的节点,叫做Flipbool UV Animation节点:
    在这里插入图片描述

    从节点可以看出,做这个UV动画,需要的参数有这些:
    1、原始的UV坐标
    2、序列图的行列数,比如我刚才那张图就是3x3的行列式
    3、播放速度
    4、第几帧开始播放
    5、当前播放的时间。
    然后返回的结果,是一个新的UV坐标
      所以从原理上来说,这个序列帧播放其实是根据当前时间,算出当前需要播放第几帧,然后通过行列数,算出截取第几帧所在的图片的UV坐标,然后返回。

    二、实现的代码

      这个UV序列帧动画的代码,我也写过好几个版本,但感觉还是ASE的看起来比较标准一点,所以我就参考ASE的Flipbool UV Animation节点,把它翻译成一个方法:

    float2 GetSequenceAnimUV(float2 uv,float cols,float rows,float speed,float startFrame)
    {
    	float totalTiles = cols * rows;
    
    	float colsOffset = 1.0f / cols;
    	float rowsOffset = 1.0f / rows;
    	float speedVal = _Time.y * speed;
    	float2 offsetTiling = float2(colsOffset, rowsOffset);
    	float currentIndex = round(fmod(speedVal + startFrame, totalTiles));
    	currentIndex += (currentIndex < 0) ? totalTiles : 0;
    	float lineNum = round(fmod(currentIndex, cols));
    	float offsetX = lineNum * colsOffset;
    	float rowCount = round(fmod((currentIndex - lineNum) / cols, rows));
    	rowCount = (int)(rows - 1) - rowCount;
    	float offsetY = rowCount * rowsOffset;
    	float2 offsetXY = float2(offsetX, offsetY);
    	float2 result = uv*offsetTiling +offsetXY;
    	return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

      使用的时候,传入uv、行列数、速度、开始帧这几个参数之后,就可以返回一个当前帧的UV,然后拿这个UV去采样整张图片就可以了;

    三、扩展应用

    1、自己控制时间流逝

    从上面的代码可以看出,这个序列帧动画会自己播放,是因为使用了_Time.y,这是一个时间,代表了从加载场景完成到当前的时间,是会自己增加的。
    如果想不用这个系统的时间,而是由自己来控制时间,有2个办法:

    1.改变speed参数

    speed可以使正数、负数或者是0。当speed越大时,播放得越快,当speed为负数时,动画就是倒着播放。当速度为0时,动画播放就停止了。
    不过我觉得速度参数只是一个控制播放快慢的手段,不是控制时间的方式。

    2.自己控制time

      这个方法是,不使用_Time.y,而是自己传入timeVal参数。这样,我们需要在其他脚本比如C#里面维护一个时间变量。
      这样做的好处是,在不改变播放正常速度时,我们可以任意的跳转到某一个时间点。比如现在需要做一个时光回退的效果,突然间整个世界都回溯到之前几秒钟,通过统一传入某个时间戳,所有动画都可以一起回退到之前的状态。
      当然,用time参数也可以实现加速减速和暂停。
    自己控制time参数的方法如下:

    float2 GetSequenceAnimUVByTime(float2 uv, float cols, float rows, float speed, float startFrame,float timeVal)
    {
    	float totalTiles = cols * rows;
    
    	float colsOffset = 1.0f / cols;
    	float rowsOffset = 1.0f / rows;
    	float speedVal = timeVal * speed;
    	float2 offsetTiling = float2(colsOffset, rowsOffset);
    	float currentIndex = round(fmod(speedVal + startFrame, totalTiles));
    	currentIndex += (currentIndex < 0) ? totalTiles : 0;
    	float lineNum = round(fmod(currentIndex, cols));
    	float offsetX = lineNum * colsOffset;
    	float rowCount = round(fmod((currentIndex - lineNum) / cols, rows));
    	rowCount = (int)(rows - 1) - rowCount;
    	float offsetY = rowCount * rowsOffset;
    	float2 offsetXY = float2(offsetX, offsetY);
    	float2 result = uv * offsetTiling + offsetXY;
    	return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2、自己控制播放指定帧

      有时候做一些比较特殊的序列帧动画,需要根据情况播放特定很准确的某一帧,或者在某几帧之间重复。
      这样的情况如果使用Time作为控制,似乎就比较的不适合了。所以,根据实际情况改一下,可以把传入的速度参数和时间参数都去掉,变成传入想播放第几帧:

    float2 GetSequenceAnimUVByIndex(float2 uv, float cols, float rows, float currentFrame)
    {
    	float totalTiles = cols * rows;
    
    	float colsOffset = 1.0f / cols;
    	float rowsOffset = 1.0f / rows;
    	float2 offsetTiling = float2(colsOffset, rowsOffset);
    	float currentIndex = currentFrame;
    	currentIndex += (currentIndex < 0) ? totalTiles : 0;
    	float lineNum = round(fmod(currentIndex, cols));
    	float offsetX = lineNum * colsOffset;
    	float rowCount = round(fmod((currentIndex - lineNum) / cols, rows));
    	rowCount = (int)(rows - 1) - rowCount;
    	float offsetY = rowCount * rowsOffset;
    	float2 offsetXY = float2(offsetX, offsetY);
    	float2 result = uv * offsetTiling + offsetXY;
    	return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  • 相关阅读:
    redis缓存穿透、击穿、雪崩
    Multimodal Graph-based Transformer Framework for BiomedicalRelation Extraction
    macOS - 安装使用 SQLite
    Kafka基础与核心概念
    Vue2-低版本编译兼容-基础语法-data-methods-双向数据绑定v-model
    如何为WPF应用程序制作一个虚拟键盘?这里有答案(Part 2)
    如何轻松做好设备巡检管理?
    UNITY AR VPS空间-视觉-特征点定位 SDK
    【DELM分类】基于matlab麻雀搜索算法改进深度学习极限学习机数据分类【含Matlab源码 2235期】
    代理模式 结构型模式之四
  • 原文地址:https://blog.csdn.net/liweizhao/article/details/132635716