1. 程式人生 > >unity3d shader 學習筆記1

unity3d shader 學習筆記1

  在unity中我們經常會使用shader,但是從來沒有深究過,最近在做專案時遇到相關問題,無從下手,決定系統學習一番,在此前提下把我學習的過程做一個記錄。學習過程中參考了淺墨和風宇衝兩位高人的部落格,本文有不對的地方,還望指正。

按照學習技術的習慣,在第一次接觸shader時,我們首先會想這兩個個問題,

問題一:什麼是shader,它能做什麼?

問題二:unity中shader使用的語言是什麼?

問題一:shader,也就是著色器,它本質就是一段程式,這段程式的作用是把Mesh(網格)以指定的方式以及指向的貼圖或顏色等處理後,然後通過繪製把處理後的影象顯示在螢幕上。所以簡而言之:

什麼是shader,shader就是一段處理顏色,貼圖紋理並且進行計算和變換到渲染器的程式。

unity中shader分兩類:Surface Shader(表面著色器)Vertex Shader &Fragment Shader(頂點著色器&片段著色器)以及Fixed Function Shader(固定功能著色器)下面會講如何在區分。

shader能做什麼呢,比如光影特效,顏色變換等。暫時只想到這些

問題二:unity中的shader使用的是一種shaderLab的語言編寫的。它的語法風格類似NVIDIA的CgFX和Direct3D,它具備了顯示材質(Material)所需要的一切資訊,在unity的shader程式碼中我們可以使用CG語言寫CG程式,所謂CG語言就是C for Graphics,說到一種語言我們就介紹下的他的資料型別,CG語言有6中資料型別,分別是 

1.    float,32位浮點資料,一個符號位。浮點資料型別被所有的profile支援(但是DirectX8 pixel profiles在一些操作中降低了浮點數的精度和範圍);

2.    half,16為浮點資料;

3.    int,32位整形資料,有些profile會將int型別作為float型別使用;

4.    fixed,12位定點數,被所有的fragment profiles所支援;

5.    bool,布林資料,通常用於if和條件操作符(?:),布林資料型別被所有的profiles支援;

6.    sampler*,紋理物件的控制代碼(the handle to a texture object),分為6類:sampler, sampler1D, sampler2D, sampler3D, samplerCUBE,和samplerRECT。DirectX profiles不支援samplerRECT型別,除此之外這些型別被所有的pixel profiles和    NV40 vertex program profile所支援(CgUsersManual 30頁)。由此可見,在不遠的未來,頂點程式也將廣泛支援紋理操作;

除了上面的基本資料型別外,Cg還提供了內建的向量資料型別(built-in vector data types),內建的向量資料型別基於基礎資料型別。例如:float4,表示float型別的4元向量;bool4,表示bool型別4元向量。

    注意:向量最長不能超過4元,即在Cg程式中可以宣告float1、float2、float3、float4型別的陣列變數,但是不能宣告超過4元的向量,例如:

float5 array ;// 編譯報錯

向量初始化方式一般為:

float4 array = float4(1.0, 2.0, 3.0, 4.0);

較長的向量還可以通過較短的向量進行構建:

float2 a = float2(1.0, 1.0);

float4 b = float4(a, 0.0, 0.0);

此外,Cg還提供矩陣資料型別,不過最大的維數不能超過4*4階。例如:

float1x1 matrix1; // 等價於 float matirx1; x 是字元,並不是乘號!

float2x3 matrix2; // 表示2*3 階矩陣,包含6 個 float 型別資料

float4x2 matrix3;// 表示 4*2 階矩陣,包含 8 個 float 型別資料

float4x4 matrix4 ;// 表示4*4 階矩陣,這是最大的維數

矩陣的初始化方式為:

float2x3 matrix5 = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};

CG語言中也有陣列資料型別,它在Cg程式中的作用是:作為函式的形參,用於大量資料的轉遞。

Cg中宣告陣列變數的方式和C語言類似:例如:

float a[10]; // 聲明瞭一個數組,包含10 個 float 型別資料

float4 b[10]; // 聲明瞭一個數組,包含10 個 float4 型別向量資料

    注意:Cg中向量、矩陣與陣列是完全不同,向量和矩陣是內建的資料型別(矩陣基於向量),而陣列則是一種資料結構,不是內建資料型別!這一點和C\C++ 中不太一樣,在C\C++中,這三者同屬於資料結構,陣列可以構建向量和矩陣。下一節中將詳細闡述Cg中的陣列型別。

下面我們來看一段unity的shader程式碼。開啟unity,Assets->Create->Shader,會在Assets路徑下建立了一個shader指令碼,可以用文字編輯工具開啟,我使用的是一個VS的shader外掛,可以顯示語法高亮。程式碼如下:

Shader "Custom/NewShader" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Lambert

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	} 
	FallBack "Diffuse"
}

我們先來看看這段程式碼的結構,它大體分四塊Shader “Custom/NewShader” ,Properties和SubShader以及FallBack “Diffuse”。
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-size:14px;"></span></span>

簡單的可以用如下的形式來概括:

Shader "name" { [Properties] SubShaders[Fallback] }

用圖來說


Shader “Custom/NewShader” :它定義了我們自定義的這個shader的目錄結構,Custom下名字叫做NewShader。目錄和它的名字我們可以隨意修改 ,並且不必和檔名相同。    “/”用來構建子選單,作用便於管理,如Custom/canglang/NewShader那麼在 Inspector  檢視視窗就會顯示成這樣

                                                                                  

Properties:unity聖典的解釋是:

Shaders can define a list of parameters to be set by artists in Unity’s material inspector. The Properties block in the shader file definesthem.我簡單理解為屬性定義並且屬性會將display name顯示在材質檢視器中,顯示GUI元素,包括紋理,顏色,滑動條,float值等,能夠很便利的讓我們進行修改

語法結構為:Properties { Property [Property ...] } 

定義屬性塊,其中可包含多個屬性,其定義如下:

name ("display name", type) =Defaultvalue

type的型別有7種:Range(min,max),Color ,2D,Rect,Cube,Float,Vector,每一種的具體含義及定義如下:

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

定義浮點數屬性,在檢視器中可通過一個標註最大最小值的滑條來修改。

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

定義顏色屬性

name ("display name", 2D) = "name" {options }

定義2D紋理屬性

name ("display name", Rect) = "name"{ options }

定義長方形(非2次方)紋理屬性

name ("display name", Cube) = "name"{ options }

定義立方貼圖紋理屬性

name ("display name", Float) = number

定義浮點數屬性

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

定義一個四元素的容器(相當於Vector4)屬性

例如_MyColor("My Color", Color) =(1,1,1,0)為例,

_MyColor ------Internal name(內部名稱)

My Color-------Inspector title(Inspector視窗顯示的名稱)

Color-----------Property type(屬性型別)

(1,1,1,0)--------Default value(預設值)

注意:

1、Properties塊內的語法都是單行的。每個屬性都是由內部名稱開始,後面括號中是顯示在檢視面板(Inspector)中的名字和該屬性的型別。等號後邊跟的是預設值。

2、對於Range和Float型別的屬性只能是單精度值。

3、對於Color和Vector型別的屬性將包含4個由括號圍住的數描述。

4、對於紋理(2D, Rect, Cube) 預設值既可以是一個空字串也可以是某個內建的預設紋理:"white", "black", "gray" or"bump"

SubShader:子著色器,是shader程式碼的主體,一個shader可以包含一個或多個子著色器,每個子著色器可以包含一個或多個pass,但是在執行時具體能不能執行或者限制性哪個子著色器完全由執行平臺決定。因為機子不同,執行的顯示卡支援環境也不同。所以多個子著色器的作用就在於此,例如對於一個效果,預想想好ABC多套方案,A在牛逼機子上能執行,B在一般機子上能執行,C在爛機子上執行。全都不行了還有FallBack呢。而且在執行時系統也會把子著色器中程式碼分成多個合適的pass。

具體結構如下:

SubShader

{

pass

{

}

}

FallBack “Diffuse”:回滾,也稱備胎,作用是處理所有SubShader都不能執行的情況,以防萬一,留個備胎,不然顯示不了咋辦呢。這裡設的備胎是Diffuse(漫反射)。

注意:SubShader在Shader程式碼中必須有,而properties和fallback是可以不寫的

下面來看下我們新建的NewShader中SubShader的程式碼

Tags { "RenderType"="Opaque" }:Tags是標籤,SubShader可以寫多個Tags(標籤)來標定這個SubShader,當然也可以不寫,硬體平臺通過這些標籤來決定什麼時候呼叫這個SubShader進行渲染,在我們新建的這個shader中使用的是unity預設的"RenderType" = "Opaque",它的含義是告訴硬體平臺在渲染非透明物體時呼叫該SubShader,與之對應的,渲染含有透明效果的物體時是"RenderType" = "Transparent",我在這列舉我們經常會使用的幾個標籤如下:

"RenderType" = "Opaque" 渲染非透明物體

"RenderType" = "Transparent" 渲染含有透明效果的物體

"IgnoreProjector"="True" 不被Projectors影 響

"ForceNoShadowCasting"="True" 從不產生陰影

"Queue"="xxx"指定渲染順序佇列

我們要著重講下這個Queue,Queue制定了該物體的渲染順序,所以尤其是在透明與不透明物體混合時我們要注意,不然可能顯示不出來。

預訂的Queue有如下幾個:

Background最先被呼叫的渲染,一般用來渲染天空和或者背景

Geometry預設值,用來渲染非透明物體(普通情況下,場景中絕大多數物體應該是非透明的)

AlphaTest用來薰染經過Alpha Test的畫素,單獨為AlphaTest設定一個Queue是出於對效率的考慮

Transparent以從後往前的順序渲染透明物體

Overlay用來渲染疊加的效果,是渲染的最後階段(比如鏡頭光暈等特效)

這 些預定義的值本質上是一組定義整數,Background = 1000, Geometry = 2000, AlphaTest = 2450, Transparent = 3000,最後Overlay = 4000。在我們實際設定Queue值時,不僅能使用上面的幾個預定義值,我們也可以指定自己的Queue值,寫成類似這 樣:"Queue"="Transparent+100",表示一個在Transparent之後100的Queue上進行呼叫。通過調整Queue值, 我們可以確保某些物體一定在另一些物體之前或者之後渲染,這個技巧有時候很有用處。

LOD 200:LOD(Level of Detail),他的值決定我們寫的Shader在unity設定的品質環境下能不能用,在Unity的Quality Settings中我們可以設定允許的最大LOD,當設定的LOD小於SubShader所指定的LOD時,這個SubShader將不可用。Unity 內建Shader定義了一組LOD的數值,我們在實現自己的Shader的時候可以將其作為參考來設定自己的LOD數值,這樣在之後調整根據裝置圖形效能 來調整畫質時可以進行比較精確的控制。

  • VertexLit及其系列 = 100
  • Decal, Reflective VertexLit = 150
  • Diffuse = 200
  • Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
  • Bumped, Specular = 300
  • Bumped Specular = 400
  • Parallax = 500
  • Parallax Specular = 600
CGPROGRAM標記語句,標識從這裡開始是一頓CG程式(我們在寫Unity的Shader時用的是Cg/HLSL語言)。最後一行的ENDCG與CGPROGRAM是對應的,表明CG程式到此結束。 #pragma surface surf Lambert這是一條編譯指令,它聲明瞭我們寫的這個shader的型別,以及光照模型。他的語法如下: #pragma surface surfaceFunction lightModel [optionalparams]
surface - 宣告該Shader是一個Surface Shader(表面著色器)

surfaceFunction - shader程式碼方法的名字

lightModel - 使用的光照模型

所以例子中我們這句指令的意義就是:宣告一個表面著色器(Surface Shader),實際的程式碼在surf函式,使用Lambert(普通的diffuse漫反射)為光照模型。

sampler2D _MainTex解釋sampler2D之前,我們先說下_MainTex,我們會發現在前面的Properties模組已經有了一個_MainTex的貼圖屬性宣告。這裡為什麼還要宣告它,原因是CGPROGRAM...ENDCG定義的程式碼塊是一段CG程式,而shader的其他部分使用shaderLab寫的,ShaderLab是unity可以直接使用和編譯的,CG程式不能,所以必須使用和之前變數相同的名字再次進行宣告,這樣就能連結到Properties的變數,使得我們的CG程式碼能夠使用這個變數。再來說這個sampler2D是一個2D貼圖的型別,從他可以獲得2d貼圖的畫素與座標的對應資料

struct Input首先它是一個結構體,名字叫做Input,這個結構體名字是CG規定的,我們不能改變,它是作為下面suf函式引數需要傳入進去的,但是他的內容可改,這就給了我們發揮的空間,我們可以自己把需要參與計算的資料放進這個結構體,例子中的結構體定義了定義了一個uv_MainTex的變數,型別是float2,CG中經常有float2,float3,float4的型別,它其實就是把2個或者3個或者4個float型別打包在一起。但是這個變數有些特別,我們在前面已經定義了一個_MainTex的2D貼圖變數,在這我們的uv_MainTex只是比它多了一個uv_字首,感覺這麼寫有什麼含義似的,沒錯!就是有特別意義的,在CG程式中,有個很特別的做法,就是在一個貼圖變數之前加上一個uv_字首,就代表提取它的uv值,那麼我們就會問什麼是uv值,所謂uv值就是值貼圖紋理的二維座標,在這個例子中我們就能去取前面定義的貼圖_MainTex的座標值來參與計算了。

void surf (Input IN, inout SurfaceOutput o) :這是一個函式,也是這段CG程式碼的核心處理函式,和C語言的語法一樣,我們說過CG語言就是C for graphics,是一種偏C的語言,所以他的含義很好理解,但是注意的是CG語言規定了這個函式引數的型別和名字,我們沒法改,只能按照規定寫,返回型別為空,兩個引數一個是 IN型別是我們剛定義的Input結構體,一個是inout的SurfaceOutput結構體,Input我們剛剛已經解釋過了,SurfaceOutput是已經定義好了內部型別的輸出結構,接下來我們來解釋例子中這個函式的函式體的具體做的事情,half4 c = tex2D (_MainTex, IN.uv_MainTex);tex2D函式,是CG程式中用來在一張貼圖中對一個點進行取樣的方法,返回一個half4(half是CG語言中的半精度型別我們可以就理解為float,精度較低,效率高),他有兩個引數,引數一是一張貼圖變數,引數二是該貼圖變數的uv資訊。o.Albedo = c.rgb; o.Alpha = c.a;這兩句把取得采樣資訊,賦值給SurfaceOutput輸出結構,這樣shader就按照貼圖上的uv資訊,直接使用rgba資訊來進行著色,於是就在螢幕中顯示出來了。 

ok,通過例子中的shader程式碼,我們把shader的整個基礎結構作了一個簡單的介紹。明天我們繼續....