游戏画面中之所以能产生动态效果主要的原因是因为 游戏循环 机制,即游戏画面每隔一个固定时间(每一帧)就会重新渲染。游戏运行时,每一帧都会更新屏幕,这种更新频率通常称为 帧率(Frames Per Second,FPS)比如 30 FPS、60 FPS 代表的就是 1秒钟更新30次,1秒钟更新60次
而之所以看起来画面是变化的,是因为我们在每一帧可能都会改变游戏中对象的位置、角度、缩放、颜色等等信息后重新渲染,一般情况下,只要帧率大于24FPS,人眼就认为一帧帧切换着的画面是流畅且连贯的了
利用Shader制作动态效果的关键就是 —— 利用时间变化来改变数据,从而导致渲染结果改变,带来画面变化
时间是关键数据,Shader中提供了对应的内置时间变量
- float4 _Time:4个分量的值分别是(t / 20, t, 2t, 3t),其中t代表该游戏场景从加载开始缩经过的时间
- float4 _SinTime:4个分量的值分别是(t / 8, t / 4, t / 2, t), 其中t代表 游戏运行的时间的正弦值
- float4 _CosTime:4个分量的值分别是(t / 8, t / 4, t / 2, t),其中t代表 游戏运行的时间的余弦值
- float4 unity_DeltaTime:4个分量的值分别是(dt, 1 / dt, smoothDt, 1 / smoothDt),dt代表帧间隔时间(上一帧到当前帧间隔时间),smoothDt是平滑处理过的时间间隔,对帧间隔时间进行某种平滑算法处理后的结果
一般会利用时间和什么数据一起计算,来达到动态效果呢?
- 颜色:通过时间控制颜色的变化,比如 渐变、闪烁 等效果
- 位置:利用时间使顶点在某个方向上移动,比如 波动 等效果
- 纹理坐标:利用时间变化来动态改变纹理坐标,比如 水流、云彩、序列帧动画 等效果
- 法线:利用时间动态修改法线方向,比如 风吹草动 等效果
- 缩放:利用时间改变物体缩放比例,比如 脉动、跳动等效果
- 透明度:利用时间控制物体透明度,比如 淡入淡出、闪烁等效果
1、序列帧动画原理
关键点:
- UV坐标范围0~1,原点为图片左下角;
- 图集序列帧动画播放顺序为从左到右,从上到下
分析问题
- 如何得到当前应该播放哪一帧动画?
- 如何将采样规则从0~1修改为在指定范围内采样?
问题解决思路
- 用内置时间参数 _Time.y 参与计算得到具体哪一帧
时间是不停增长的数值,用它对总帧数取余,便可以循环获取到当前帧数
- 利用时间得到当前应该绘制哪一帧后
我们只需要确认从当前小图片中,采样开始位置,采样范围即可,采样开始位置,可以利用当前帧和行列一起计算,采样范围可以将0~1范围 缩放转换到 小图范围内
以下图为例
Shader "ShaderProj/8/SequentialFrameAnimation"
{Properties{_MainTex ("Texture", 2D) = "white" {}// 图集行列 _Rows ("Rows", int) = 8_Columns ("Columns", int) = 8_Speed ("Speed", float) = 1}SubShader{// 因为背景是透明的,所以要以透明模式来渲染,并且关掉深度写入Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector" = "True" }Pass{ZWrite OffBlend SrcAlpha OneMinusSrcAlphaCGPROGRAM#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 data;data.vertex = UnityObjectToClipPos(v.vertex);data.uv = v.texcoord;return data;}fixed4 frag (v2f i) : SV_Target{//得到当前帧 利用时间变量计算float frameIndex = floor(_Time.y * _Speed) % (_Rows * _Columns);//小格子(小图片)采样时的起始位置计算//除以对应的行和列 目的是将行列值 转换到 0~1的坐标范围内// 1 - (floor(frameIndex / _Columns) + 1)/_Rows// +1 是因为把格子左上角转换为格子左下角// 1- 因为UV坐标采样时从左下角进行采样的float2 frameUV = float2(frameIndex % _Columns / _Columns, 1 - (floor(frameIndex / _Columns) + 1) / _Rows);//得到uv缩放比例 相当于从0~1大图 隐射到一个 0~1/n的一个小图中float2 size = float2(1 / _Columns, 1 / _Rows);//计算最终的uv采样坐标信息//*size 相当于把0~1范围 缩放到了 0~1/8范围//+frameUV 相当于把起始的采样位置 移动到了 对应帧小格子的起始位置float2 uv = i.uv * size + frameUV;return tex2D(_MainTex, uv);}ENDCG}}
}