【淺墨Unity3D Shader編程】之一 夏威夷篇:游戲場(chǎng)景的創(chuàng)建 & 第一個(gè)Shader的書(shū)寫(xiě)

作為一個(gè)系統(tǒng)介紹Unity3D中Shader編寫(xiě)的系列文章的開(kāi)篇,本文的第一部分為系列文章的前言,然后第二部分介紹了這個(gè)系列文章中我們會(huì)使用的游戲場(chǎng)景創(chuàng)建方式,最后一部分講解了如何在Unity中創(chuàng)建和使用Shader,為后面專注于介紹如何在Unity中進(jìn)行Shader編程打好了基礎(chǔ)。

因?yàn)楹竺嫱瞥龅南盗形恼聲?huì)著重介紹各種Shader的寫(xiě)法和實(shí)現(xiàn),不會(huì)再具體講解如何創(chuàng)建場(chǎng)景和寫(xiě)出Shader代碼后如何使用,相信這篇文章作為本系列的開(kāi)篇,發(fā)表出來(lái)肯定還是會(huì)對(duì)大家多少有些幫助的。大家以后閱讀稍后推出的Unity Shader系列文章的時(shí)候,有場(chǎng)景創(chuàng)建或者Shader代碼寫(xiě)好了如何使用方面的疑問(wèn)的話,可以隨時(shí)回過(guò)頭來(lái)查閱這篇文章。

OK,就讓我們從這篇文章開(kāi)始一趟精彩萬(wàn)分的Shader游戲編程旅途。
依舊國(guó)際慣例,看幾張文章中實(shí)現(xiàn)的場(chǎng)景美圖先:

上圖中展示的文本配套Unity工程的可運(yùn)行exe淺墨也為大家準(zhǔn)備好了,有興趣的朋友們可以點(diǎn)擊 這里進(jìn)行下載、運(yùn)行和探索:

PS:文章配套的三個(gè)unitypackage和最終的工程源碼在文章末尾處提供下載。

一、系列文章前言

在這個(gè)系列開(kāi)頭,淺墨想說(shuō)的是,其實(shí)這個(gè)系列文章中我們學(xué)的主要是著色器編程技術(shù),重點(diǎn)不是學(xué)Unity。
甚至可以這樣說(shuō),我們學(xué)的是HLSL——沒(méi)錯(cuò),就是DirectX中的那個(gè)HLSL。

為什么這樣講,讓我們來(lái)捋一捋。
首先,Unity中編寫(xiě)Shader的語(yǔ)言叫做ShaderLab,而ShaderLab說(shuō)白了就是裹著一層皮的CG著色器語(yǔ)言而已。Cg,即C forgraphics,即用于圖形的C語(yǔ)言,是微軟Microsoft和英偉達(dá)NVIDIA相互協(xié)作在標(biāo)準(zhǔn)硬件光照語(yǔ)言的語(yǔ)法和語(yǔ)義上達(dá)成的一種一致性協(xié)議。
HLSL和CG其實(shí)是同一種語(yǔ)言(參見(jiàn)Cg教程_可編程實(shí)時(shí)圖形權(quán)威指南29頁(yè)的致謝部分)。很多時(shí)候,我們會(huì)發(fā)現(xiàn)用HLSL寫(xiě)的代碼可以直接當(dāng)中Cg代碼使用。
Microsoft和NVIDIA聯(lián)手推出CG語(yǔ)言,想在經(jīng)濟(jì)和技術(shù)上實(shí)現(xiàn)雙贏,從而通過(guò)這種方式聯(lián)手打擊他們共同的對(duì)手GLSL。

既然Unity主打Shader編程的語(yǔ)言ShaderLab是CG語(yǔ)言披上一層皮,而CG語(yǔ)言又約等于HLSL。這就是說(shuō),在Unity中寫(xiě)Shader約等于用HLSL寫(xiě)Shader,也就約等于給DirectX寫(xiě)Shader。雖然有點(diǎn)繞orz,最后總結(jié)一下也就是:
在Unity中寫(xiě)Shader約等于給DirectX寫(xiě)Shader

而Unity又是這樣一個(gè)集萬(wàn)千寵愛(ài)于一身的可見(jiàn)即所得的目前在移動(dòng)互聯(lián)網(wǎng)時(shí)代火到不行的游戲引擎。可以說(shuō),Unity可見(jiàn)即所得的開(kāi)發(fā)環(huán)境非常適合Shader的學(xué)習(xí),而且在Unity分分鐘可以創(chuàng)建出來(lái)一個(gè)漂亮的場(chǎng)景里面寫(xiě)寫(xiě)Shader,心情都會(huì)好很多。不再是苦逼地面朝代碼背朝天了。
所以淺墨決定開(kāi)始在Unity中進(jìn)行這個(gè)shader學(xué)習(xí)系列,畢竟Unity、CG、HLSL不分家。
另外促成這個(gè)系列文章的成型的一個(gè)原因是Unity的Asset store。淺墨很喜歡Unity引領(lǐng)的Asset store這樣一站式的游戲素材商店,里面有數(shù)不盡的游戲插件、素材、資源、腳本代碼參考、shader資料等等,甚至現(xiàn)成的游戲工程范例。Asset store給開(kāi)發(fā)者們帶來(lái)了很大的便利,簡(jiǎn)直就是游戲開(kāi)發(fā)界的大寶庫(kù)。
但需要說(shuō)明的是,Unity這款引擎的缺點(diǎn)也是很明顯的。比如Unity不對(duì)外開(kāi)源,我們看不到源碼,坑比較多,遇到坑了看不到
源碼給解決帶來(lái)了很大難度。執(zhí)行效率還是不太行,渲染大場(chǎng)景和做高級(jí)渲染的時(shí)候還是顯得力不從心。比如渲染大片的近乎
真實(shí)的動(dòng)態(tài)水面時(shí),幀數(shù)就立馬降下來(lái)了,需要后期做很多的優(yōu)化。就畫(huà)面質(zhì)量來(lái)說(shuō)和Unreal Engine和cryEngine等次時(shí)代
引擎比還是有差距。是最近幾年互聯(lián)網(wǎng)的浪潮和得屌絲者的天下商業(yè)模式促進(jìn)了其最近幾年如此的成功。

說(shuō)了這么多,總結(jié)一下。
Unity只是我們學(xué)習(xí)CG、HLSL編程可見(jiàn)即所得的好幫手好工具而已。我們主要還是利用它來(lái)更好的學(xué)計(jì)算機(jī)圖形學(xué)和Shader編程,順便掌握目前熱門(mén)的Unity引擎的基本使用和研發(fā)思路。
我們還是忘不了C++和DirectX,我們還是渴望通過(guò)自己的努力,最終有能力用C/C++一句一句寫(xiě)出自己的游戲引擎,我們還是想從零開(kāi)始造輪子,畢竟那樣一句一句寫(xiě)出來(lái)的代碼都是自己的,而不是那些別人為我們準(zhǔn)備好的現(xiàn)成的API函數(shù)。
謹(jǐn)以此前言,與諸君共勉。

二、用Unity創(chuàng)建第一個(gè)美麗的游戲場(chǎng)景

首先要說(shuō)明的是,作為一個(gè)從入門(mén)內(nèi)容開(kāi)始逐漸深入介紹的系列教程,這一部分在Unity下創(chuàng)建場(chǎng)景的內(nèi)容是為還不太熟悉Unity的朋友們準(zhǔn)備的,如果你已經(jīng)熟悉了Unity的使用,這部分可以快速跳讀。

OK,正式開(kāi)始吧。

2.1 【第一步】當(dāng)然是新建一個(gè)項(xiàng)目

大家肯定都知道,每次新建項(xiàng)目后或者新建場(chǎng)景后,會(huì)得到一片Unity中默認(rèn)為全藍(lán)的場(chǎng)景。想要場(chǎng)景變得有生機(jī),一般都是去菜單欄的GameObject里面新建Terrain(地形)然后進(jìn)行編輯。Terrain的制作很耗時(shí)而且水很深,想要精通也是得花一些功夫。
甚至Unity Asset Store中有各種可以輔助快速生成AAA級(jí)大作風(fēng)格的真實(shí)場(chǎng)景的插件,如Terrain Composer,配合著Unity中有名的Relief Terrain Pack v3地形輔助著色渲染工具,可以生成近乎以假亂真的三維自然風(fēng)光出來(lái)。
漂亮地形的創(chuàng)建當(dāng)然不屬于我們講解的重點(diǎn),網(wǎng)絡(luò)上有數(shù)不清的文章和視頻講這方面的內(nèi)容,有需要的話,大家去學(xué)一些基礎(chǔ),或者直接用Asset Store中現(xiàn)成的各種漂亮場(chǎng)景。反正淺墨是各種在網(wǎng)上下載AssetStore中美工大牛們做場(chǎng)景,然后簡(jiǎn)單的修改,為自己測(cè)試和平常調(diào)數(shù)值所用。
這不,淺墨為了寫(xiě)這篇博客,就為大家再加工“創(chuàng)作“了一個(gè)夏威夷風(fēng)格的場(chǎng)景來(lái):)

2.2 【第二步】導(dǎo)入Hawaii Environment.unitypackage場(chǎng)景包

上文講到,為了節(jié)約時(shí)間,淺墨提前為大家修改好了一個(gè)場(chǎng)景,然后這個(gè)場(chǎng)景已經(jīng)打包,叫做“HawaiiEnvironment.unitypackage “,在文章末尾提供下載。(限于Unity對(duì)中文的支持拙計(jì),無(wú)奈只能取英文名,不然直接導(dǎo)入就報(bào)錯(cuò))。

HawaiiEnvironment.unitypackage單獨(dú)下載請(qǐng)點(diǎn)我】

雙擊這個(gè)包,導(dǎo)入到我們空空如也的工程中,經(jīng)過(guò)一段時(shí)間的讀條,就導(dǎo)入完畢了。然后我們雙擊打開(kāi)出現(xiàn)在Project面板中Assets文件夾下的場(chǎng)景文件Hawaii Environment.unity



接著便打開(kāi)了場(chǎng)景,如果打開(kāi)成功,Scene面板中應(yīng)該便出現(xiàn)了如下類似的場(chǎng)景畫(huà)面:


因?yàn)槁匀チ藞?chǎng)景編輯部分,直接導(dǎo)入,所以過(guò)程是非常簡(jiǎn)單的。但是,這還完全不夠。讓我們?cè)趫?chǎng)景中添加一個(gè)可以自由控制的攝像機(jī)吧。

2.3 【第三步】添加第一人稱攝像機(jī)

淺墨準(zhǔn)備的這個(gè)場(chǎng)景包是沒(méi)有攝像機(jī)的,單單就是場(chǎng)景,所以我們還需要在場(chǎng)景中添加一個(gè)攝像機(jī)。

大家應(yīng)該清楚,比較常見(jiàn)添加攝像機(jī)的做法是通過(guò)菜單欄中的GameObject->CreateOther->Camera來(lái)添加。但這種方式的攝像機(jī)是固定的,不合我們的要求。我們想添加的是一個(gè)在游戲運(yùn)行時(shí)可以自由移動(dòng)視角的第一人稱攝像機(jī)。其實(shí)Unity自帶的資源包中剛好可以滿足我們的要求。于是我們?cè)赑roject面板中右鍵【Import Package】,或者菜單欄中Assets->ImportPackage->Character Controller來(lái)導(dǎo)入官網(wǎng)為我們準(zhǔn)備的的角色控制資源包。如下圖:


點(diǎn)擊之后,會(huì)彈出如下的資源導(dǎo)入確認(rèn)窗口,我們直接點(diǎn)確定就行了:


因?yàn)檫@個(gè)包很小,所以很快就導(dǎo)入完成,這時(shí)Assets根文件夾下會(huì)出現(xiàn)一個(gè)名為Standard Assets的文件夾,展開(kāi)它或者點(diǎn)進(jìn)去,就是名為【CharacterControllers】的文件夾,繼續(xù)點(diǎn)進(jìn)去,發(fā)現(xiàn)了一個(gè)膠囊狀的叫【First PersonController】的家伙,這就是我們需要的了。
然后我們先在Scene面板中利用【右鍵+鍵盤(pán)W、A、S、D】以及滾輪等操作調(diào)整好場(chǎng)景,然后在我們剛才的【CharacterControllers】下點(diǎn)擊這個(gè)膠囊裝的【First Person Controller】按住不放,拖動(dòng)到Scene場(chǎng)景中,選到合適的地方(如草坪上)后就放手,操作如下:

Unity會(huì)自動(dòng)將這個(gè)【CharacterControllers】的中心位置依附到地形表面。

這個(gè)時(shí)候我們會(huì)發(fā)現(xiàn)之前是黑屏的Game面板中也有了畫(huà)面:


這時(shí)我們還要將這個(gè) First Person Controller的底部向上拖動(dòng)一點(diǎn),不然運(yùn)行游戲時(shí)我們會(huì)不停的往下掉,因?yàn)樵赨nity默認(rèn)情況下First Person Controller的中心位于中部,這會(huì)照成它的底部已經(jīng)穿透地形,懸空位于地形的下方,自然一運(yùn)行就往下掉。

我們Hierarchy面板中選中First Person Controller,工具欄中選擇【移動(dòng)】工具

然后對(duì)著場(chǎng)景中膠囊上的代表Y軸的綠色箭頭向上適當(dāng)拖動(dòng),讓膠囊的底部確保位于草地之上就行了。

這時(shí)候我們點(diǎn)擊unity中間三角尖的【運(yùn)行】按鈕,就可以自由地在場(chǎng)景中觀察和移動(dòng)了~


Unity第一人稱控制器默認(rèn)操作方式類似CS,CF一類的FPS游戲。W、A、S、D前后左右移動(dòng),空格跳躍,鼠標(biāo)移動(dòng)調(diào)整視角,非常有親切感有木有~
這就很好地體現(xiàn)了Unity的入門(mén)容易的特點(diǎn),只用點(diǎn)點(diǎn)鼠標(biāo)一個(gè)漂亮的場(chǎng)景就展現(xiàn)在眼前。

2.4 【第四步】在游戲場(chǎng)景中加入背景音樂(lè)

話說(shuō)這么美麗的場(chǎng)景怎么能沒(méi)有音樂(lè)?
不妨就讓我們添加一段優(yōu)美的鋼琴曲吧。曲子淺墨都為大家準(zhǔn)備好了,上文已經(jīng)導(dǎo)入的HawaiiEnvironment.unitypackage包中,在Assets根目錄下包含了一首林海的《日光告別》。

我們要做的只要是把這個(gè)音樂(lè)文件拖拽到第一人稱攝像機(jī)First PersonController上就可以了,就像箭頭中指的這樣:


拖拽完成后,F(xiàn)irst Person Controller的Inspector面板中就應(yīng)該會(huì)多了一個(gè)Audio Source組件。


運(yùn)行場(chǎng)景,伴隨著美麗的場(chǎng)景,“吹著海風(fēng)”,優(yōu)美的鋼琴曲入耳,非常怡人。

先放兩張測(cè)試過(guò)程中的場(chǎng)景美圖,再繼續(xù)我們下一部分的講解吧:

非常逼真的水效,采用大名鼎鼎的NGUI工作室Tasharen Entertainment出品的水面插件:


三、導(dǎo)入QianMo’s Toolkit并使用

3.1 認(rèn)識(shí)QianMo's Toolkit

所謂的QianMo's Toolkit,其實(shí)就是淺墨為場(chǎng)景測(cè)試寫(xiě)的一個(gè)小腳本工具集,打包成一個(gè)unitypackage方便多次使用而已。若有需要,淺墨會(huì)在其中添加更多的功能。
以后我們每次新建工程的時(shí)候,只要導(dǎo)入這個(gè)小工具就可以使用我們之前已經(jīng)寫(xiě)好的各種特性,非常便捷。

QianMo's Toolkit v1.0.unitypackage單獨(dú)下載請(qǐng)點(diǎn)我】

QianMo's Toolkit v1.0版的內(nèi)容如下:


也就是包含了五個(gè)腳本文件,兩張圖片。這五個(gè)腳本文件的功能分別為:

ShowFPS:在游戲運(yùn)行時(shí)顯示幀率相關(guān)信息
ShowObjectInfo:在場(chǎng)景中和游戲窗口中分別顯示添加給任意物體文字標(biāo)簽信息。隱藏和顯示可選,基于公告板技術(shù)實(shí)現(xiàn)。
ShowGameInfo:在游戲運(yùn)行時(shí)顯示GUI相關(guān)說(shuō)明
ShowLogo:在游戲運(yùn)行時(shí)顯示Logo
ShowUI:在游戲運(yùn)行時(shí)顯示簡(jiǎn)單的鑲邊UI。

這個(gè)五個(gè)腳本的代碼淺墨都已經(jīng)詳細(xì)注釋,在后續(xù)文章中有機(jī)會(huì)我們會(huì)介紹其具體寫(xiě)法。這篇文章中就先簡(jiǎn)單的認(rèn)識(shí)一下他們就好。PS:下文第四節(jié)中貼出了ShowGameInfo腳本的全部代碼。

3.2 使用QianMo's Toolkit

上文已經(jīng)說(shuō)了,既然這是一個(gè)unitypackage,那么只用雙擊它導(dǎo)入到我們當(dāng)前的項(xiàng)目中就行了。導(dǎo)入完成之后。Assets文件夾下就又多了一個(gè)名為” QianMo's Toolkit v1.0“的文件夾,內(nèi)容就是我們剛才介紹的5個(gè)腳本文件兩張圖:

暫時(shí)我們要使用的是ShowGameInfo、ShowLogo、ShowUI這三個(gè)腳本文件,把它們一起拖到我們之前創(chuàng)建的第一人稱攝像機(jī)First Person Controller上就行了:


拖動(dòng)完成后,我們?cè)贔irst Person Controller的Inspector面板中發(fā)現(xiàn)其多了三個(gè)組件,就是我們給他添加的這個(gè)三個(gè)腳本:


其實(shí)Show Logo無(wú)關(guān)緊要,就是顯示了淺墨自己的logo而已,當(dāng)然你可以換成自己的logo。show UI也無(wú)關(guān)緊要,就是顯示了一個(gè)頂部的鑲邊png。主要的是這個(gè)ShowGameInfo,它是用于顯示幀率等相關(guān)文字消息的。(其實(shí)簡(jiǎn)約黨會(huì)覺(jué)得三個(gè)都無(wú)關(guān)緊要,orz)

拖動(dòng)完成之后,再次運(yùn)行,我們來(lái)看一看效果:


可以發(fā)現(xiàn),游戲窗口的邊邊角角多了一些說(shuō)明和圖片,以及有了幀率的顯示。

四、書(shū)寫(xiě)和使用第一個(gè)Shader

上文講解的都是一般的場(chǎng)景構(gòu)建過(guò)程,接下來(lái)就要正式開(kāi)始我們的核心部分,書(shū)寫(xiě)第一個(gè)Shader了。在完成上文中講解的創(chuàng)建好漂亮的場(chǎng)景之后,我們首先可以在Assets根目錄下創(chuàng)建一個(gè)文件夾,用于以后Shader和Material文件的存放。創(chuàng)建過(guò)程可以是在Project面板的空白處右鍵->Create->Folder,也可以是點(diǎn)擊Project面板中的Create下拉菜單->Folder


給新出來(lái)的這個(gè)文件夾取名Shaders,然后回車(chē)。然后再用同樣的方法在Assets根目錄下創(chuàng)建一個(gè)名為T(mén)extures的文件夾,用于稍后素材圖片的存放。那么,如果你按照淺墨按照目前描述的步驟來(lái)的話,Assets根目錄到現(xiàn)在就是這樣的5個(gè)文件夾:

接著,進(jìn)去到我們創(chuàng)建的Shader文件夾。同樣在空白處右鍵->Create->Shader,或者是直接點(diǎn)Create下拉菜單->Shader,創(chuàng)建一個(gè)Shader文件,取名為 “0.TheFirstShader”。然后雙擊打開(kāi)它,Unity會(huì)默認(rèn)使用名為MonoDevelop的編輯器打開(kāi)這個(gè)Shader文件。


小tips:可以在菜單欄中Edit->Preferences->ExternalTools中調(diào)成默認(rèn)用Visual Studio打開(kāi),但未經(jīng)修改配置文件的Visual Studio對(duì)Shader后綴的文件是不支持語(yǔ)法高亮的,淺墨修改了部分配置文件才讓Visual Studio支持了Unity Shader書(shū)寫(xiě)的語(yǔ)法高亮。對(duì)于不太清楚如何修改的朋友,可以善用搜索引擎,或者過(guò)些天淺墨會(huì)單獨(dú)發(fā)一篇名為《Unity中使用Visual Studio編寫(xiě)shader并設(shè)置代碼高亮》的文章來(lái)專門(mén)講解。


作為初次寫(xiě)Shader,我們暫且先用MonoDevelop頂一頂,后面的文章再換用修改了配置文件的Visual Studio。

好了,用MonoDevelop打開(kāi)我們新建的這個(gè)Shader文件,發(fā)現(xiàn)Unity已經(jīng)為我們寫(xiě)好了很多代碼。
我們不妨自己重新寫(xiě)點(diǎn)不一樣的東西。刪掉原本的這些代碼,拷貝淺墨寫(xiě)的如下代碼到編輯器中:

//-----------------------------------------------【Shader說(shuō)明】----------------------------------------------
//      Shader功能:   凹凸紋理顯示+自選邊緣顏色和強(qiáng)度
//     使用語(yǔ)言:   Shaderlab
//     開(kāi)發(fā)所用IDE版本:Unity4.5 06f 、Monodevelop   
//     2014年11月2日  Created by 淺墨    
//     更多內(nèi)容或交流請(qǐng)?jiān)L問(wèn)淺墨的博客:http://blog.csdn.net/poem_qianmo
//---------------------------------------------------------------------------------------------------------------------


Shader "淺墨Shader編程/0.TheFirstShader" 
{
    //-------------------------------【屬性】-----------------------------------------
    Properties 
    {
        _MainTex ("【紋理】Texture", 2D) = "white" {}
        _BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}
        _RimColor ("【邊緣顏色】Rim Color", Color) = (0.17,0.36,0.81,0.0)
        _RimPower ("【邊緣顏色強(qiáng)度】Rim Power", Range(0.6,9.0)) = 1.0
    }

    //----------------------------【開(kāi)始一個(gè)子著色器】---------------------------
    SubShader 
    {
        //渲染類型為Opaque,不透明
        Tags { "RenderType" = "Opaque" }

        //-------------------開(kāi)始CG著色器編程語(yǔ)言段-----------------
        CGPROGRAM

        //使用蘭伯特光照模式
        #pragma surface surf Lambert
        
        //輸入結(jié)構(gòu)
        struct Input 
        {
            float2 uv_MainTex;//紋理貼圖
            float2 uv_BumpMap;//法線貼圖
            float3 viewDir;//觀察方向
        };

        //變量聲明
        sampler2D _MainTex;//主紋理
        sampler2D _BumpMap;//凹凸紋理
        float4 _RimColor;//邊緣顏色
        float _RimPower;//邊緣顏色強(qiáng)度

        //表面著色函數(shù)的編寫(xiě)
        void surf (Input IN, inout SurfaceOutput o)
        {
            //表面反射顏色為紋理顏色
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
            //表面法線為凹凸紋理的顏色
            o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
            //邊緣顏色
            half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
            //邊緣顏色強(qiáng)度
            o.Emission = _RimColor.rgb * pow (rim, _RimPower);
        }

        //-------------------結(jié)束CG著色器編程語(yǔ)言段------------------
        ENDCG
    } 

    //“備胎”為普通漫反射
    Fallback "Diffuse"
}

由于這是第一篇Shader系列文章,已經(jīng)涉及到很多內(nèi)容了,所以淺墨不可能展開(kāi)講解這段代碼的具體思路和寫(xiě)法,不過(guò)已經(jīng)詳細(xì)注釋,大家應(yīng)該會(huì)多少有點(diǎn)明白。隨著稍后文章的深入,這段代碼就顯得很簡(jiǎn)單易懂了。
拷貝完成,保存一下這段代碼,unity會(huì)自動(dòng)檢測(cè)和編譯被保存的代碼,我只需返回Unity窗口,等待編譯完成即可。若沒(méi)有錯(cuò)誤,在“0.TheFirstShader”的inspector面板中得到的結(jié)果應(yīng)該是有紅色的錯(cuò)誤提示的。

需要注意的是,Shader想要使用到游戲物體上,一般得有個(gè)媒介,這個(gè)媒介就是我們的老朋友——材質(zhì)(Material)。我們把Shader作用于材質(zhì),接著再把材質(zhì)對(duì)應(yīng)地作用于給游戲物體,這樣寫(xiě)的Shader就間接地給物體表面使用了。

而這一層關(guān)系,在Unity中完全可以通過(guò)點(diǎn)點(diǎn)鼠標(biāo),拖動(dòng)來(lái)完成。下面我們就來(lái)講一講如何將一個(gè)著色程序的結(jié)果顯示到物體表面上。

知道以上原理了就很簡(jiǎn)單了,在“0.TheFirstShader.shader”的同一目錄下創(chuàng)建一個(gè)Material。同樣是可以通過(guò)Create下拉菜單->Material或者空白處右鍵->create->Material來(lái)完成。


為了到時(shí)候方便對(duì)應(yīng),我們將這個(gè)材質(zhì)也取名為0.TheFirstShader。

接著,將0.TheFirstShader.shader拖動(dòng)到0.TheFirstShader材質(zhì)身上然后釋放。


拖動(dòng)完成后,我們單擊0.TheFirstShader材質(zhì),打開(kāi)他的面板,發(fā)現(xiàn)他已經(jīng)和一開(kāi)始不一樣了,泛著藍(lán)光:


還沒(méi)完,接下來(lái)我們還得給這個(gè)材質(zhì)添加兩張紋理圖片。圖片淺墨也已經(jīng)提前準(zhǔn)備好了,在名為T(mén)extures01 by QianMo.unitypackage的Unity包中,同樣雙擊這個(gè)包然后打開(kāi)導(dǎo)入到項(xiàng)目中。

Textures01 by QianMo.unitypackage單獨(dú)下載請(qǐng)點(diǎn)我】

我們?cè)赥extures文件夾下找到這兩張紋理,接下來(lái)做的就是將他們拖動(dòng)到0.TheFirstShader材質(zhì)對(duì)應(yīng)的紋理區(qū)域中,如下:


或者點(diǎn)擊這里的Select分別選擇,操作如下:


兩張紋理選擇完畢后,我們的材質(zhì)就準(zhǔn)備好了,最后的結(jié)果,有點(diǎn)黑科技,如各種科幻游戲和電影中發(fā)光的礦石,非常炫酷:

OK,那么就只剩下最后一步了,就是在場(chǎng)景中創(chuàng)建一個(gè)物體,然后將我們做好的材質(zhì)拖拽到物體身上賦給這個(gè)物體就行了。

菜單欄【GameObject】->【Create Other】->【Capsule】或者【Create】下拉菜單->【Capsule】來(lái)在場(chǎng)景中創(chuàng)建一個(gè)膠囊裝的物體。把他拖動(dòng)到和我們的第一人稱攝像機(jī)【First Person Controller】很近的地方,這樣方便觀察,接著就可以把我們的“0.TheFirstShader”材質(zhì)直接拖拽給場(chǎng)景中的這個(gè)膠囊,或者Hierachy面板中【Capsule】名字上就行了,操作如下圖中的箭頭所示:


經(jīng)過(guò)拖拽,Capsule加上Material后的效果如下:


4.1 給使用Shader的物體加上文字說(shuō)明

為了以后介紹多個(gè)Shader寫(xiě)法時(shí)能更清晰更方便,淺墨專門(mén)在QianMo’s Toolkit中做了一個(gè)可以在場(chǎng)景中和游戲窗口中分別顯示附加給任意物體文字標(biāo)簽信息的工具腳本,叫做ShowObjectInfo,其詳細(xì)注釋的代碼如下:


//-----------------------------------------------【腳本說(shuō)明】-------------------------------------------------------
//      腳本功能:    在場(chǎng)景中和游戲窗口中分別顯示給任意物體附加的文字標(biāo)簽信息
//      使用語(yǔ)言:   C#
//      開(kāi)發(fā)所用IDE版本:Unity4.5 06f 、Visual Studio 2010    
//      2014年10月 Created by 淺墨    
//      更多內(nèi)容或交流,請(qǐng)?jiān)L問(wèn)淺墨的博客:http://blog.csdn.net/poem_qianmo
//---------------------------------------------------------------------------------------------------------------------

//-----------------------------------------------【使用方法】-------------------------------------------------------
//      第一步:在Unity中拖拽此腳本到某物體之上,或在Inspector中[Add Component]->[淺墨's Toolkit v1.0]->[ShowObjectInfo]
//      第二步:在Inspector里,Show Object Info 欄中的TargetCamera參數(shù)中選擇需面向的攝像機(jī),如MainCamera
//      第三步:在text參數(shù)里填需要顯示輸出的文字。
//      第四步:完成。運(yùn)行游戲或在場(chǎng)景編輯器Scene中查看顯示效果。

//      PS:默認(rèn)情況下文本信息僅在游戲運(yùn)行時(shí)顯示。
//      若需要在場(chǎng)景編輯時(shí)在Scene中顯示,請(qǐng)勾選Show Object Info 欄中的[Show Info In Scene Editor]參數(shù)。
//      同理,勾選[Show Info In Game Play]參數(shù)也可以控制是否在游戲運(yùn)行時(shí)顯示文本信息
//---------------------------------------------------------------------------------------------------------------------


//預(yù)編譯指令,檢測(cè)到UNITY_EDITOR的定義,則編譯后續(xù)代碼
#if UNITY_EDITOR    


//------------------------------------------【命名空間包含部分】----------------------------------------------------
//  說(shuō)明:命名空間包含
//----------------------------------------------------------------------------------------------------------------------
using UnityEngine;
using UnityEditor;
using System.Collections;

//添加組件菜單
[AddComponentMenu("淺墨's Toolkit v1.0/ShowObjectInfo")]


//開(kāi)始ShowObjectInfo類
public class ShowObjectInfo : MonoBehaviour
{

    //------------------------------------------【變量聲明部分】----------------------------------------------------
    //  說(shuō)明:變量聲明部分
    //------------------------------------------------------------------------------------------------------------------
    public string text="鍵入你自己的內(nèi)容 by淺墨";//文本內(nèi)容
    public Camera TargetCamera;//面對(duì)的攝像機(jī)
    public bool ShowInfoInGamePlay = true;//是否在游戲運(yùn)行時(shí)顯示此信息框的標(biāo)識(shí)符
    public bool ShowInfoInSceneEditor = false;//是否在場(chǎng)景編輯時(shí)顯示此信息框的標(biāo)識(shí)符
    private static GUIStyle style;//GUI風(fēng)格



    //------------------------------------------【GUI 風(fēng)格的設(shè)置】--------------------------------------------------
    //  說(shuō)明:設(shè)定GUI風(fēng)格
    //------------------------------------------------------------------------------------------------------------------
    private static GUIStyle Style
    {
        get
        {
            if (style == null)
            {
                //新建一個(gè)largeLabel的GUI風(fēng)格
                style = new GUIStyle(EditorStyles.largeLabel);
                //設(shè)置文本居中對(duì)齊
                style.alignment = TextAnchor.MiddleCenter;
                //設(shè)置GUI的文本顏色
                style.normal.textColor = new Color(0.9f, 0.9f, 0.9f);
                //設(shè)置GUI的文本字體大小
                style.fontSize = 26;
            }
            return style;
        }

    }




    //-----------------------------------------【OnGUI()函數(shù)】-----------------------------------------------------
    // 說(shuō)明:游戲運(yùn)行時(shí)GUI的顯示
    //------------------------------------------------------------------------------------------------------------------
    void OnGUI( )
    {
        //ShowInfoInGamePlay為真時(shí),才進(jìn)行繪制
        if (ShowInfoInGamePlay)
        {
            //---------------------------------【1.光線投射判斷&計(jì)算位置坐標(biāo)】-------------------------------
            //定義一條射線
            Ray ray = new Ray(transform.position + TargetCamera.transform.up * 6f, -TargetCamera.transform.up);
            //定義光線投射碰撞
            RaycastHit raycastHit;
            //進(jìn)行光線投射操作,第一個(gè)參數(shù)為光線的開(kāi)始點(diǎn)和方向,第二個(gè)參數(shù)為光線碰撞器碰到哪里的輸出信息,第三個(gè)參數(shù)為光線的長(zhǎng)度
            collider.Raycast(ray, out raycastHit, Mathf.Infinity);
            
            //計(jì)算距離,為當(dāng)前攝像機(jī)位置減去碰撞位置的長(zhǎng)度
            float distance = (TargetCamera.transform.position - raycastHit.point).magnitude;
            //設(shè)置字體大小,在26到12之間插值
            float fontSize = Mathf.Lerp(26, 12, distance / 10f);
            //將得到的字體大小賦給Style.fontSize
            Style.fontSize = (int)fontSize;
            //將文字位置取為得到的光線碰撞位置上方一點(diǎn)
            Vector3 worldPositon = raycastHit.point + TargetCamera.transform.up * distance * 0.03f;
            //世界坐標(biāo)轉(zhuǎn)屏幕坐標(biāo)
            Vector3 screenPosition = TargetCamera.WorldToScreenPoint(worldPositon);
            //z坐標(biāo)值的判斷,z值小于零就返回
            if (screenPosition.z <= 0){return;}
            //翻轉(zhuǎn)Y坐標(biāo)值
            screenPosition.y = Screen.height - screenPosition.y;
            
            //獲取文本尺寸
            Vector2 stringSize = Style.CalcSize(new GUIContent(text));
            //計(jì)算文本框坐標(biāo)
            Rect rect = new Rect(0f, 0f, stringSize.x + 6, stringSize.y + 4);
            //設(shè)定文本框中心坐標(biāo)
            rect.center = screenPosition - Vector3.up * rect.height * 0.5f;


            //----------------------------------【2.GUI繪制】---------------------------------------------
            //開(kāi)始繪制一個(gè)簡(jiǎn)單的文本框
            Handles.BeginGUI();
            //繪制灰底背景
            GUI.color = new Color(0f, 0f, 0f, 0.8f);
            GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);
            //繪制文字
            GUI.color = new Color(1, 1, 1, 0.8f);
            GUI.Label(rect, text, Style);
            //結(jié)束繪制
            Handles.EndGUI();
        }
    }

    //-------------------------------------【OnDrawGizmos()函數(shù)】---------------------------------------------
    // 說(shuō)明:場(chǎng)景編輯器中GUI的顯示
    //------------------------------------------------------------------------------------------------------------------
    void OnDrawGizmos()
    {
        //ShowInfoInSeneEditor為真時(shí),才進(jìn)行繪制
        if (ShowInfoInSceneEditor)
        {
            //----------------------------------------【1.光線投射判斷&計(jì)算位置坐標(biāo)】----------------------------------
            //定義一條射線
            Ray ray = new Ray(transform.position + Camera.current.transform.up * 6f, -Camera.current.transform.up);
            //定義光線投射碰撞
            RaycastHit raycastHit;
            //進(jìn)行光線投射操作,第一個(gè)參數(shù)為光線的開(kāi)始點(diǎn)和方向,第二個(gè)參數(shù)為光線碰撞器碰到哪里的輸出信息,第三個(gè)參數(shù)為光線的長(zhǎng)度
            collider.Raycast(ray, out raycastHit, Mathf.Infinity);
            
            //計(jì)算距離,為當(dāng)前攝像機(jī)位置減去碰撞位置的長(zhǎng)度
            float distance = (Camera.current.transform.position - raycastHit.point).magnitude;
            //設(shè)置字體大小,在26到12之間插值
            float fontSize = Mathf.Lerp(26, 12, distance / 10f);
            //將得到的字體大小賦給Style.fontSize
            Style.fontSize = (int)fontSize;
            //將文字位置取為得到的光線碰撞位置上方一點(diǎn)
            Vector3 worldPositon = raycastHit.point + Camera.current.transform.up * distance * 0.03f;
            //世界坐標(biāo)轉(zhuǎn)屏幕坐標(biāo)
            Vector3 screenPosition = Camera.current.WorldToScreenPoint(worldPositon);
            //z坐標(biāo)值的判斷,z值小于零就返回
            if (screenPosition.z <= 0) { return; }
            //翻轉(zhuǎn)Y坐標(biāo)值
            screenPosition.y = Screen.height - screenPosition.y;
            
            //獲取文本尺寸
            Vector2 stringSize = Style.CalcSize(new GUIContent(text));
            //計(jì)算文本框坐標(biāo)
            Rect rect = new Rect(0f, 0f, stringSize.x + 6, stringSize.y + 4);
            //設(shè)定文本框中心坐標(biāo)
            rect.center = screenPosition - Vector3.up * rect.height * 0.5f;



            //----------------------------------【2.GUI繪制】---------------------------------------------
            //開(kāi)始繪制一個(gè)簡(jiǎn)單的文本框
            Handles.BeginGUI();
            //繪制灰底背景
            GUI.color = new Color(0f, 0f, 0f, 0.8f);
            GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);
            //繪制文字
            GUI.color = new Color(1, 1, 1, 0.8f);
            GUI.Label(rect, text, Style);
            //結(jié)束繪制
            Handles.EndGUI();

        }

    }

}

//預(yù)編譯命令結(jié)束
#endif

這個(gè)腳本的用法倒是很簡(jiǎn)單,在代碼的說(shuō)明部分已經(jīng)詳細(xì)寫(xiě)出,在這里我們?cè)倭谐鲆槐椋?/p>

第一步:在Unity中拖拽此腳本到某物體之上,或在Inspector中[Add Component]->[淺墨's Toolkit v1.0]->[ShowObjectInfo]
第二步:在Inspector里,ShowObject Info 欄中的TargetCamera參數(shù)中選擇需面向的攝像機(jī),如Main Camera,F(xiàn)irstPerson Controller等
第三步:在text參數(shù)里填需要顯示輸出的文字。
第四步:完成。運(yùn)行游戲或在場(chǎng)景編輯器Scene中查看顯示效果。

也就是拖拽ShowObjectInfo腳本或者直接添加組件給需要附加文字的物體,然后在Inspector中輸入需要顯示的文字,然后選擇其面對(duì)的攝像機(jī)就可以了。

我們將ShowObjectInfo腳本拖拽給上文中剛剛變得炫酷外形黑科技的Capsule:


那么他在Inspector就多了一個(gè)“ShowObject Info(Script)”組件,將該組件的Text項(xiàng)中填上“凹凸紋理+邊緣發(fā)光效果”,TargetCamera中填上First Person Controller的子物體Main Camera:


最后,得到的效果就是這樣:


五、總結(jié)、配套資源&最終工程下載

好了,本篇的文章到這里就大概結(jié)束了。

今天講的內(nèi)容還是非常多的,對(duì)于新接觸Unity的朋友們來(lái)說(shuō)或許還得好好消化消化,而熟悉Unity的朋友應(yīng)該很快就可以看懂,或者覺(jué)得淺墨講了一堆廢話,orz。

這篇文章的內(nèi)容說(shuō)白了就非常簡(jiǎn)單,也就是新建工程,然后導(dǎo)入三個(gè)淺墨提前準(zhǔn)備好的unitypackage游戲資源,點(diǎn)一點(diǎn)鼠標(biāo)拖動(dòng)拖動(dòng)腳本,新建一個(gè)Shader,寫(xiě)點(diǎn)代碼,再創(chuàng)建一個(gè)Material,Shader賦給這個(gè)Material,最后創(chuàng)建一個(gè)膠囊狀Capsule,Material賦給這個(gè)Capsule,點(diǎn)運(yùn)行查看最終效果。一切,就是這么簡(jiǎn)單。:)

本文配套的三個(gè)unitypackage打包請(qǐng)點(diǎn)擊此處下載:

【淺墨Unity3D Shader編程】之一 配套的三個(gè)unitypackage打包下載

本文最終的Unity工程請(qǐng)點(diǎn)擊此處下載:

【淺墨Unity3D Shader編程】之一 配套Unity工程

最后放幾張最終的場(chǎng)景美圖吧。

站在亭子上看世界:


逼真的光暈:


漂亮的天空:


亂真的水面:

藍(lán)天和草地樹(shù)木交相輝映:


OK,全文到此結(jié)束。
新的游戲編程之旅已經(jīng)開(kāi)啟,下周一,我們不見(jiàn)不散。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容