【淺墨Unity3D Shader編程】之一 夏威夷篇:遊戲場景的創建 & 第一個Shader的書寫



本系列文章由@淺墨_毛星雲 出品,轉載請註明出處。  

文章鏈接: http://blog.csdn.NET/poem_qianmo/article/details/40723789

作者:毛星雲(淺墨)    微博:http://weibo.com/u/1723155442

郵箱: [email protected]

 

 

作爲一個系統介紹Unity3D中Shader編寫的系列文章的開篇,本文的第一部分爲系列文章的前言,然後第二部分介紹了這個系列文章中我們會使用的遊戲場景創建方式,最後一部分講解了如何在Unity中創建和使用Shader,爲後面專注於介紹如何在Unity中進行Shader編程打好了基礎。

 

因爲後面推出的系列文章會着重介紹各種Shader的寫法和實現,不會再具體講解如何創建場景和寫出Shader代碼後如何使用,相信這篇文章作爲本系列的開篇,發表出來肯定還是會對大家多少有些幫助的。大家以後閱讀稍後推出的Unity Shader系列文章的時候,有場景創建或者Shader代碼寫好了如何使用方面的疑問的話,可以隨時回過頭來查閱這篇文章。

 

OK,就讓我們從這篇文章開始一趟精彩萬分的Shader遊戲編程旅途。

依舊國際慣例,看幾張文章中實現的場景美圖先:



 

 





上圖中展示的文本配套Unity工程的可運行exe淺墨也爲大家準備好了,有興趣的朋友們可以點擊 這裏進行下載、運行和探索:


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

 







一、系列文章前言





在這個系列開頭,淺墨想說的是,其實這個系列文章中我們學的主要是着色器編程技術,重點不是學Unity。

甚至可以這樣說,我們學的是HLSL——沒錯,就是DirectX中的那個HLSL。


爲什麼這樣講,讓我們來捋一捋。

首先,Unity中編寫Shader的語言叫做ShaderLab,而ShaderLab說白了就是裹着一層皮的CG着色器語言而已。Cg,即C forgraphics,即用於圖形的C語言,是微軟Microsoft和英偉達NVIDIA相互協作在標準硬件光照語言的語法和語義上達成的一種一致性協議。

HLSL和CG其實是同一種語言(參見Cg教程_可編程實時圖形權威指南29頁的致謝部分)。很多時候,我們會發現用HLSL寫的代碼可以直接當中Cg代碼使用。

Microsoft和NVIDIA聯手推出CG語言,想在經濟和技術上實現雙贏,從而通過這種方式聯手打擊他們共同的對手GLSL。


既然Unity主打Shader編程的語言ShaderLab是CG語言披上一層皮,而CG語言又約等於HLSL。這就是說,在Unity中寫Shader約等於用HLSL寫Shader,也就約等於給DirectX寫Shader。雖然有點繞orz,最後總結一下也就是:

在Unity中寫Shader約等於給DirectX寫Shader


而Unity又是這樣一個集萬千寵愛於一身的可見即所得的目前在移動互聯網時代火到不行的遊戲引擎。可以說,Unity可見即所得的開發環境非常適合Shader的學習,而且在Unity分分鐘可以創建出來一個漂亮的場景裏面寫寫Shader,心情都會好很多。不再是苦逼地面朝代碼背朝天了。

所以淺墨決定開始在Unity中進行這個shader學習系列,畢竟Unity、CG、HLSL不分家。

另外促成這個系列文章的成型的一個原因是Unity的Asset store。淺墨很喜歡Unity引領的Asset store這樣一站式的遊戲素材商店,裏面有數不盡的遊戲插件、素材、資源、腳本代碼參考、shader資料等等,甚至現成的遊戲工程範例。Asset store給開發者們帶來了很大的便利,簡直就是遊戲開發界的大寶庫。

但需要說明的是,Unity這款引擎的缺點也是很明顯的。比如Unity不對外開源,我們看不到源碼,坑比較多,遇到坑了看不到

源碼給解決帶來了很大難度。執行效率還是不太行,渲染大場景和做高級渲染的時候還是顯得力不從心。比如渲染大片的近乎

真實的動態水面時,幀數就立馬降下來了,需要後期做很多的優化。就畫面質量來說和Unreal Engine和cryEngine等次時代

引擎比還是有差距。是最近幾年互聯網的浪潮和得屌絲者的天下商業模式促進了其最近幾年如此的成功。


 

說了這麼多,總結一下。

Unity只是我們學習CG、HLSL編程可見即所得的好幫手好工具而已。我們主要還是利用它來更好的學計算機圖形學和Shader編程,順便掌握目前熱門的Unity引擎的基本使用和研發思路。

我們還是忘不了C++和DirectX,我們還是渴望通過自己的努力,最終有能力用C/C++一句一句寫出自己的遊戲引擎,我們還是想從零開始造輪子,畢竟那樣一句一句寫出來的代碼都是自己的,而不是那些別人爲我們準備好的現成的API函數。

謹以此前言,與諸君共勉。

 

 


 

 

 




二、用Unity創建第一個美麗的遊戲場景





首先要說明的是,作爲一個從入門內容開始逐漸深入介紹的系列教程,這一部分在Unity下創建場景的內容是爲還不太熟悉Unity的朋友們準備的,如果你已經熟悉了Unity的使用,這部分可以快速跳讀。

 

 

OK,正式開始吧。

 





2.1 【第一步】當然是新建一個項目

 



大家肯定都知道,每次新建項目後或者新建場景後,會得到一片Unity中默認爲全藍的場景。想要場景變得有生機,一般都是去菜單欄的GameObject裏面新建Terrain(地形)然後進行編輯。Terrain的製作很耗時而且水很深,想要精通也是得花一些功夫。

甚至Unity Asset Store中有各種可以輔助快速生成AAA級大作風格的真實場景的插件,如Terrain Composer,配合着Unity中有名的Relief Terrain Pack v3地形輔助着色渲染工具,可以生成近乎以假亂真的三維自然風光出來。

漂亮地形的創建當然不屬於我們講解的重點,網絡上有數不清的文章和視頻講這方面的內容,有需要的話,大家去學一些基礎,或者直接用Asset Store中現成的各種漂亮場景。反正淺墨是各種在網上下載AssetStore中美工大牛們做場景,然後簡單的修改,爲自己測試和平常調數值所用。

這不,淺墨爲了寫這篇博客,就爲大家再加工“創作“了一個夏威夷風格的場景來:)


 




2.2 【第二步】導入Hawaii Environment.unitypackage場景包



上文講到,爲了節約時間,淺墨提前爲大家修改好了一個場景,然後這個場景已經打包,叫做“HawaiiEnvironment.unitypackage “,在文章末尾提供下載。(限於Unity對中文的支持拙計,無奈只能取英文名,不然直接導入就報錯)。

      HawaiiEnvironment.unitypackage單獨下載請點我】

 

雙擊這個包,導入到我們空空如也的工程中,經過一段時間的讀條,就導入完畢了。然後我們雙擊打開出現在Project面板中Assets文件夾下的場景文件Hawaii Environment.unity

 

接着便打開了場景,如果打開成功,Scene面板中應該便出現瞭如下類似的場景畫面:

 


 

因爲略去了場景編輯部分,直接導入,所以過程是非常簡單的。但是,這還完全不夠。讓我們在場景中添加一個可以自由控制的攝像機吧。

 

 




2.3 【第三步】添加第一人稱攝像機



淺墨準備的這個場景包是沒有攝像機的,單單就是場景,所以我們還需要在場景中添加一個攝像機。

 

大家應該清楚,比較常見添加攝像機的做法是通過菜單欄中的GameObject->CreateOther->Camera來添加。但這種方式的攝像機是固定的,不合我們的要求。我們想添加的是一個在遊戲運行時可以自由移動視角的第一人稱攝像機。其實Unity自帶的資源包中剛好可以滿足我們的要求。於是我們在Project面板中右鍵【Import Package】,或者菜單欄中Assets->ImportPackage->Character Controller來導入官網爲我們準備的的角色控制資源包。如下圖:

 

 

 

 

點擊之後,會彈出如下的資源導入確認窗口,我們直接點確定就行了:

 

 

 

因爲這個包很小,所以很快就導入完成,這時Assets根文件夾下會出現一個名爲Standard Assets的文件夾,展開它或者點進去,就是名爲【CharacterControllers】的文件夾,繼續點進去,發現了一個膠囊狀的叫【First PersonController】的傢伙,這就是我們需要的了。

然後我們先在Scene面板中利用【右鍵+鍵盤W、A、S、D】以及滾輪等操作調整好場景,然後在我們剛纔的【CharacterControllers】下點擊這個膠囊裝的【First Person Controller】按住不放,拖動到Scene場景中,選到合適的地方(如草坪上)後就放手,操作如下:

Unity會自動將這個【CharacterControllers】的中心位置依附到地形表面。

 

這個時候我們會發現之前是黑屏的Game面板中也有了畫面:

 

 

這時我們還要將這個 First Person Controller的底部向上拖動一點,不然運行遊戲時我們會不停的往下掉,因爲在Unity默認情況下First Person Controller的中心位於中部,這會照成它的底部已經穿透地形,懸空位於地形的下方,自然一運行就往下掉。

 

我們Hierarchy面板中選中First Person Controller,工具欄中選擇【移動】工具

然後對着場景中膠囊上的代表Y軸的綠色箭頭向上適當拖動,讓膠囊的底部確保位於草地之上就行了。

 

這時候我們點擊unity中間三角尖的【運行】按鈕,就可以自由地在場景中觀察和移動了~



Unity第一人稱控制器默認操作方式類似CS,CF一類的FPS遊戲。W、A、S、D前後左右移動,空格跳躍,鼠標移動調整視角,非常有親切感有木有~

這就很好地體現了Unity的入門容易的特點,只用點點鼠標一個漂亮的場景就展現在眼前。





 

2.4 【第四步】在遊戲場景中加入背景音樂

 

 


話說這麼美麗的場景怎麼能沒有音樂?

不妨就讓我們添加一段優美的鋼琴曲吧。曲子淺墨都爲大家準備好了,上文已經導入的HawaiiEnvironment.unitypackage包中,在Assets根目錄下包含了一首林海的《日光告別》。

 

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

 

 

拖拽完成後,First Person Controller的Inspector面板中就應該會多了一個Audio Source組件。


運行場景,伴隨着美麗的場景,“吹着海風”,優美的鋼琴曲入耳,非常怡人。

 

先放兩張測試過程中的場景美圖,再繼續我們下一部分的講解吧:




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



 






 

 

 

三、導入QianMo’s Toolkit並使用







3.1 認識QianMo's Toolkit

 



所謂的QianMo's Toolkit,其實就是淺墨爲場景測試寫的一個小腳本工具集,打包成一個unitypackage方便多次使用而已。若有需要,淺墨會在其中添加更多的功能。

以後我們每次新建工程的時候,只要導入這個小工具就可以使用我們之前已經寫好的各種特性,非常便捷。


QianMo's Toolkit v1.0.unitypackage單獨下載請點我】


QianMo's Toolkit v1.0版的內容如下:

 

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

 

ShowFPS:在遊戲運行時顯示幀率相關信息

ShowObjectInfo:在場景中和遊戲窗口中分別顯示添加給任意物體文字標籤信息。隱藏和顯示可選,基於公告板技術實現。

ShowGameInfo:在遊戲運行時顯示GUI相關說明

ShowLogo:在遊戲運行時顯示Logo

ShowUI:在遊戲運行時顯示簡單的鑲邊UI。

 

這個五個腳本的代碼淺墨都已經詳細註釋,在後續文章中有機會我們會介紹其具體寫法。這篇文章中就先簡單的認識一下他們就好。PS:下文第四節中貼出了ShowGameInfo腳本的全部代碼。

 

 

 

 




3.2 使用QianMo's Toolkit




上文已經說了,既然這是一個unitypackage,那麼只用雙擊它導入到我們當前的項目中就行了。導入完成之後。Assets文件夾下就又多了一個名爲” QianMo's Toolkit v1.0“的文件夾,內容就是我們剛纔介紹的5個腳本文件兩張圖:

 

 

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

 

 


拖動完成後,我們在First Person Controller的Inspector面板中發現其多了三個組件,就是我們給他添加的這個三個腳本:

 

其實Show Logo無關緊要,就是顯示了淺墨自己的logo而已,當然你可以換成自己的logo。show UI也無關緊要,就是顯示了一個頂部的鑲邊png。主要的是這個ShowGameInfo,它是用於顯示幀率等相關文字消息的。(其實簡約黨會覺得三個都無關緊要,orz)

 

拖動完成之後,再次運行,我們來看一看效果:


 

可以發現,遊戲窗口的邊邊角角多了一些說明和圖片,以及有了幀率的顯示。

 

 

 

 

 

 


 

 

四、書寫和使用第一個Shader

 




上文講解的都是一般的場景構建過程,接下來就要正式開始我們的核心部分,書寫第一個Shader了。在完成上文中講解的創建好漂亮的場景之後,我們首先可以在Assets根目錄下創建一個文件夾,用於以後Shader和Material文件的存放。創建過程可以是在Project面板的空白處右鍵->Create->Folder,也可以是點擊Project面板中的Create下拉菜單->Folder

 

 

 

給新出來的這個文件夾取名Shaders,然後回車。然後再用同樣的方法在Assets根目錄下創建一個名爲Textures的文件夾,用於稍後素材圖片的存放。那麼,如果你按照淺墨按照目前描述的步驟來的話,Assets根目錄到現在就是這樣的5個文件夾:

 

 

接着,進去到我們創建的Shader文件夾。同樣在空白處右鍵->Create->Shader,或者是直接點Create下拉菜單->Shader,創建一個Shader文件,取名爲   “0.TheFirstShader”。然後雙擊打開它,Unity會默認使用名爲MonoDevelop的編輯器打開這個Shader文件。


小tips:可以在菜單欄中Edit->Preferences->ExternalTools中調成默認用Visual Studio打開,但未經修改配置文件的Visual Studio對Shader後綴的文件是不支持語法高亮的,淺墨修改了部分配置文件才讓Visual Studio支持了Unity Shader書寫的語法高亮。對於不太清楚如何修改的朋友,可以善用搜索引擎,或者過些天淺墨會單獨發一篇名爲《Unity中使用Visual Studio編寫shader並設置代碼高亮》的文章來專門講解。

 

 

作爲初次寫Shader,我們暫且先用MonoDevelop頂一頂,後面的文章再換用修改了配置文件的Visual Studio。

 

好了,用MonoDevelop打開我們新建的這個Shader文件,發現Unity已經爲我們寫好了很多代碼。

我們不妨自己重新寫點不一樣的東西。刪掉原本的這些代碼,拷貝淺墨寫的如下代碼到編輯器中:

 

 

[cpp] view plain copy
  1. //-----------------------------------------------【Shader說明】----------------------------------------------  
  2. //      Shader功能:   凹凸紋理顯示+自選邊緣顏色和強度  
  3. //     使用語言:   Shaderlab  
  4. //     開發所用IDE版本:Unity4.5 06f 、Monodevelop     
  5. //     2014年11月2日  Created by 淺墨      
  6. //     更多內容或交流請訪問淺墨的博客:http://blog.csdn.net/poem_qianmo  
  7. //---------------------------------------------------------------------------------------------------------------------  
  8.   
  9.   
  10. Shader "淺墨Shader編程/0.TheFirstShader"   
  11. {  
  12.     //-------------------------------【屬性】-----------------------------------------  
  13.     Properties   
  14.     {  
  15.         _MainTex ("【紋理】Texture", 2D) = "white" {}  
  16.         _BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}  
  17.         _RimColor ("【邊緣顏色】Rim Color", Color) = (0.17,0.36,0.81,0.0)  
  18.         _RimPower ("【邊緣顏色強度】Rim Power", Range(0.6,9.0)) = 1.0  
  19.     }  
  20.   
  21.     //----------------------------【開始一個子着色器】---------------------------  
  22.     SubShader   
  23.     {  
  24.         //渲染類型爲Opaque,不透明  
  25.         Tags { "RenderType" = "Opaque" }  
  26.   
  27.         //-------------------開始CG着色器編程語言段-----------------  
  28.         CGPROGRAM  
  29.   
  30.         //使用蘭伯特光照模式  
  31.         #pragma surface surf Lambert  
  32.           
  33.         //輸入結構  
  34.         struct Input   
  35.         {  
  36.             float2 uv_MainTex;//紋理貼圖  
  37.             float2 uv_BumpMap;//法線貼圖  
  38.             float3 viewDir;//觀察方向  
  39.         };  
  40.   
  41.         //變量聲明  
  42.         sampler2D _MainTex;//主紋理  
  43.         sampler2D _BumpMap;//凹凸紋理  
  44.         float4 _RimColor;//邊緣顏色  
  45.         float _RimPower;//邊緣顏色強度  
  46.   
  47.         //表面着色函數的編寫  
  48.         void surf (Input IN, inout SurfaceOutput o)  
  49.         {  
  50.             //表面反射顏色爲紋理顏色  
  51.             o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;  
  52.             //表面法線爲凹凸紋理的顏色  
  53.             o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));  
  54.             //邊緣顏色  
  55.             half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));  
  56.             //邊緣顏色強度  
  57.             o.Emission = _RimColor.rgb * pow (rim, _RimPower);  
  58.         }  
  59.   
  60.         //-------------------結束CG着色器編程語言段------------------  
  61.         ENDCG  
  62.     }   
  63.   
  64.     //“備胎”爲普通漫反射  
  65.     Fallback "Diffuse"  
  66. }  


 


由於這是第一篇Shader系列文章,已經涉及到很多內容了,所以淺墨不可能展開講解這段代碼的具體思路和寫法,不過已經詳細註釋,大家應該會多少有點明白。隨着稍後文章的深入,這段代碼就顯得很簡單易懂了。

拷貝完成,保存一下這段代碼,unity會自動檢測和編譯被保存的代碼,我只需返回Unity窗口,等待編譯完成即可。若沒有錯誤,在“0.TheFirstShader”的inspector面板中得到的結果應該是有紅色的錯誤提示的。

 

需要注意的是,Shader想要使用到遊戲物體上,一般得有個媒介,這個媒介就是我們的老朋友——材質(Material)。我們把Shader作用於材質,接着再把材質對應地作用於給遊戲物體,這樣寫的Shader就間接地給物體表面使用了。


 


而這一層關係,在Unity中完全可以通過點點鼠標,拖動來完成。下面我們就來講一講如何將一個着色程序的結果顯示到物體表面上。

 

知道以上原理了就很簡單了,在“0.TheFirstShader.shader”的同一目錄下創建一個Material。同樣是可以通過Create下拉菜單->Material或者空白處右鍵->create->Material來完成。


爲了到時候方便對應,我們將這個材質也取名爲0.TheFirstShader。

 

 

接着,將0.TheFirstShader.shader拖動到0.TheFirstShader材質身上然後釋放。

 

 

拖動完成後,我們單擊0.TheFirstShader材質,打開他的面板,發現他已經和一開始不一樣了,泛着藍光:




還沒完,接下來我們還得給這個材質添加兩張紋理圖片。圖片淺墨也已經提前準備好了,在名爲Textures01 by QianMo.unitypackage的Unity包中,同樣雙擊這個包然後打開導入到項目中。


Textures01 by QianMo.unitypackage單獨下載請點我】


我們在Textures文件夾下找到這兩張紋理,接下來做的就是將他們拖動到0.TheFirstShader材質對應的紋理區域中,如下:

 

 

或者點擊這裏的Select分別選擇,操作如下:


 

兩張紋理選擇完畢後,我們的材質就準備好了,最後的結果,有點黑科技,如各種科幻遊戲和電影中發光的礦石,非常炫酷:

 

 


OK,那麼就只剩下最後一步了,就是在場景中創建一個物體,然後將我們做好的材質拖拽到物體身上賦給這個物體就行了。

 

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

 

 

 

 

經過拖拽,Capsule加上Material後的效果如下:



 

 




 

4.1 給使用Shader的物體加上文字說明




爲了以後介紹多個Shader寫法時能更清晰更方便,淺墨專門在QianMo’s Toolkit中做了一個可以在場景中和遊戲窗口中分別顯示附加給任意物體文字標籤信息的工具腳本,叫做ShowObjectInfo,其詳細註釋的代碼如下:

 


 

[csharp] view plain copy
  1. //-----------------------------------------------【腳本說明】-------------------------------------------------------  
  2. //      腳本功能:    在場景中和遊戲窗口中分別顯示給任意物體附加的文字標籤信息  
  3. //      使用語言:   C#  
  4. //      開發所用IDE版本:Unity4.5 06f 、Visual Studio 2010      
  5. //      2014年10月 Created by 淺墨      
  6. //      更多內容或交流,請訪問淺墨的博客:http://blog.csdn.net/poem_qianmo  
  7. //---------------------------------------------------------------------------------------------------------------------  
  8.   
  9. //-----------------------------------------------【使用方法】-------------------------------------------------------  
  10. //      第一步:在Unity中拖拽此腳本到某物體之上,或在Inspector中[Add Component]->[淺墨's Toolkit v1.0]->[ShowObjectInfo]  
  11. //      第二步:在Inspector裏,Show Object Info 欄中的TargetCamera參數中選擇需面向的攝像機,如MainCamera  
  12. //      第三步:在text參數裏填需要顯示輸出的文字。  
  13. //      第四步:完成。運行遊戲或在場景編輯器Scene中查看顯示效果。  
  14.   
  15. //      PS:默認情況下文本信息僅在遊戲運行時顯示。  
  16. //      若需要在場景編輯時在Scene中顯示,請勾選Show Object Info 欄中的[Show Info In Scene Editor]參數。  
  17. //      同理,勾選[Show Info In Game Play]參數也可以控制是否在遊戲運行時顯示文本信息  
  18. //---------------------------------------------------------------------------------------------------------------------  
  19.   
  20.   
  21. //預編譯指令,檢測到UNITY_EDITOR的定義,則編譯後續代碼  
  22. #if UNITY_EDITOR      
  23.   
  24.   
  25. //------------------------------------------【命名空間包含部分】----------------------------------------------------  
  26. //  說明:命名空間包含  
  27. //----------------------------------------------------------------------------------------------------------------------  
  28. using UnityEngine;  
  29. using UnityEditor;  
  30. using System.Collections;  
  31.   
  32. //添加組件菜單  
  33. [AddComponentMenu("淺墨's Toolkit v1.0/ShowObjectInfo")]  
  34.   
  35.   
  36. //開始ShowObjectInfo類  
  37. public class ShowObjectInfo : MonoBehaviour  
  38. {  
  39.   
  40.     //------------------------------------------【變量聲明部分】----------------------------------------------------  
  41.     //  說明:變量聲明部分  
  42.     //------------------------------------------------------------------------------------------------------------------  
  43.     public string text="鍵入你自己的內容 by淺墨";//文本內容  
  44.     public Camera TargetCamera;//面對的攝像機  
  45.     public bool ShowInfoInGamePlay = true;//是否在遊戲運行時顯示此信息框的標識符  
  46.     public bool ShowInfoInSceneEditor = false;//是否在場景編輯時顯示此信息框的標識符  
  47.     private static GUIStyle style;//GUI風格  
  48.   
  49.   
  50.   
  51.     //------------------------------------------【GUI 風格的設置】--------------------------------------------------  
  52.     //  說明:設定GUI風格  
  53.     //------------------------------------------------------------------------------------------------------------------  
  54.     private static GUIStyle Style  
  55.     {  
  56.         get  
  57.         {  
  58.             if (style == null)  
  59.             {  
  60.                 //新建一個largeLabel的GUI風格  
  61.                 style = new GUIStyle(EditorStyles.largeLabel);  
  62.                 //設置文本居中對齊  
  63.                 style.alignment = TextAnchor.MiddleCenter;  
  64.                 //設置GUI的文本顏色  
  65.                 style.normal.textColor = new Color(0.9f, 0.9f, 0.9f);  
  66.                 //設置GUI的文本字體大小  
  67.                 style.fontSize = 26;  
  68.             }  
  69.             return style;  
  70.         }  
  71.   
  72.     }  
  73.   
  74.   
  75.   
  76.   
  77.     //-----------------------------------------【OnGUI()函數】-----------------------------------------------------  
  78.     // 說明:遊戲運行時GUI的顯示  
  79.     //------------------------------------------------------------------------------------------------------------------  
  80.     void OnGUI( )  
  81.     {  
  82.         //ShowInfoInGamePlay爲真時,才進行繪製  
  83.         if (ShowInfoInGamePlay)  
  84.         {  
  85.             //---------------------------------【1.光線投射判斷&計算位置座標】-------------------------------  
  86.             //定義一條射線  
  87.             Ray ray = new Ray(transform.position + TargetCamera.transform.up * 6f, -TargetCamera.transform.up);  
  88.             //定義光線投射碰撞  
  89.             RaycastHit raycastHit;  
  90.             //進行光線投射操作,第一個參數爲光線的開始點和方向,第二個參數爲光線碰撞器碰到哪裏的輸出信息,第三個參數爲光線的長度  
  91.             collider.Raycast(ray, out raycastHit, Mathf.Infinity);  
  92.               
  93.             //計算距離,爲當前攝像機位置減去碰撞位置的長度  
  94.             float distance = (TargetCamera.transform.position - raycastHit.point).magnitude;  
  95.             //設置字體大小,在26到12之間插值  
  96.             float fontSize = Mathf.Lerp(26, 12, distance / 10f);  
  97.             //將得到的字體大小賦給Style.fontSize  
  98.             Style.fontSize = (int)fontSize;  
  99.             //將文字位置取爲得到的光線碰撞位置上方一點  
  100.             Vector3 worldPositon = raycastHit.point + TargetCamera.transform.up * distance * 0.03f;  
  101.             //世界座標轉屏幕座標  
  102.             Vector3 screenPosition = TargetCamera.WorldToScreenPoint(worldPositon);  
  103.             //z座標值的判斷,z值小於零就返回  
  104.             if (screenPosition.z <= 0){return;}  
  105.             //翻轉Y座標值  
  106.             screenPosition.y = Screen.height - screenPosition.y;  
  107.               
  108.             //獲取文本尺寸  
  109.             Vector2 stringSize = Style.CalcSize(new GUIContent(text));  
  110.             //計算文本框座標  
  111.             Rect rect = new Rect(0f, 0f, stringSize.x + 6, stringSize.y + 4);  
  112.             //設定文本框中心座標  
  113.             rect.center = screenPosition - Vector3.up * rect.height * 0.5f;  
  114.   
  115.   
  116.             //----------------------------------【2.GUI繪製】---------------------------------------------  
  117.             //開始繪製一個簡單的文本框  
  118.             Handles.BeginGUI();  
  119.             //繪製灰底背景  
  120.             GUI.color = new Color(0f, 0f, 0f, 0.8f);  
  121.             GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);  
  122.             //繪製文字  
  123.             GUI.color = new Color(1, 1, 1, 0.8f);  
  124.             GUI.Label(rect, text, Style);  
  125.             //結束繪製  
  126.             Handles.EndGUI();  
  127.         }  
  128.     }  
  129.   
  130.     //-------------------------------------【OnDrawGizmos()函數】---------------------------------------------  
  131.     // 說明:場景編輯器中GUI的顯示  
  132.     //------------------------------------------------------------------------------------------------------------------  
  133.     void OnDrawGizmos()  
  134.     {  
  135.         //ShowInfoInSeneEditor爲真時,才進行繪製  
  136.         if (ShowInfoInSceneEditor)  
  137.         {  
  138.             //----------------------------------------【1.光線投射判斷&計算位置座標】----------------------------------  
  139.             //定義一條射線  
  140.             Ray ray = new Ray(transform.position + Camera.current.transform.up * 6f, -Camera.current.transform.up);  
  141.             //定義光線投射碰撞  
  142.             RaycastHit raycastHit;  
  143.             //進行光線投射操作,第一個參數爲光線的開始點和方向,第二個參數爲光線碰撞器碰到哪裏的輸出信息,第三個參數爲光線的長度  
  144.             collider.Raycast(ray, out raycastHit, Mathf.Infinity);  
  145.               
  146.             //計算距離,爲當前攝像機位置減去碰撞位置的長度  
  147.             float distance = (Camera.current.transform.position - raycastHit.point).magnitude;  
  148.             //設置字體大小,在26到12之間插值  
  149.             float fontSize = Mathf.Lerp(26, 12, distance / 10f);  
  150.             //將得到的字體大小賦給Style.fontSize  
  151.             Style.fontSize = (int)fontSize;  
  152.             //將文字位置取爲得到的光線碰撞位置上方一點  
  153.             Vector3 worldPositon = raycastHit.point + Camera.current.transform.up * distance * 0.03f;  
  154.             //世界座標轉屏幕座標  
  155.             Vector3 screenPosition = Camera.current.WorldToScreenPoint(worldPositon);  
  156.             //z座標值的判斷,z值小於零就返回  
  157.             if (screenPosition.z <= 0) { return; }  
  158.             //翻轉Y座標值  
  159.             screenPosition.y = Screen.height - screenPosition.y;  
  160.               
  161.             //獲取文本尺寸  
  162.             Vector2 stringSize = Style.CalcSize(new GUIContent(text));  
  163.             //計算文本框座標  
  164.             Rect rect = new Rect(0f, 0f, stringSize.x + 6, stringSize.y + 4);  
  165.             //設定文本框中心座標  
  166.             rect.center = screenPosition - Vector3.up * rect.height * 0.5f;  
  167.   
  168.   
  169.   
  170.             //----------------------------------【2.GUI繪製】---------------------------------------------  
  171.             //開始繪製一個簡單的文本框  
  172.             Handles.BeginGUI();  
  173.             //繪製灰底背景  
  174.             GUI.color = new Color(0f, 0f, 0f, 0.8f);  
  175.             GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);  
  176.             //繪製文字  
  177.             GUI.color = new Color(1, 1, 1, 0.8f);  
  178.             GUI.Label(rect, text, Style);  
  179.             //結束繪製  
  180.             Handles.EndGUI();  
  181.   
  182.         }  
  183.   
  184.     }  
  185.   
  186. }  
  187.   
  188. //預編譯命令結束  
  189. #endif  

 

這個腳本的用法倒是很簡單,在代碼的說明部分已經詳細寫出,在這裏我們再列出一遍:

 

第一步:在Unity中拖拽此腳本到某物體之上,或在Inspector中[Add Component]->[淺墨's Toolkit v1.0]->[ShowObjectInfo]

第二步:在Inspector裏,ShowObject Info 欄中的TargetCamera參數中選擇需面向的攝像機,如Main Camera,FirstPerson Controller等

第三步:在text參數裏填需要顯示輸出的文字。

第四步:完成。運行遊戲或在場景編輯器Scene中查看顯示效果。

 

 

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

 

 

 

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

 

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

 

 

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


 









 


五、總結、配套資源&最終工程下載

 




好了,本篇的文章到這裏就大概結束了。

 

今天講的內容還是非常多的,對於新接觸Unity的朋友們來說或許還得好好消化消化,而熟悉Unity的朋友應該很快就可以看懂,或者覺得淺墨講了一堆廢話,orz。

 

這篇文章的內容說白了就非常簡單,也就是新建工程,然後導入三個淺墨提前準備好的unitypackage遊戲資源,點一點鼠標拖動拖動腳本,新建一個Shader,寫點代碼,再創建一個Material,Shader賦給這個Material,最後創建一個膠囊狀Capsule,Material賦給這個Capsule,點運行查看最終效果。一切,就是這麼簡單。:)

 

 

本文配套的三個unitypackage打包請點擊此處下載:


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




本文最終的Unity工程請點擊此處下載:


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




最後放幾張最終的場景美圖吧。

 

站在亭子上看世界:

 

 

逼真的光暈:

 

 

漂亮的天空:

 

 

 

亂真的水面:

 

 

藍天和草地樹木交相輝映:

 

 

 

OK,全文到此結束。

新的遊戲編程之旅已經開啓,下週一,我們不見不散。

本系列文章由@淺墨_毛星雲 出品,轉載請註明出處。  

文章鏈接: http://blog.csdn.NET/poem_qianmo/article/details/40723789

作者:毛星雲(淺墨)    微博:http://weibo.com/u/1723155442

郵箱: [email protected]

 

 

作爲一個系統介紹Unity3D中Shader編寫的系列文章的開篇,本文的第一部分爲系列文章的前言,然後第二部分介紹了這個系列文章中我們會使用的遊戲場景創建方式,最後一部分講解了如何在Unity中創建和使用Shader,爲後面專注於介紹如何在Unity中進行Shader編程打好了基礎。

 

因爲後面推出的系列文章會着重介紹各種Shader的寫法和實現,不會再具體講解如何創建場景和寫出Shader代碼後如何使用,相信這篇文章作爲本系列的開篇,發表出來肯定還是會對大家多少有些幫助的。大家以後閱讀稍後推出的Unity Shader系列文章的時候,有場景創建或者Shader代碼寫好了如何使用方面的疑問的話,可以隨時回過頭來查閱這篇文章。

 

OK,就讓我們從這篇文章開始一趟精彩萬分的Shader遊戲編程旅途。

依舊國際慣例,看幾張文章中實現的場景美圖先:



 

 





上圖中展示的文本配套Unity工程的可運行exe淺墨也爲大家準備好了,有興趣的朋友們可以點擊 這裏進行下載、運行和探索:


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

 







一、系列文章前言





在這個系列開頭,淺墨想說的是,其實這個系列文章中我們學的主要是着色器編程技術,重點不是學Unity。

甚至可以這樣說,我們學的是HLSL——沒錯,就是DirectX中的那個HLSL。


爲什麼這樣講,讓我們來捋一捋。

首先,Unity中編寫Shader的語言叫做ShaderLab,而ShaderLab說白了就是裹着一層皮的CG着色器語言而已。Cg,即C forgraphics,即用於圖形的C語言,是微軟Microsoft和英偉達NVIDIA相互協作在標準硬件光照語言的語法和語義上達成的一種一致性協議。

HLSL和CG其實是同一種語言(參見Cg教程_可編程實時圖形權威指南29頁的致謝部分)。很多時候,我們會發現用HLSL寫的代碼可以直接當中Cg代碼使用。

Microsoft和NVIDIA聯手推出CG語言,想在經濟和技術上實現雙贏,從而通過這種方式聯手打擊他們共同的對手GLSL。


既然Unity主打Shader編程的語言ShaderLab是CG語言披上一層皮,而CG語言又約等於HLSL。這就是說,在Unity中寫Shader約等於用HLSL寫Shader,也就約等於給DirectX寫Shader。雖然有點繞orz,最後總結一下也就是:

在Unity中寫Shader約等於給DirectX寫Shader


而Unity又是這樣一個集萬千寵愛於一身的可見即所得的目前在移動互聯網時代火到不行的遊戲引擎。可以說,Unity可見即所得的開發環境非常適合Shader的學習,而且在Unity分分鐘可以創建出來一個漂亮的場景裏面寫寫Shader,心情都會好很多。不再是苦逼地面朝代碼背朝天了。

所以淺墨決定開始在Unity中進行這個shader學習系列,畢竟Unity、CG、HLSL不分家。

另外促成這個系列文章的成型的一個原因是Unity的Asset store。淺墨很喜歡Unity引領的Asset store這樣一站式的遊戲素材商店,裏面有數不盡的遊戲插件、素材、資源、腳本代碼參考、shader資料等等,甚至現成的遊戲工程範例。Asset store給開發者們帶來了很大的便利,簡直就是遊戲開發界的大寶庫。

但需要說明的是,Unity這款引擎的缺點也是很明顯的。比如Unity不對外開源,我們看不到源碼,坑比較多,遇到坑了看不到

源碼給解決帶來了很大難度。執行效率還是不太行,渲染大場景和做高級渲染的時候還是顯得力不從心。比如渲染大片的近乎

真實的動態水面時,幀數就立馬降下來了,需要後期做很多的優化。就畫面質量來說和Unreal Engine和cryEngine等次時代

引擎比還是有差距。是最近幾年互聯網的浪潮和得屌絲者的天下商業模式促進了其最近幾年如此的成功。


 

說了這麼多,總結一下。

Unity只是我們學習CG、HLSL編程可見即所得的好幫手好工具而已。我們主要還是利用它來更好的學計算機圖形學和Shader編程,順便掌握目前熱門的Unity引擎的基本使用和研發思路。

我們還是忘不了C++和DirectX,我們還是渴望通過自己的努力,最終有能力用C/C++一句一句寫出自己的遊戲引擎,我們還是想從零開始造輪子,畢竟那樣一句一句寫出來的代碼都是自己的,而不是那些別人爲我們準備好的現成的API函數。

謹以此前言,與諸君共勉。

 

 


 

 

 




二、用Unity創建第一個美麗的遊戲場景





首先要說明的是,作爲一個從入門內容開始逐漸深入介紹的系列教程,這一部分在Unity下創建場景的內容是爲還不太熟悉Unity的朋友們準備的,如果你已經熟悉了Unity的使用,這部分可以快速跳讀。

 

 

OK,正式開始吧。

 





2.1 【第一步】當然是新建一個項目

 



大家肯定都知道,每次新建項目後或者新建場景後,會得到一片Unity中默認爲全藍的場景。想要場景變得有生機,一般都是去菜單欄的GameObject裏面新建Terrain(地形)然後進行編輯。Terrain的製作很耗時而且水很深,想要精通也是得花一些功夫。

甚至Unity Asset Store中有各種可以輔助快速生成AAA級大作風格的真實場景的插件,如Terrain Composer,配合着Unity中有名的Relief Terrain Pack v3地形輔助着色渲染工具,可以生成近乎以假亂真的三維自然風光出來。

漂亮地形的創建當然不屬於我們講解的重點,網絡上有數不清的文章和視頻講這方面的內容,有需要的話,大家去學一些基礎,或者直接用Asset Store中現成的各種漂亮場景。反正淺墨是各種在網上下載AssetStore中美工大牛們做場景,然後簡單的修改,爲自己測試和平常調數值所用。

這不,淺墨爲了寫這篇博客,就爲大家再加工“創作“了一個夏威夷風格的場景來:)


 




2.2 【第二步】導入Hawaii Environment.unitypackage場景包



上文講到,爲了節約時間,淺墨提前爲大家修改好了一個場景,然後這個場景已經打包,叫做“HawaiiEnvironment.unitypackage “,在文章末尾提供下載。(限於Unity對中文的支持拙計,無奈只能取英文名,不然直接導入就報錯)。

      HawaiiEnvironment.unitypackage單獨下載請點我】

 

雙擊這個包,導入到我們空空如也的工程中,經過一段時間的讀條,就導入完畢了。然後我們雙擊打開出現在Project面板中Assets文件夾下的場景文件Hawaii Environment.unity

 

接着便打開了場景,如果打開成功,Scene面板中應該便出現瞭如下類似的場景畫面:

 


 

因爲略去了場景編輯部分,直接導入,所以過程是非常簡單的。但是,這還完全不夠。讓我們在場景中添加一個可以自由控制的攝像機吧。

 

 




2.3 【第三步】添加第一人稱攝像機



淺墨準備的這個場景包是沒有攝像機的,單單就是場景,所以我們還需要在場景中添加一個攝像機。

 

大家應該清楚,比較常見添加攝像機的做法是通過菜單欄中的GameObject->CreateOther->Camera來添加。但這種方式的攝像機是固定的,不合我們的要求。我們想添加的是一個在遊戲運行時可以自由移動視角的第一人稱攝像機。其實Unity自帶的資源包中剛好可以滿足我們的要求。於是我們在Project面板中右鍵【Import Package】,或者菜單欄中Assets->ImportPackage->Character Controller來導入官網爲我們準備的的角色控制資源包。如下圖:

 

 

 

 

點擊之後,會彈出如下的資源導入確認窗口,我們直接點確定就行了:

 

 

 

因爲這個包很小,所以很快就導入完成,這時Assets根文件夾下會出現一個名爲Standard Assets的文件夾,展開它或者點進去,就是名爲【CharacterControllers】的文件夾,繼續點進去,發現了一個膠囊狀的叫【First PersonController】的傢伙,這就是我們需要的了。

然後我們先在Scene面板中利用【右鍵+鍵盤W、A、S、D】以及滾輪等操作調整好場景,然後在我們剛纔的【CharacterControllers】下點擊這個膠囊裝的【First Person Controller】按住不放,拖動到Scene場景中,選到合適的地方(如草坪上)後就放手,操作如下:

Unity會自動將這個【CharacterControllers】的中心位置依附到地形表面。

 

這個時候我們會發現之前是黑屏的Game面板中也有了畫面:

 

 

這時我們還要將這個 First Person Controller的底部向上拖動一點,不然運行遊戲時我們會不停的往下掉,因爲在Unity默認情況下First Person Controller的中心位於中部,這會照成它的底部已經穿透地形,懸空位於地形的下方,自然一運行就往下掉。

 

我們Hierarchy面板中選中First Person Controller,工具欄中選擇【移動】工具

然後對着場景中膠囊上的代表Y軸的綠色箭頭向上適當拖動,讓膠囊的底部確保位於草地之上就行了。

 

這時候我們點擊unity中間三角尖的【運行】按鈕,就可以自由地在場景中觀察和移動了~



Unity第一人稱控制器默認操作方式類似CS,CF一類的FPS遊戲。W、A、S、D前後左右移動,空格跳躍,鼠標移動調整視角,非常有親切感有木有~

這就很好地體現了Unity的入門容易的特點,只用點點鼠標一個漂亮的場景就展現在眼前。





 

2.4 【第四步】在遊戲場景中加入背景音樂

 

 


話說這麼美麗的場景怎麼能沒有音樂?

不妨就讓我們添加一段優美的鋼琴曲吧。曲子淺墨都爲大家準備好了,上文已經導入的HawaiiEnvironment.unitypackage包中,在Assets根目錄下包含了一首林海的《日光告別》。

 

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

 

 

拖拽完成後,First Person Controller的Inspector面板中就應該會多了一個Audio Source組件。


運行場景,伴隨着美麗的場景,“吹着海風”,優美的鋼琴曲入耳,非常怡人。

 

先放兩張測試過程中的場景美圖,再繼續我們下一部分的講解吧:




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



 






 

 

 

三、導入QianMo’s Toolkit並使用







3.1 認識QianMo's Toolkit

 



所謂的QianMo's Toolkit,其實就是淺墨爲場景測試寫的一個小腳本工具集,打包成一個unitypackage方便多次使用而已。若有需要,淺墨會在其中添加更多的功能。

以後我們每次新建工程的時候,只要導入這個小工具就可以使用我們之前已經寫好的各種特性,非常便捷。


QianMo's Toolkit v1.0.unitypackage單獨下載請點我】


QianMo's Toolkit v1.0版的內容如下:

 

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

 

ShowFPS:在遊戲運行時顯示幀率相關信息

ShowObjectInfo:在場景中和遊戲窗口中分別顯示添加給任意物體文字標籤信息。隱藏和顯示可選,基於公告板技術實現。

ShowGameInfo:在遊戲運行時顯示GUI相關說明

ShowLogo:在遊戲運行時顯示Logo

ShowUI:在遊戲運行時顯示簡單的鑲邊UI。

 

這個五個腳本的代碼淺墨都已經詳細註釋,在後續文章中有機會我們會介紹其具體寫法。這篇文章中就先簡單的認識一下他們就好。PS:下文第四節中貼出了ShowGameInfo腳本的全部代碼。

 

 

 

 




3.2 使用QianMo's Toolkit




上文已經說了,既然這是一個unitypackage,那麼只用雙擊它導入到我們當前的項目中就行了。導入完成之後。Assets文件夾下就又多了一個名爲” QianMo's Toolkit v1.0“的文件夾,內容就是我們剛纔介紹的5個腳本文件兩張圖:

 

 

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

 

 


拖動完成後,我們在First Person Controller的Inspector面板中發現其多了三個組件,就是我們給他添加的這個三個腳本:

 

其實Show Logo無關緊要,就是顯示了淺墨自己的logo而已,當然你可以換成自己的logo。show UI也無關緊要,就是顯示了一個頂部的鑲邊png。主要的是這個ShowGameInfo,它是用於顯示幀率等相關文字消息的。(其實簡約黨會覺得三個都無關緊要,orz)

 

拖動完成之後,再次運行,我們來看一看效果:


 

可以發現,遊戲窗口的邊邊角角多了一些說明和圖片,以及有了幀率的顯示。

 

 

 

 

 

 


 

 

四、書寫和使用第一個Shader

 




上文講解的都是一般的場景構建過程,接下來就要正式開始我們的核心部分,書寫第一個Shader了。在完成上文中講解的創建好漂亮的場景之後,我們首先可以在Assets根目錄下創建一個文件夾,用於以後Shader和Material文件的存放。創建過程可以是在Project面板的空白處右鍵->Create->Folder,也可以是點擊Project面板中的Create下拉菜單->Folder

 

 

 

給新出來的這個文件夾取名Shaders,然後回車。然後再用同樣的方法在Assets根目錄下創建一個名爲Textures的文件夾,用於稍後素材圖片的存放。那麼,如果你按照淺墨按照目前描述的步驟來的話,Assets根目錄到現在就是這樣的5個文件夾:

 

 

接着,進去到我們創建的Shader文件夾。同樣在空白處右鍵->Create->Shader,或者是直接點Create下拉菜單->Shader,創建一個Shader文件,取名爲   “0.TheFirstShader”。然後雙擊打開它,Unity會默認使用名爲MonoDevelop的編輯器打開這個Shader文件。


小tips:可以在菜單欄中Edit->Preferences->ExternalTools中調成默認用Visual Studio打開,但未經修改配置文件的Visual Studio對Shader後綴的文件是不支持語法高亮的,淺墨修改了部分配置文件才讓Visual Studio支持了Unity Shader書寫的語法高亮。對於不太清楚如何修改的朋友,可以善用搜索引擎,或者過些天淺墨會單獨發一篇名爲《Unity中使用Visual Studio編寫shader並設置代碼高亮》的文章來專門講解。

 

 

作爲初次寫Shader,我們暫且先用MonoDevelop頂一頂,後面的文章再換用修改了配置文件的Visual Studio。

 

好了,用MonoDevelop打開我們新建的這個Shader文件,發現Unity已經爲我們寫好了很多代碼。

我們不妨自己重新寫點不一樣的東西。刪掉原本的這些代碼,拷貝淺墨寫的如下代碼到編輯器中:

 

 

[cpp] view plain copy
  1. //-----------------------------------------------【Shader說明】----------------------------------------------  
  2. //      Shader功能:   凹凸紋理顯示+自選邊緣顏色和強度  
  3. //     使用語言:   Shaderlab  
  4. //     開發所用IDE版本:Unity4.5 06f 、Monodevelop     
  5. //     2014年11月2日  Created by 淺墨      
  6. //     更多內容或交流請訪問淺墨的博客:http://blog.csdn.net/poem_qianmo  
  7. //---------------------------------------------------------------------------------------------------------------------  
  8.   
  9.   
  10. Shader "淺墨Shader編程/0.TheFirstShader"   
  11. {  
  12.     //-------------------------------【屬性】-----------------------------------------  
  13.     Properties   
  14.     {  
  15.         _MainTex ("【紋理】Texture", 2D) = "white" {}  
  16.         _BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}  
  17.         _RimColor ("【邊緣顏色】Rim Color", Color) = (0.17,0.36,0.81,0.0)  
  18.         _RimPower ("【邊緣顏色強度】Rim Power", Range(0.6,9.0)) = 1.0  
  19.     }  
  20.   
  21.     //----------------------------【開始一個子着色器】---------------------------  
  22.     SubShader   
  23.     {  
  24.         //渲染類型爲Opaque,不透明  
  25.         Tags { "RenderType" = "Opaque" }  
  26.   
  27.         //-------------------開始CG着色器編程語言段-----------------  
  28.         CGPROGRAM  
  29.   
  30.         //使用蘭伯特光照模式  
  31.         #pragma surface surf Lambert  
  32.           
  33.         //輸入結構  
  34.         struct Input   
  35.         {  
  36.             float2 uv_MainTex;//紋理貼圖  
  37.             float2 uv_BumpMap;//法線貼圖  
  38.             float3 viewDir;//觀察方向  
  39.         };  
  40.   
  41.         //變量聲明  
  42.         sampler2D _MainTex;//主紋理  
  43.         sampler2D _BumpMap;//凹凸紋理  
  44.         float4 _RimColor;//邊緣顏色  
  45.         float _RimPower;//邊緣顏色強度  
  46.   
  47.         //表面着色函數的編寫  
  48.         void surf (Input IN, inout SurfaceOutput o)  
  49.         {  
  50.             //表面反射顏色爲紋理顏色  
  51.             o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;  
  52.             //表面法線爲凹凸紋理的顏色  
  53.             o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));  
  54.             //邊緣顏色  
  55.             half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));  
  56.             //邊緣顏色強度  
  57.             o.Emission = _RimColor.rgb * pow (rim, _RimPower);  
  58.         }  
  59.   
  60.         //-------------------結束CG着色器編程語言段------------------  
  61.         ENDCG  
  62.     }   
  63.   
  64.     //“備胎”爲普通漫反射  
  65.     Fallback "Diffuse"  
  66. }  


 


由於這是第一篇Shader系列文章,已經涉及到很多內容了,所以淺墨不可能展開講解這段代碼的具體思路和寫法,不過已經詳細註釋,大家應該會多少有點明白。隨着稍後文章的深入,這段代碼就顯得很簡單易懂了。

拷貝完成,保存一下這段代碼,unity會自動檢測和編譯被保存的代碼,我只需返回Unity窗口,等待編譯完成即可。若沒有錯誤,在“0.TheFirstShader”的inspector面板中得到的結果應該是有紅色的錯誤提示的。

 

需要注意的是,Shader想要使用到遊戲物體上,一般得有個媒介,這個媒介就是我們的老朋友——材質(Material)。我們把Shader作用於材質,接着再把材質對應地作用於給遊戲物體,這樣寫的Shader就間接地給物體表面使用了。


 


而這一層關係,在Unity中完全可以通過點點鼠標,拖動來完成。下面我們就來講一講如何將一個着色程序的結果顯示到物體表面上。

 

知道以上原理了就很簡單了,在“0.TheFirstShader.shader”的同一目錄下創建一個Material。同樣是可以通過Create下拉菜單->Material或者空白處右鍵->create->Material來完成。


爲了到時候方便對應,我們將這個材質也取名爲0.TheFirstShader。

 

 

接着,將0.TheFirstShader.shader拖動到0.TheFirstShader材質身上然後釋放。

 

 

拖動完成後,我們單擊0.TheFirstShader材質,打開他的面板,發現他已經和一開始不一樣了,泛着藍光:




還沒完,接下來我們還得給這個材質添加兩張紋理圖片。圖片淺墨也已經提前準備好了,在名爲Textures01 by QianMo.unitypackage的Unity包中,同樣雙擊這個包然後打開導入到項目中。


Textures01 by QianMo.unitypackage單獨下載請點我】


我們在Textures文件夾下找到這兩張紋理,接下來做的就是將他們拖動到0.TheFirstShader材質對應的紋理區域中,如下:

 

 

或者點擊這裏的Select分別選擇,操作如下:


 

兩張紋理選擇完畢後,我們的材質就準備好了,最後的結果,有點黑科技,如各種科幻遊戲和電影中發光的礦石,非常炫酷:

 

 


OK,那麼就只剩下最後一步了,就是在場景中創建一個物體,然後將我們做好的材質拖拽到物體身上賦給這個物體就行了。

 

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

 

 

 

 

經過拖拽,Capsule加上Material後的效果如下:



 

 




 

4.1 給使用Shader的物體加上文字說明




爲了以後介紹多個Shader寫法時能更清晰更方便,淺墨專門在QianMo’s Toolkit中做了一個可以在場景中和遊戲窗口中分別顯示附加給任意物體文字標籤信息的工具腳本,叫做ShowObjectInfo,其詳細註釋的代碼如下:

 


 

[csharp] view plain copy
  1. //-----------------------------------------------【腳本說明】-------------------------------------------------------  
  2. //      腳本功能:    在場景中和遊戲窗口中分別顯示給任意物體附加的文字標籤信息  
  3. //      使用語言:   C#  
  4. //      開發所用IDE版本:Unity4.5 06f 、Visual Studio 2010      
  5. //      2014年10月 Created by 淺墨      
  6. //      更多內容或交流,請訪問淺墨的博客:http://blog.csdn.net/poem_qianmo  
  7. //---------------------------------------------------------------------------------------------------------------------  
  8.   
  9. //-----------------------------------------------【使用方法】-------------------------------------------------------  
  10. //      第一步:在Unity中拖拽此腳本到某物體之上,或在Inspector中[Add Component]->[淺墨's Toolkit v1.0]->[ShowObjectInfo]  
  11. //      第二步:在Inspector裏,Show Object Info 欄中的TargetCamera參數中選擇需面向的攝像機,如MainCamera  
  12. //      第三步:在text參數裏填需要顯示輸出的文字。  
  13. //      第四步:完成。運行遊戲或在場景編輯器Scene中查看顯示效果。  
  14.   
  15. //      PS:默認情況下文本信息僅在遊戲運行時顯示。  
  16. //      若需要在場景編輯時在Scene中顯示,請勾選Show Object Info 欄中的[Show Info In Scene Editor]參數。  
  17. //      同理,勾選[Show Info In Game Play]參數也可以控制是否在遊戲運行時顯示文本信息  
  18. //---------------------------------------------------------------------------------------------------------------------  
  19.   
  20.   
  21. //預編譯指令,檢測到UNITY_EDITOR的定義,則編譯後續代碼  
  22. #if UNITY_EDITOR      
  23.   
  24.   
  25. //------------------------------------------【命名空間包含部分】----------------------------------------------------  
  26. //  說明:命名空間包含  
  27. //----------------------------------------------------------------------------------------------------------------------  
  28. using UnityEngine;  
  29. using UnityEditor;  
  30. using System.Collections;  
  31.   
  32. //添加組件菜單  
  33. [AddComponentMenu("淺墨's Toolkit v1.0/ShowObjectInfo")]  
  34.   
  35.   
  36. //開始ShowObjectInfo類  
  37. public class ShowObjectInfo : MonoBehaviour  
  38. {  
  39.   
  40.     //------------------------------------------【變量聲明部分】----------------------------------------------------  
  41.     //  說明:變量聲明部分  
  42.     //------------------------------------------------------------------------------------------------------------------  
  43.     public string text="鍵入你自己的內容 by淺墨";//文本內容  
  44.     public Camera TargetCamera;//面對的攝像機  
  45.     public bool ShowInfoInGamePlay = true;//是否在遊戲運行時顯示此信息框的標識符  
  46.     public bool ShowInfoInSceneEditor = false;//是否在場景編輯時顯示此信息框的標識符  
  47.     private static GUIStyle style;//GUI風格  
  48.   
  49.   
  50.   
  51.     //------------------------------------------【GUI 風格的設置】--------------------------------------------------  
  52.     //  說明:設定GUI風格  
  53.     //------------------------------------------------------------------------------------------------------------------  
  54.     private static GUIStyle Style  
  55.     {  
  56.         get  
  57.         {  
  58.             if (style == null)  
  59.             {  
  60.                 //新建一個largeLabel的GUI風格  
  61.                 style = new GUIStyle(EditorStyles.largeLabel);  
  62.                 //設置文本居中對齊  
  63.                 style.alignment = TextAnchor.MiddleCenter;  
  64.                 //設置GUI的文本顏色  
  65.                 style.normal.textColor = new Color(0.9f, 0.9f, 0.9f);  
  66.                 //設置GUI的文本字體大小  
  67.                 style.fontSize = 26;  
  68.             }  
  69.             return style;  
  70.         }  
  71.   
  72.     }  
  73.   
  74.   
  75.   
  76.   
  77.     //-----------------------------------------【OnGUI()函數】-----------------------------------------------------  
  78.     // 說明:遊戲運行時GUI的顯示  
  79.     //------------------------------------------------------------------------------------------------------------------  
  80.     void OnGUI( )  
  81.     {  
  82.         //ShowInfoInGamePlay爲真時,才進行繪製  
  83.         if (ShowInfoInGamePlay)  
  84.         {  
  85.             //---------------------------------【1.光線投射判斷&計算位置座標】-------------------------------  
  86.             //定義一條射線  
  87.             Ray ray = new Ray(transform.position + TargetCamera.transform.up * 6f, -TargetCamera.transform.up);  
  88.             //定義光線投射碰撞  
  89.             RaycastHit raycastHit;  
  90.             //進行光線投射操作,第一個參數爲光線的開始點和方向,第二個參數爲光線碰撞器碰到哪裏的輸出信息,第三個參數爲光線的長度  
  91.             collider.Raycast(ray, out raycastHit, Mathf.Infinity);  
  92.               
  93.             //計算距離,爲當前攝像機位置減去碰撞位置的長度  
  94.             float distance = (TargetCamera.transform.position - raycastHit.point).magnitude;  
  95.             //設置字體大小,在26到12之間插值  
  96.             float fontSize = Mathf.Lerp(26, 12, distance / 10f);  
  97.             //將得到的字體大小賦給Style.fontSize  
  98.             Style.fontSize = (int)fontSize;  
  99.             //將文字位置取爲得到的光線碰撞位置上方一點  
  100.             Vector3 worldPositon = raycastHit.point + TargetCamera.transform.up * distance * 0.03f;  
  101.             //世界座標轉屏幕座標  
  102.             Vector3 screenPosition = TargetCamera.WorldToScreenPoint(worldPositon);  
  103.             //z座標值的判斷,z值小於零就返回  
  104.             if (screenPosition.z <= 0){return;}  
  105.             //翻轉Y座標值  
  106.             screenPosition.y = Screen.height - screenPosition.y;  
  107.               
  108.             //獲取文本尺寸  
  109.             Vector2 stringSize = Style.CalcSize(new GUIContent(text));  
  110.             //計算文本框座標  
  111.             Rect rect = new Rect(0f, 0f, stringSize.x + 6, stringSize.y + 4);  
  112.             //設定文本框中心座標  
  113.             rect.center = screenPosition - Vector3.up * rect.height * 0.5f;  
  114.   
  115.   
  116.             //----------------------------------【2.GUI繪製】---------------------------------------------  
  117.             //開始繪製一個簡單的文本框  
  118.             Handles.BeginGUI();  
  119.             //繪製灰底背景  
  120.             GUI.color = new Color(0f, 0f, 0f, 0.8f);  
  121.             GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);  
  122.             //繪製文字  
  123.             GUI.color = new Color(1, 1, 1, 0.8f);  
  124.             GUI.Label(rect, text, Style);  
  125.             //結束繪製  
  126.             Handles.EndGUI();  
  127.         }  
  128.     }  
  129.   
  130.     //-------------------------------------【OnDrawGizmos()函數】---------------------------------------------  
  131.     // 說明:場景編輯器中GUI的顯示  
  132.     //------------------------------------------------------------------------------------------------------------------  
  133.     void OnDrawGizmos()  
  134.     {  
  135.         //ShowInfoInSeneEditor爲真時,才進行繪製  
  136.         if (ShowInfoInSceneEditor)  
  137.         {  
  138.             //----------------------------------------【1.光線投射判斷&計算位置座標】----------------------------------  
  139.             //定義一條射線  
  140.             Ray ray = new Ray(transform.position + Camera.current.transform.up * 6f, -Camera.current.transform.up);  
  141.             //定義光線投射碰撞  
  142.             RaycastHit raycastHit;  
  143.             //進行光線投射操作,第一個參數爲光線的開始點和方向,第二個參數爲光線碰撞器碰到哪裏的輸出信息,第三個參數爲光線的長度  
  144.             collider.Raycast(ray, out raycastHit, Mathf.Infinity);  
  145.               
  146.             //計算距離,爲當前攝像機位置減去碰撞位置的長度  
  147.             float distance = (Camera.current.transform.position - raycastHit.point).magnitude;  
  148.             //設置字體大小,在26到12之間插值  
  149.             float fontSize = Mathf.Lerp(26, 12, distance / 10f);  
  150.             //將得到的字體大小賦給Style.fontSize  
  151.             Style.fontSize = (int)fontSize;  
  152.             //將文字位置取爲得到的光線碰撞位置上方一點  
  153.             Vector3 worldPositon = raycastHit.point + Camera.current.transform.up * distance * 0.03f;  
  154.             //世界座標轉屏幕座標  
  155.             Vector3 screenPosition = Camera.current.WorldToScreenPoint(worldPositon);  
  156.             //z座標值的判斷,z值小於零就返回  
  157.             if (screenPosition.z <= 0) { return; }  
  158.             //翻轉Y座標值  
  159.             screenPosition.y = Screen.height - screenPosition.y;  
  160.               
  161.             //獲取文本尺寸  
  162.             Vector2 stringSize = Style.CalcSize(new GUIContent(text));  
  163.             //計算文本框座標  
  164.             Rect rect = new Rect(0f, 0f, stringSize.x + 6, stringSize.y + 4);  
  165.             //設定文本框中心座標  
  166.             rect.center = screenPosition - Vector3.up * rect.height * 0.5f;  
  167.   
  168.   
  169.   
  170.             //----------------------------------【2.GUI繪製】---------------------------------------------  
  171.             //開始繪製一個簡單的文本框  
  172.             Handles.BeginGUI();  
  173.             //繪製灰底背景  
  174.             GUI.color = new Color(0f, 0f, 0f, 0.8f);  
  175.             GUI.DrawTexture(rect, EditorGUIUtility.whiteTexture);  
  176.             //繪製文字  
  177.             GUI.color = new Color(1, 1, 1, 0.8f);  
  178.             GUI.Label(rect, text, Style);  
  179.             //結束繪製  
  180.             Handles.EndGUI();  
  181.   
  182.         }  
  183.   
  184.     }  
  185.   
  186. }  
  187.   
  188. //預編譯命令結束  
  189. #endif  

 

這個腳本的用法倒是很簡單,在代碼的說明部分已經詳細寫出,在這裏我們再列出一遍:

 

第一步:在Unity中拖拽此腳本到某物體之上,或在Inspector中[Add Component]->[淺墨's Toolkit v1.0]->[ShowObjectInfo]

第二步:在Inspector裏,ShowObject Info 欄中的TargetCamera參數中選擇需面向的攝像機,如Main Camera,FirstPerson Controller等

第三步:在text參數裏填需要顯示輸出的文字。

第四步:完成。運行遊戲或在場景編輯器Scene中查看顯示效果。

 

 

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

 

 

 

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

 

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

 

 

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


 









 


五、總結、配套資源&最終工程下載

 




好了,本篇的文章到這裏就大概結束了。

 

今天講的內容還是非常多的,對於新接觸Unity的朋友們來說或許還得好好消化消化,而熟悉Unity的朋友應該很快就可以看懂,或者覺得淺墨講了一堆廢話,orz。

 

這篇文章的內容說白了就非常簡單,也就是新建工程,然後導入三個淺墨提前準備好的unitypackage遊戲資源,點一點鼠標拖動拖動腳本,新建一個Shader,寫點代碼,再創建一個Material,Shader賦給這個Material,最後創建一個膠囊狀Capsule,Material賦給這個Capsule,點運行查看最終效果。一切,就是這麼簡單。:)

 

 

本文配套的三個unitypackage打包請點擊此處下載:


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




本文最終的Unity工程請點擊此處下載:


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




最後放幾張最終的場景美圖吧。

 

站在亭子上看世界:

 

 

逼真的光暈:

 

 

漂亮的天空:

 

 

 

亂真的水面:

 

 

藍天和草地樹木交相輝映:

 

 

 

OK,全文到此結束。

新的遊戲編程之旅已經開啓,下週一,我們不見不散。

發佈了40 篇原創文章 · 獲贊 12 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章