第一個(gè)shader
1.前言
什么是shader?著色器(Shader)是用來(lái)實(shí)現(xiàn)圖像渲染的用來(lái)替代固定渲染管線(xiàn)的可編輯程序。在具體學(xué)習(xí)shader之前,我們有必要了解渲染流水線(xiàn)的概念.
很多計(jì)算機(jī)圖形學(xué)的書(shū)籍都把渲染管線(xiàn)分為三個(gè)階段:應(yīng)用程序階段、幾何階段、光柵化階段。

2.應(yīng)用階段(CPU處理)
想象一下一個(gè)模型從場(chǎng)景渲染到屏幕的過(guò)程,這其中由CPU和GPU之間相互共同合作完成的,在第一階段,由CPU開(kāi)始的,在這一階段中開(kāi)發(fā)都有3個(gè)主要的任務(wù):首先,需要準(zhǔn)備好場(chǎng)景數(shù)據(jù)(攝相機(jī)位置、視錐體、模型和光源等)接著,還需要做粗粒度的剔除工作最后,需要設(shè)置好每個(gè)模型的渲染狀態(tài)(使用的材質(zhì)、使用的紋理、使用的Shader等).
在該階段的末端將產(chǎn)生幾何體數(shù)據(jù),包括頂點(diǎn)坐標(biāo)、法向量、紋理坐標(biāo)、紋理等,通過(guò)數(shù)據(jù)總線(xiàn)傳送到圖形硬件以供渲染(時(shí)間瓶頸),進(jìn)行幾何階段。
在所有數(shù)據(jù)準(zhǔn)備完成之后,CPU就會(huì)調(diào)用一個(gè)渲染命令來(lái)告訴GPU進(jìn)行渲染,這個(gè)命令就是我們熟悉的DrawCall.
3.幾何階段(GPU)

數(shù)據(jù)由頂點(diǎn)轉(zhuǎn)換到屏幕圖像的一系列過(guò)程
1)頂點(diǎn)著色器
頂點(diǎn)著色器的處理單位是頂點(diǎn),每一個(gè)頂點(diǎn)都會(huì)調(diào)用一次頂點(diǎn)著色器,需要注意的是,我們無(wú)法在頂點(diǎn)著色器中創(chuàng)建或銷(xiāo)毀任何頂點(diǎn),也無(wú)法得到頂點(diǎn)和頂點(diǎn)之間的關(guān)系.
頂點(diǎn)著色器主要工作可以概括為“變換三維頂點(diǎn)坐標(biāo)”和“光照計(jì)算”。我們出入到計(jì)算機(jī)中的是一系列三維坐標(biāo)點(diǎn),但我們最終看到的從視點(diǎn)出發(fā)觀(guān)察到的特定點(diǎn)。我們電腦顯示器是二維的,GPU所需要做的,就是把三維頂點(diǎn)數(shù)據(jù)經(jīng)過(guò)轉(zhuǎn)換繪制到二維屏幕上,并讓二維畫(huà)面看起來(lái)有3D效果。頂點(diǎn)的變換涉及一系列的坐標(biāo)系統(tǒng),頂點(diǎn)變換過(guò)程,就是通過(guò)各個(gè)變化矩陣,把一個(gè)坐標(biāo)系統(tǒng)下的頂點(diǎn)信息,變化到另外一個(gè)坐標(biāo)系統(tǒng)上,從而實(shí)現(xiàn)3D的頂點(diǎn)數(shù)據(jù)最終可以在2D屏幕上進(jìn)行顯示。
一個(gè)最基本的頂點(diǎn)著色器的功能是:把頂點(diǎn)從模型空間轉(zhuǎn)換到齊次裁剪空間 , 接著由硬件進(jìn)行透視除法,得到歸一化的設(shè)備坐標(biāo)(Normalized Device Coordinates NDC).
OpenGL 的z值是[-1.1] DirectX的z值是[0,1]
2)裁剪(Clipping)
裁剪掉不在攝像機(jī)視野內(nèi)的區(qū)域圖元與攝像機(jī)有三種關(guān)系:完全在視野內(nèi),部分在視野內(nèi),完全不在視野。完全在視野內(nèi)的交由傳遞給下一個(gè)流水階段,完全不在視野的不處理。部分在視野內(nèi),要有Clipping處理。這一步不可編程;

3)屏幕映射(Screen Mapping)
把每一個(gè)圖元的坐標(biāo)轉(zhuǎn)換到屏幕坐標(biāo)空間.需要注意的是OpenGL和DirectX的區(qū)別,OpenGl的(0,0)點(diǎn)在左下角,DirectX的坐標(biāo)在左上角.
4.光柵化(GPU階段)
光柵化就是把頂點(diǎn)數(shù)據(jù)轉(zhuǎn)換為片元的過(guò)程。片元中的每一個(gè)元素對(duì)應(yīng)于幀緩沖區(qū)中的一個(gè)像素。
可以叫做柵格化或者像素化。就是把矢量圖形轉(zhuǎn)化成像素點(diǎn)兒的過(guò)程。我們屏幕上顯示的畫(huà)面都是由像素組成,而三維物體都是點(diǎn)線(xiàn)面構(gòu)成的。要讓點(diǎn)線(xiàn)面,變成能在屏幕上顯示的像素,需要柵格化這個(gè)過(guò)程。就是從矢量的點(diǎn)線(xiàn)面的描述,變成像素的描述。

- 三角形設(shè)置
到目前為止我們得到了一堆頂點(diǎn)的數(shù)據(jù),這一步就是根據(jù)這些頂點(diǎn)的原始連接關(guān)系還原出網(wǎng)格結(jié)構(gòu)。網(wǎng)格由頂點(diǎn)和索引組成,這個(gè)階段就是根據(jù)索引將頂點(diǎn)鏈接到一起,組成線(xiàn)、面單元,然后進(jìn)行剪裁,如果一個(gè)三角形超出屏幕以外,例如兩個(gè)頂點(diǎn)在屏幕內(nèi),一個(gè)頂點(diǎn)在屏幕外,這時(shí)我們?cè)谄聊簧峡吹降木褪且粋€(gè)四邊形,然后把這個(gè)四邊形切成兩個(gè)小的三角形。
2)三角形遍歷
在這個(gè)階段,將檢查每個(gè)被三角形覆蓋了中心(或某樣品)的像素,并為和三角形重疊的像素部分生成片段。查找哪些樣品或像素在三角形內(nèi)部這一過(guò)程叫做三角形遍歷或掃描轉(zhuǎn)換。每個(gè)三角形片段的屬性都被使用三角形的三個(gè)頂點(diǎn)間的插值數(shù)據(jù)生成。這些屬性包括片段的深度和來(lái)自幾何形狀階段的著色數(shù)據(jù)。
這一步的輸出就是得到一個(gè)片元序列.需要注意的是,一個(gè)片元不是真正意義上的像素,而是包含了很多狀態(tài)的集合,這些狀態(tài)用于計(jì)算每個(gè)像素的最終顏色.包括屏幕坐標(biāo),深度信息,法線(xiàn),紋理等.
3)片元著色器
片元著色器的輸入數(shù)據(jù)是上一個(gè)階段對(duì)頂點(diǎn)著色器的頂點(diǎn)信息插值得到的結(jié)果,在這一階段,可以完成很多重要的渲染技術(shù).例如紋理采樣,逐像素的光照等.
4)逐片元操作
在DireectX中,這一階段也叫輸出合并階段(OutPut-Merger).這一階段主要要幾個(gè)任務(wù):
1.決定片元的可見(jiàn)性,例如深度測(cè)試,模板測(cè)試等.
2.如果通過(guò)測(cè)試,就需要把這個(gè)片元的顏色值和顏色緩沖區(qū)中的顏色進(jìn)行混合.
注意的是,測(cè)試順序不是固定的,比如說(shuō),很多GPU會(huì)盡可能的把深度測(cè)試放在片元著色器之前進(jìn)行,以節(jié)省大量的片元計(jì)算(Early-Z)
當(dāng)模型的圖元經(jīng)過(guò)了上面的層層計(jì)算和測(cè)試后,就會(huì)顯示到我們的屏幕上.但是,為了我們看到正在光柵化的圖元,GPU會(huì)使用雙重緩存的策略,這意味著,對(duì)場(chǎng)景的渲染是在幕后發(fā)生的.
5.坐標(biāo)空間和坐標(biāo)轉(zhuǎn)換

深色區(qū)域就是頂點(diǎn)坐標(biāo)空間的變換流程.
1)從object space到world space
object space有兩層核心含義,第一,object space中的坐標(biāo)值就是模型文件中的頂點(diǎn)值,這些值是在建立模型時(shí)得到的,例如一個(gè).max文件,里面包含的數(shù)據(jù)就是object space的坐標(biāo)。第二,object space的坐標(biāo)與其他物體沒(méi)有任何參照關(guān)系,這是object space和world space區(qū)分的關(guān)鍵。world space坐標(biāo)的實(shí)際意義就有有一個(gè)坐標(biāo)原點(diǎn),物體跟坐標(biāo)原點(diǎn)相比較才能知道自己的確切位置。例如在unity中,我們將一個(gè)模型導(dǎo)入到場(chǎng)景中以后,它的transform就是世界坐標(biāo)。
將頂點(diǎn)坐標(biāo)轉(zhuǎn)從模型空間轉(zhuǎn)換到世界空間的過(guò)程,叫做模型變換(model transform).
2)從world space到eye space (視角空間(view space),或者叫攝像機(jī)空間(Camera space),觀(guān)察空間)
所謂eye space,就是以攝像機(jī)為原點(diǎn),由視線(xiàn)方向、視角和遠(yuǎn)近平面,共同組成的一個(gè)梯形體,如下圖,稱(chēng)之為視錐(viewing frustum)。近平面,是梯形體較小的矩形面,也是靠近攝像機(jī)的平面,遠(yuǎn)平面就是梯形體較大的矩形,作為投影平面。在這個(gè)梯形體的內(nèi)的數(shù)據(jù)是可見(jiàn)的,超出的部分會(huì)被視點(diǎn)去除,也叫視錐剪裁。
將頂點(diǎn)坐標(biāo)轉(zhuǎn)從世界空間轉(zhuǎn)換到視角空間的過(guò)程,叫做觀(guān)察變換(view transform).

3) 從eye space到project and clip space (裁剪空間(clip space),也叫做齊次裁剪空間)
eye space坐標(biāo)轉(zhuǎn)換到project and clip space坐標(biāo)的過(guò)程其實(shí)就是一個(gè)投影、剪裁、映射的過(guò)程。因?yàn)樵诓灰?guī)則的視錐體內(nèi)剪裁是一件非常困難的事,所以前人們將剪裁安排到一個(gè)單位立方體中進(jìn)行,這個(gè)立方體被稱(chēng)為規(guī)范立方體(CVV),CVV的近平面(對(duì)應(yīng)視錐體的近平面)的x、y坐標(biāo)對(duì)應(yīng)屏幕像素坐標(biāo)(左下角0、0),z代表畫(huà)面像素深度。所以這個(gè)轉(zhuǎn)換過(guò)程事實(shí)上由三步組成:
(1)用透視變換矩陣把頂點(diǎn)從視錐體變換到CVV中;
(2)在CVV內(nèi)進(jìn)行剪裁;
(3)屏幕映射:將經(jīng)過(guò)前兩步得到的坐標(biāo)映射到屏幕坐標(biāo)系上。
將頂點(diǎn)坐標(biāo)轉(zhuǎn)從視角空間轉(zhuǎn)換到裁剪空間的過(guò)程,叫做投影變換(projection transform),注意,這里并不會(huì)進(jìn)行真正的投影,而是為投影做準(zhǔn)備,真正的投影發(fā)生在后面的齊次除法過(guò)程中,也就是從裁剪空間到屏幕空間的過(guò)程.
6.矩陣基本知識(shí)
1)矩陣乘法
一個(gè)R * N的矩陣A和一個(gè)N* C的矩陣B相乘,他們的結(jié)果是一個(gè)R*C的矩陣,第一個(gè)矩陣的列數(shù)必須和第二個(gè)矩陣的行數(shù)相同.
矩陣乘法不滿(mǎn)足交換律?。粒拢。剑拢?br>
矩陣乘法滿(mǎn)足結(jié)合律?。粒拢茫剑粒ǎ拢茫?br>
行和列相等的矩陣叫方陣.
一個(gè)矩陣和它的逆矩陣相乘,結(jié)果是一個(gè)單位矩陣.
矩陣可以表示一個(gè)變化,而逆矩陣可以還原這個(gè)變換
Mul(M,V)==mul(V,tranpose(M));//左乘一個(gè)矩陣,等于右乘這個(gè)矩陣的轉(zhuǎn)置矩陣

https://docs.unity3d.com/Manual/UpgradeGuide54.html
7.Shader的結(jié)構(gòu)
Shader按管線(xiàn)分類(lèi)一般分為固定渲染管線(xiàn)與可編程渲染管線(xiàn)
(1)固定渲染管線(xiàn) ——這是標(biāo)準(zhǔn)的幾何&光照(Transforming&Lighting)管線(xiàn),功能是固定的,它控制著世界、視、投影變換及固定光照控 制和紋理混合。T&L管線(xiàn)可以被渲染狀態(tài)控制,矩陣,光照和采制參數(shù)。功能比較有限。基本所有的顯卡都能正常運(yùn)行。
(2)可編程渲染管線(xiàn)——對(duì)渲染管線(xiàn)中的頂點(diǎn)運(yùn)算和像素運(yùn)算分別進(jìn)行編程處理,而無(wú)須象固定渲染管線(xiàn)那樣套用一些固定函數(shù),取代設(shè)置參數(shù)來(lái)控制管線(xiàn)。
unity3d的三種Shader
(1)Fixed function shader 屬于固定渲染管線(xiàn) Shader, 基本用于高級(jí)Shader在老顯卡無(wú)法顯示時(shí)的Fallback(之后有詳細(xì)介紹)。
(2)Vertex and Fragment Shader 最強(qiáng)大的Shader類(lèi)型,屬于可編程渲染管線(xiàn). 使用的是CG/HLSL語(yǔ)法。
(3)Surface Shader Unity3d推崇的Shader類(lèi)型,使用Unity預(yù)制的光照模型來(lái)進(jìn)行光照運(yùn)算。使用的也是CG/HLSL語(yǔ)法。
standard surface shader包含標(biāo)準(zhǔn)光照模型的表面著色器模板
unlit shader不包含光照但包含霧效的基本頂點(diǎn)/片元著色器
image effect shader實(shí)現(xiàn)各種屏幕后處理效果基本模板
compute shader利用gpu的并行性來(lái)進(jìn)行一些與常規(guī)渲染無(wú)關(guān)的計(jì)算
Pass,F(xiàn)allBack
8.Properties變量定義
https://docs.unity3d.com/Manual/SL-Properties.html
需要注意的是,盡量使用默認(rèn)的名字,例如MainTex,Color,_Alpha等,使用默認(rèn)的變量,就可以在代碼里使用默認(rèn)的變量賦值給shader例如sharedMaterial.mainTexture = texture;
變量例子:
_Float("Float",Float) = 1.5
_Range("Range",Range(0.0,10.0)) = 3.0
_Color ("Color", Color) = (1,1,1,1)
_Vector("Vector",Vector) = (2,3,6,1)
_2D("2D",2D) = ""{}
_Cube("Cube",Cube) = "while"{}
_3D("3D",3D) = "black"{}
9.語(yǔ)義
從應(yīng)用階段傳遞模型數(shù)據(jù)到頂點(diǎn)著色器時(shí)unity支持的常用語(yǔ)義:
float4 vertex : POSITION; //頂點(diǎn)坐標(biāo)
float4 tangent : TANGENT; // tangent,三角函數(shù)的一種,縮寫(xiě)為tan我們很熟悉了,他的值是mesh到表面法線(xiàn)的正切值
float3 normal : NORMAL; //表面法向量,以對(duì)象的坐標(biāo)系標(biāo)準(zhǔn)化至單位長(zhǎng)度
float4 texcoord : TEXCOORD0;//紋理坐標(biāo)系的第0個(gè)集合
float4 texcoord1 : TEXCOORD1; //紋理坐標(biāo)系的第1個(gè)集合
fixed4 color : COLOR;//顏色,通常為常數(shù)
Unity內(nèi)建的預(yù)定義輸入結(jié)構(gòu)體:
只要引用UnityCg.cginc頭文件(目錄Unity > Editor > Data > CGIncludes下)就可以使用預(yù)先設(shè)定好的結(jié)構(gòu)體直接使用,他們分別有appdata_base appdata_tan和appdata_full:
struct appdata_full {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
#if defined(SHADER_API_XBOX360)
half4 texcoord4 : TEXCOORD4;
half4 texcoord5 : TEXCOORD5;
#endif
fixed4 color : COLOR;
};
文章大量使用了網(wǎng)絡(luò)資源,例如candyCat的書(shū)<Unity shader 入門(mén)精要>,推薦新入門(mén)的人買(mǎi)來(lái)使用,有源碼項(xiàng)目可以學(xué)習(xí)