1. 程式人生 > >【Unity Shaders】使用CgInclude讓你的Shader模組化——建立CgInclude檔案儲存光照模型

【Unity Shaders】使用CgInclude讓你的Shader模組化——建立CgInclude檔案儲存光照模型

這裡是本書所有的插圖。這裡是本書所需的程式碼和資源(當然你也可以從官網下載)。

========================================== 分割線 ==========================================

寫在前面

瞭解內建的CgInclude檔案當然很好,但是如果我們想要建立自己的CgInclude檔案來儲存光照模型和輔助函式又該怎麼辦呢?

好訊息是我們的確可以建立自己的CgInclude檔案,壞訊息是我們需要再瞭解一點程式碼語法。好啦,那就開始吧!

準備工作

好訊息是這次的準備工作終於有點不同了。。。壞訊息是我不能再複製貼上了。。。

  1. 首先,建立一個新的文字檔案,例如MyCgInclude.txt。
  2. 然後,把檔案字尾改為.cginc。當然,作業系統一般會給你一些警示資訊,說這個檔案將變得不可用,但相信我,我們這個是可用的。
  3. 將新的.cginc檔案匯入到我們的Unity專案中(注意,在我的專案裡,它的位置在一個新的名為CgIncludes的資料夾下)。等編譯完成後,我們可以看到Unity把該檔案當成一個CgInclude檔案編譯好了。像下面這樣:

現在,我們已經做好準備可以建立自定義的CgInclude程式碼啦。雙擊CgInclude檔案,在MonoDevelop中開啟它吧~

實現

開啟CgInclude檔案後,開始鍵入如下程式碼。
  1. 首先,使用下面的預處理指令開始我們的CgInclude檔案。這些宣告和#pragma、#include類似,在這裡,我們想要去定義一個新的程式碼集合,只要我們的Surface Shader在它的編譯指令裡面包含了這個檔案,這些程式碼就可以執行了。在CgInclude檔案的最開始鍵入如下程式碼:
    #ifndef MY_CG_INCLUDE
    #define MY_CG_INCLUDE

  2. 然後,我們必須確保#ifndef或者#ifdef要有一個#endif來結束定義檢查。就和一個if語句需要兩個花括號一樣。在#define指令下面鍵入如下程式碼:
    #endif

  3. 接下來,我們就可以填充剩餘部分了。鍵入如下程式碼:
    // Custom Build-in Variables
    fixed4 _MyColor;
    
    // Lighting models
    inline fixed4 LightingHalfLambert (SurfaceOutput s, fixed3 lightDir, fixed atten) {
    	fixed diff = max (0, dot (s.Normal, lightDir));
    	
    	diff = (diff + 0.5) * 0.5;
    	
    	fixed4 c;
    	c.rgb = s.Albedo * _LightColor0.rgb * ((diff * _MyColor.rgb) * atten * 2);
    	c.a = s.Alpha;
    	return c;
    }
    

  4. 下面是完整的MyCgInlcude.cginc檔案:
    #ifndef MY_CG_INCLUDE
    #define MY_CG_INCLUDE
    
    // Custom Build-in Variables
    fixed4 _MyColor;
    
    // Lighting models
    inline fixed4 LightingHalfLambert (SurfaceOutput s, fixed3 lightDir, fixed atten) {
    	fixed diff = max (0, dot (s.Normal, lightDir));
    	
    	diff = (diff + 0.5) * 0.5;
    	
    	fixed4 c;
    	c.rgb = s.Albedo * _LightColor0.rgb * ((diff * _MyColor.rgb) * atten * 2);
    	c.a = s.Alpha;
    	return c;
    }
    
    #endif

    上面相當於一個頭檔案,但想要完整利用它還需要一些其他的步驟。我們需要告訴當前的Shader,我們想要使用自己的檔案和程式碼。

  5. 返回上一節所用的Shader。我們需要在塊中包含我們自己的CgInclude檔案,就像C++中需要在開頭新增標頭檔案引用一樣。同時,之前我們的Shader使用內建的Lambert光照模型,但現在我們想要使用自定義的Half Lambert光照模型。因為我們已經包含了該CgInclude檔案,我們可以直接在#pragma指令中指明這一模型:
    		CGPROGRAM
    		#include "../CgIncludes/MyCgInclude.cginc"
    		#pragma surface surf HalfLambert

    解釋:這裡需要指明.cginc檔案的相對與該Shader的路徑。也就是說,如果它和Shader放在同一個資料夾下,那麼直接寫名稱即可。但在我的專案中,Shader放在了Shaders資料夾下,而.cginc放在了CgIncludes資料夾下,因此需要上述寫法。

  6. 最後,還記得我們在CgInclude檔案中聲明瞭一個_MyColor變數嗎?我們還需要在Shader的Properties中新增該屬性:
    	Properties {
    		_MainTex ("Base (RGB)", 2D) = "white" {}
    		_DesatValue ("Desaturate", Range(0, 1)) = 0.5
    		_MyColor ("My Color", Color) = (1, 1, 1, 1)
    	}


最後,返回Unity。如果出現編譯錯誤,說找不到.cginc檔案,那麼就是你的位置寫的有問題,重新看上面的解釋更改一下就可以嘍。最後的結果如下所示。注意到,這裡Unity已經使用了我們新的Half Lambert光照模型(和原來相比,就是提亮了背光面的亮度),並且添加了一個新的樣色樣本。左側為上一篇結果,右側為本篇結果。
 

解釋

當編寫Shader的時候,我們可以像使用C++中的標頭檔案一樣,使用#include預處理指令來包含其他程式碼集合。這告訴Unity我們想要當前的Shader使用包含的這些檔案中的程式碼。我們這樣做實際上是在相應位置包含了Cg程式碼片段。一旦我們聲明瞭#include指令,Unity就可以在專案中找到該檔案,然後Unity會在檔案中查詢定義的程式碼片段。也就是指,我們使用#ifndef指令和#ifndef指令的地方。當我們宣告#ifndef指令時,我們就是在告訴Unity,如果沒有定義這個名字,那麼就使用這個名字去定義一些東西!在本節中,我們是想要去#defineMY_CG_INCLUDE。因此,如果Unity沒有找到一個名為MY_CG_INCLUDE的定義,它就會在編譯該CgInclude檔案時建立它。而#endif就是告訴Unity,這是該定義在這裡結束啦,下面的不用再找啦!現在,你看到了自定義的CgInclude檔案是多麼強大(和C++中的標頭檔案類似),我們可以使用它們來儲存所有的自定義光照模型,以減少程式碼的重複。其他好處,像靈活性等,你可以聯想C++標頭檔案來得出啦~