圖形學的入門課--shader

引言 
shader到底是幹什麼用的?shader的工作原理是什麼? 
其實當我們對這個問題還很懵懂的時候,就已經開始急不可耐的要四處搜尋有關shader的資料,恨不得立刻上手寫一個出來。但看了一些資料甚至看了不少cg的語法之後,我們還是很迷茫,UNITY_MATRIX_MVP到底是個什麼矩陣?它和v.vertex相乘出來的又是什麼玩意?當這些問題困擾我們很久之後,我們才發現,原來我們是站在浮沙上築高臺,根基都沒有打牢當然不可能蓋得起高樓大廈了。
那根基是什麼呢?大牛曰,計算機圖形學。 
shader中文名叫着色器,顧名思義,它的作用可以先簡單理解爲給屏幕上的物體畫上顏色。而什麼東西負責給屏幕上畫顏色?當然是GPU,所以我們寫shader的目的就是告訴GPU往屏幕哪裏畫、怎麼畫。說到這其實大家應該很明白了,如果我們連GPU的工作原理都不知道,何談指揮它?
說到計算機圖形學,包括我在內很多同學都非常害怕它,因爲裏面包含了各種艱深的理論、變換,大量的公式什麼的。其實我們大可不必一開始就嚇倒自己,先從基本概念開始,慢慢來,總有一天我們也會成爲大牛~!
最後,這篇文章不算是原創,最多算是摘要+讀後感,很多概念性文字都是我從書裏搬過來後再加上自己的理解,算是和大家一起學習,有理解不當之處還請多多指教。 
廢話不多說,讓我們來進入第一章的學習,GPU的渲染管線。 


正文 
所謂GPU的渲染管線,聽起來好像很高深的樣子,其實我們可以把它理解爲一個流程,就是我們告訴GPU一堆數據,最後得出來一副二維圖像,而這些數據就包括了”視點、三維物體、光源、照明模型、紋理”等元素。
在各種圖形學的書中,渲染管線主要分爲三個階段:應用程序階段、幾何階段、光柵階段。 

1,應用程序階段。 
這個階段相對比較好理解,就比如我們在Unity裏開發了一個遊戲,其實很多底層的東西Unity都幫我們實現好了,例如碰撞檢測、視錐剪裁等等,這個階段主要是和CPU、內存打交道,在把該計算的都計算完以後,在這個階段的末端,這些計算好的數據(頂點座標、法向量、紋理座標、紋理)就會通過數據總線傳給圖形硬件,作爲我們進一步處理的源數據。

2,幾何階段。 
主要負責頂點座標變換、光照、裁剪、投影以及屏幕映射,改階段基於GPU進行運算,在該階段的末端得到了經過變換和投影之後的頂點座標、顏色、以及紋理座標。簡而言之,幾何階段的主要工作就是“變換三維頂點座標”和“光照計算”。
問題隨之而來,爲什麼要變換頂點座標?我是這麼理解的,比如你有一個三維遊戲場景,場景中的每個模型都可以用一個向量來確定它的位置,但如何讓計算機根據這些座標把模型正確的、有層次的畫在屏幕上?這就是我們需要變換三維頂點座標的原因,最終目的就是讓GPU可以將這些三維數據繪製到二維屏幕上。
根據頂點座標變換的先後順序,主要有如下幾個座標空間:Object space,模型座標空間;World space,世界座標空間;Eye space,觀察座標空間;Clip and Project space,屏幕座標空間。下圖就是GPU的整個處理流程,深色區域就是頂點座標空間的變換流程,大家瞭解一下即可,我們需要關注的是每個座標空間的具體含義和座標空間之間轉換的方法。

圖片:image1.png



2.1,從object space到world space 
object space有兩層核心含義,第一,object space中的座標值就是模型文件中的頂點值,這些值是在建立模型時得到的,例如一個.max文件,裏面包含的數據就是object space的座標。第二,object space的座標與其他物體沒有任何參照關係,這是object space和world space區分的關鍵。world space座標的實際意義就有有一個座標原點,物體跟座標原點相比較才能知道自己的確切位置。例如在unity中,我們將一個模型導入到場景中以後,它的transform就是世界座標。

2.2,從world space到eye space 
所謂eye space,就是以攝像機爲原點,由視線方向、視角和遠近平面,共同組成的一個梯形體,如下圖,稱之爲視錐(viewing frustum)。近平面,是梯形體較小的矩形面,也是靠近攝像機的平面,遠平面就是梯形體較大的矩形,作爲投影平面。在這個梯形體的內的數據是可見的,超出的部分會被視點去除,也叫視錐剪裁。
例如在遊戲中的漫遊功能,屏幕的內容隨攝像機的移動而變化,這是因爲GPU將物體的頂點座標從world space轉換到了eye space。 

圖片:image2.png


2.3,從eye space到project and clip space 
eye space座標轉換到project and clip space座標的過程其實就是一個投影、剪裁、映射的過程。因爲在不規則的視錐體內剪裁是一件非常困難的事,所以前人們將剪裁安排到一個單位立方體中進行,這個立方體被稱爲規範立方體(CCV),CVV的近平面(對應視錐體的近平面)的x、y座標對應屏幕像素座標(左下角0、0),z代表畫面像素深度。所以這個轉換過程事實上由三步組成:
(1),用透視變換矩陣把頂點從視錐體變換到CVV中; 
(2),在CVV內進行剪裁; 
(3),屏幕映射:將經過前兩步得到的座標映射到屏幕座標系上。 

2.4,primitive assembly(圖元裝配)和triangle setup(三角形處理) 
到目前爲止我們得到了一堆頂點的數據,這一步就是根據這些頂點的原始連接關係還原出網格結構。網格由頂點和索引組成,這個階段就是根據索引將頂點鏈接到一起,組成線、面單元,然後進行剪裁,如果一個三角形超出屏幕以外,例如兩個頂點在屏幕內,一個頂點在屏幕外,這時我們在屏幕上看到的就是一個四邊形,然後把這個四邊形切成兩個小的三角形。
現在我們得到了一堆在屏幕座標上的三角形面片,這些面片是用於光柵化的。 

3,光柵化階段。 
經過上面的步驟之後,我們得到了每個點的屏幕座標值,和我們需要繪製的圖元,但此時還有兩個問題: 
(1)屏幕座標是浮點數,但像素是用整數來表示的,如何確定屏幕座標值所對應的像素? 
(2)如何根據已確定位置的點,在屏幕上畫出線段或者三角形? 
對於問題1,繪製的位置只能接近兩指定端點間的實際線段位置,例如,一條線段的位置是(10.48, 20.51),轉換爲像素位置就是(10,21)。 
問題2,涉及到具體的畫線和填充算法,有興趣的話可以研究。 
這個過程結束後,頂點和圖元已經對應到像素,之後的流程就是如何處理像素,即給像素賦予顏色值。 
給像素賦予顏色的階段稱爲Pixel Operation,是在更新幀緩存之前,執行最後一系列針對每個片段的操作,其目的是計算出每個像素的顏色值。在這個階段,被遮擋的面通過一個被稱爲深度測試的過程消除。
pixel operation包含下面這些流程: 
(1)消除遮擋面; 
(2)Texture operation,紋理操作,根據像素的紋理座標,查詢對應的紋理值; 
(3)Blending,通常稱爲alpha blending,根據目前已經畫好的顏色,與正在計算的顏色的alpha值混合,形成新的顏色。 
(4)Filtering,將正在計算的顏色經過某種濾鏡後輸出。 

該階段之後,像素的顏色值被寫入幀緩存中。



--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

學習着色器,並理解着色器的工作機制,就要對OpenGL的固定功能管線有深入的瞭解。

首先要知道幾個OpenGL的術語

渲染(rendering):計算機根據模型(model)創建圖像的過程。
模型(model):根據幾何圖元創建的物體(object)。
幾何圖元:包括點、直線和多邊形等,它是通過頂點(vertex)指定的。

最終完成了渲染的圖像是由在屏幕上繪製的像素組成的。在內存中,和像素有關的信息(如像素的顏色)組織成位平面的形式,位平面是一塊內存區域,保存了屏幕上每個像素的一個位的信息。例如,它指定了一個特定像素的顏色中紅色成分的強度。位平面又可以組織成幀緩衝區(framebuffer)的形式,後者保存了圖形硬件爲了控制屏幕上所有像素的顏色和強度所需要的全部信息。

OpenGL的固定功能管線

理清了基本的概念,下面瞭解了一些關於OpenGL渲染管線的知識.看了這個之後對於OpenGL的學習我想應當是很有幫助.關於這麼一篇的原文則是GLSL-LIGHTSOURCE 教程一個開篇部分.點擊這裏訪問原文。原文是英文的,以下是中文的翻譯,點擊訪問下文的原文地址

關於渲染管線將什麼呢?無非就是在OpenGL的管道當中各個部分的功能以及如何在管道當中形成了我們想要的最終的一幅圖.(像素).而管線當中的操作可分爲以下幾個部分:

階段1. 指定幾何對象.

如:點 線 三角形.等一些幾何圖元..OpenGL繪製幾何圖元的方法有以下三種:

  • <1> 一次一個頂點.即使用glBegin()  glVertex() glEnd() 指定幾何對象.
  • <2> 使用頂點數組..如glDrawArrays.glDrawElements.等.一次性的繪製大量圖元.

上面這兩種模式則是立即模式.即指定完圖元之後會被立即渲染.即將所有數據發往渲染管線後立即被渲染.

  • <3>顯示列表模式.它存儲於OpenGL服務端 (接收OpenGL命令的一端),操作函數有 glNewList、 glEndList、 glCallList .

階段2   頂點處理操作:

不管以上的幾何對象是如何指定的,所有的幾何數據都將會經過這個階段,這個階段負責的則是逐個頂點的操作.

在這個階段能做的工作則是:

  1.  頂點變換:根據模型視圖和投影矩陣變換
  2. 光照計算和法線變換(法線矩陣 是模型矩陣的左上角3*3的逆矩陣)和法線規格化
  3.  紋理座標變換.(紋理矩陣)
  4. 材質狀態:紋理座標生成

而最重要的則是變換以及光照. 每個頂點在這個階段分別是單獨處理的.

這個階段所接收到的數據則是每個頂點的屬性特徵..輸出則是變換後的頂點數據.

階段3  圖元組裝

在頂點處理之後,頂點的全部屬性都已經被確定。在這個階段頂點將會根據應用程序送往的圖元規則如GL_POINTS 、GL_TRIANGLES 等將會被組裝成圖元。

階段4 圖元處理(裁剪 消隱)

  • <1>這個步驟第一個所做的應當是裁剪操作,會將圖元與用戶定義的裁剪平面,即glClipPlane 和模型投影矩陣所建立的視景比較. 這將會裁剪且丟棄位於視景和裁剪平面外部的圖元.不在予以處理.
  • <2> 其次.若是採用透視投影 那麼.將會對每個頂點的x,y z座標分別除以w.
  • <3>緊接着,則是由視口變換將頂點座標變換至窗口座標.
  • <4> 執行消隱操作

階段5  柵格化操作

  • <1>由圖元處理傳遞過來的圖元數據.在此將會被分解成更小的單元並對應幀緩衝區的各個像素.這些單元被稱之爲片元. 一個片元可能包含窗口左邊、深度、顏色、紋理座標等屬性.
  • <2> 片元的屬性則是圖元上頂點數據等經過插值而確定的..這裏生成的片元將會包含主顏色和次顏色.   glShadeMode() 函數的作用將會這裏體現.即使用插值(平滑着色) 或者使用最後一個頂點顏色(平面着色)
  • <3> 點寬 線寬.多邊形模式,正面背面等一些特徵也將是這階段發生作用.
  • <4> 反走樣也是這個階段起作用.

階段6 片元處理

  • <1>上紋理:通過紋理座標取得紋理內存中相對應的顏色。
  • <2> 霧化:通過片元距離當前視點位置修改顏色.
  • <3> 顏色彙總..這個與混合完全不同概念.將紋理,主定義的顏色,霧化的顏色,次顏色光照階段計算的顏色 彙總一起.

階段7  逐個片元的操作

  • <1> 所有的一些測試 像素所有權 剪切(glScissor) Alpha測試(glAlphaFunc) 模版測試(glStencilFunc) 深度測試 (glDephtFunc) 混合(glBlendFunc)

這些操作將會最後影響其在幀緩衝區的顏色值.

階段8  幀緩衝操作

  • <1>這個階段執行幀緩衝的寫入等操作等..最後產生了顯示出來的像素.

glColorMask、glStrncilMask、glDepthMask、glClearDepht、glClearStencil、glClearColor 等.將在這個階段影響寫入的值.

以上只是討論OpenGL 圖元繪製的基本過程 那麼基於像素圖像繪製.幾乎形同之上..只是在光柵化處理前的操作不一樣.即經過像素解碼 像素傳輸.柵格化 最後形成片元...片元之後的處理完全一樣..

可編程管線可以替換的功能

在着色器編程領域..你將可實現

  • Vertex Shader(頂點着色器) 替換 頂點處理階段
  • Fragment Shader(片元着色器,又叫像素着色器) 替換 片元處理階段
  • Geometry Shader(幾何着色器) 替換 圖元組裝階段..

因爲這三個階段所決定都是最重要效果的階段..對於這些的可編程將帶來非常大的好處以及可控制的渲染!!

在前面的固定功能管線提到了,在階段5:柵格化操作 過程中, 片元的屬性會由圖元上頂點數據等經過插值而確定。在頂點着色器處理完畢後,OpenGL都會將頂點與頂點之間的片元(基本上可以理解爲像素)的屬性(如位置座標、紋理座標)進行線性插值。所以,在紋理座標爲(1,0)和(0,0)中間的片元會得到一個(0.5,0)的紋理座標,在紋理座標爲(0,0)和(1,1)之間的片元會得到一個(0.5,0.5)的紋理座標。然後將這些經過差值處理之後的片元交給片元着色器處理。片元着色器確定最終的片元顏色。


-----------------------------------------------------------------------------------------------------------------------------

  渲染管線過程:

       3D 物體經過一系列的變化(過程),最終顯示在玩家的眼簾的過程。

  3d(面片),3D Object ,經過一系列複雜的過程,這個過程硬件變化是看不到的

  輸入的是一個面片,輸出的是一個形狀

  貼圖:要分uv,要對uv做紋理座標的映射。

  拿到美術資源,有貼圖,是錯覺 ,美術爲了便於設計,在製作時候是有貼圖,分uv,及映射,

  提交給引擎是圖片和模型是分離的,還有一個材質球是索引所有圖片的,材質球裏面帶有參數。

  黑盒裏麪包含的步驟:

   頂點處理:

       通過一系列的座標系轉換,講模型的頂點在攝像機前進行位移,並最終投影到攝像機的投影屏幕上。

  本地座標  

  世界座標

  觀察座標

  投影座標

   本地--世界 

        將每個物體模型的位置從以自己爲中心的座標系,擺放到整體遊戲世界的具體某一個位置中

  到世界座標系後才能形成一個場景。

  世界--觀察

       將每個物體模型的位置以世界爲參照系,轉換到以觀察者爲參考系。

  觀察--投影

       將每個物體模型的位置從觀察者所在的座標體系中,轉到觀察者投影平面上

       最特殊的一次座標系轉換(非線性,模型倍扭曲)

  光照: 在觀察座標系下進行。 (觀察可以從不同的角度去觀察object)

       

-------------------------------------------------------------------------------------------------

   面處理:通常有引擎來處理

    面的組裝

    point list  Triangle List  Line List Triangle strip Line strip Triangle Fan

    面截取

    面剔除

      U3D Shader可以設置

      HRESULT SetRenderState(D3DRS_CULLMODE,value)

      可選擇的值有:

      D3DCULL_NONE:對任何頂點排列方式都不進行剔除

      D3DCULL_CW:  順時針排列的頂點被認爲是反面,對他們進行剔除

      D3DCULL_CCW: 逆時針排列的頂點被認爲是反面,對他們進行剔除

    注:正反面是根據法線

    視錐剔除:去掉在視錐外的面的部分

    在截取過程中,落在屏幕外面的面的部分已經被去除,視錐剔除階段主要處理落

在近端截除平面和遠端截除平面之外的面的部分。

   解決方式:通過硬件提供的深度緩存Depth Buffer或者z-Buffer來判讀。

 這裏的深度指面距離鏡頭所在平面的距離。  

-------------------------------------------------------------------------------------------------

   光柵化

   像素處理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章