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

作為一個系統介紹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已經為我們寫好了很多代碼。
我們不妨自己重新寫點不一樣的東西。刪掉原本的這些代碼,拷貝淺墨寫的如下代碼到編輯器中:

//-----------------------------------------------【Shader說明】----------------------------------------------
//      Shader功能:   凹凸紋理顯示+自選邊緣顏色和強度
//     使用語言:   Shaderlab
//     開發所用IDE版本:Unity4.5 06f 、Monodevelop   
//     2014年11月2日  Created by 淺墨    
//     更多內容或交流請訪問淺墨的博客: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 ("【邊緣顏色強度】Rim Power", Range(0.6,9.0)) = 1.0
    }

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

        //-------------------開始CG著色器編程語言段-----------------
        CGPROGRAM

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

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

        //表面著色函數的編寫
        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));
            //邊緣顏色強度
            o.Emission = _RimColor.rgb * pow (rim, _RimPower);
        }

        //-------------------結束CG著色器編程語言段------------------
        ENDCG
    } 

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

由于這是第一篇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,其詳細注釋的代碼如下:


//-----------------------------------------------【腳本說明】-------------------------------------------------------
//      腳本功能:    在場景中和游戲窗口中分別顯示給任意物體附加的文字標簽信息
//      使用語言:   C#
//      開發所用IDE版本:Unity4.5 06f 、Visual Studio 2010    
//      2014年10月 Created by 淺墨    
//      更多內容或交流,請訪問淺墨的博客:http://blog.csdn.net/poem_qianmo
//---------------------------------------------------------------------------------------------------------------------

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

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


//預編譯指令,檢測到UNITY_EDITOR的定義,則編譯后續代碼
#if UNITY_EDITOR    


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

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


//開始ShowObjectInfo類
public class ShowObjectInfo : MonoBehaviour
{

    //------------------------------------------【變量聲明部分】----------------------------------------------------
    //  說明:變量聲明部分
    //------------------------------------------------------------------------------------------------------------------
    public string text="鍵入你自己的內容 by淺墨";//文本內容
    public Camera TargetCamera;//面對的攝像機
    public bool ShowInfoInGamePlay = true;//是否在游戲運行時顯示此信息框的標識符
    public bool ShowInfoInSceneEditor = false;//是否在場景編輯時顯示此信息框的標識符
    private static GUIStyle style;//GUI風格



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

    }




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


            //----------------------------------【2.GUI繪制】---------------------------------------------
            //開始繪制一個簡單的文本框
            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);
            //結束繪制
            Handles.EndGUI();
        }
    }

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



            //----------------------------------【2.GUI繪制】---------------------------------------------
            //開始繪制一個簡單的文本框
            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);
            //結束繪制
            Handles.EndGUI();

        }

    }

}

//預編譯命令結束
#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,全文到此結束。
新的游戲編程之旅已經開啟,下周一,我們不見不散。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容