Shader的簡介和基礎
什么是Shader
Shader中文名為著色器,可以從兩個不同的方面來解釋
1:Shader是代碼實現的光照在物體表面微觀層面的模擬,使得最終的成像看起來比較逼真
2:在GPU上面運行的代碼
用Shader來模擬光照
光照Shader模擬光線對不同表面的作用,要么是從物理的層面來模擬光線,要么就是通過不斷地試錯來從美術的層面上達到相對真實的畫面。
在現實生活中,不同材質的表面(木頭、金屬、人的皮膚等)對于光照的反應都不一樣。不同的表面的微觀層面則是原子構成的,光具有波粒二象性,其既是粒子(Particles)也是波(Wave)(這也影響著圖形學中光線追蹤算法與光子圖的算法)。光、表面和觀察的眼睛之間的相互作用決定著物體的表面看起來是怎樣的。當光線(為了簡化模型,這里假設光是光線)從光源發出,照射到物體的表面,根據物體表面不同的性質其吸收、反射、折射或散射這條光線不同,物體看起來也不同。如下圖所示,則是光照模型最簡單的示意圖。
現實中,即使一個物體的表面看起來挺光滑的,但是其微觀層面上看起來可能是粗糙的。在計算機圖形學中,我們沒有足夠的計算力去模擬如此微觀的層面(原子級)。在很多渲染器中,通常采用面來表示三維模型,也就是由頂點(Vertex)構成三角面(Triangle),然后再由諸多三角面組合稱為一個三維模型。一個三維場景通常由模型、貼圖和著色器組成,然后再渲染成一個二維的由像素組成的圖像。在渲染的過程中則是將這些頂點的位置映射為最終輸出圖像的二維的位置,同時將貼圖賦予相應的面上并且執行每個三維模型的頂點對應的著色器。
Shader實質上就是在GPU上運行的代碼,著色是一個單向的數據流處理(數據從一端進去,經過處理從另一端出來)。也就是說,輸入的是頂點數組(Vertices)、貼圖(Textures)和著色器(Shaders),出來的是顏色作為一個像素,最后再放到一個2D的圖像中。
Shader執行的流程
那么Shader到底是怎樣執行的呢?其流程又是如何?
1、假設一個只有一個立方體的場景,一共有8個頂點數據。那么第一步就是將這8個頂點數據傳入頂點著色器(Vertex Shader)
2、一個頂點著色器對每個頂點執行一遍著色操作。
3、對每個頂點來說,頂點著色器得到一個輸出的數據結構(data structure),這個數據結構包含最終圖像中的頂點的諸如位置、顏色的數據。
4、頂點序列被裝配成圖元(Primitive Assembly),像是點、線段和三角面。
5、光柵器(Rasterizer)以一個圖元作為輸入,并將其轉換為一串像素。對三角面中每個可能的像素進行插值操作,并傳給像素著色器。舉個例子,插值就是說如果有2個頂點,一個是紅色、一個是綠色,那么光柵器就會自動將兩個頂點中的顏色輸出為紅到綠之間的過度。光柵器是GPU上的一部分,我們沒法對其進行編程。
6、片元著色器(Rragment Shader)運行在每個可能的像素上。大部分的光照計算都是在這個階段進行的。
7、如果是前向渲染(Forward Render),對于除了第一個以外的每個光源片元著色器還需要使用這個光源的信息再運行一遍。
8、對于每個可能的像素(也叫片元)將會檢查是否附近還有其他的離相機更近的片元,如果有,那么當前的片元則會被拒絕。
9、所有的片元著色器的光照通道被混合在一起。
10、所有的像素顏色被寫入輸出圖像中。
上述Shader的執行流程其實對應著渲染管線(Render Pipeline)的一些步驟,但是我們不會在現在講解渲染管線,因為有其他的更加急需了解的知識。
Shader的種類
根據上面講到的Shader的流程中,我們可以發現Shader有以下幾類:
1、頂點著色器:在每個頂點上執行的著色器
2、片元著色器:在每個最終圖像中可能出現的像素上的著色器
當然,在Unity中還有如下的一些著色器
3、Unlit著色器:將Vertex和Pixel著色器放在一個文件內
4、Surface著色器:包含Vertex和Fragment著色器的功能。
5、Image-effect著色器:實現屏幕特效,諸如抗鋸齒、環境光遮蔽、模糊、溢光等...
6、Compute著色器:進行一些計算的著色器,實現諸如物理模擬、圖像處理、光線追蹤等。
坐標系統
每個著色器中的計算都是在特定的坐標系統下面進行的。Shader中的坐標系統主要有如下幾個:
1、本地空間(Local Space)或物體空間(Object Space):指的是帶渲染模型的相對坐標系。
2、世界空間(World Space):指的是整個帶渲染場景的坐標系。
3、視圖空間(View Space)或Eye Space:從觀察者(相機)的視角來看的相應的坐標系。
4、剪裁空間(Clip Space):范圍從-1.0到1.0
5、屏幕空間(Screen Space):二維的坐標系統
6、切線空間(Tangent Space):用來計算法線貼圖
在著色器中,經常性地需要從一個坐標空間轉換到另一個坐標空間,選擇合適的坐標空間將會減少這些轉換操作,從而使得計算更加方便,渲染更加快速。
光源類型
Unity中有以下幾種光源類型:
1、點光源(Point):一個點向周圍相等地發射光,強度隨距離衰減
2、方向光源(Directionnal):發出的每條光線都是平行的,沒有強度衰減
3、區域光源(Area):只能在烘焙的時候使用,光從一個區域內發出。
渲染公式(Rendering Equation)
在圖形學中的渲染公式看起來是這樣子的:
渲染公式用來定義光在物體表面的行為,為了理解光照公式,我們先來學習一下光的行為。
光線的行為
一束光照射到一個物體的表面上之后,一部分光會從物體的表面反射出去。對于渲染來說,我們感興趣的是到底反射了多少光,和光具體的反射方向是哪里。這都是由很多因素決定的。
1、首先是入射角,入射角越平行于物體表面,反射的光就越少。如上圖,顏色深的那條光線反射的光照就要更小一些。
2、然后便是物體表面的顏色,一個黑色的表面將會吸收所有的入射光,而綠色的表面將會吸收所有入射光中除了綠色以外的所有光線,只反射綠色的部分。
3、物體表面的光滑度(Smoothness)或是粗糙度(Roughness)。物體的微觀層面看起來比他的宏觀層面看起來可能要粗糙很多。微觀層面上,光線可能會反射到很多其他的地方。
4、半透明的層,試著想象一個小水坑,水是透明的,水坑底面的泥土是不透明的。那么光線在照射到水面時會反射一部分光,另一部分會繼續照射下去(不考慮折射)并且亮度會降低,這一部分的光線照射到泥土之后繼續反射出來。
間接光照(Bounced Light)
在一個場景中,一個照射到物體的光線會反射出去,如果這束光線仍然存在能量那么他會在遇到其他物體表面的時候會繼續反射。直接照射到物體的光線叫做直接照明(Direct Light),反射出來的光線叫做間接光照(Indirect Light),這也是全局照明(Global Illumination)需要做的地方。
因為全局照明會消耗大量地計算資源,因此2010年之前的游戲開發中通常采用環境光(Ambient light)來補償直接照明的黑暗?;蚴鞘褂们蛑C光照來補償全局照明。
渲染類型
Unity中的渲染類型又如下幾種
1、前向渲染(Forward):在前向渲染中,場景的信息輸入到渲染器中,每個三角面都被光柵化,對應每個光源都有一個著色通道。
2、延遲渲染(Deferred):將光照/渲染計算推遲到第二步進行計算。我們這樣做的目的是為了避免多次(超過1次)渲染同一個像素。
結語
在這部分,我們講解了Shader的基本概念包括著色器是什么,著色器是怎樣工作的,同時也簡單介紹了幾種不同的坐標空間,最后講了光照的類型和渲染的類型。
在下一節中,我們將會講解Unity中Shader開發的步驟,并編寫一個簡單的Shader程序。