0.本文示例代码地址
1. Unity 内置时间变量
动画的本质就是根据跟随时间显示不同画面。我们要在 Shader 中来实现动画,离不开时间变量的获取。目前 Unity Shader 提供了一系列内置变量支持对于时间的访问,所有与时间相关的内置变量如下表:
2. 序列帧动画实现
2.1 Shader 中实现序列帧动画的思路
在 Shader 中实现序列帧动画,有以下几个关键点:
- 所有的帧都绘制在同一张图片上,作为纹理传递给 Shader,实际采样时采样大图中对应的小图部分
- 使用时间变量来计算当下应该展示的帧在纹理中的位置
- 在片元着色器中针对纹理采样时,计算新的 uv 坐标
2.2 代码分析
在场景中新建一个 Quad,面朝摄像机,然后创建shader和材质,并将材质赋给 Quad。我们先贴最终代码
Shader "Shader_Examples/07_SequenceAnim"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Speed ("Speed", Range(1, 100)) = 30
_RowCount ("Row Count", float) = 8
_ColumnCount ("Column Count", float) = 8
}
SubShader
{
Tags { "Queue"="Transparent" }
LOD 100
Pass
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _RowCount;
float _ColumnCount;
int _Speed;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float time = floor(_Time.y * _Speed);
float row = floor(time / _ColumnCount);
float column = time - row * _ColumnCount;
half2 uv = float2(i.uv.x /_ColumnCount, i.uv.y / _RowCount);
uv.x += column / _ColumnCount;
uv.y -= row / _RowCount;
fixed4 col = tex2D(_MainTex, uv);
return col;
}
ENDCG
}
}
}
- 变量声明中使用
Speed
来控制序列帧动画播放的速度 -
_RowCount
和_ColumnCount
声明这大图中小图的排布方式 - 序列帧动画一般都是包含透明度的图片,所以这里的 Shader 中需要渲染半透明物体的标配Tags
Tags { "Queue"="Transparent" }
以及半透明物体的渲染开关
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
- 顶点着色器没有什么好说的,只干最简单的事:将顶点坐标从模型坐标转换到裁剪坐标
- 重点在片元着色器中,核心是如何计算当前应该渲染的小图的实际 uv
float time = floor(_Time.y * _Speed);
float row = floor(time / _ColumnCount);
float column = time - row * _ColumnCount;
half2 uv = float2(i.uv.x /_ColumnCount, i.uv.y / _RowCount);
uv.x += column / _ColumnCount;
uv.y -= row / _RowCount;
2.3 最终渲染效果
3. 无限滚动背景
实现比较简单,直接上 shader 代码吧
Shader "Shader_Examples/07_ScrollingBackground"
{
Properties
{
_NearTex ("Near Texture", 2D) = "white" {}
_FarText ("Far Texture", 2D) = "white" {}
_Lightness ("Lightness", float) = 1.0
_SpeedNear ("Near Speed", Range(0, 1)) = 0.05
_SpeedFar ("Far Speed", Range(0, 1)) = 0.08
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _NearTex;
float4 _NearTex_ST;
sampler2D _FarText;
float4 _FarText_ST;
float _Lightness;
float _SpeedNear;
float _SpeedFar;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _NearTex) + frac(float2(_SpeedNear, 0.0) * _Time.y);
o.uv.zw = TRANSFORM_TEX(v.uv, _FarText) + frac(float2(_SpeedFar, 0.0) * _Time.y);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 nearColor = tex2D(_NearTex, i.uv.xy);
fixed4 farColor = tex2D(_FarText, i.uv.zw);
fixed4 color = lerp(nearColor, farColor, farColor.a);
color.rgb *= _Lightness;
return color;
}
ENDCG
}
}
}
几个关键点:
- 用于无限循环的图片,设置中的
WrapMode
必须设置为Repeat
,如图所示:
- 核心思想和 uv 动画一样,根据时间变量对 uv 进行水平坐标的偏移,得到动画效果
- 近层图片和远程图片的速度不一样
- 使用近层图片的 alpha 值来对两个图片的颜色进行插值
-
可以把 uv 偏移值的计算放到 CPU 中计算,然后设置材质参数,可以得到完全一样的效果
最终得到的动画效果