在Github上看到一篇關(guān)于Unity-Shader的教程,感覺還不錯,作者寫的很好,很適合Unity-Shader的基礎(chǔ)入門,我在這里翻譯一下,分享給大家,英文水平很爛,大致能明白Unity-Shader是什么,渲染管線的工作流程,以及Unity Shader的一些類型和怎樣編寫Unity Shader。(原文鏈接) 第一部分:什么是Shader?Shader是計算機圖形渲染管線的一部分,它是一小段應(yīng)用程序,告訴計算機在場景中怎樣對物體渲染和著色。這個過程包括計算顏色和光照值,并將其給予對象,以至于對象被顯示在屏幕上。和上面一樣,Shader也被用來創(chuàng)建游戲中的一些特殊的和后期處理效果。 在現(xiàn)代游戲引擎中(包括Unity),Shader運行在可編程的GPU渲染管道中,在GPU中允許并行運行,并且能夠很快速的進行許多著色計算。 第二部分:渲染管道為了學(xué)習(xí)Shader,我們將簡單的了解渲染管道,我們將在本教程中討論下面這張圖片: 我更加傾向把Shader看做是由一種信息類型(模型數(shù)據(jù)、顏色等等)到另外一種信息類型(像素/片元)的變換,對象數(shù)據(jù)繼承與對象本身,例如模型中的點、法線、三角形、UV坐標(biāo)等等。我們可以把自定義數(shù)據(jù)/傳遞到shader中使用,顏色、紋理、數(shù)字等等這些。 著色器流水線的第一步是頂點函數(shù)。正如你所知的,頂點就是一些點。頂點函數(shù)將處理模型中的一些點(連同其它的一些數(shù)據(jù)諸如法線)并將它們渲染流水線的下一個階段,片元函數(shù)。 片元函數(shù)將使用這些頂點,并對它們著色。將它想象為一個畫家和他們的畫筆,它最終以(R,G,B,A)的格式輸出像素數(shù)據(jù)。 最后,將像素添加到幀緩沖中,在幀緩沖中這些數(shù)據(jù)有可能被進一步處理,直到這些數(shù)據(jù)被繪制到屏幕上。 第三部分:Scene 配置在開始寫Shader代碼之前,需要在Unity中配置一下我們的場景。在Unity中創(chuàng)建一個工程,并導(dǎo)入所有的資源。
在新場景中添加一個Cube、Sphere和Bowl Model(碗模型),并保存場景,之后,你的場景將向下面這樣: 接下來,在Project視圖中單擊右鍵(或者點擊Create)并添加一個新的Unlit Shader(無光照著色器),將其命名為Tutorial_Shader. 如果你對其它類型的shaders好奇的話,我會在文章的末尾談?wù)撍?/p> 然后在剛才創(chuàng)建的shader上點擊右鍵Create>Material 創(chuàng)建一個材質(zhì)球,Unity將自動創(chuàng)建一個材質(zhì)球并使用剛才創(chuàng)建的著色器的名字。 Note:一個“Material”在Unity中僅僅是著色器的一個實例,它僅保存自定義數(shù)據(jù)/屬性的值。 最后,通過單擊或者拖動的方式將材質(zhì)賦予我們在場景中創(chuàng)建的所有對象上。 之后場景中的每個對象看起來是這樣的,白色,并且沒有陰影或者shading: 第四部分:一個Unlit Shader(無光照著色器)的大致骨架終于到了開始寫我們自己的shader的時候了,在寫之前,首先打開之前創(chuàng)建的Tutorial_Shader文件,你將看到Unity自動生成了一些代碼供我們使用。為了繼續(xù)本教程,刪除所有代碼并使文件變空白。 Note:所有的shader在Unity中使用的是一種被稱為shaderlab的語言寫的。Shadrlab是HLSL/CG語法的包裝器,為了使Unity能夠跨平臺編譯Shader代碼并且在Inspector面板中暴露一些屬性結(jié)點。 我們將添加一些初始代碼,如下: 這行代碼的作用是指定著色器代碼存放在什么位置。雙引號中的字符串告訴Unity在哪里查找Shader. 例如: 如果你保存你的shader代碼,并切回到Unity中,你將注意到所有使用這個材質(zhì)的對象都已經(jīng)變成了粉紅色。 當(dāng)你的著色器中有錯誤時,Unity中將調(diào)用FallBack著色器,你將會得到一個粉紅色的物體。你可以在Project中點擊shader文件查看相應(yīng)的錯誤。目前,我們得到一個粉紅色的物體,因為我們的shader文件還沒有完成。 接下來是屬性塊,如下: 在屬性塊中,我們可以傳遞一些自定義數(shù)據(jù)。我們在這里聲明的數(shù)據(jù)將被顯示在Unity Editor面板中,在Editor中更改也會驅(qū)動腳本更改。 每一個shader有一個或者多個subshaders,如果你的應(yīng)用將被部署到多種平臺(移動、PC、主機),添加多個Subshader是非常有用的。例如:你可能想要寫為PC/Desktop寫一個高質(zhì)量的Subshader,為移動端寫一個低質(zhì)量,但速度更快的Subshader. pass語句塊: 每個Subshader至少有一個pass語句塊,它實際上是對象渲染的位置。一些特效要求有多個pass語句塊,目前,我們僅僅專注于一個。 在pass語句塊中,是一些實際渲染的代碼: 我們實際寫的所有Shader代碼都在CGPROGRAM和ENDCG中,對于Unity來說,shaderlab是HLSL和CG的變體。 下面,我們需要告訴Unity,頂點函數(shù)和片元函數(shù)是什么: 這里,我們將vertex函數(shù)聲明為vertexFunction,fragment函數(shù)聲明為fragmentFunction. 我們也將定義這些函數(shù): 在開始著色之前,我們需要設(shè)置一些數(shù)據(jù)結(jié)構(gòu)和兩個函數(shù),這樣,我們就可以使用Unity給定的數(shù)據(jù),并把數(shù)據(jù)返回到Unity中。首先,添加UnityCG.cginc語句塊,我們可以使用這個文件中包含的一些助手函數(shù)。 我們將添加一個數(shù)據(jù)結(jié)構(gòu)a2v(此處和原文不一致),并修改頂點函數(shù),將a2v作為參數(shù)傳遞給頂點函數(shù)。 當(dāng)傳遞一個參數(shù)到vertexFunction中時,Unity將解析這個函數(shù)的結(jié)構(gòu),并基于正在繪制的對象模型傳遞值。我們可以傳遞一些自定義的數(shù)據(jù),如下面聲明的那樣: 例如,可以要求Unity獲取模型對象的頂點坐標(biāo),如下: 我們也可以從Unity中獲取頂點坐標(biāo)和UV紋理坐標(biāo),如下: 最后配置頂點函數(shù),創(chuàng)建一個結(jié)構(gòu)體,并將其命名v2f(代表從vertex to fragment,頂點數(shù)據(jù)傳遞到片元),將vertex中包含的數(shù)據(jù)傳遞到片元函數(shù),同時確保vertexFunction 返回 v2f的數(shù)據(jù)類型,在我們使用它時,創(chuàng)建并返回一個空白數(shù)據(jù)。 像之前一樣,我們可以在v2f結(jié)構(gòu)體中定義一些數(shù)據(jù),我們可能想要把這些數(shù)據(jù)從頂點函數(shù)傳遞到片元函數(shù)。 如果你對SV_POSITION和POSITION 感到好奇,SV代表“system value”,在v2f結(jié)構(gòu)中表示最終渲染的頂點的位置。 現(xiàn)在基本準(zhǔn)備好了,我們僅僅需要編輯片元函數(shù),使它接受一個v2f結(jié)構(gòu)并返回一個fixed4的值。 輸出的片元函數(shù)將是一個有(R,G,B,A)代表的顏色值 最后,我們將為片元函數(shù)添加一個SV_TARGET的輸出語義,如下: 這個過程告訴Unity我們將輸出一個color去渲染,現(xiàn)在準(zhǔn)備開始實際的編碼了,肉和土豆使我們的vertex和fragment函數(shù),到這個點,大致的骨架已經(jīng)出來了 第五部分:Shader 基礎(chǔ)首選我們要做的是獲取頂點的正確位置,使用Unity中提供的UnityObjectToClipPos()函數(shù)(這個函數(shù)的作用是將世界空間的模型坐標(biāo)轉(zhuǎn)換到裁剪空間,函數(shù)內(nèi)部封裝了實現(xiàn)頂點坐標(biāo)變換的具體細(xì)節(jié),如矩陣變換等等),如下: 這個函數(shù)將在局部空間中表示的頂點,變換到渲染相機的裁剪空間。注意,我們通過設(shè)置o.position的位置來傳遞轉(zhuǎn)換的點。接下來,給片元函數(shù)一個輸出。 現(xiàn)在,等待一會兒。保存你的shader并且返回到Unity,你將看到我們的精美的綠色的物體。如下: 當(dāng)然,這對你來說可能印象并不深刻,因此,讓我們繼續(xù)構(gòu)建,而不是返回一個基本的綠色,可能我們想要編輯shader使得其能返回一個我們想要的顏色,為了做到這一點,我們需要回到開始的自定義屬性。 我們可以使用如下語法添加一些屬性: 如下,我們將暴露出一個顏色值,如下: 在這里定義了一個顏色供我們使用,將其稱之為_Color并且它將顯示為 “Totally Rad Color!”,在Unity面板中。我們也將給予一個默認(rèn)白色的值,現(xiàn)在保存并返回Unity,在Inspect的材質(zhì)面板中,你將看到如下: 在我們使用這個color之前,我們需要把它傳遞到CG代碼中,Unity會通過變量的名字進行自動綁定,如下: 現(xiàn)在,可以在片元函數(shù)中使用_Color值了,讓它返回我們期待的顏色值,而不是返回一個綠色: 現(xiàn)在,保存并返回到Unity中,如果你在Inspect中的Material中改變_Color的值,你應(yīng)該能看到所有對象做出了相應(yīng)的改變。 現(xiàn)在我們知道了如何添加屬性,讓我們嘗試添加一張標(biāo)準(zhǔn)的紋理貼圖,這里需要添加一個新的屬性給我們的紋理: 注意它的類型是2D,默認(rèn)給它一張白色的紋理,我們還需要獲取這個屬性在CG片段中使用它: 然后,需要從模型傳遞UV紋理坐標(biāo)到片元函數(shù),我們可以通過返回頂點函數(shù)并將其傳遞v2f中,如下: 為了能在片元函數(shù)中使用紋理的顏色,我們需要對紋理進行采樣。謝天謝地,CG中已經(jīng)有一個tex2D()函數(shù)幫我們做了一切。 tex2D獲取我們想要采樣的紋理以及我們想要采樣的UV坐標(biāo),在這種情況下。我們提供了它的主紋理并給定模型的點,我們可以得到我們想要的顏色,最后返回的是最終的顏色?,F(xiàn)在,保存并返回到Unity 的material insepct面板中,選擇bowel紋理賦予"Main Texture"",你會發(fā)現(xiàn),模型發(fā)生了改變,尤其是碗的模型看起來尤其像一碗湯。 提示:我們可以改變紋理在Unity中的采樣方式,通過選擇紋理文件并在Inspector面板中改變filter mode(過濾模式):
第六部分:試著改變Shader現(xiàn)在,我們已經(jīng)大致了解了一些基礎(chǔ),我們可以做一些有趣的效果并做一些簡單的特效。首先,我們將使用一張噪聲貼圖實現(xiàn)“溶解” 或者“切斷”效應(yīng),首先我們將添加另一個紋理屬性和一個float 屬性,如下: 注意這里是如何設(shè)置_DissolveCutoff 為一個Range(0,1),它代表一個從(0,1)(包含)的float值,并且這種計數(shù)法允許我么容易的使用slider(滑動條)來設(shè)置值,接下來,讓我們在CGPROGRAM中添加他們。 現(xiàn)在能在片元函數(shù)中對溶解紋理采樣: 提示:我們將為我們的主紋理使用相同的UV紋理坐標(biāo),接下里,魔術(shù)發(fā)生了: clip 函數(shù)檢查這個給定的值是否小于0.如果小于0,我們將丟棄這個像素并且不繪制它。如果大于0,繼續(xù)保持像素、正常的渲染,我們的代碼的工作方式如下:
現(xiàn)在,保存并返回Unity,回到材質(zhì)面板,賦予“Dissolve Texture"我們的noise紋理,移動”Dissolve Cutoff" 滑動條,你應(yīng)該會看到一個效果,向下面這樣:
很酷吧? 我們也能做更多。在將其傳遞給fragment函數(shù)之前,讓我們嘗試更改這些頂點,在Inspector面板中暴露出一些結(jié)點屬性。 我們還將使用模型中的法線信息,因此,讓我們添加這個字段到a2v的結(jié)構(gòu)體中,以至于我們能訪問它。 現(xiàn)在,添加一個單行到頂點函數(shù)中: 我么在這里做的是,在將頂點轉(zhuǎn)換為局部模型空間之前,我們將通過增加他們的法線方向時間來抵消它們的外加量,法線是一個向量代表頂點面向的方向,現(xiàn)在保存并返回Unity中,改變"Extrude Amount"的值,你應(yīng)該看到下面這樣的效果:
我們也能為這些屬性制作動畫: _Time是一個代表時間的變量被包含在UnityCH.cginc中,y值代表秒,確?!癆nimated Materials” 在場景視圖中被勾選,如下:
下面是我們最終的代碼: 第七部分:Scripting 和Shaders接下來,我們將討論怎樣使用Unity腳本來控制Shader(即C#和Shader的交互),例如,我們將再次使用之前添加的_Color屬性。首先,我們再片元函數(shù)中讓其為著色器的顏色進行著色,如下: 我們將輸出顏色和_Color屬性相乘,在Editor中如下:
現(xiàn)在,讓我們開始寫腳本吧,我們將為每一個對象添加一個名為RainbowColour.cs的腳本。
在腳本中,聲明兩個私有變量 Rendeerer和Materail: 在Satrt()函數(shù)中,設(shè)置引用值。 我們將在shader中使用Material.SetColor()函數(shù)設(shè)置顏色值,這個函數(shù)的第一個參數(shù)是一個字符串,它的名字使我們想要設(shè)置的屬性的名字,第二個參數(shù)是我們想要設(shè)置的顏色的值。 當(dāng)我們運行游戲的時候,顏色變?yōu)槠芳t。
第八部分:陰影? 表面著色器?到目前為止,我們寫了一個Unlit Shader(無光照著色器),Unity還允許你寫表面著色器,表面著色器實際上就像vertex/fragment著色器,除了它們?nèi)サ袅嗽S多使著色器與光照與陰影交互的示例代碼。如果你對寫光照和陰影感興趣,這是一份很棒的教程 here 在這個章節(jié),我將展示的是,表面著色器的每個部分如何與我們的頂點/片元著色器相關(guān)聯(lián),如果你在Unity中創(chuàng)建一個新的“Standard Shader",你會看到一些自動生成的代碼,如下: 讓我們看一看每一個部分并解釋一下他們都做了什么。 標(biāo)簽幫助你告訴渲染引擎如何以及何時你的著色器被渲染。在這種情況下,我們只是指定我們的著色器是透明的,這個對于深度紋理/地圖是非常有用的。 多細(xì)節(jié)層次或者(LOD)有助于指定再默寫硬件上使用哪種著色器,LOD值越大,著色器越復(fù)雜且它的值與模型的LOD無關(guān)。 類似于我們定義頂點和片元函數(shù),我們在這里定義了一個稱之為surf的表面函數(shù),Stadard指定Unity Shader使用標(biāo)準(zhǔn)光照模型,而fullforwardshadows指定著色器啟用所有常規(guī)陰影類型。 這里指定編譯使用的光照版本,值越大,效果越好,也越復(fù)雜,同時對系統(tǒng)有更高的要求。 這是著色器的核心部分,Unity定義了一個SurfaceOutputStandard 結(jié)構(gòu)體來替代指定像素的顏色值。你可以設(shè)置一些諸如“Albedo"的屬性,由于我們正在處理光照和陰影,不單單是直接獲取顏色值,需要能夠通過SurfaceOutputStandard保存的值來進行計算,下面是SurfaceOutputStandard的所有屬性值的一部分: Okay,討論一下關(guān)于verties把。 定義vert函數(shù): 提示:如果你在改變頂點坐標(biāo)的時候,陰影沒有隨之改變,你需要確保添加了”addshadow“ paagma 片段聲明,如下: 再表面著色器的內(nèi)部是非常復(fù)雜的,但是,它最終會被編譯成我們之前寫的頂點和片元函數(shù)那樣。我強烈的建議去讀官方文檔,以了解更多關(guān)于這方面的信息。 更多內(nèi)容,歡迎關(guān)注我的公眾號: 
|
|
|