純粹的靜態美景宛如一張漂亮的貼圖,而在遊戲中,這種沒有一點動畫的情況往往是十分無趣且讓人感到彆扭的。所以本文會介紹一些簡單的UV動畫。
一. 時間變量在我們寫遊戲邏輯時,涉及到隨時間移動或旋轉這種動作時,我們一般都會使用 Time.time 這個變量,同樣,在 Unity Shader 中,我們需要實現一些動畫時,也需要時間變量。下圖是 Unity 內置的時間變量
名稱類別作用_Timefloat4t 是從場景加載開始時經歷的時間,(t/20 , t , 2t , 3t)_SinTimefloat4t 是時間的正弦值,(t/8 , t/4 , t/2 ,t)_CosTimefloat4t 是時間的餘弦值,(t/8 , t/4 , t/2 ,t)unity_DeltaTimefloat4dt 是時間增量,(dt , 1/dt , smoothDt, 1/smoothDT)比如我們使用 _Time.y 時,就相當於 _Time 的 t 變量,即會記錄場景加載後經歷的時間。下面我們使用它來實現一些效果
二. 序列幀動畫序列幀動畫是一種十分常見的動畫,它就像播放電影一樣,把一連串的關鍵幀圖像以一定的速度播放出來,看起來就是一段連續的動畫。而它的優缺點也十分明顯:
本文以製作一個火焰效果為例。我們需要用到一張序列幀圖像,讀者可以在本文末端下載,也可以使用自己的圖像,先看一下我們要實現的效果
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 的定義
1
2 #define 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)利用索引值得到真正的採樣坐標。
(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 查看效果
2.3 總結
序列幀動畫是一種很常見的應用,讀者也許使用 UI 製作過序列幀動畫,而本文則是側重於 shader 的實現。原理也是十分的簡單,只是對正確的 UV 坐標做紋理採樣。不過需要注意一些細節之處,比如行列索引的相關計算,只要明白這一點,相信讀者能十分輕鬆地理解本例。
三. 背景滾動在筆者的童年時,曾玩過紅白機,裡面的遊戲許多都是一些橫版過關的遊戲。在這種 2D 型遊戲中,我們可以發現有許多場景中背景一直在滾動,營造了一種主角在移動的感覺。而在現今的 2D 遊戲中,這種滾動的背景依舊是我們常用的,所以此處我們來介紹這種效果的 shader 實現。
先看一下我們要實現的效果:
實現這個效果我們使用了兩張圖像,讀者可以在本文末端下載,也可以使用自行準備的圖像
3.1 準備工作(1)創建一個場景,去掉天空盒子
(2)創建一個 Quad,一個 Material,一個 shader,命名為 ScrollingBackground,Quad 最好調整為充滿屏幕
(3)準備兩張圖像,一張 「遠景」(Far),一張 「近景」(Near)
3.2 shader 實現I. 定義 Properties 塊
Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("FarLayer ", 2D) = "white" {} _DetailTex("NearLayer ", 2D) = "white" {} _ScrollX ("Far layer scroll Speed",Float) = 1.0 _Scroll2X("Near layer scroll Speed",Float) = 1.0 _Multiplier ("Layer Multiplier",Float) = 1.0 }_MainTex 代表遠景圖,這裡我使用的是一張純背景色的圖像;_DetailTex 代表近景圖,這裡我使用的是一張有樓宇的圖像;兩個 _Scroll 代表了兩張圖像的滾動速度。_Multiplier 代表了紋理整體亮度,這個如果覺得沒必要可以不寫。
II. 定義相關屬性和做出聲明
Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"} Pass { Tags{"LightMode" = "ForwardBase"} ZWrite Off Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM #include "UnityCG.cginc" #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag
fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _DetailTex; float4 _DetailTex_ST; float _ScrollX; float _Scroll2X; float _Multiplier;我們同樣把它當做透明物體看待,關閉深度寫入和開啟混合,再定義相匹配的變量
III. 定義輸入輸出結構體
struct a2v { float4 vertex : POSITION; float4 texcoord : TEXCOORD0; };
struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; };這裡只是簡單的處理圖片採樣,所以輸入輸出結構體比較簡單
IV. 定義頂點著色器
v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y); o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X, 0.0) * _Time.y); return o; }我們使用一個插值寄存器存儲兩張紋理的坐標,兩張紋理都進行了同樣的操作:先回復到正確的紋理坐標,再在水平方向上進行偏移 。我們使用了 frac 函數進行偏移,有關 frac 函數的定義,我們可以在 MSDN 上找到
這個函數會返回參數 x 的小數部分,相當於在 0 ~ 1 之間循環,紋理會在水平方向上循環偏移
V. 定義片元著色器
fixed4 frag(v2f i) : SV_Target { fixed4 firstLayer = tex2D(_MainTex,i.uv.xy); fixed4 secondLayer = tex2D(_DetailTex, i.uv.zw); fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a); c.rgb *= _Multiplier; c.rgb *= _Color.rgb; return c; }片元著色器比較簡單,主要是對兩張紋理採樣,然後進行混合
VI. 最後關閉 FallBack 或者 Fallback "VertexLit" 均可
VII. 完整代碼
Shader "Unity/02-ScrollingBackground" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("FarLayer ", 2D) = "white" {} _DetailTex("NearLayer ", 2D) = "white" {} _ScrollX ("Far layer scroll Speed",Float) = 1.0 _Scroll2X("Near layer scroll Speed",Float) = 1.0 _Multiplier ("Layer Multiplier",Float) = 1.0 } SubShader { Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"} Pass { Tags{"LightMode" = "ForwardBase"} ZWrite Off Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _DetailTex; float4 _DetailTex_ST; float _ScrollX; float _Scroll2X; float _Multiplier;
struct a2v { float4 vertex : POSITION; float4 texcoord : TEXCOORD0; };
struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; };
v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y); o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X, 0.0) * _Time.y); return o; }
fixed4 frag(v2f i) : SV_Target { fixed4 firstLayer = tex2D(_MainTex,i.uv.xy); fixed4 secondLayer = tex2D(_DetailTex, i.uv.zw); fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a); c.rgb *= _Multiplier; c.rgb *= _Color.rgb; return c; }
ENDCG } } FallBack "VertexLit"}VIII. 回到 Unity ,把準備好的圖像賦予 shader ,查看效果
3.3 總結
背景滾動是十分常用的技術,實現起來也是比較簡單,只是對紋理坐標進行水平上的循環偏移,然後進行採樣即可,關於視覺效果,讀者則可以按照自己喜歡進行調參。
四. 總結本文介紹了兩種紋理動畫,在實現上思路相似,都是對 UV 值進行偏移修改,然後對紋理進行採樣。紋理動畫實現起來是比較簡單的,與之相關的另外一種動畫,稱為頂點動畫,我們將在下一篇博文中介紹這種動畫效果並列出值得注意的事項。
雖然紋理動畫並不複雜,但其仍然是我們常用的技術實現。本文篇幅不多,希望能對讀者學 UV 動畫這一知識點有所幫助。
聲明:發布此文是出於傳遞更多知識以供交流學習之目的。若有來源標註錯誤或侵犯了您的合法權益,請作者持權屬證明與我們聯繫,我們將及時更正、刪除,謝謝。
作者:愛喝檸檬的二哈
來源: cnblogs
More:【微信公眾號】 u3dnotes