使用Unity著色器實(shí)現(xiàn)精靈(Sprite)涂鴉效果

來源:Unity官方平臺(tái)
作者:Alan Zucconi
時(shí)間:2020-01-03
4793
本文將由來自英國(guó)的游戲開發(fā)工程師Alan Zucconi分享如何在Unity中使用著色器制作近來流行的精靈涂鴉效果。

精靈涂鴉效果在過去幾年逐漸流行起來,《GoNNER》和《Baba is You》等游戲大量使用了這種美術(shù)效果。

1.gif

本文將展示在無需繪制多個(gè)不同圖像的情況下,如何實(shí)現(xiàn)精靈涂鴉效果。本文將介紹從Unity著色器編程的基礎(chǔ)到所應(yīng)用的數(shù)學(xué)原理等所有必要知識(shí)。

引言

本文會(huì)涉及一些比較高級(jí)的話題,包括:反向運(yùn)動(dòng)學(xué)的數(shù)學(xué)原理和大氣的瑞利散射效果。但是既對(duì)這些內(nèi)容感興趣,又有理解所需的必備技術(shù)知識(shí)的開發(fā)者其實(shí)并不多。

在游戲開發(fā)者Nick Kaman的一則推文中,他展示了如何在Unity實(shí)現(xiàn)涂鴉效果。

Nick Kaman:我想分享一個(gè)在Unity實(shí)現(xiàn)“涂鴉”效果的技巧:

我們不必繪制相同精靈的不同幀,我們可以把精靈放到網(wǎng)格,然后使用法線貼圖偏移頂點(diǎn)即可,該法線貼圖會(huì)每秒X次大幅進(jìn)行滾動(dòng)。

2.gif


這篇推文獲得大量的點(diǎn)贊和轉(zhuǎn)發(fā),我們發(fā)現(xiàn),讓即使沒有著色器編程知識(shí)的人也可以理解的簡(jiǎn)單教程是很有必要的。

如果想要制作2D精靈動(dòng)畫的專業(yè)而高效的方法,并且需要完整的藝術(shù)級(jí)控制功能,你可以使用Doodle Studio 95!資源。

獲取Doodle Studio 95!:

https://fernandoramallo.itch.io/doodle-studio-95

下面是使用Doodle Studio 95!時(shí)的動(dòng)畫圖片。

3.gif

涂鴉效果的剖析

為了實(shí)現(xiàn)涂鴉效果,我們首先需要理解實(shí)現(xiàn)原理以及使用了哪些技術(shù)。

著色器效果

首先,我們想要涂鴉效果盡可能輕量,不使用任何額外腳本。我們可以通過著色器實(shí)現(xiàn)這種效果,指導(dǎo)Unity在屏幕上渲染3D模型或者平面模型

精靈著色器

Unity提供了多種著色器的類型,如果使用Unity提供的2D工具,開發(fā)者可能想要處理精靈。在這種情況下,你需要使用精靈(Sprite)著色器,它是一種特殊類型的著色器,與Unity的SpriteRenderer兼容。此外,你也可以使用較為傳統(tǒng)的Unlit著色器。

頂點(diǎn)替換

在手動(dòng)繪制精靈時(shí),不會(huì)有相同的兩個(gè)幀。我們想要通過使精靈進(jìn)行“搖晃”,模擬出這種效果。使用著色器有一種非常高效的實(shí)現(xiàn)方法,該方法需要使用頂點(diǎn)替換功能。這種方法可以修改3D對(duì)象的頂點(diǎn)位置。如果隨機(jī)變化這些位置,我們就可以實(shí)現(xiàn)想要的效果。

對(duì)齊時(shí)間

手繪動(dòng)畫通常有較低的幀率,如果我們想要模擬出諸如每秒5幀的畫面,我們需要每秒5次修改精靈的頂點(diǎn)位置。但是,Unity可能會(huì)在更高刷新速率下運(yùn)行游戲,可能會(huì)有每秒30幀或60幀的幀率。為了確保我們的精靈不以每秒60次的速度發(fā)生變化,我們需要處理動(dòng)畫的時(shí)間組件。

擴(kuò)展精靈著色器

如果想要在Unity創(chuàng)建新的著色器,我們可以使用Unlit Shader,盡管它不一定是特定應(yīng)用程序的最佳選擇。

如果想讓涂鴉著色器完全兼容Unity的SpriteRenderer,我們需要擴(kuò)展它的現(xiàn)有精靈著色器。但是,在Unity中無法直接獲取該著色器。

獲取該著色器的方法是:訪問Unity下載存檔頁面,下載正在使用Unity版本的Build in shaders資源包,該Zip壓縮文件包含特定Unity版本推出的所有著色器源代碼。

下載Build in shaders資源包:

https://unity3d.com/get-unity/download/archive

4.jpg

下載完成后,提取文件,然后在builtin_shaders-2018.1.6f1\DefaultResourcesExtra文件夾內(nèi)找到Sprites-Diffuse.shader文件,它就是我們?cè)诒疚闹行枰褂玫奈募?/span>

5.jpg

如果Sprites-Diffuse文件不是默認(rèn)的精靈著色器,該怎么辦?

在創(chuàng)建新的精靈時(shí),默認(rèn)材質(zhì)使用的著色器名為Sprites-Default.shader,而不是Sprites-Diffuse.shader。

兩者的區(qū)別在于:前者是無光著色器,而后者會(huì)對(duì)場(chǎng)景的光線做出反應(yīng)。由于Unity的實(shí)現(xiàn)方法,相對(duì)無光著色器,漫反射著色器可以更簡(jiǎn)單地進(jìn)行編輯。

頂點(diǎn)替換功能

在Sprites-Diffuse.shader文件中,有一個(gè)稱為vert的函數(shù),它就是之前提到的頂點(diǎn)函數(shù)。它的名稱并不重要,只要它符合#pragma指令的“vertex: ”部分內(nèi)的名稱即可。

#pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing

簡(jiǎn)單來說,頂點(diǎn)函數(shù)會(huì)在3D模型的每個(gè)頂點(diǎn)調(diào)用,并決定如何在2D屏幕空間進(jìn)行映射。對(duì)于本文而言,我們僅對(duì)理解如何替換對(duì)象感興趣。

參數(shù)appdata_full v包含名為vertex的字段,該字段包含對(duì)象空間中每個(gè)頂點(diǎn)的3D位置,修改它的數(shù)值會(huì)移動(dòng)頂點(diǎn)。

例如:下面的代碼會(huì)使用該著色器把對(duì)象沿著X軸平移一個(gè)單位。

void vert (inout appdata_full v, out Input o)

{

           v.vertex = UnityFlipSprite(v.vertex, _Flip);

           v.vertex.x += 1;

           #if defined(PIXELSNAP_ON)

           v.vertex = UnityPixelSnap (v.vertex);

           #endif

           UNITY_INITIALIZE_OUTPUT(Input, o);

           o.color = v.color * _Color * _RendererColor;

}

默認(rèn)情況下,使用Unity制作的2D游戲僅處理X軸和Y軸,因此我們需要修改v.vertex.xy,從而在2D平面上移動(dòng)精靈。

什么是對(duì)象空間?

結(jié)構(gòu)appdata_full的vertex字段包含著色器在對(duì)象空間處理的當(dāng)前頂點(diǎn)位置,如果對(duì)象處于游戲世界的中心點(diǎn)即(0,0,0)坐標(biāo),它就是該對(duì)象未經(jīng)過縮放和旋轉(zhuǎn)時(shí)頂點(diǎn)的位置。

相對(duì)地,在世界空間表示的頂點(diǎn)會(huì)反映頂點(diǎn)在Unity場(chǎng)景內(nèi)的實(shí)際位置。

為什么對(duì)象不會(huì)以每幀1米的速度移動(dòng)?

如果對(duì)C#腳本的Update方法內(nèi)transform.position的x部分加1,我們會(huì)看到對(duì)象以每幀1個(gè)單位速度飛行,換算的速度約為每小時(shí)216千米。

發(fā)生這種情況是因?yàn)镃#對(duì)位置的改動(dòng)會(huì)改變位置本身。在頂點(diǎn)函數(shù)中,這種情況不會(huì)發(fā)生,著色器僅會(huì)改變模型的視覺效果,但不會(huì)更新或改變模型上已保存的頂點(diǎn),因此給v.vertex.x添加+1僅會(huì)每次移動(dòng)對(duì)象1米的距離。

別忘了以Tight類型導(dǎo)入精靈。

該效果會(huì)替換精靈上的頂點(diǎn)。傳統(tǒng)情況下,精靈會(huì)作為四邊形(即下圖左側(cè))導(dǎo)入U(xiǎn)nity。這意味著精靈僅有4個(gè)頂點(diǎn)。如果是這樣,只有這些頂點(diǎn)可以進(jìn)行移動(dòng),從而會(huì)減少涂鴉效果的總體強(qiáng)度。

為了實(shí)現(xiàn)更為緊密和逼真的扭曲效果,我們應(yīng)該確保精靈以Mesh Type設(shè)為Tight的情況進(jìn)行導(dǎo)入,這樣會(huì)把精靈包裝為凸面外殼(即下圖右側(cè))。

6.jpg

這樣做會(huì)提高頂點(diǎn)的數(shù)量,雖然這不總是理想的選擇,但卻是我們所需要的。

隨機(jī)的替換效果

涂鴉效果會(huì)隨機(jī)改變每個(gè)頂點(diǎn)的位置。在著色器采樣隨機(jī)數(shù)字是一件需要技巧的事,這是由于GPU的無狀態(tài)架構(gòu),它使模擬大多數(shù)庫使用的相同算法變得更加困難和低效。

Nick Kaman提供的方法是使用噪聲紋理,該紋理在采樣時(shí)會(huì)得到隨機(jī)的感覺。對(duì)我們的情況而言,這種方法可能不是最高效的方法,因?yàn)樗鼤?huì)加倍著色器必須執(zhí)行的紋理查詢次數(shù)。

因此,許多著色器需要使用比較模糊和混亂的函數(shù),即使它們的效果是確定的,而且在我們看來沒有任何模式。

由于函數(shù)必須是無狀態(tài)的,每個(gè)隨機(jī)數(shù)必須通過其自帶的種子代碼來生成。這種方法的效果很好,因?yàn)槊總€(gè)頂點(diǎn)的位置都應(yīng)該是獨(dú)特的。我們可以使用它關(guān)聯(lián)每個(gè)頂點(diǎn)的隨機(jī)數(shù),我們會(huì)在后面討論這種隨機(jī)函數(shù)的實(shí)現(xiàn)方法,現(xiàn)在我們把該函數(shù)稱為random3。

我們可以使用random3函數(shù)生成每個(gè)頂點(diǎn)隨機(jī)的替換效果。在下面例子中,隨機(jī)數(shù)會(huì)通過_NoiseScale屬性調(diào)整,這樣可以控制替換效果的強(qiáng)度。

void vert (inout appdata_full v, out Input o)

{

           ...

           float2 noise = random3(v.vertex.xyz).xy * _NoiseScale;

           v.vertex.xy += noise;

           ...

}

現(xiàn)在我們要編寫random3函數(shù)的代碼。

7.jpg

著色器內(nèi)的隨機(jī)效果

著色器中最常用和最具標(biāo)志性的偽隨機(jī)函數(shù)來自W.J.J. Rey在1998年發(fā)表的論文。

float rand(float2 co)

{

    return fract(sin(dot(co.xy ,float2(12.9898,78.233))) * 43758.5453);

}

該函數(shù)是確定性的,也就是說它不是真正具有隨機(jī)效果,但是它的行為非常不規(guī)律,使它看起來完全是隨機(jī)的,這類函數(shù)被稱為偽隨機(jī)函數(shù)。對(duì)于本教程,我使用了Nikita Miropolskiy編寫的高級(jí)函數(shù)。

添加時(shí)間

通過使用已經(jīng)編寫好的代碼,我們現(xiàn)在可以實(shí)現(xiàn)每個(gè)點(diǎn)都會(huì)在每幀替換相同的次數(shù)。這樣會(huì)實(shí)現(xiàn)搖擺的精靈,而不是涂鴉效果。

為了解決該問題,我們需要找到隨時(shí)間改變效果的方法,最簡(jiǎn)單的方法是使用頂點(diǎn)位置和當(dāng)前時(shí)間來生成隨機(jī)數(shù)。

在這種情況下,我們添加了以秒為單位的當(dāng)前時(shí)間值_Time.y到頂點(diǎn)位置。

float time = float3(_Time.y, 0, 0);

float2 noise = random3(v.vertex.xyz + time).xy * _NoiseScale;

v.vertex.xy += noise;

更高級(jí)的效果需要更復(fù)雜的方法來集成時(shí)間到計(jì)算方程式中,但由于我們只想實(shí)現(xiàn)間隔的隨機(jī)效果,因此添加兩個(gè)數(shù)值就足夠了。

8.gif

對(duì)齊時(shí)間

添加_Time.y的主要問題是:它會(huì)造成精靈在每幀都發(fā)生變化。這是不理想的效果,因?yàn)榇蠖鄶?shù)手繪的動(dòng)畫都有較低的幀率

時(shí)間組件不應(yīng)該有連續(xù)的效果,而是應(yīng)該變得離散化,這意味著如果我們想實(shí)現(xiàn)每秒5幀,它應(yīng)該僅在每秒改變5次。使用熟悉術(shù)語的話說,那就是:時(shí)間應(yīng)該“對(duì)齊”為一秒的五分之一。因此,可以使用的數(shù)值應(yīng)該為:0/5 = 0,1/5 = 0.2,2/5 = 0.4,3/5 = 0.6,4/5 = 0.8,5/5 = 1 ,以此類推。

下面的函數(shù)會(huì)接收數(shù)值x,對(duì)齊到Snap值的整數(shù)倍數(shù)。

inline float snap (float x, float snap)

{

           return snap * round(x / snap);

}

因此,我們可以更新為以下代碼:

float time = snap(_Time.y, _NoiseSnap);

float2 noise = random3(v.vertex.xyz + float3(time, 0.0, 0.0) ).xy * _NoiseScale;

v.vertex.xy += noise;

大功告成,最后的效果如下圖所示。

9.gif

立即登錄,閱讀全文
原文鏈接:點(diǎn)擊前往 >
文章來源:Unity官方平臺(tái)
版權(quán)說明:本文內(nèi)容來自于Unity官方平臺(tái),本站不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。文章內(nèi)容系作者個(gè)人觀點(diǎn),不代表快出海對(duì)觀點(diǎn)贊同或支持。如有侵權(quán),請(qǐng)聯(lián)系管理員(zzx@kchuhai.com)刪除!
掃碼關(guān)注
獲取更多出海資訊的相關(guān)信息
優(yōu)質(zhì)服務(wù)商推薦
更多
掃碼登錄
打開掃一掃, 關(guān)注公眾號(hào)后即可登錄/注冊(cè)
加載中
二維碼已失效 請(qǐng)重試
刷新
賬號(hào)登錄/注冊(cè)
小程序
快出海小程序
公眾號(hào)
快出海公眾號(hào)
商務(wù)合作
商務(wù)合作
投稿采訪
投稿采訪
出海管家
出海管家