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

效果如下:

相关推荐
charon87783 小时前
UE ARPG | 虚幻引擎战斗系统
游戏引擎
小春熙子4 小时前
Unity图形学之Shader结构
unity·游戏引擎·技术美术
Sitarrrr7 小时前
【Unity】ScriptableObject的应用和3D物体跟随鼠标移动:鼠标放置物体在场景中
3d·unity
极梦网络无忧7 小时前
Unity中IK动画与布偶死亡动画切换的实现
unity·游戏引擎·lucene
逐·風15 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
_oP_i16 小时前
Unity Addressables 系统处理 WebGL 打包本地资源的一种高效方式
unity·游戏引擎·webgl
UTwelve18 小时前
【UE5】一种老派的假反射做法,可以用于移动端,或对反射的速度、清晰度有需求的地方
ue5·虚幻引擎·着色器·虚幻4
代码盗圣20 小时前
GODOT 4 不用scons编译cpp扩展的方法
游戏引擎·godot
Leoysq1 天前
【UGUI】实现点击注册按钮跳转游戏场景
游戏·unity·游戏引擎·ugui
PandaQue1 天前
《潜行者2切尔诺贝利之心》游戏引擎介绍
游戏引擎