1. 程式人生 > >【U3D】進入shader程式設計的世界

【U3D】進入shader程式設計的世界

本系列文章由CSDN

        接觸shader程式設計已經有很長一段時間,最近有很多初學者問我許多關於Unity3D Shader方面的問題,我打算寫一篇關於shader教學的文章,方便解決大家初入影象程式設計~大坑時遇到的麻煩。
       簡單來說,學習Shader Programming就是學習如何利用GPU的力量。剛開始我會詳細解釋shader的作用、建立方式、基本語法等等,之後我會詳細教給大家如何寫出一個簡單有效的shader案例。

神馬是Shader?

Shader並不神祕,它只是很小的一段程式,包含著數學計算和演算法模型,執行在計算機的影象管道層面,告訴計算機影象上的每一個畫素應該怎樣顯示在螢幕上,它將一個個輸入的網格繪製到螢幕之上,就能得到一個

Material(材質),之後通過渲染器進行一系列的渲染輸出操作後,我們就可以觀察到客觀的物體。Shader一般被稱作著色器,它可以通過改變自身的屬性來改變材質渲染到螢幕上的效果。以下是3中不同的Shader渲染到同一個材質上的例子:

Unity3D中編寫的Shader是通過.shader檔案來進行實現的,它使用ShaderLab語言,極其類似Cg/HLSL。ShaderLab語言很類似於C語言,如果你有不錯的C/C++程式設計功底的話,學習ShaderLab將會很容易。

    Shader分為三種類型:

           1、Fixed Function Shader( 固定渲染管線著色器)

           2、Surface Shader(表面著色器)

           3、Vertex Shader&Fragment Shader (頂點著色器&片段著色器)

    接下來我們在Unity中建立Shader和Material,詳細介紹每一種Shader的建立,用法等。

在Unity中建立第一個Shader

首先,我們新建一個場景,在場景下的Project面板中新建一個叫做“Shader”的資料夾,方便我們管理今後建立的Shader檔案。然後右鍵點選空白處--->Creat--->Shader,這樣操作後,我們就能得到一個名為“NewShader”的.shader型別的檔案了。

      之前說過,shader檔案只有依附在Material,我們才能看到正確渲染出來的影象,所以我們還應建立一個新的Material,和之前類似,右鍵點選空白處--->Creat--->Material,建立一個新的Material,命名為“TestMaterial_1”。


    那麼怎麼將新建立的Shader賦予到Material上呢,我們點選“TestMaterial_1”,觀察它的Inspector面板,在Shader-->Custom下就能找到我們剛剛建立的"NewShader"檔案了。


Shader的基本框架

雙擊Shader檔案,就能看到Shader的基本寫法。總體來說,我們可以概括Shader的框架寫法:

Shader "name" { [Properties] Subshaders [Fallback] [CustomEditor] }

用圖形可以這樣表示:


      在一個完整的Shader裡,有一個Property,它定義了所有輸入到Shader中屬性的名稱和型別,Property是一個可以自行設定的列表;Shader中有多個(至少一個)SubShader,根據GPU或者其它硬體處理效能來決定使用哪一個SubShader。通常為了保證一個遊戲能在不同效能的機器上正確的執行,會有大量的備選SubShader方案,這些SubShader或許會選擇放棄實現某些細節來保證效果大致的顯示,每個SubShader下可能會有多個Pass通道,用來處理多種輸入方式。並且在最後會有個FallBack,用來處理所有SubShader不能解決的情況。

     用編輯器開啟剛才的“NewShader”檔案後,程式碼大概是如下的樣子(可能會因為Unity版本略有不同):

Shader "Custom/NewShader" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_Glossiness ("Smoothness", Range(0,1)) = 0.5
		_Metallic ("Metallic", Range(0,1)) = 0.0
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200		
		CGPROGRAM	
		
		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};
		half _Glossiness;
		half _Metallic;
		fixed4 _Color;
		void surf (Input IN, inout SurfaceOutputStandard o) {			
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;			
			o.Metallic = _Metallic;
			o.Smoothness = _Glossiness;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
  • 首先,第一行Shader "Custom/NewShader"為我們說明了這是一個.Shader檔案,緊接的“Custom/NewShader”表示了此Shader檔案的目錄位置和名稱。注意,.shader檔案不會自動更新檔名,當我們更改一個Shader檔案的名稱之後,也要對這裡的路徑下的名稱進行更改。
  • 接下來的Properties後的{}表示這個Shader的屬性。屬性類似於c/c#裡的變數,我們可以定義它的名稱、型別以及屬性數值。關於Properties的相關語法說明可以參考:
         _Name("Display Name", type) = defaultValue[{options}]
          我們可以定義如下的型別:    

1、name ("display name", Range (min, max)) = number   

          自定義浮點型屬性,在面板上通過介於最大/最小值間的滑條修改 

         2、name ("display name", Float) = number 

          自定義浮點型屬性,在面板上可以手動輸入浮點值

         3、name ("display name", Int) = number

          自定義整型數值,在面板上可以手動輸入整型數值

         4、name ("display name", Color) = (number,number,number,number) 

          自定義顏色屬性,在面板上可以開啟顏色選擇器

         5、name ("display name", Vector) = (number,number,number,number) 

          自定義四維向量數值,在面板上可以手動輸入四維向量(Float型別)

         6、name ("display name", 2D) = "defaulttexture" {}

          自定義2D Texture

         7、name ("display name", Cube) = "defaulttexture" {}

          自定義Cubemap

         8、name ("display name", 3D) = "defaulttexture" {}

          自定義3D Texture
  • 接下來是SubShader,它代表一個子著色器,裡邊所有宣告的變數和輸入輸出都不會影響到其他的SubShader。我們看看它的第一行做了什麼。
    Tags { "RenderType"="Opaque" }

             首先是個Tags標籤,渲染管道會在進入每個SubShader之前,通過這些Tags標籤來決定是否呼叫它們。Tag的語法如下:
Tags{ "TagName1" = "Value1" "TagName2" = "Value2" }
Tags標籤是用鍵值對的方式進行宣告,上述例子中的“RenderType”="Opaque",表示告訴引擎,當渲染非透明物體時,我們會呼叫這個子著色器。RenderType表示渲染型別,除了Opaque(非透明物)還有有很多種,例如:
Transparent
半透明著色器,包括絕大多數粒子Shader,地形Shader等; TransparentCutout 半透明著色器補充類(Cutout Shader型別不允許繪製部分透明的區域),包括雙通道(Pass)植被Shader Background 背景著色器,比如典型的天空盒; Overlay 覆蓋類著色器,常用於GUITexture和Flare Shaders; TreeOpaque 不透明樹木著色器;
 TreeTransparentCutout 透明樹木類著色器,比如樹葉的渲染;
        Grass

草地類渲染著色器;

除了RenderTyper,還有一些其它常用的標籤型別,例如:

"DisableBatching tag"="true",表示對此著色器禁用批處理繪製方式;

"ForceNoShadowCasting tag"="true" 表示該SubShader不會對著色物體產生陰影;

"IgnoreProjector tag"="true"表示該SubShader產生的陰影不受投影機(Projector)的影響;

"Queue"=" "表示制定的的渲染順序佇列;

這裡需要詳細解釋Queue這個屬性。可以想象,在一個大型遊戲中,遊戲物體的渲染大致順序應該是由近到遠逐步進行,從而保證遊戲鏡頭逐步進行呈現,透明的水後邊應該繪製不透明的物體等等。這樣我們可以通過制定渲染順序佇列,來確保讓透明的著色器型別能渲染在不透明物體前邊。

   Unity為我們提供了四種預定義的渲染佇列,當然,我們也可以自行新增自定義的渲染方式。官方提供的預定義渲染佇列如下:

  • BackGroud:背景渲染佇列,它優先於其他佇列,主要用於渲染天空盒等背景元素;
  • Geometry(default):這是預設渲染佇列,一般來說,場景中的大部分不透明物體都用此方式進行渲染。
  • AlphaTest:Alpha渲染通道,它是一種比較特殊的佇列,用於渲染Alpha-tested的物件。
  • Transparent:透明物體渲染佇列(Alpha值小於100),適合用於玻璃、粒子效果等。
  • Overlay:這個佇列用來處理覆蓋效果,一般來說最後呈現的渲染特效都在這裡進行處理。
上述幾種渲染佇列都有預定義的渲染優先順序,優先順序以整型數字進行表示,數字越小優先順序越高。其中Background是1000,Geometry是2000,AlphaTest為2450,Transparent是3000,Overlay是4000.我們可以自定義佇列型別,比如:Tag{"Queue"="AlphaTest+100"}
  • 說完Tags標籤作用和型別,下邊是一句LOD宣告 : 
    LOD 200 
          LOD即Level of Detail,它的值可以在Unity主介面--Edit--Project Settings--Quality下進行設定,如下:
  此頁面用來設定遊戲每種畫質的引數,如果SubShader的LOD數值小於該畫質所設定的LOD時,此SubShader將會不可用。
  • 接下來是Shader中的主體部分
                CGPROGRAM		
		#pragma surface surf Lambert		
		#pragma target 3.0
		sampler2D _MainTex;
		struct Input {
			float2 uv_MainTex;
		};
		half _Glossiness;
		half _Metallic;
		fixed4 _Color;
		void surf (Input IN, inout SurfaceOutputStandard o) {
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			// Metallic and smoothness come from slider variables
			o.Metallic = _Metallic;
			o.Smoothness = _Glossiness;
			o.Alpha = c.a;
		}
		ENDCG
       首先,CGPROGRAM表示開始位,與結束位ENDCG相對應,表明這之中是子著色器編譯命令,所有的輸入輸出操作在其中進行。之後是一句#pragma surface指令,它的語法是:          #pragma surface surfaceFunction lightModel[optionalparams]        其中surfaceFunciton表示指定的Cg函式裡有surface shader的函式或者相關程式碼,比如void surf(Input IN,inout SurfaceOutputStandard)。lightModel表示表面著色器所使用的光照模型,一般有兩種:Lamert(defuse)和BlinnPhong(specular)兩種,大多數情況下會用Lamert,即漫反射光照模式。    
#paragma target 3.0//<span style="font-family: Arial, Helvetica, sans-serif;">是Unity5.X版本新加入的指令,用來處理某些2.0版本的Surface shader指令集限制。</span>
                struct Input {
			float2 uv_MainTex;
		};
之後是對_MainTex的宣告,這裡要注意,雖然我們已經在Properties中對_MainTex進行宣告,但是這個_MainTex是在CG程式碼塊中,它和Properties分屬於兩個部分。因此如果我們想在CG中使用Properties中預定義的變數,需要重新進行宣告。還有一點需要重新說明,在Properties中_MainTex的型別是2D型別,來CG塊中它變為了2D紋理貼圖型別,即sampler2D。       宣告完_MainTex之後,緊接著是一個結構體,我們在結構體中聲明瞭一個叫做uv_MainTex的變數,它的型別是Float2,即一個二維float型別。它的作用是將一個2D貼圖上的點按照一定的規則對映到3D模型上,從而對3D模型進行渲染處理。通過宣告uv_MainTex變數,我們就可以在之後的surf函式中通過訪問uv_MainTex來獲得2D貼圖上的需要計算的點(float2)。
                half _Glossiness;
		half _Metallic;
		fixed4 _Color;
接下來,就像之前對_MainTex進行宣告一樣,我們還需對輸入的_Glossiness變數、_Metallic變數和_Color變數進行宣告。其中half型別是Cg語言中對Float/Double等浮點型數字處理的一種方式,它是半精度浮點數,執行效率很快,適合用於大規模的平行計算。
                void surf (Input IN, inout SurfaceOutputStandard o) {
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			// Metallic and smoothness come from slider variables
			o.Metallic = _Metallic;
			o.Smoothness = _Glossiness;
			o.Alpha = c.a;
		}
    現在到了最後的關鍵函式surf,在surf裡首先對_MainTex進行取樣,將它和_Color疊加後進行輸出。這裡的tex2D函式是Cg中用於2D紋理取樣,它的函式原型很多:
float4 tex2D(sampler2D samp, float2 s)
float4 tex2D(sampler2D samp, float2 s, inttexelOff)
float4 tex2D(sampler2D samp, float3 s)
float4 tex2D(sampler2D samp, float3 s, inttexelOff)
float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy)
float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy, int texelOff)
float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy)
float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy, int texelOff)
int4 tex2D(isampler2D samp, float2 s)
int4 tex2D(isampler2D samp, float2 s, inttexelOff)
int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy)
int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy, int texelOff)
unsigned int4 tex2D(usampler2D samp, float2s)
unsigned int4 tex2D(usampler2D samp, float2s, int texelOff)
unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy)
unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy,int texelOff)
      tex2D函式的返回值是取樣後的紋理,這樣就能得到最終的著色效果。       最後感謝上善大神的文章~~~點這裡,給了我許多幫助。   ------------------------------------------------------------------------------------------------------------------------------------------------------------        今晚就先寫這麼多,以後想到在寫.......困死了。。我要睡覺~~~