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

2021-02-23 Unity3D遊戲開發精華教程乾貨
前言

純粹的靜態美景宛如一張漂亮的貼圖,而在遊戲中,這種沒有一點動畫的情況往往是十分無趣且讓人感到彆扭的。所以本文會介紹一些簡單的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

相關焦點

  • Unity Shader屏幕特效之馬賽克(Mosaic)材質
    原理要實現馬賽克的效果,需要把圖片一個相當大小的正方形區域用同一個點的顏色來表示,相當於將連續的顏色離散化,因此我們可以想到用取整的方法來離散顏色,但是在我們的圖片紋理坐標採樣時在0到1的連續範圍,因此我們需要將紋理坐標轉換成實際大小的整數坐標。
  • 實現unity級聯陰影的過渡和屏幕空間改善shadowmap漏光!
    實現unity 級聯陰影的過渡需求場景的shadowmap 精度勉強滿足場景物件的需求,但對於近距離角色的小掛件投影 衣服厚度 領子等投影精度是不足的。雖然獨佔的shadowmap可以實現,但為了不增加開銷我嘗試了用級聯陰影的最近1級做角色陰影。這樣操作很簡單不值得寫這篇內容,但這樣做需要解決2個級聯接縫問題。
  • 使用Unity著色器實現精靈(Sprite)塗鴉效果
    本文將展示在無需繪製多個不同圖像的情況下,如何實現精靈塗鴉效果。本文將介紹從Unity著色器編程的基礎到所應用的數學原理等所有必要知識。本文會涉及一些比較高級的話題,包括:反向運動學的數學原理和大氣的瑞利散射效果。但是既對這些內容感興趣,又有理解所需的必備技術知識的開發者其實並不多。
  • Unity URP中的深度depth用法全解
    演示基於Unity URP, shader用shader graph 或者HLSL
  • Unity3D Shader:科幻風線框渲染
    ,卻意外的發現了Geometry Shader,它介於頂點和片元之間,可以在這一階段通過圖元來修改頂點,下面的實現參考:(文章底部擴展閱讀)下面來開始編寫我們的shader:#pragma vertex vert#pragma geometry geom#pragma fragment frag現在我們從原先的頂點
  • Unity實現《陰陽師》的召喚畫符表現
    畫符召喚要把RT的像素Clear成純黑並且完全透明狀態; Graphics.SetRenderTarget(destTexture); GL.PushMatrix(); GL.Clear(true, true, new Color(0,0,0,0f)); GL.PopMatrix();筆刷的shader
  • Unity中實現2D光照系統
    所涉及到的前置技術棧包括 Unity, C#, render pipeline, shader programming 等。顯然在實現難度和運行效率上來說,選擇 Deferred 的渲染方式更方便Render Pipeline在 Unity 中實現這樣的一個光照渲染系統,一些開發者選擇生成一張覆蓋屏幕的 Mesh,用該 Mesh 渲染光照,最終利用 Unity 渲染管線中的透明度混合實現光照效果。這樣的實現具有很好的平臺兼容性,但也存在可擴展性較差,難以進行更複雜的光照和軟陰影生成等問題。
  • Shadertoy | 水母生物的創作小記
    其實就是通過控制 noise 來實現的。注意下面 noise() 函數的參數,我們使用的不是位置 p,而是 normalize(p),這樣保證相同方向(以原點為出發點)得到的噪聲值是一樣的。我們利用上面貼圖的 R 通道值控制 fresnel 的值,即反射和折射光線的混合比,從而實現玻璃汙漬的感覺。
  • 《天涯明月刀》用Unity實現GPUDriven地形!
    zhuanlan.zhihu.com/p/335325149想著自己把GPU Driven的地形在Unity簡單實現一下思路由於那兩篇關於原理都講得挺好,我這裡就只做一個大致地闡述。主要是把地形拆成4x4的小格子,考慮lod,得到一個全量的金字塔形的NodeList。
  • 暴改一個帶紋理貼圖的 Shader
    我們首先要嘗試了解的 Shader 是 2D 精靈使用的 shader--builtin-sprite,它在基礎繪圖 shader 的基礎上增加了紋理的處理。創建一個獨立的 Effect 文件 foo.effect,拷貝 builtin-sprite 內所有的內容,讓我們嘗試著了解一下這部分。
  • Unity手遊開發札記——使用Shader進行UGUI的優化
    五、基於DoTween的動畫效果優化在遊戲中,UI會大量使用了DoTween插件製作動畫效果,以此來強調一些需要重點提醒玩家的醒目信息。DoTween是一個非常好用的插件,無論是對於程序還是對於UI來說,都可以經過簡單的操作來實現較為好的動畫效果。
  • unity中使用playmaker對animator人物實現控制
    在unity中利用playmaker實現對animator人物實現控制非常簡單,只需要了解animator動畫樹的相應動作參數就能實現動畫的控制
  • Unity VR全景漫遊
    Unity5.3.1 X64 http://unity3d.com/cn/get-unity/download/archive
  • 【UE4 Shader】從球開始做一隻史萊姆
    很巧昨天下班後在Shade上看到一個史萊姆的效果特別棒,花了一晚上加第二天上午用ue4還原了下(技術實現都是自己想的沒有參考什麼)。:首先我想將所有的造型都程序化,模型用一個簡簡單單帶uv的球就行。uv,然後用這套uv去採樣嘴巴的形狀,和上方的嘴巴做混合即可。
  • Unity入門課|01讓世界暗下來
    ,比如Sphere,Cube,就可以從菜單直接新建一個對象默認的Material叫HDRP LitMaterial材質由哪些組件構成1 Shader 著色器,unity有shader graph連連看做shader也有shader程式語言來編寫shader2 各類texture 貼圖(非必須),用來顯示顏色的貼圖,像是穿了件衣服。
  • 如何運用Unity製作VR全景漫遊?
    Unity5.3.1 X64: http://unity3d.com/cn/get-unity/download/archivePTGui[可選]: 把全景圖轉成6個立方小圖 http://www.ptgui.com/download.htmlGoogle VR SDK For Unity: https://github.com/googlevr