【Unity Shader入门精要 第11章】让画面动起来(一)

1. Unity Shader中的时间变量

Shader控制这物体的显示,当向Shader中引入时间变量后,就可以让物体的显示效果随时间发生变化,以实现动画效果。

Unity中常见的时间变量如下表:

变量 类型 描述
_Time float4 (t/20, t, 2t, 3t),其中 t 为自该场景加载开始所经过的时间
_SinTime float4 (t/8, t/4, t/4, t),其中 t 为时间的正弦值
_CosTime float4 (t/8, t/4, t/4, t),其中 t 为时间的余弦值
unity_DeltaTime float4 (dt, 1/dt, smoothDt, 1/smoothDt),其中 dt 为时间增量

通过Shader实现的动画效果,最常见的可以分为两种形式:

  • 纹理动画------通过时间改变纹理采样的位置
  • 顶点动画------通过时间改变顶点的位置

2. 纹理动画

2.1 序列帧动画

序列帧动画是常见的一类纹理动画。将一个动画效果中的关键帧图像按顺序保存到一张纹理上,在游戏中根据时间推移,计算当前应该显示关键帧中的哪一帧,并计算其在纹理上的位置,修改采样坐标后进行采样,即可达到动画显示的效果。

另外,由于序列帧动画,特别是特效动画,往往包含半透效果,因此这类动画需要按照透明度混合的方式进行渲染。

下面的例子展示了一个简单的序列帧动画。

使用的序列帧纹理如下:

测试Shader如下:

csharp 复制代码
Shader "MyShader/Chapter_11/Chapter_11_Boom_Shader"
{
    Properties
    {
        _MainTex("MainTex", 2D) = "white"{}
        _NumInRow("NumInRow", Int) = 3
        _NumInColumn("NumInColumn", Int) = 5
        _Speed("Speed", float) = 10
    }
    SubShader
    {
        Tags{"Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "true"}
    
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            
            struct a2v
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _NumInRow;
            fixed _NumInColumn;
            fixed _Speed;
            
            v2f vert(a2v v)
            {
               v2f o;
               o.pos = UnityObjectToClipPos(v.vertex);
               o.uv = TRANSFORM_TEX(v.uv, _MainTex);
               return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                
                //计算当前应该显示的关键帧(第几行第几列)
                float _num = floor(_Time.y * _Speed);
                float _row = floor(_num / _NumInRow);
                float _column = _num - _row * _NumInRow; 
                
                //原始的uv是针对整张纹理的采样坐标,由于我们只需要显示其中的一个关键帧,因此需要将uv转换为针对这一帧图像的坐标
                //整张纹理被等分成了 _NumInColumn 行、_NumInRow 列,因此只需要与总行数和总列数相除,即可将原始uv缩放到单帧图像的uv
                //i.uv = float2(i.uv.x / _NumInRow, i.uv.y / _NumInColumn);
                //针对第_row行、_column列的关键帧图像计算偏移
                //同样因为整张纹理被等分成了 _NumInColumn 行、_NumInRow 列,且纹理坐标的范围为[0,1]
                //因此行中每一个图像的偏移为 1/_NumInRow,列中每一个图像的偏移为 -1/_NumInColumn(图像是从上向下排列的)
                //i.uv += float2(_column / _NumInRow, - _row / _NumInColumn);
                
                //对上面的计算过程整理合并,可以减少除法的次数
                i.uv += float2(_column, -_row);
                i.uv.x /= _NumInRow;
                i.uv.y /= _NumInColumn;
                
                return tex2D(_MainTex, i.uv);
            }
            
            ENDCG
        }
    }
}

效果如下:

2.2 滚动背景

无限滚动背景的原理是以一张在某方向上可以与自身进行拼接的图片作为纹理,在每个时刻下计算当前时刻在该方向上的UV偏移,并进行采样显示。

下面的例子通过两张图片(一张背景图,一张前景图)的纹理动画实现分层滚动背景的效果。

例子中用到了 frac 方法,其作用是返回参数的小数部分,从而做到循环采样。

Shader如下:

csharp 复制代码
Shader "MyShader/Chapter_11/Chapter_11_BackGround_Shader"
{
    Properties
    {
        _BackTex("BackTex", 2D) = "white"{}
        _FrontTex("FrontTex", 2D) = "white"{}
        _Speed_1("Speed1", float) = 0.2
        _Speed_2("Speed2", float) = 0.3
    }
    SubShader
    {
        Tags{"Queue" = "BackGround" "RenderType" = "Opaque" "IgnoreProjector" = "true"}
    
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc" 
            
            struct a2v
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
            };
            
            sampler2D _BackTex;
            float4 _BackTex_ST;
            sampler2D _FrontTex;
            float4 _FrontTex_ST;
            half _Speed_1;
            half _Speed_2;
            
            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.uv, _BackTex) + frac(float2(_Speed_1, 0 ) * _Time.y);
                o.uv.zw = TRANSFORM_TEX(v.uv, _FrontTex) + frac(float2(_Speed_2, 0 ) * _Time.y);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 _backColor = tex2D(_BackTex, i.uv.xy);
                fixed4 _frontColor = tex2D(_FrontTex, i.uv.zw);

                fixed3 _finalColor = lerp(_backColor.rgb, _frontColor.rgb, _frontColor.a);
                return fixed4(_finalColor, 1);
            }
            
            ENDCG
        }
    }
}

效果如下:

相关推荐
aerror3 分钟前
如何正确的用Trae 打开 Unity 3D 项目
3d·unity·游戏引擎
步、步、为营2 小时前
.NET 的IOC框架Unity代码示例
unity·游戏引擎·.net
Magnum Lehar2 小时前
wpf游戏引擎的script实现
游戏引擎·wpf
留待舞人归6 小时前
【Unity优化】提高热更新和打包速度
游戏·unity·游戏引擎·unity3d·优化
tealcwu6 小时前
【Unity踩坑】Unity 6在Mac平台编译运行时去除‘trial version‘
macos·unity·游戏引擎
为你写首诗ge7 小时前
【热更新知识】学习一 Lua语法学习
unity·lua
为你写首诗ge8 小时前
【热更新知识】学习三 XLua学习
unity·lua
程序员小刘10 小时前
如何开始HarmonyOS 5与Godot引擎融合开发?
华为·游戏引擎·godot·harmonyos
T.D.C18 小时前
【动画】Unity2D骨骼动画-Animation2D
unity·动画
AgilityBaby18 小时前
Unity实现不倒翁
笔记·unity·c#·游戏引擎