Unity Shader: 一个简单的(规则化)序列帧动画(基础显示)

规则化序列帧动画是指: 每帧宽高一致,仅通过行列即可表示信息.

源码如下:

// 规则化序列帧播放,每帧大小应该一致
// @author Danny_Yan
Shader "Test/SimpleMovieClip"
{
    Properties
    {
        _MainTex ("Image Sequence", 2D) = "white" { }// 序列帧图片
        _RowCount ("行", Float) = 1 // 行数
        _ColumnCount ("列", Float) = 1 // 列数
        _FrameRate ("帧率", Range(1, 100)) = 30 
    }
    SubShader
    {
        //一般序列帧动画的纹理会带有Alpha通道,因此要按透明效果渲染,需要设置标签,关闭深度写入,使用并设置混合
        Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector"="True"}

        Pass
        {
            Tags { "LightMode"="ForwardBase" }
            ZWrite off
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _InfoTex;
            float4 _InfoTex_ST;

            fixed4 _Color;
            float _RowCount;
            float _ColumnCount;
            float _FrameRate;

            float _Total;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                // 是用原始uv,不进行平铺和偏移
                // o.uv = v.uv.xy;// * _MainTex_ST.xy + _MainTex_ST.zw;

                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // 将时间取整(变成以秒为单位)相当于1秒1帧,放大到_FrameRate后,相当于得到帧index,通过index去计算行列索引.
                // 必须将纹理的wrap mode设置为Repeat(或类似的设定),因为当time>_ColumnCount*2时,row会大于_RowCount
                // uvoff中计算的y值会大于1,需要通过纹理的Repeat机制来重复显示.
                // 或者在外部维护一个index变量,并传进来,这样可以在外层将这个index进行重置为0
                float index = floor(_Time.y * _FrameRate); 
                // 取整得到行索引(播放顺序设计为从左到右,先行后列)
                float rowIndex = floor(index / _ColumnCount);
                // 余数为列索引 
                float columnIndex = fmod(index, _ColumnCount); // index - rowIndex * _ColumnCount;
                
                half2 iuv = i.uv.xy; // /_MainTex_ST.xy;
                // 使用中的行列值作为分割计算的元值(总比值). 相当于一个窗口,通过该窗口的上下左右定位得到每帧图片的uv
                half2 rawSplit = half2(_ColumnCount, _RowCount);
                // 当前uv通过rawSplit分割后,得到当前uv在总uv中的占比. 相当于(窗口的)固定大小
                iuv /= rawSplit;
                // 通过当前计算出的行列值与总比值的比例,得到uv的起始偏移量. 相当于(窗口的)起始位置, row是从上到下,取反后转换为uv的从下到上
                half2 uvoff = half2(columnIndex, -rowIndex)/rawSplit;
                iuv += uvoff;
                
                // iuv*=-1;
                fixed4 col = tex2D(_MainTex, iuv);
                
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }

    FallBack "Transparent/VertexLit"
}

为方便观察,制作了一个序列帧测试图:


image.png

效果如下:


效果.gif

有时我们会在游戏中进行简单的方阵显示,此时会联想到直接使用tiling来平铺显示,但如果此时设置Tiling,会发现在方阵中的其他列(或行,根据你实际是按行优先还是列优先)有出现跳帧的情况,如下图:



上图中(右侧数字是平铺出来的),当左侧数字变为4的时候,右侧数字变为了0,左侧为9时,右侧变为了5,因为在这2个时刻,columnIndex被重置为了0,但平铺列还未显示到最后一个列索引.

可以将序列帧处理为单行(会限制帧数量):


序列帧测试图2.png

效果如下:


效果2.gif

上述方式只能利用一个单行的序列帧合集,实际情况中我们更多的是多个动作序列帧图片做成图集,所以可以用单行来表示一个action,通过指定行索引来切换action.
如下图所示,数字action和字母action合并为了一个图集,且有各自的帧数量:


序列帧测试图3.png

修改后的代码如下:

Shader "Test/SimpleMovieClip2"
{
    Properties
    {
        _MainTex ("Image Sequence", 2D) = "white" { }// 序列帧图片

        _RowCount ("最大行数", Float) = 1 // 行数
        _ColumnCount ("最大列数", Float) = 1 // 列数
        _FrameRate ("帧率", Range(1, 100)) = 30 // speed
        
        _ActionRowIndex ("action索引", Range(0, 100)) = 0
        _ActionFrames("当前action帧数", Range(0, 100)) = 0
    }
    SubShader
    {
        //一般序列帧动画的纹理会带有Alpha通道,因此要按透明效果渲染,需要设置标签,关闭深度写入,使用并设置混合
        Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector"="True"}

        Pass
        {
            Tags { "LightMode"="ForwardBase" }
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float _RowCount;
            float _ColumnCount;
            float _FrameRate;

            float _ActionRowIndex;
            float _ActionFrames;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                // o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                // 是用原始uv,不进行平铺和偏移
                o.uv.xy = v.uv.xy;// * _MainTex_ST.xy + _MainTex_ST.zw;

                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // 将时间取整(变成以秒为单位)相当于1秒1帧,放大到_FrameRate后,相当于得到帧index,通过index去计算行列索引.
                // 必须将纹理的wrap mode设置为Repeat(或类似的设定),因为当time>_ColumnCount*2时,row会大于_RowCount
                // uvoff中计算的y值会大于1,需要通过纹理的Repeat机制来重复显示.
                // 或者在外部维护一个index变量,并传进来,这样可以在外层将这个index进行重置为0
                float index = floor(_Time.y * _FrameRate);

                // 指定行
                float rowIndex = _ActionRowIndex;//floor(index / _ColumnCount);
                // 余数为列索引 
                float columnIndex = fmod(index, _ActionFrames); // index - rowIndex * _ColumnCount;
                
                half2 iuv = i.uv.xy; // /_MainTex_ST.xy;
                // 使用中的行列值作为分割计算的元值(总比值). 相当于一个窗口,通过该窗口的上下左右定位得到每帧图片的uv
                half2 rawSplit = half2(_ColumnCount, _RowCount);
                // 当前uv通过rawSplit分割后,得到当前uv在总uv中的占比. 相当于(窗口的)固定大小
                iuv /= rawSplit;
                // 通过当前计算出的行列值与总比值的比例,得到uv的起始偏移量. 相当于(窗口的)起始位置, row是从上到下,取反后转换为uv的从下到上
                half2 uvoff = half2(columnIndex, -rowIndex)/rawSplit;
                iuv += uvoff;
                
                // iuv*=-1;
                fixed4 col = tex2D(_MainTex, iuv);

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }

    FallBack "Transparent/VertexLit"
}

同时显示2个action效果如下(通过2个Material分别设置相关参数进行处理):


效果3.gif

对于大量序列帧动画的性能处理,参考下篇: Unity Shader: 一个简单的(规则化)序列帧动画(性能处理)

转载请注明出处: https://www.jianshu.com/p/6946971c22f8

参考:
https://developpaper.com/unity-shader-realizes-the-effect-of-sequential-frame-animation/

你可能感兴趣的:(Unity Shader: 一个简单的(规则化)序列帧动画(基础显示))