Unity Shader- UV動畫原理及簡易實現

開發MrsFu123發表於2022-05-21

2.1 準備工作

(1)建立一個場景,這次為了效果明顯,我們去掉天空盒子

(2)建立一個 Quad,一個 Material,一個 shader,命名為 SequenceAnimation

(3)準備一張序列幀影像,這裡筆者使用的是一張包含了 4 x 4 張關鍵幀的影像

這 16 張關鍵幀影像的大小相同,我們要實現的是讓它們從左到右,從上到下播放。所以我們要做的就很簡單了,只需要在播放時記錄下應該播放的關鍵幀的位置(UV座標),然後進行取樣就行了。

2.2 Shader 實現

序列幀影像往往被當成是一個半透明物件,所以我們以對待半透明物件的方法來對待它。如果對半透明原理及實現方法不熟悉的讀者可以翻看這篇博文 【Unity Shader】(五) ------ 透明效果之半透明效果的實現及原理

I.定義 Properties 塊

    Properties    {        _Color ("Color", Color) = (1,1,1,1)        _MainTex ("Sequence Image", 2D) = "white" {}        _Speed("Speed", Range(1,100)) = 50        _HorizontalAmount ("Horizontal Amount",float) = 4        _VerticalAmount ("Vertical Amount",float) = 4    }

    MainTex 對應著我們準備的序列幀影像,Speed 代表播放速度,HorizontalAmount 和 VerticalAmount 代表著影像在水平方向和豎直方向包含的關鍵幀影像個數。

    II.定義 Tags

      Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}

      序列幀影像一般都是透明紋理,所以這裡我們設定為 Transparent

      III. 定義相關屬性與做出宣告

        
        Tags{
        "LightMode" = 
        "ForwardBase"}
        
                    ZWrite Off
        
                    Blend SrcAlpha OneMinusSrcAlpha
        
        
        
                   CGPROGRAM             # pragma multi_compile_fwdbase             #include "UnityCG.cginc"             # pragma vertex vert             # pragma fragment frag
                   fixed4 _Color;            sampler2D _MainTex;            float4 _MainTex_ST;             float _Speed;             float _HorizontalAmount;             float _VerticalAmount;


        由於是半透明物體,所以我們關閉深度寫入並開啟混合。定義與 Properties 塊中想匹配的屬性

        IV. 定義輸入輸出結構體

          v2f vert(a2v v)            {                v2f o;                o.pos = UnityObjectToClipPos(v.vertex);                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);                return o;            }


          這個 shader 中我們主要是計算關鍵幀的位置和紋理取樣,所以輸入輸出結構體我們不需要太複雜

          V. 定義頂點著色器

            v2f vert(a2v v)            {                v2f o;                o.pos = UnityObjectToClipPos(v.vertex);                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);                return o;            }


            我們使用 TRANSFORM_TEX 來得到最終的紋理座標。我們可以在 UnityCG.cginc 找到 TRANSFORM_TEX 的定義

             
              (tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

            name##_ST.xy 代表縮放,name##_ST.zw 代表偏移,這裡的 name##_ST 就是我們定義的 _MainTex_ST

            VI. 定義片元著色器

              
              
              fixed4 
              frag(v2f i) : SV_Target
              
                          
              {
              
                              
              float 
              time = floor(_Time.y * _Speed);
              
                              
              float 
              row = floor(time / _HorizontalAmount);
              
                              
              float 
              colum = time - row * _HorizontalAmount;
              
              
              
                              half2 uv = i.uv + half2(colum, -row);                 uv.x /= _HorizontalAmount;                 uv.y /= _VerticalAmount;
                              fixed4 c = tex2D(_MainTex, uv);                 c.rgb += _Color;                 return c;             }


              (1)定義時間變數,記錄場景經歷的時間,當然要記得乘上播放速度。其中 floor 函式是一個  向下取整  的函式,我們可以在MSDN上找到它的定義

              (2)計算行列索引值。我們使用的序列幀影像是包含 n x n 張關鍵幀紋理的影像,所以可以把它當做 n x n 的陣列。而行列索引值的計算也很好理解。

              • 時間 / 行個數 = 行索引

              • 時間 - 行個數 * 行索引  =  時間 % 行個數 ,即餘數就是列索引

              (3)利用索引值得到真正的取樣座標。

              • 在原先的 UV 加上一個由第2步求得的行列索引構建成的 half2 ,代表偏移。這個偏移值會隨著時間而改變

              • 在取樣之前要先進行等分,實際上相當於 UV原點 + 偏移量(行索引 / 行等分個數 , 列索引 / 列等分個數)

              (4)最後進行取樣並加上主顏色即可

              疑惑點:

              • 隨著時間的增長,變數 time 不是會變得越來越大嗎,同時 row 也會越來越大,當 row 很大的時候,取樣不會出錯嗎?

              • 進行偏移時,為什麼加的是 half2(colum,-row),而不是 half2(row,colum)?

              解答點:

              (1)

              • 隨著時間增長,row 會越來越大,所以為了限制 UV 在可取樣範圍內,我們  需要把序列幀影像 Wrap Mode 設定為 Repeat ,如下圖。

              • 在 Repeat 模式下,當 UV 值超過 1 時,會捨棄整數值,使用小數部分進行取樣,這樣就會形成紋理重複或者說迴圈的效果 。

              • 可能有的讀者想到使用 % 求餘操作,如果 只是單純的求餘有可能會導致部分少數的關鍵幀沒有被採集到 ,因為在 uv 座標數值上對映不到一些關鍵幀的位置。當然讀者可以自行實現一下。檢視效果。

              (2)

              • 進行偏移時使用的是 half2(colum,-row) 是因為:對 x 軸進行偏移時,我們使用列索引來進行操作,對 y 軸進行偏移時,我們使用行索引來進行操作,所以是 (colum,row)。

              • 之所以 row 取負,是因為 在 Unity 中進行取樣時,豎直方向即 y 軸的座標順序是(從下往上遞增),而我們所期待的播放順序是(從上往下遞增),兩者相反 ,所以這裡的 row 取負

              VII. 最後關閉 FallBack 或者 Fallback "Transparent/VertexLit" 均可

              VIII. 完整程式碼

                
                Shader 
                "Unity/01-SequenceAnimation" 
                
                {
                
                    Properties
                
                    {
                
                        _Color (
                "Color", Color) = (
                1,
                1,
                1,
                1)
                
                        _MainTex (
                "Sequence Image", 
                2D) = 
                "white" {}
                
                        _Speed(
                "Speed", Range(
                1,
                100)) = 
                50
                
                        _HorizontalAmount (
                "Horizontal Amount",
                float) = 
                4
                
                        _VerticalAmount (
                "Vertical Amount",
                float) = 
                4
                
                    }
                
                    SubShader
                
                    {
                
                        Tags{
                "Queue" = 
                "Transparent" 
                "IgnoreProjector" = 
                "True" 
                "RenderType" = 
                "Transparent"}
                
                
                
                       Pass        {            Tags{ "LightMode" = "ForwardBase"}            ZWrite Off            Blend SrcAlpha OneMinusSrcAlpha
                           CGPROGRAM             # pragma multi_compile_fwdbase             #include "UnityCG.cginc"             # pragma vertex vert             # pragma fragment frag
                           fixed4 _Color;            sampler2D _MainTex;            float4 _MainTex_ST;             float _Speed;             float _HorizontalAmount;             float _VerticalAmount;
                            struct a2v            {                float4 vertex : POSITION;                float4 texcoord : TEXCOORD0;            };
                            struct v2f            {                float4 pos : SV_POSITION;                float2 uv : TEXCOORD0;            };
                            v2f vert( a2v v)            {                v2f o;                o.pos = UnityObjectToClipPos(v.vertex);                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);                 return o;            }
                            fixed4 frag( v2f i) : SV_Target            {                 float time = floor(_Time.y * _Speed);                 float row = floor(time  _HorizontalAmount);
                                float colum = time - row * _HorizontalAmount;
                               half2 uv = i.uv + half2(colum,-row);                uv.x /=  _HorizontalAmount;                uv.y /=  _VerticalAmount;
                               fixed4 c = tex2D(_MainTex, uv);                c.rgb += _Color;                 return c;            }

                           ENDCG
                       }
                   }    Fallback "Transparent/VertexLit" }


                IX. 儲存,回到 Unity,把準備好的序列幀影像賦予 MainTex 檢視效果


                來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70016819/viewspace-2895942/,如需轉載,請註明出處,否則將追究法律責任。

                相關文章