【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
        }
    }
}

效果如下:

相关推荐
技术小甜甜11 小时前
【Godot】【入门】信号系统从 0 到 1(UI/玩法彻底解耦的通用写法)
ui·游戏引擎·godot
技术小甜甜12 小时前
【Godot】【入门】节点生命周期怎么用(避免帧循环乱写导致卡顿的范式)
游戏引擎·godot
tealcwu12 小时前
【Unity踩坑】Simulate Touch Input From Mouse or Pen 导致检测不到鼠标点击和滚轮
unity·计算机外设·游戏引擎
ThreePointsHeat12 小时前
Unity WebGL打包后启动方法,部署本地服务器
unity·游戏引擎·webgl
erxij13 小时前
【游戏引擎之路】《古今东西4》正式立项——新的一年,开始长征
游戏引擎
迪普阳光开朗很健康13 小时前
UnityScrcpy 可以让你在unity面板里玩手机的插件
unity·游戏引擎
陈言必行1 天前
Unity 之 设备性能分级与游戏画质设置与设备自动适配指南
游戏·unity·游戏引擎
明洞日记1 天前
【ITK手册001】ITK 架构核心:itk::Object 基类解析与应用指南
c++·图像处理·架构·图形渲染·itk
CreasyChan1 天前
Unity DOTS技术栈详解
unity·c#·游戏引擎
gshh__1 天前
SuperMap Hi-Fi 3D SDK for Unreal 如何实现横断面分析
3d·ue5·游戏引擎·supermap