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

效果如下:

相关推荐
dangoxiba1 小时前
[Unity Demo]从零开始制作空洞骑士Hollow Knight第十三集:制作小骑士的接触地刺复活机制以及完善地图的可交互对象
游戏·unity·visualstudio·c#·游戏引擎
摩尔线程5 小时前
使用MTVerseXR SDK实现VR串流
3d·xr·图形渲染·vr·摩尔线程
先生沉默先19 小时前
使用Materialize制作unity的贴图,Materialize的简单教程,Materialize学习日志
学习·unity·贴图
十画_8241 天前
Visual Studio 小技巧记录
unity·visual studio
red_redemption1 天前
cpp,git,unity学习
git·unity·游戏引擎
tealcwu1 天前
【Unity踩坑】Unity更新Google Play结算库
unity·游戏引擎
先生沉默先1 天前
unity 默认渲染管线材质球的材质通道,材质球的材质通道
unity·游戏引擎·材质
白鹭float.1 天前
【Unity AI】基于 WebSocket 和 讯飞星火大模型
人工智能·websocket·unity
一个程序员(●—●)1 天前
Unity各个操作功能+基本游戏物体创建与编辑+Unity场景概念及文件导入导出
unity·游戏引擎
起司锅仔1 天前
OpenGL ES 着色器(5)
android·安卓·opengl·着色器