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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345