Unity Shader- UV動畫原理及簡易實現
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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 讓動畫實現更簡單,Flutter 動畫簡易教程!動畫Flutter
- MapReduce原理及簡單實現
- vue 實現原理及簡單示例實現Vue
- Flutter 動畫簡易教程Flutter動畫
- Unity3D中暫停時的動畫及粒子效果實現Unity3D動畫
- async/await 原理及簡單實現AI
- namedtuple簡易實現
- Unity Shader-後處理:Bloom全屏泛光UnityOOM
- Flutter動畫實現原理淺析Flutter動畫
- 解析 iOS 動畫原理與實現iOS動畫
- 簡易版 vue實現Vue
- WPF簡單動畫實現動畫
- DI 原理解析 並實現一個簡易版 DI 容器
- 微前端調研及簡析SPA實現原理前端
- js拖拽原理及簡單實現(渣渣自學)JS
- kmp演算法實現原理及簡單示例KMP演算法
- 前端動畫實現以及原理淺析前端動畫
- Android動畫實現繪製原理Android動畫
- Unity實現簡單的物件池Unity物件
- 簡易實現一個expressExpress
- 簡易RPC框架實現RPC框架
- 使用canvas實現簡單動畫Canvas動畫
- CRC原理及實現
- 熱更新技術簡易原理及技術推薦
- AOP如何實現及實現原理
- Fiori裡花瓣的動畫效果實現原理動畫
- 自已做動畫及編寫程式搞清楚最大堆的實現原理動畫
- 實現一個簡易版WebpackWeb
- 實現一個簡易的vueVue
- Go 實現簡易 RPC 框架GoRPC框架
- UNIX Domain Socket實現簡易聊天AI
- 學習Promise && 簡易實現PromisePromise
- 模擬實現簡易版shell
- QT實現簡易串列埠助手QT串列埠
- 簡易執行緒池實現執行緒
- CoreAnimation解析及中高階動畫實現動畫
- Promise原理探究及實現Promise
- KVO使用及實現原理