圖形學基礎概念筆記(一)

圖形學筆記

綜述

圖形學知識結構

什麼是計算機圖形學?

“計算的目的是洞察事物的本質,而不是獲得數字。” ——Richard Hamming,1962

計算機圖形學的核心目標是三個基本任務:表示、交互、繪製。表示是將主、客觀世界放入計算機,通過數字對二維、三維對象進行建模與儲存。繪製是將計算機中的對象通過直觀的人眼易讀的圖形圖像方式表現出來。交互是通過直觀的圖形圖像手段,改善真實用戶通過計算機錄入、修改、獲取數據的體驗。

從圖形學的核心思想的角度講,圖形學就是和顏色打交道。一切數學模型,一切算法,一切數據結構的最終目的,都是在正確的位置產生正確的顏色,僅此而已。除此之外的網格、模型、幾何體、光照,都不是必須的。

計算機圖形學的研究方向?

計算機圖形學的主要研究對象是點、線、面、體、場的數學構造方法和圖形顯示,以及其隨時間變化的情況,它需要研究以下幾方面的內容:

  • 描述複雜物體圖形的方法與數學算法,包括曲面、曲線的造型技術,實體造型技術,紋理、雲彩、波浪等自然景物的造型和模擬,三維場景的顯示如光柵圖形成生成算法和線框圖、真實感圖形的理論和算法。
  • 物體圖形描述數據的輸入。
  • 幾何和圖形數據的儲存、壓縮和解壓。
  • 物體圖形數據的運算,如基於圖形和圖像的混合繪製、自然景物仿真、圖形用戶接口、虛擬現實、動畫技術和可視化技術等。
  • 物體圖形數據的輸出顯示,包括圖形硬件和圖形交互技術。
  • 實時動畫和多媒體技術,研究實現高速高精度動畫的各種軟、硬件方法,開發工具、動畫語言以及多媒體接口。
  • 制定與圖形應用軟件有關的技術標準。

底層知識背景

顯卡和GPU

GPU和CPU的區別?

主流CPU(Central Processing Unit,中央處理器)芯片上有四級緩存,消耗了大量晶體管,在運行時需要大量電力;主流GPU(Graphics Processing Unit,圖形處理器)芯片最多有兩層緩存,且GPU可以利用晶體管上的空間和能耗做成ALU單元,因此GPU比CPU效率高。

CPU重在實時響應,對單任務速度要求高,需要針對延遲優化,所以晶體管數量和能耗都需要用在分支預測、亂序執行、低延遲緩存等控制部分;GPU主要使用於具有極高可預測性和大量相似運算的批處理,以及高延遲、高吞吐的架構運算,對緩存的要求相對很低,順序運算效率很高,同時相對的亂序處理效率很低。

CPU除了負責浮點和整型運算,還有很多其它的指令集的負載,如多媒體解碼和硬件解碼,CPU注重單線程性能,保證指令流不中斷,需要消耗更多晶體管和能耗用在控制部分,於是CPU分配在浮點運算的功耗會減少;GPU基本只進行浮點運算,設計結構簡單,效率更高,GPU注重吞吐率,單指令能驅動更多的計算,相比較GPU消耗在控制的能耗就少得多,因此可以將資源留給浮點運算使用。GPU的浮點運算能力比CPU高10~12倍。

什麼是NVIDIA/AMD?

顯卡品牌。

NVIDIA公司譯爲英偉達,其生產的顯卡又被稱爲N卡。AMD譯爲超微半導體,其生產的顯卡又被稱爲A卡。

N卡奉行大核心戰略,GPU內部採用大量1D單元,在執行效率上理論可以達到100%,實際效率也可以維持在90%以上,因爲架構執行效率高,靈活性強,所以在實際應用中易發揮應有性能。但是大核心的設計複雜,內部集成的SP數量也不會太多,成本和功耗也會比較高,控制單元在晶體管的消耗上佔了更大比例,在相同晶體管數量的情況下,N卡能做的運算單元相對較少。N卡在軟件上具有明顯優勢,包括微軟在內的軟件商都爲N卡開發優化,使得大量工具軟件和遊戲在N卡環境下有更好的表現。

A卡奉行小核心戰略,採用VLIW5或VLIW4的設LI計,分別採用4D+1D的設計和4D設計,可以在較小的晶體管代價和較小的核心面積下裝入更多的SPU,以SP的數量取勝。其理論計算能力遠超N卡,但實際執行效率並不高,一旦進入GPU的圖形信息是1D或3D形式這一的非標準數據形式,A卡的執行效率最低可降至25%至20%。

顯卡是個人計算機最基本組成部分之一,用途是將計算機系統所需要的顯示信息進行轉換以驅動顯示器,並向顯示器提供逐行或隔行掃描信號,控制顯示器的正確顯示,其高效的並行計算能力現階段也用於深度學習等運算。

GPU和顯卡有什麼關係?

GPU是顯卡上的核心處理芯片,顯卡上除了GPU,還包括顯存、電路板和BIOS固件等。由於GPU在顯卡上十分重要,所以時常用GPU代指顯卡。

顯卡也叫顯示適配器,分爲獨立顯卡和集成顯卡。獨立顯卡由GPU、顯存和接口電路組成;集成顯卡和CPU共用風扇和緩存,沒有獨立顯存,而是使用主板上的內存。

顯存是什麼?

顯卡儲存體系的設計哲學是更大的內存帶寬而非更低的訪問延遲,這也是顯存訪問的特點:高帶寬,高延遲。

顯存既可以是物理上的,也可以是邏輯上的。

對集成顯卡如Intel HD Graphics來說,GPU使用CPU專門劃分出來的一份內存空間,即**UMA(Unified Memory Architecture,一致性儲存架構)作爲顯存,GPU和CPU用不同的虛擬地址對UMA中的同一個物理地址尋址。使用集成顯卡時,CPU和GPU共享總線。在渲染時,CPU將頂點等數據存入主存,然後GPU可以通過GART(Graphic Address Remapping Table,顯存地址重定位表)**訪問UMA。GART的作用是將UMA虛擬地址映射到GPU尋址空間。而由於UMA屬於CPU內存範圍,CPU可以直接訪問它。

對獨立顯卡來說,GPU可以使用專門的顯存條,並使用顯存條的物理地址進行尋址,這是最常見的顯卡類型。在獨立顯卡結構中,GPU可以直接從顯存中讀寫信息。而CPU訪問顯存條中的儲存空間時,需要映射一部分GPU儲存空間到CPU地址空間,典型大小爲256MB或512MB,CPU地址空間的獲取一般由API完成。

無論使用哪種顯卡,CPU和GPU交流必然要經過總線。獨立顯卡中CPU與顯卡的溝通,是通過異步的**DMA(Direct Memory Access,內存直接訪問)**實現的。主機將DMA命令塊寫入內存,DMA命令塊由傳輸來源的源地址、傳輸目標地址和傳輸的字節數組成。CPU將這個命令塊的地址寫入DMA控制器,然後繼續其它工作。隨後DMA控制塊會直接操縱內存總線,脫離主CPU的幫助下實現傳輸,將數據提供給顯卡驅動。反過來,顯卡驅動發送信息給DMA控制器請求線路,DMA控制器於是佔用內存總線,併發送所需地址到內存地址總線,然後發送信號到DMA確認線路。當顯存收到DMA確認信號時,他就傳輸數據到內存,並清楚DMA請求信號。每當一次溝通結束,DMA都會觸發一次CPU中斷。

什麼是流處理器SP?

流處理器又稱流處理單元,簡稱**SP單元(Streaming Processor)**或SPU,有些顯卡生產商也會將其稱作core(核心)。流處理器的數量能直接影響顯卡性能。

之前的顯卡具有兩個重要的運算單元——頂點處理單元和像素處理單元。但自從DirectX10開始,微軟引入了流處理器這個概念,頂點處理單元和像素處理單元很快被業界拋棄。流處理器是頂點處理單元和像素處理單元的統一,負責了渲染中的頂點和像素渲染。將頂點處理單元和像素處理單元合併的概念又被稱作統一着色器架構(Unified Shader Architecture)

業界之所以拋棄之前的頂點+像素結構而使用SPU架構,是因爲傳統的頂點和像素分離渲染架構存在嚴重的資源分配不均的問題,兩種單元渲染任務量不同,效率低下。而SP架構是統一結構,不再區分頂點和像素渲染,進行不同渲染任務時都能保證效率。

4D+1D/4D/1D都是什麼?

D是維度Dimension的意思,在圖形學中的nD指n維浮點向量運算,nD單元指由n個流處理單元整合成的n維浮點向量運算單元

像素座標XYZW、色彩參數RGBA以及紋理座標參數STPQ正好都是4維運算,這導致頂點處理單元和像素處理單元都是4D單元,在引入流處理器後,主流的流處理器也是4D單元。

而1D即一維向量,也就是標量。由於流處理器合併了頂點和像素處理單元,圖形渲染中標量運算成分開始增多,GPU不再像早年那樣只需要處理單純的4D向量運算了。在這樣的背景下,英偉達完全拋棄4D結構,設計了G80這樣的1D標量處理器,將矢量運算分解爲4次或更多次標量運算,這使N卡的靈活性大幅提升,在任意維度的運算環境下都可以得到滿意的性能。

AMD沒有放棄4D架構,而是進行了改良,增加了一個標量運算單元,這就是4D+1D矢量標量混合架構,也就是VLIW5(Very Long Instruction Word,超長指令口令)架構,它把需要計算的指令組合成適合4D+1D架構的長指令,比如將一個2D運算和一個3D運算合併爲一個4D+1D運算,這樣理論上每個統一處理器每個週期都可以進行一次4D運算加一次1D運算,是N卡1D單元運算效率的4~5倍,這種將指令組合的算法被稱爲co-issue算法。這五個ALU只需要一個發射端口,電路設計更加簡單,功效與發熱也更容易控制,但缺點就是依賴指令組合,一旦非最優指令組合,這些運算單元中部分維度就只能空轉,運算效率將顯著降低。

什麼是流多處理器SM?

SM(Streaming Multiprocessor,流多重處理器)由多個SP加上共享內存特殊函數單元寄存器多邊形引擎指令緩存L1緩存等的組合。

SM中的任務主要由SP承擔,SM中SP的數量一般爲32個,有時也有16或64個SP組成的SM。進行輔助計算的還有SFU(Special Function Units,特殊數學運算單元),它們用於進行三角函數和指對數等運算。

SM由Warp Scheduler(束管理器)驅動,束管理器會將指令移交給Dispatch Unit(指令分派單元),由於SM中每束處理的事務具有相似性,這個單元會從指令緩存新的讀取指令,並一次性向整束的所有流處理器發送同一個指令。這種通過一條指令驅動若干線程的特性被稱爲SIMT(Single Instruction Multiple Thread,單指令多線程),在這個框架下,指令分派單元可以讀取一條指令,然後向多個SP分派不同的參數,以讓它們在不同的寄存器地址進行讀寫。

SM中一般配有一個多邊形引擎(Polymorph Engine)。這個引擎的作用是實現屬性裝配、頂點拉取、曲面細分、裁剪和光柵化等渲染流水線中的固定步驟。

每個SM中具有一個足夠大的寄存器,一般能達到128KB。所有SP共用這個寄存器中的空間,所以如果單個線程需要的寄存器空間過大,可能使每束的最多線程數減少,影響並行性。

L1緩存是開始時用於儲存頂點數據的緩存,在頂點處理階段結束後,SM會將處理結果送到SM外的L2緩存中。多邊形將在SM外進行光柵化,然後將生成的片元重新分發到SM中,這時L1緩存中儲存的就是片元數據了。

一個顯卡上可能有10~20個SM,一般顯卡廠商將若干SM的組合稱爲一個GPC(Graphic Processor Cluster,圖形處理簇),每個簇可以處理一批(batch)頂點或片元。在有GPC結構的顯卡上,L2緩存一般位於GPC中,而對於沒有GPC結構的顯卡,L2一般位於顯存旁或顯存中。

顯卡有什麼樣的儲存結構?

顯卡中的物理儲存器按存取速度的大小從快到慢依次有:寄存器、共享內存、L1緩存、L2緩存、紋理緩存、常量緩存、全局內存(顯存)。

寄存器位於SM中,訪問速度是1個時間週期,SP運行時可以隨意的讀和寫寄存器。在指令分派單元的控制下,每個線程都會獲得自己的寄存器空間,

共享內存和L1緩存都位於SM中,它們都可以被SM中的所有SP共用。L1緩存重點存放頂點和片元數據,而共享內存重點存放材質參數、光照、攝像機等常量數據。共享內存和L1緩存的訪問速度都低於32個時間週期。

L1緩存和L2緩存重點用於頂點和片元數據的交換。L2緩存位於SM之外,它的訪問速度相對L1較慢,大致需要32至64個時間週期,但由於大量SM會共用一個L2緩存,L2緩存的吞吐量是數個L1緩存的總和。在有些顯卡設計中,L2緩存還可以作爲紋理緩存和常量緩存的新一層緩存,供其它顯卡硬件使用。

紋理緩存和常量緩存是顯卡上重要的緩存類型,它們都是顯存的直接緩存,訪問延遲在400個時間週期以上,如果出現未命中,要等待訪問顯存的話,這個延遲甚至很可能超過1000個時間週期。

全局內存也就是顯存,它的數據直接來自CPU總線。顯卡驅動會在GPU流水線空閒時將任務數據從GPU緩衝區導入顯存。顯存的訪問延遲在500個時間週期以上,顯卡的訪問一般在紋理緩存和常量緩存缺失時纔會發生。但存在一種高吞吐GPU結構,使用更大量的GPU而減少了緩存的層數,這種結構中顯存可能直接被共享內存訪問,通過高吞吐量與高帶寬來緩解緩存層級少帶來的性能缺陷。

GPU是怎麼應對阻塞的?

GPU不像CPU那樣實現流水線,因爲設計者認定GPU中的一批數據應當具有相對固定的處理過程,比如在一個屏幕後期處理的draw call中,GPU可能處理了1920x1080個片元,但對每個片元運行相同的一套算法。

GPU並不關心跳轉,其性能瓶頸主要來自讀取顯存產生的阻塞而非跳轉產生的阻塞。由於紋理數據儲存在顯存中,在每次遇到採樣語句時,爲了將顯存中的數據調入核心,可能需要使處理器阻塞幾百上千個時鐘週期。

爲了緩解這樣的問題,GPU使用**換入換出操作(swapping)**來隱藏延遲。

SM中的寄存器可以備份若干份SP的執行狀態。每當有一批片元在等待採樣紋理數據時,可以將此時SP組的上下文備份保存在寄存器中,然後導入下一批片元進行處理,這個操作被稱爲換出(swap-out)。換出使得核心不需要空等採樣結果,可以繼續執行更多的片元。

由於GPU中的數據具有相對固定的處理過程,我們認爲每一批片元都會在相同的週期後遇到一次換出操作。假設我們的GPU中只有一個核心(當然不存在這樣的GPU),運行一批具有2000個片元的數據,這2000個片元都會在運行完前5個週期後遇到第一次採樣語句,然後被執行換出。假設採樣結果從顯存中返回到核心需要1000個時間週期,那麼在運行完第200個片元並將其換出後,第1個片元所需的採樣數據就可以抵達核心,這時核心將第1個片元的上下文從寄存中拷貝回SP中,繼續進行這個片元採樣之後的運算,直到它再次被換出,這個操作被稱爲換入(swap-in)。如果這些片元在採樣語句結束後還有5個時間週期用於算數運算,那麼第1個片元執行完時第2個片元也恰好可以被執行換入。以此類推,執行完每200個片元所用的總時間是2000個時間週期,假如我們不使用換入換出操作,200個片元所用的時間將是200200個時間週期。

換入換出的延遲僅僅只有不到10個時間週期,在實際的應用中,不止是採樣運算,延遲超過30個週期的很多運算都會使用換入換出操作。

當然,在上面的例子中,加入我們沒有2000個片元數據,而僅有20個,那麼我們不得不在運行完第20個片元的換出後等待1900個週期才能繼續運算。實際的應用中,如果每個線程需要的寄存器數越多,就意味着每束處理的線程數越少,能被用於儲存鏡像的空間也越少,鏡像短缺意味着換入換出策略的效果大幅下降,將嚴重影響GPU的併發執行。

GPU是怎麼實現並行的?

執行一個着色器的最小單位是線程(thread)。多個線程被打包在一起稱爲線程束(英偉達稱之爲warp,AMD稱之爲wavefront,可以統一翻譯爲線程束)。多個線程束被打包爲一個線程組(block),每個線程組中的所有線程可以通過共享內存來通信,不同線程組中的線程是無法通信的。

在實際運行中,處理器會將一個線程組分配給一個SM。每個SM獲得線程組後,會通過束管理器將其分爲若干線程束,每個線程束中線程的個數一般等於流多重處理器中流處理器的個數,如果一個SM被分爲多個束,則線程束中線程的個數等於一束中流處理器的個數。如一個具有100個線程的線程組分配給一個具有32個流處理器的SM,SM會將其分爲4個線程束,第031號線程分爲第1束,第3263號線程分爲第2束,第6495號線程分爲第3束,第9699號線程分爲第4束。在運行時,每個線程束中的32個線程會被分配到32個流處理器中進行並行運算。由於GPU任務的相似性,這32個線程很可能在同一時間遇到換出操作,那麼SM就會安排下一個線程束進入流處理器。

動態分支語句如何影響GPU並行效率?

動態分支包括if語句和循環語句。它們在着色器語言中存在,但可能嚴重影響GPU性能。

在一個線程束中,如果線程中不存在動態分支語句,那麼它們的所有行爲都是可預測相同的。但一旦遇見一次動態分支語句,就可能產生分裂。如果線程束中所有線程執行相同的分支,那麼運行結果不會有什麼不同。但我們提到過,在SM中的一束線程共享同一份指令,但凡有一個線程執行另一個分支,那麼整個線程束就不得不被執行兩遍,將兩個分支的結果都運行一次,並讓每個線程扔掉它們各自不需要的結果。如果在着色器編寫中出現連續的分支預測,甚至複雜的循環語句,每個線程束的執行次數可能呈指數級遞增,這個效應被稱爲線程分歧(thread divergence)

根據此我們也可以發現,使用循環語句運行常數次來讀取某數組中的信息並不會導致線程分歧,線程分歧的嚴重與否關鍵在於相鄰的元素通過動態分支語句能否得到基本相似的分支。

什麼是垂直同步?

早期CRT顯示器中,電子槍從上到下進行掃描,掃描完成後顯示器就顯示一幀畫面,然後電子槍回到初始位置進行下一次掃描。爲了同步顯示器的使用過程和系統的視頻控制器,顯示器會用硬件時鐘產生一系列的定時信號:當電子槍換行掃描時,顯示器會發送一個水平同步信號,簡稱HSync,當一幀畫面繪製完成後,電子槍回覆到原位,準備畫下一幀前,顯示器會發出一個垂直同步信號,簡稱VSync

技術升級帶來液晶屏後,視頻控制器(Vedio Controller)仍在根據同步信號逐幀讀取幀緩衝區的數據。爲了緩解單緩衝下幀緩衝區的讀取和刷新效率低的問題,GPU中常用兩個緩衝區,即雙緩衝機制,在一幀渲染完後,視頻控制器會將兩個緩衝互換。由視頻控制器直接讀取的緩衝區稱爲前緩衝區,而在後臺承擔渲染任務的叫後緩衝區

雙緩衝會引入一個新問題:在視頻控制器讀取前緩衝區只讀取了一半時,GPU將新的一幀內容提交到幀緩衝區,並把兩個緩衝區交換。視頻控制器可能把新一幀數據的後半段渲染到了屏幕上,這使得屏幕得上半部分和下半部分分屬不同得兩幀,產生畫面撕裂。

爲了解決這個問題,GPU等待顯示器發送得垂直同步信號來交換緩衝區,垂直同步信號沿用了曾經得VSync這一稱謂。垂直同步解決了畫面撕裂現象,也增加了畫面流暢度,但需要消費更多計算資源,也會帶來部分延遲。

圖形API和着色語言

什麼是OpenGL/DirectX?

圖像編程接口(圖形API),是對GPU硬件的抽象,其地位類似C語言,屬於GPU編程的中低層。幾乎所有GPU都既可以和OpenGL合作也可以和DirectX合作。

OpenGL和DirectX的區別?

OpenGL是純粹的圖形API;DirectX是多種API的集合體,其中DirectX包含圖形API——Direct3D和Direct2D。

DirectX支持Windows和Xbox;OpenGL支持Windows、MacOC、Linux等更多平臺,在Android、IOS上允許使用OpenGL的簡化版本OpenGL ES。

OpenGL相對來說易上手門檻低;DirectX難上手門檻高。OpenGL渲染效率相對低,特性少;DirectX相對效率高,特性多。如DirectX12提供了底層API,允許用戶一定程度上繞過顯卡驅動之間操縱底層硬件。

二者在圖形學領域一樣重要。OpenGL在各種領域都喫香,包括許多專業領域如特效和CG建模軟件。DirectX在遊戲中更通用。

什麼是顯卡驅動?

一個應用程序向顯卡接口發送渲染命令,這些接口會依次向顯卡驅動發送渲染命令。顯卡驅動的地位類似於C語言編譯器,可以將OpenGL或DirectX的函數調用翻譯成GPU能讀取的機器指令,即二進制文件。顯卡驅動同時也負責把紋理等數據轉換成GPU支持的格式。

什麼是HLSL/GLSL/CG/CUDA?

HLSL、GLSL和CG是着色語言,專門用於編寫着色器。其中HLSL(High Level Shading Language)屬於DirectX,GLSL(OpenGL Shading Language)屬於OpenGL,而CG(C for Graphic)是NVIDIA研發的,因爲英偉達與微軟合作密切,CG語法與HLSL極其相似。

CUDA(Compute Unified Device Architecture,統一計算架構)也是NVIDIA研發的,和前三者類似,但並不專注於圖形領域,常用在機器學習等領域,不需要像着色語言那樣使用圖形計算的邏輯進行數字運算。

Draw Call

什麼是Draw Call?

調用一次圖像編程接口(圖形API),以命令GPU進行渲染的過程就稱爲一次Draw Call。

Draw Call的流程是什麼?

Draw Call的準備工作由CPU完成。

第一步,CPU把一個網格的頂點數據從硬盤中加載到內存中(存在這一步的原因是大規模3D渲染中內存可能不足)。

第二步,CPU對這個網格設置渲染狀態(每個網格不等於每個模型/圖片,因爲存在批處理)。所謂渲染狀態包括紋理貼圖、材質屬性和被編譯爲二進制文件的着色器。隨着渲染狀態一起被傳遞到GPU的還有光照和攝像機相關的信息。圖形API可以更深層次的定義渲染狀態需要的數據。

第三步,CPU將網格頂點數據與渲染狀態打包,將數據包按照指定格式交給DMA,由DMA將數據包傳入顯卡。

顯存完成接受數據包後,DMA向CPU返回一箇中斷信號,Draw Call正式結束。

GPU是如何接受Draw Call的?

指令到達顯卡驅動程序後,驅動會首先檢查指令的合法性,如果指令非法,驅動會通過DMA向CPU發送錯誤信息。如果指令合法,驅動通過DMA確認Draw Call接收,然後將指令放入GPU緩衝。

一段時間後當顯卡中存在空閒流水線,或者CPU顯式發送flush命令後,驅動程序把緩衝區中的一份指令發送給GPU,GPU通過主機接口接受命令,並開始處理命令。

GPU將所有頂點存入頂點緩衝區(Vertex Buffer),GPU中的**圖元分配器(Primitive Distributer)**開始通過頂點生成三角形,並將他們分成批次(batch),發送給一個或多個GPCs,如果顯卡不存在GPCs,則直接分發給SMs。SM獲得數據後,束管理器安排多邊形引擎將三角形數據提取出來存入SM的L1緩存,隨後開始頂點着色器階段。

GPU會依次處理緩存中的每一個網格,網格的處理順序與CPU的提交順序有關。正因如此,CPU總是最後提交透明物體。等一幀中的所有Draw Call處理完畢後,顯示器纔會將圖像打印在屏幕上。

什麼是批處理?

對2D物體(UI)來說,如果兩個或多個UI元素符合以下條件則可以被批處理:

  • 使用相同的材質與紋理,即使用相同渲染狀態。
  • 這些元素的層級相同,或這些元素之間不夾雜使用其它渲染狀態的元素的層級。

對網格(模型)來說,如果兩個或多個網格符合以下條件則可以被批處理:

  • 使用相同的材質與紋理,即使用相同渲染狀態。
  • 頂點總數不超過一個閾值,該閾值大小與使用的處理引擎有關。
爲什麼要進行批處理?

Draw Call的性能瓶頸是CPU而非GPU。CPU每進行一次Draw Call,都要調用一次DMA將數據輸入顯存。在每次調用時,對顯存的映射尋址、DMA控制塊的注入、等待DMA響應等系統消耗都會浪費時間週期,多次進行Draw Call就會有多次消耗。同時,DMA擅長一次傳輸大量數據,而不擅長多次傳輸少量數據。這使得降低Draw Call對於優化顯示性能很有必要。

批處理可以顯著降低Draw Call。通過將類似的網格當作同一網格,CPU可以在一次Draw Call中將更多的數據傳輸到GPU中,降低系統消耗。

渲染流水線

綜述

渲染流水線的運行過程是怎樣的?

渲染流水線是在顯存中開啓的。CPU將網格、材質、貼圖、着色器等注入顯存後,GPU開始渲染流水線。

渲染流水線分爲幾何階段和光柵化階段(也被稱爲像素階段),並最終將運算結果送到顯示器的緩衝區中。

幾何階段分爲頂點着色器->曲面細分着色器(DirectX11和OpenGL4.x以上可編程)->幾何着色器->裁剪->屏幕映射五個步驟。

光柵化階段分爲三角形設置->三角形遍歷->片元着色器->逐片元操作四個步驟。

顯卡廠商會通過硬件實現常用且功能變化不大的幾個流水線階段,因爲通過硬件實現的效率遠高於軟件實現。這些階段優的根據API的設計,提供了一些可以設置的參數,但總的來說不會脫離GPU的控制。

在這個過程中我們可以知道,頂點着色器和片元着色器的線程數並不是等同的。片元着色器的線程數變化幅度一般不大,而頂點着色器的線程數變化幅度隨應用不同可能大幅浮動。

如何理解GPU渲染流水線和CPU指令流水線的區別?

渲染流水線重點強調的和CPU的指令流水線並不同。

在CPU指令流水線中,我們強調的是:不需要等待一個指令處理完畢再讀取下一條,而是在指令處理完一個階段後開始處理下一條。這裏考慮的重點是兩條指令間的流水處理。

對於渲染流水線,一種常見的誤解是認爲渲染流水線和指令流水線一樣,不同階段處理的是不同的Draw Call,較後的階段處理着較早提交的Draw Call,較前的階段處理着較晚提交的Draw Call。**這種理解是錯誤的。**正確的理解是,渲染流水線中,不同階段運行的是同一次Draw Call,但一次Draw Call中先提交的數據可以不等待它之後的數據就進入下一個流水線階段。

幾何階段

頂點着色器是如何工作的?

頂點着色器可以深度自定義,開發者需要根據顯存中的原始數據(raw data)輸出頂點顏色和頂點的齊次裁剪空間座標

束管理器將第一批頂點數據(如果一束中有32個線程,則存入32個頂點)以及其它需要的參數存入寄存器,然後將頂點着色器代碼存入指令緩存,並要求指令分派單元從指令緩存中讀取指令分派給SP。這個過程結束後,寄存器中必須至少保存着所有頂點的目標位置和頂點顏色。如果還有其它需要留給其它流水線部分的數據,也可以存放在寄存器中。在這之後,SM中頂點着色器階段產生的運算結果會被存入L1緩存,然後由GPU將多個SM的結果組合衝入位於SM結構外的L2緩存。

在硬件中,將齊次裁剪空間座標通過透視除法轉化爲NDC(Normalized Device Coordinates,歸一設備座標)。不同顯卡的NDC格式可能不同,其中OpenGL中的NDC是左手座標系,位於座標範圍在(-1,-1,-1)至(1,1,1)的立方體中。

曲面細分着色器是如何工作的?

曲面細分着色器又被譯爲鑲嵌着色器,它是一個可編輯着色器,在顯卡上的一個名爲**視口變換器(Viewport Transform)**的硬件模塊上運行實現,一個顯卡上可能有1~4個視口變換器集成電路,它們有些是可編程的有些是不可編程的,在具有GPC結構的顯卡中,視口變換器一般位於GPC外。

視口變換器可以從L2緩存中獲取它需要的信息,並將L2緩存作爲它與顯存溝通的橋樑。

曲面細分着色器將複雜的曲面轉換爲簡單的點、線、三角形。曲面細分着色器可以遞歸的增加網格細度,並在細分後的頂點上生成插值色彩。由於它處理了鄰接頂點的信息,它處理的結果會更加平滑,而不會產生跳躍間隙。

通過細分,模型外觀會更加平滑,且顏色的過度也會更加自然。

在比較老舊的API版本中曲面細分着色器由硬件實現無法編程。在DirectX11以上或OpenGL4.x以上的版本中,可以通過API編輯這個着色器的工作任務。

幾何着色器是如何工作的?

幾何着色器是一個可選着色器,也在視口變換器上實現。

幾何着色器同樣也可以實現曲面細分,但它的實現效果並不好。

幾何着色器用於實現逐三角形的操作。由於相比頂點或片元數量,三角形數量並不多,所以這一步並沒有分派到多SM中實現。幾何着色器在投影的接受上十分重要,它還能實現擴展幾何圖形和繪製簡單的粒子。

裁剪階段是如何工作的?

裁剪也由視口變換器實現,但它完全不可編輯。

GPU會將完全留在視野範圍內的三角形保留,將完全在視野範圍外的三角形拋棄,將部分留在視野範圍內的三角形修正爲新的幾何體,即生成新的頂點,拋棄原來在視野外的頂點。

屏幕映射是如何工作的?

屏幕映射緊跟在裁剪階段之後,也由視口變換器負責,不可編輯。

屏幕映射簡單來說就是將NDC中的座標轉換到屏幕座標系(Screen Coordinate)。所謂屏幕座標系,就是將範圍在(-1,1)的x和y軸座標,縮放到與目標分辨率相同大小,而z座標則不做處理。注意,目標分辨率不一定等同於屏幕分辨率。這個階段將輸出屏幕座標系下的頂點座標、頂點深度、頂點法線方向、視角方向等頂點屬性。

在OpenGL中,屏幕的左下角爲屏幕座標系原點,右上角爲座標最大值。而在DirectX中,屏幕左上角爲最小屏幕座標,而右下角爲最大屏幕座標。這個差異是OpenGL和DirectX很多不兼容性產生的源泉。

光柵化階段/像素階段

三角形設置是如何工作的?

這個階段由視口變換器負責,不可編輯。

在這個階段,視口變換器先將頂點座標轉化成像素座標,也就獲得三角形的像素座標,即網格。通過頂點的像素座標,GPU得知哪些網格與哪些頂點有關,並在掃描的同時將網格打包給對應的GPC處理。

GPU的處理策略是,處理過某批頂點的SM,儘量用來處理由同一批頂點生成的片元。

三角形遍歷是如何工作的?

視口變換器將打包好的網格數據交給GPC後,GPC會將這些網格交給名爲ROP(Raster Operations Units,光柵化引擎)的元件,在這裏網格被進掃描變換(Scan Conversion),ROP中的元件ROPU並行地計算像素是否被網格覆蓋,如果是,則產生一個片元(fragment),其中片元的狀態是對網格3個頂點的信息進行插值得到的。ROP不可編程。

在生成片元的同時,ROP還會同時進行裁剪、背面剔除和早期深度剔除。這幾個操作是可以通過API進行配置的,但不可編程。

片元還不是一個像素(pixel),一個片元是用於生成一個像素的數據包,它包含了座標、顏色、深度、法線、導數和紋理座標等一系列計算像素所需要的數據。而像素則是片元經過整個光柵化階段後,由片元所含的數據計算得出的,僅包含座標和顏色信息。

在生成片元后,ROP將片元分配給同一個GPC中的幾個SM。

片元着色器是如何工作的?

片元着色器可以深度編程,開發者需要根據提供的片元數據輸出一個像素顏色。

束管理器會將片元數據存入寄存器,然後將片元着色器代碼存入指令緩存,並要求指令分派單元從指令緩存中讀取指令分派給SP。每一批次中指令會從寄存器中取出若干片元數據開始處理,如果一束有32個線程,則就是32個片元,準確來說是8個2x2的片元塊,2x2是片元着色器的最小工作單位。所有線程運行完後,寄存器中必鬚生成所有片元的目標顏色。

這些計算得到的目標顏色會和片元座標一起存入L1緩衝,然後由GPU將多個SM的結果組合衝入L2緩存。

爲什麼片元着色器使用2x2的工作單位?

在片元着色器中會將四個相鄰像素作爲不可分割的一組送入同一個SM內的4個不同的SP中。這麼做可以精簡和加速像素分派的工作並精簡SM的架構,降低功耗。注意,2x2塊中可能存在無效像素,當網格覆蓋的片元不是完整的2x2塊時,比如說一個網格只覆蓋了單個片元,那麼在進入片元着色器時,會將它與相鄰的3個空片元綁定到一起,這會導致有3個SP空轉。在極端環境下,整個網格可能全部都處於這樣的狀態,使得SM的效率低至25%。這種爲了覆蓋完整2x2片元而浪費資源的情況被稱爲過度渲染(Over Draw)

逐片元操作(輸出合併階段)是如何工作的?

這個步驟在顯卡上的一個名爲渲染輸出器的元件中實現,它從L2緩存中按照三角形的原始API順序讀取片元,處理可見性測試和混合。這兩個個過程是可配置的,但不可編程。

以模板測試和深度測試爲例:渲染輸出器會首先將片元與模板緩衝(Stencil Buffer)中的模板值比對,捨棄沒有通過模板測試的片元。片元通過模板測試後,渲染輸出器就會將該片元與深度緩衝(Z-Buffer)中的深度信息比對進行深度測試,捨棄掉沒有通過深度測試的片元。通過深度測試的片元就會與後置緩衝區(Back Buffer)中的像素進行混合。由於數據是高度可並行的,渲染輸出器中的多個渲染輸出單元會並行的執行這個過程。

在像素混合時,深度和顏色的設置必須是原子操作,否則會發生同步異常。

在一次渲染結束後,視頻控制器會將後置緩衝區與前置緩衝區(Front Buffer)交換,而顯示器可以讀取前置緩衝區中的像素進行打印。使用前置和後置兩個緩衝區的這種策略被稱爲雙重緩衝區(Double Buffer)策略,二者合稱爲幀緩衝區(Frame Buffer),它可以保障顯示器顯示的連續性,由於渲染過程始終在幕後發送,可以避免顯示器打印出正在處理中的圖元以致於產生屏幕撕裂。

什麼是可見性測試?

透明度測試是簡單的將透明度低於開發者設置的閾值的片元丟棄,透明度測試不能用於實現半透明效果,只能用於實現鏤空效果。實現半透明應該使用透明度混合。

模板測試有一個對應的模板緩衝,這個緩衝區有一個大小等於目標分辨率的數據結構,對每一個像素儲存了一個整數。模板測試是高度可編程的,可以通過圖形API設定在模板緩衝中的什麼部位寫入什麼數值,在渲染其它網格時可以將這些數值讀取出來進行測試。比如“魔鏡”效果:場景中有一些通常不能被看到的“幽靈”物體,只有透過一個
“魔鏡”去觀察才能看到幽靈物體。這時我們可以讓“魔鏡”進行模板寫入,將其覆蓋的部分模板值寫爲1,而讓幽靈物體進行模板測試,只渲染模板值爲1的片元,以實現這個效果。

模板測試和模板寫入的編程自由度非常大,可以使用很多數學運算和邏輯運算,這使得模板測試有很多高級的用法,如渲染陰影和渲染輪廓等。

深度測試用於拋棄那些被其它片元遮擋的片元,它是高度可定製但不可編程的。與深度測試對應的是深度寫入,深度緩衝記錄着當前離攝像機最近的片元的深度座標,只有深度比這個片元更小的片元纔有權利通過深度測試並將新的深度寫入對應區域。我們可以關閉深度測試或深度寫入,比如透明或半透明物體應該關閉深度寫入,因爲我們不希望透明物體遮擋它背後的片元。

什麼是混合?

在一個片元寫入後置緩衝區中時,後置緩衝區中的對應位置有可能已經存在有像素信息了。妥善的處理舊的和新的像素信息就是混合要做的事。混合操作是高度可訂製但不可編程的。

最常見的,同時也是默認混合策略是覆蓋(Cover),有時叫做關閉混合(Blend Off)。覆蓋策略將舊像素信息丟棄而用新像素信息重寫它,這種策略在所有不透明物體上使用,因爲通過了深度測試的不透明新片元顯然會遮擋舊片元。

對半透明物體來說,最常見的混合策略是透明度混合(Alpha Blend),其思路是將新舊像素對透明度帶權進行加法,得到新的顏色存入後置緩衝區。

總結歸納

流水線中有多少緩衝區?
  • 頂點緩衝區(Vertex Buffer):由於GPU與CPU是異步的,頂點緩衝區被用於平衡兩種速度不一致的硬件。通過頂點緩衝區,GPU可以訪問CPU設定的頂點數組,通過圖形API我們可以手動定製頂點緩衝區的大小。
  • 幀緩衝區(Frame Buffer):分爲前置緩衝區和後置緩衝區,通過交換兩個緩衝區可以保證顯示器渲染的連續性,避免屏幕撕裂。幀緩衝區的大小主要由顏色緩衝區的大小決定。
  • 顏色緩衝區(Color Buffer):顏色緩衝區是幀緩衝區的一部分,和幀緩衝區、顯示器中的視頻控制器相連。顏色緩衝區早期用4個字節來儲存顏色,俗稱十六位圖,但現在的計算機一般通過32位RGBA儲存顏色,俗稱真彩。實現了HDR技術的顯示器配置的顯卡,可能具有64位RGBA的顏色緩衝區。
  • 深度緩衝區(Z-Buffer):如果場景中兩個物體在同一個像素產生片元,GPU會比較二者的深度,保留離觀察者較近的物體。如果兩個片元的深度一致,由於GPU的並行性,無法確定某個片元始終處於另一個之上,進而使這兩個片元出現閃爍,這個效應被稱爲深度衝突(Z-Fighting)。深度緩衝位數過低時,深度衝突發生的可能性就會增加,目前的深度緩衝一般使用24位或32位精度。
  • 模板緩衝(Stencil Buffer):模板緩衝爲每個像素保存一個無符號整數值,這個值的含義由開發者定義。模板緩衝是完全面向開發者的緩衝區設計,可以用於實現很多有趣的功能。模板測試發生在透明度測試之後,深度測試之前。一般的模板緩衝使用8位無符號整數。
  • 幾何緩衝(Geometry Buffer,或G-Buffer):詳情見什麼是前向渲染,什麼是延遲渲染?
緩衝區內存該如何計算?

假設屏幕在真彩模式下顯示一個2160×1080的圖像,那麼每個像素需要4個字節儲存顏色,那麼單個顏色緩衝需要的空間是:
216010804B=8.90MB 2160*1080*4B=8.90MB
使用雙緩衝區技術,則空間翻倍,每個像素使用8個字節儲存顏色,再加上24位的深度緩衝,8位的模板緩衝,現在佔用的空間是:
21601080(24B+3B+1B)=26.70MB 2160*1080*(2*4B+3B+1B)=26.70MB
如果使用抗鋸齒處理,比如超級採樣或多重採樣,需要的儲存空間會更多。

什麼是前向渲染,什麼是延遲渲染?

前向渲染和延遲渲染是兩種光照渲染模式。

假設有一個光源和1000個具有光照反射的三角形在NDC沿z軸正方向延申擺放,法線與z軸平行,所有三角形全等,旋轉和縮放相同,僅有z軸座標不同。從屏幕上實際你只能看到一個帶光照的三角形,其它的都被擋住了。

前向渲染會這樣做:

  • 取出一個片元
  • 進行深度檢測,拋棄沒有通過的片元
  • 片元着色器對通過的片元進行光照計算
  • 更新幀緩衝區
  • 返回第一步直到遍歷結束

由於GPU的並行性,我們不能控制GPU取出片元的順序。在極端條件下,1000次深度檢測全部都能通過,那麼光照計算會進行1000次,但由於實際上999次都被覆蓋了,所以有999次多餘計算。

延遲渲染引入了G-Buffer,它會這樣做:

  • 取出一個片元
  • 進行深度檢測,拋棄沒有通過的片元
  • 對通過的片元,將座標、光照等信息寫入G-Buffer
  • 返回第一步直到遍歷結束
  • 從G-Buffer中取出一個像素的幾何信息
  • 片元着色器利用G-Buffer中的信息進行光照計算
  • 更新幀緩衝區
  • 返回第五步直到遍歷結束

延遲渲染把參數保存了下來,沒有像前向渲染那樣邊運行片元着色器邊進行輸出合併,而是先完成完整的深度檢測,再運行片元着色器,對於每個像素只進行一次光照計算就實現了效果,大大節約了光照計算複雜度。光源越多、計算越複雜,節省下的性能就越明顯。

然而,延遲渲染只能給屏幕上的每一個點保存一份光照數據,所以如果這些三角形都是半透明的,延遲渲染就不能體現出半透明的細節。換句話來說,延遲渲染完全不支持Blend。同理,延遲渲染也不能實現多重採樣抗鋸齒的功能

一般的G-Buffer精度爲64位,舊的分配方式是分別使用16位浮點數儲存Normal.x、Normal.y、深度信息和漫反射顏色(十六位圖)。一種新的分配模式是去掉深度,同時使用8位浮點數分別儲存Normal.x、Normal.y、漫反射顏色、高光顏色,再使用24位儲存RGB色彩,這樣還留下了一個空閒的8位通道用作機動,並且色彩精度也提升了。新分配模式的問題是normal位數下降了很多必須通過片元着色器來代行平滑。

新的支持延遲渲染的顯卡可能提供超過64位的精度,可以使延遲渲染的效果更上一層樓。

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