三維圖形渲染管線


三維圖形渲染管線(Graphics pipeline)就是將三維場景轉化為一幅二維圖像的過程。 

圖像中物體所處位置及外形由其幾何數據和攝像機的位置共同決定,物體外表是受到其材質屬性、光源、紋理及着色模型所影響。

管線過程由3個大的階段組成:

 

Application(應用程序階段):運行在CPU上,能被開發者完全控制,該過程所做操作包括:

① 准備場景數據

     a. 加載模型:Mesh、Material、Shader、Texture(硬盤 --> 內存 --> 顯存)

     b. 攝像機(位置、朝向、視錐體)

     c. 光源(位置、類型等參數信息)

② 裁剪和剔除 :視錐裁剪、遮擋剔除 (Occlusion Culling)

③ 計算模型視圖矩陣

④ 設置渲染狀態(RenderState)

渲染管線內部維護着一些狀態值。在我們調用渲染API函數進行繪制之前我們需要設置這些狀態值。

這些狀態值指導GPU如何渲染我們傳遞到顯存的模型和紋理數據。我們稱這些狀態值為“渲染狀態(Render States) ”。

渲染狀態包括Shader、Texture、Material、Light內部定義的各種狀態等

最后,發起DrawCall調用

 

Geometry(幾何階段):負責與每個渲染圖元打交道,進行逐頂點、逐多邊形的操作。

其重要任務是把頂點坐標變換到帶有深度的屏幕空間中,再交給光柵器進行處理。

可進一步分割成:模型視圖變換,頂點着色,[曲面細分],[幾何着色],投影,裁剪及屏幕映射

 

模型變換:將模型從模型空間(Model Space)變換到世界空間(World Space)

注1:在模型空間和世界空間中,D3D為左手系,OpenGL為右手系

注2:在模型空間和世界空間中,UE為左手系(x向前,y向右,z向上),Unity為左手系(z向前,x向右,y向上)

注3:左手系中順時針為旋轉正方向,而右手系中逆時針為旋轉正方向

 

視圖變換:將各個模型從世界空間變換到眼空間(View Space,以攝像機為原點,又稱觀察空間、攝像機空間)

注1:在眼空間中,D3D為左手系,OpenGL為右手系

注2:在眼空間中,UE為左手系,與D3D一致;Unity為右手系,與OpenGL一致

通常會把這兩個變換矩陣結合成modelview矩陣,並將這個過程稱之為模型視圖變換

 

投影變換:將各個模型從眼空間變換到裁剪空間(Homogeneous Space,又稱齊次裁剪空間)

這里用到的矩陣為投影矩陣(DIP矩陣,又稱裁剪矩陣),按照投影類型分為透視投影與正交投影

轉換到裁剪空間的目的是為了方便高效地對圖元進行裁剪 

 

頂點着色器(Vertex Shader):主要功能是修改頂點屬性。如:通過傳入模型視圖矩陣(MVP)進行頂點空間變換(位置屬性)、逐頂點光照(顏色屬性)、紋理坐標變換(uv屬性)等

頂點着色器的處理單元是頂點,也就是說,輸入進來的每個頂點都會調用一次頂點着色器。

頂點着色器只能對輸入頂點的相關屬性進行修改、創建和忽略,不可以創建或銷毀任何頂點,而且無法得到頂點與頂點間的關系。

輸入一般是一個變換矩陣和一個相對坐標;輸出為裁剪空間的齊次坐標(這個是VS必須完成的任務)及每個頂點所附帶的其他屬性,如顏色、紋理坐標

 

曲面細分着色器(Tessellation Shader):用於細分圖元,分為3個階段。

a. Tessellation Control Shader(D3D中叫Hull Shader),負責把控后續階段的初始化操作,例如細化程度(可編程)

b. 處理Control階段的輸出,細化Patch數據(不可編程)

c. Tessellation Evaluation Shader(D3D中叫Domain Shader)的輸入為Patch數據;輸出數據為頂點着色器所應輸出的數據,但是是批量的(可編程)

 

幾何着色器(Geometry Shader):輸入是1個圖元,輸出是N個圖元(N>=0)

通過Shader程序可以指定Geometry Shader對頂點信息進行增減。還有,因為實際增減的是圖元頂點,所以對各種的線段、多邊形、粒子等圖元也可以進行增減。

利用Geometry Shader的各種方法被創造出來,因為可以自由的生成多邊形,那么就可以在地面上生長出草的多邊形,或者讓3D角色生長出毛發等是最基本的使用方法。

在游戲中,還可以把不需要做邏輯交互處理的例如火花等特效的表現,使用Geometry Shader來生成。

注:Geometry Shader通常是在display driver中實現的,也就是說其實是由CPU負責計算,當重新返回GPU的VS時,對流水線的影響很大,所以Geometry Shader的實際效能並不高,甚至是非常低

 

裁剪:在裁剪空間中,將那些不在視景體中的頂點裁剪掉

同時滿足以下3個條件的頂點表明在視景體內,否則就是在視景體外

−Wc<=Xc<=Wc

−Wc<=Yc<=Wc

−Wc<=Zc<=Wc(OpenGL)、0<=Zc<=Wc(D3D)

 

在此階段可設置裁剪方式(如:設置背面剔除),以及添加自定義的裁剪面

 

屏幕映射:進行透視除法得到CVV(canonical view volume,規則觀察體),並將每個圖元在CVV的NDC坐標(Normalized Device Coordinates,歸一化的設備坐標)的x、y分量轉換到屏幕空間

需要注意的是,在NDC為左手系坐標,x、y的范圍為[-1.0, 1.0];而z的范圍OpenGL中為[-1.0, 1.0],D3D中為[0.0, 1.0]

最后,將NDC坐標進一步轉換成屏幕坐標和z深度值

注1:D3D9的原點為左上角像素的左上角,D3D10+的原點為左上角像素的中心,x軸向右,y軸向下;OpenGL的原點為左下角像素的中心,x軸向右,y軸向上

注2:D3D直接將NDC的z作為深度值;OpenGL會將NDC的(z+1)/2來作為深度值,都歸一化到[0.0, 1.0]

注3:屏幕坐標,UE與D3D一致,Unity與OpenGL一致

 

Rasterizer(光柵化):對上個階段得到的圖元各頂點進行插值(z深度值、法線方向、紋理坐標、顏色等)來產生屏幕上的像素,並渲染出最終的圖像。

光柵化的任務主要是決定每個渲染圖元中的哪些像素應該被繪制在屏幕上

三角形設置:對三個頂點插值計算三角形邊上的像素

三角形遍歷:掃描三角形邊上的像素來插值計算整個三角形內的像素

片元着色器(Fragment Shader,D3D中叫Pixel Shader):逐片元的進行着色計算(即逐像素光照)。該階段可以完成很多重要的渲染技術  如:紋理采樣

逐像素、逐頂點光照差異性主要體現在對於非精細模型,在執行逐頂點光照時,由於點距較大,在進行顏色線性插值的過程中,無法精細平滑過渡,導致效果變差。

另外逐像素光照可以在渲染時添加並不存在的表面細節。如通過bump貼圖或normal貼圖,在原本平坦的表面表現出近似的凹凸效果。

當然,逐像素的計算量要比逐頂點要大

 

逐片元操作:有時也被稱為光柵操作(raster operations ,ROP)或混合操作(blend operations),通過設置來淘汰一些不合格的片元以及如何合並問題

如果一個片元通過了所有的測試,新生成的片元才能和顏色緩沖區中已存在的像素顏色進行Alpha混合,並寫入顏色緩沖區

Alpha測試

注1:並非所有顯卡都支持Alpha測試特性,使用前需要檢查顯卡是否有該能力

注2:由於大量片元會在該階段舍棄,Alpha測試可提高含大量透明物件場景的性能

模板測試

注1:若建立模板緩沖區為8bits,則模板值的范圍為:[0, 255]的整數;其初始值為清理模板緩沖區的所設置的值

注2:若在模板測試時,關閉了深度測試,則深度測試始終通過

深度測試

注1:深度值范圍(OpenGL和D3D的深度范圍為:[0.0, 1.0] )

注2:建立深度緩沖區位數越多,則深度值的精度就會越高;其初始值為清理深度緩沖區的所設置的值

注3:關閉了深度測試,意味着該片元始終通過深度測試

Alpha混合

FrameBuffer(幀緩沖)

幀緩沖器frame buffer):在顯卡中硬件實現,用於存放渲染的最終結果。分為:單緩沖、雙緩沖(double buffering)、三重緩沖(Triple Buffering)

單緩沖:各個物體的渲染會直接畫在屏幕上,效率比較低,由於能看到中間繪制過程,會導致屏幕不斷閃爍。一般只用於顯示非動態的圖像

雙緩沖(double buffering):繪制是在一個后備緩沖器(backbuffer)中以離屏的方式進行的。一旦在后備緩沖器中完成繪制,

通過交換指令(D3為Present、OpenGL為SwapBuffer)就可將后備緩沖器中的內容與已經在屏幕上顯示過的前台緩沖器(frontbuffer)中的內容進行交換,使得一個完整的幀顯示在屏幕上。

完成交換后,后備緩沖器變為前台緩沖區,而前台緩沖區變為后備緩沖區,為下一幀的繪制工作提前做好准備。

我們將前后緩沖區功能互換的行為成為提交(Presenting)。

由於只是將前台緩沖區的指針和后備緩沖區的指針做一個簡單的交換,提交是一個運行速度很快的操作。

// OpenGL單緩沖
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glFlush(); //單緩沖的刷新模式;

// OpenGL雙緩沖
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutSwapBuffers(); //雙緩沖的刷新模式;

三重緩沖(triple buffering):一個前台緩沖區,兩個后備緩沖區。

在開啟了VSync垂直同步時,若游戲的FPS低於顯示器刷新頻率,三重緩沖可緩解卡頓現象,然而由於存在2個后備緩沖區,三重緩沖會導致畫面有一幀的延遲。(見下文說明)

顯示器

以CRT顯示器為例(液晶顯示器原理類似),CRT的電子槍從左到右,從上到下進行逐行掃描,掃描完成后顯示器就呈現一幀畫面,隨后電子槍回到初始位置繼續下一次掃描。
為了把顯示器的顯示過程和系統的視頻控制器進行同步,顯示器(或者其他硬件)會用硬件時鍾產生一系列的定時信號。
當電子槍換到新的一行,准備進行掃描時,顯示器會發出一個水平同步信號(horizonal synchronization),簡稱 HSync;
而當一幀畫面繪制完成后,電子槍回復到原位,准備畫下一幀前,顯示器會發出一個垂直同步信號(vertical synchronization),簡稱 VSync。
顯示器通常以固定頻率(如60HZ)進行刷新,這個刷新率就是 VSync 信號產生的頻率。

將顯卡與顯示器的刷新頻率通過一個稱為VSync的信號同步起來,保證顯示器上顯示的是一幀完整的畫面,來解決Tearing(撕裂)現象(多幀畫面同時繪制在顯示器上)。

 

假設游戲的FPS是100,顯示器的刷新頻率是75Hz,顯卡將比顯示器快1/3;這意味着,在1個顯示器刷新周期內,顯卡將寫入4/3的幀數據,也就是說,下一幀的1/3覆蓋在前一幀之上;
當然,隨着系統運行,1/3這個比例會發生變化,1/3,2/3,1,1/3,循環;這種幀與幀之間的不完全覆蓋重合現象就是撕裂現象。

D3DPRESENT_PARAMETERS md3dPP;
md3dPP.PresentationInterval = D3DPRESENT_INTERVAL_ONE; // 開啟垂直同步

當開啟了垂直同步,若游戲FPS高於顯示器刷新頻率時,顯卡會將一部分時間浪費在等待上,顯卡必須等待VSync信號的到來才能將繪制好的畫面推送給顯示器,這也使得游戲的最大FPS下降為顯示器的刷新頻率

這避免了顯卡做一些無用的工作,降低顯卡的功耗;然而,VSync技術也有缺點,會導致玩家輸入的響應出現延遲;另外,若游戲的FPS低於顯示器刷新頻率,那么系統的FPS將迅速下降為顯示器刷新頻率的分數倍上,加劇畫面卡頓(Shutter)

Triple Buffering(三重緩沖)可以緩解這一問題,示意圖如下:

然而,從上圖可以看出由於存在2個后備緩沖區,三重緩沖會導致畫面有一幀的延遲

 

總結

(Vertex Shader) => Clip Space => (透視除法) => NDC => (視口變換) => Screen Space => (Fragment Shader)

 

參考

【《Real-Time Rendering 3rd》 提煉總結】(二) 第二章 · 圖形渲染管線 The Graphics Rendering Pipeline

【《Real-Time Rendering 3rd》 提煉總結】(三) 第三章 · GPU渲染管線與可編程着色器

 深入理解OpenGL之投影矩陣推導

《Unity Shader入門精要》隨書彩色插圖 

UE4:坐標空間術語en) 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM