GPU圖形繪製管線
摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文名“GPU程式設計與CG語言之陽春白雪下里巴人”第二章。
圖形繪製管線描述GPU渲染流程,即“給定視點、三維物體、光源、照明模式,和紋理等元素,如何繪製一幅二維影象”。本章內容涉及GPU的基本流程和實時繪製技術的根本原理,在這些知識點之上才能延伸發展出基於GPU的各項技術,所以本章的重要性怎麼說都不為過。欲登高而窮目,勿築臺於浮沙!
本章首先討論整個繪製管線(不僅僅是GPU繪製)所包含的不同階段,然後對每個階段進行獨立闡述,最後講解GPU上各類緩衝器的相關知識點。
在《實時計算機圖形學》一書中,將圖形繪製管線分為三個主要階段:應用程式階段、幾何階段、光柵階段。
應用程式階段,使用高階程式語言(C、C++、JAVA等)進行開發,主要和CPU、記憶體打交道,諸如碰撞檢測、場景圖建立、空間八叉樹更新、視錐裁剪等經典演算法都在此階段執行。在該階段的末端,幾何體資料(頂點座標、法向量、紋理座標、紋理等)通過資料匯流排傳送到圖形硬體(時間瓶頸);資料匯流排是一個可以共享的通道,用於在多個裝置之間傳送資料;埠是在兩個裝置之間傳送資料的通道;頻寬用來描述埠或者總線上的吞吐量,可以用每秒位元組(b/s)來度量,資料匯流排和埠(如加速圖形埠,Accelerated Graphic Port,AGP)將不同的功能模組“粘接”在一起。由於埠和資料匯流排均具有資料傳輸能力,因此通常也將埠認為是資料匯流排(實時計算機圖形學387 頁)。
幾何階段,主要負責頂點座標變換、光照、裁剪、投影以及螢幕對映(實時計算機圖形學234頁),該階段基於GPU進行運算,在該階段的末端得到了經過變換和投影之後的頂點座標、顏色、以及紋理座標(實時計算機圖形學10頁)。
光柵階段,基於幾何階段的輸出資料,為畫素(Pixel)正確配色,以便繪製完整影象,該階段進行的都是單個畫素的操作,每個畫素的資訊儲存在顏色緩衝器(color buffer或者frame buffer)中。
值得注意的是:光照計算屬於幾何階段,因為光照計算涉及視點、光源和物體的世界座標,所以通常放在世界座標系中進行計算;而霧化以及涉及物體透明度的計算屬於光柵化階段,因為上述兩種計算都需要深度值資訊(Z值),而深度值是在幾何階段中計算,並傳遞到光柵階段的。
下面具體闡述從幾何階段到光柵化階段的詳細流程。
2.1 幾何階段
幾何階段的主要工作是“變換三維頂點座標”和“光照計算”,顯示卡資訊中通常會有一個標示為“T&L”硬體部分,所謂“T&L”即 Transform & Lighting。那麼為什麼要對三維頂點進行座標空間變換?或者說,對三維頂點進行座標空間變換有什麼用?為了解釋這個問題,我先引用一段文獻【3】中的一段敘述:
Because, your application supplies the geometric data as a collection of vertices, but the resulting image typically represents what an observer or camera would see from a particular vantage point.
As the geometric data flows through the pipeline, the GPU’s vertex processor transforms the continuant vertices into one or more different coordinate system, each of which serves a particular purpose. CG vertex programs provide a way for you to program these transformations yourself.
上述英文意思是:輸入到計算機中的是一系列三維座標點,但是我們最終需要看到的是,從視點出發觀察到的特定點(這句話可以這樣理解,三維座標點,要使之顯示在二維的螢幕上)。一般情況下,GPU幫我們自動完成了這個轉換。基於GPU的頂點程式為開發人員提供了控制頂點座標空間轉換的方法。
一定要牢記,顯示屏是二維的,GPU所需要做的是將三維的資料,繪製到二維螢幕上,併到達“躍然紙面”的效果。頂點變換中的每個過程都是為了這個目的而存在,為了讓二維的畫面看起具有三維立體感,為了讓二維的畫面看起來“躍然紙面”。
根據頂點座標變換的先後順序,主要有如下幾個座標空間,或者說座標型別:Object space,模型座標空間;World space,世界座標系空間;Eye space,觀察座標空間;Clip and Project space,螢幕座標空間。圖 3表述了GPU的整個處理流程,其中茶色區域所展示的就是頂點座標空間的變換流程。大家從中只需得到一個大概的流程順序即可,下面將詳細闡述空間變換的每個獨立階段。
2.1.1 從object space到world space
When an artist creates a 3D model of an object, the artist selects a convenient orientation and position with which to place the model’s continent vertices.
The object space for one object may have no relationship to the object space of another object.【3】
上述語句表示了object space 的兩層核心含義:其一,object space coordinate就是模型檔案中的頂點值,這些值是在模型建模時得到的,例如,用3DMAX建立一個球體模型並匯出為.max檔案,這個檔案中包含的資料就是 object space coordinate;其二,object space coordinate與其他物體沒有任何參照關係,注意,這個概念非常重要,它是將object space coordinate和world space coordinate區分開來的關鍵。無論在現實世界,還是在計算機的虛擬空間中,物體都必須和一個固定的座標原點進行參照才能確定自己所在的位置,這是 world space coordinate的實際意義所在。
毫無疑問,我們將一個模型匯入計算機後,就應該給它一個相對於座標原點的位置,那麼這個位置就是world space coordinate,從object space coordinate 到world space coordinate的變換過程由一個四階矩陣控制,通常稱之為world matrix。
光照計算通常是在world coordinate space(世界座標空間)中進行的,這也符合人類的生活常識。當然,也可以在eye coordinate space中得到相同的光照效果,因為,在同一觀察空間中物體之間的相對關係是儲存不變的。
需要高度注意的是:頂點法向量在模型檔案中屬於object space,在GPU的頂點程式中必須將法向量轉換到world space中才能使用,如同必須將頂點座標從object space轉換到world space中一樣,但兩者的轉換矩陣是不同的,準確的說,法向量從object space到world space的轉換矩陣是world matrix的轉置矩陣的逆矩陣(許多人在頂點程式中會將兩者的轉換矩陣當作同一個,結果會出現難以查詢的錯誤)。(參閱潘李亮的3D變換中法向量變換矩陣的推導一文)
可以閱讀電子工業出版社的《計算機圖形學(第二版)》第11章,進一步瞭解三維頂點變換具體的計算方法,如果對矩陣運算感到陌生,則有必要複習一下線性代數。
2.1.2 從world space到eye space
每個人都是從各自的視點出發觀察這個世界,無論是主觀世界還是客觀世界。同樣,在計算機中每次只能從唯一的視角出發渲染物體。在遊戲中,都會提供視點漫遊的功能,螢幕顯示的內容隨著視點的變化而變化。這是因為GPU將物體頂點座標從world space轉換到了eye space。
所謂eye space,即以camera(視點或相機)為原點,由視線方向、視角和遠近平面,共同組成一個梯形體的三維空間,稱之為viewing frustum(視錐),如圖 4所示。近平面,是梯形體較小的矩形面,作為投影平面,遠平面是梯形體較大的矩形,在這個梯形體中的所有頂點資料是可見的,而超出這個梯形體之外的場景資料,會被視點去除(Frustum Culling,也稱之為視錐裁剪)。
2.1.3 從eye space到project and clip space
Once positions are in eye space, the next step is to determine what positions are actually viewable in the image you eventually intend trend.【3】
即:一旦頂點座標被轉換到eye space中,就需要判斷哪些點是視點可見的。位於viewing frustum梯形體以內的頂點,被認定為可見,而超出這個梯形體之外的場景資料,會被視點去除(Frustum Culling,也稱之為視錐裁剪)。這一步通常稱之為“clip(裁剪)”,識別指定區域內或區域外的圖形部分的過程稱之為裁剪演算法。
很多人在理解該步驟時存在一個混亂,即“不清楚裁減與投影的關係和兩者發生的先後順序”,不少人覺得是“先裁減再投影”,不過事實並非如此。因為在不規則的體(viewing frustum)中進行裁剪並非易事,所以經過圖形學前輩們的精心分析,裁剪被安排到一個單位立方體中進行,該立方體的對角頂點分別是(-1,-1,- 1)和(1,1,1),通常稱這個單位立方體為規範立方體(Canonical view volume, CVV)(實時計算機圖形學第9頁)。CVV的近平面(梯形體較小的矩形面)的X、Y座標對應螢幕畫素座標(左下角是0、0),Z座標則是代表畫面畫素深度。
多邊形裁剪就是CVV中完成的。所以,從視點座標空間到螢幕座標空間(screen coordinate space)事實上是由三步組成:
1.用透視變換矩陣把頂點從視錐體中變換到裁剪空間的CVV中;
2.在CVV進行圖元裁剪;
3.螢幕對映:將經過前述過程得到的座標對映到螢幕座標系上。
在這裡,我們尤其要注意第一個步驟,即把頂點從viewing frustum變換到CVV中,這個過程才是我們常說或者聽說的“投影”。主要的投影方法有兩種:正投影(也稱平行投影)和透視投影。由於投影投影更加符合人類的視覺習慣,所以在附錄中會詳細講解透視投影矩陣的推導過程,有興趣的朋友可以查閱潘巨集(網名Twinsen)的“透視投影變換推導”一文。更詳細全面的投影演算法可以近一步閱讀《計算機圖形學(第二版)》第12章第3節。
確定只有當圖元完全或部分的存在於視錐內部時,才需要將其光柵化。當一個圖元完全位於視體(此時視體以及變換為CVV)內部時,它可以直接進入下一個階段;完全在視體外部的圖元,將被剔除;對於部分位於視體內的圖元進行裁減處理。詳細的裁剪演算法可以近一步閱讀《計算機圖形學(第二版)》第12章第5節。
附1:透視投影矩陣的推導過程,建議閱讀潘巨集(網名Twinsen)的“透視投影變換推導”一文。
附2:視點去除,不但可以在GPU中進行,也可以使用高階語言(C\C++)在CPU上實現。使用高階語言實現時,如果一個場景實體完全不在視錐中,則該實體的網格資料不必傳入GPU,如果一個場景實體部分或完全在視錐中,則該實體網格資料傳入GPU中。所以如果在高階語言中已經進行了視點去除,那麼可以極大的減去GPU的負擔。使用C++進行視錐裁剪的演算法可以參閱OGRE(Object-Oriented Graphics Rendering Engine,面向物件的圖形渲染引擎)的原始碼。
2.2 Primitive Assembly && Triangle setup
Primitive Assembly,圖元裝配,即將頂點根據primitive(原始的連線關係),還原出網格結構。網格由頂點和索引組成,在之前的流水線中是對頂點的處理,在這個階段是根據索引將頂點連結在一起,組成線、面單元。之後就是對超出螢幕外的三角形進行裁剪,想象一下:一個三角形其中一個頂點在畫面外,另外兩個頂點在畫面內,這是我們在螢幕上看到的就是一個四邊形。然後將該四邊形切成兩個小的三角形。
此外還有一個操作涉及到三角形的頂點順序(其實也就是三角形的法向量朝向),根據右手定則來決定三角面片的法向量,如果該法向量朝向視點(法向量與到視點的方向的點積為正),則該面是正面。一般是頂點按照逆時針排列。如果該面是反面,則進行背面去除操作(Back-face Culling)。在OpenGL中有專門的函式enable和disable背面去除操作。所有的裁剪剔除計算都是為了減少需要繪製的頂點個數。
附:在2.2和2.3節都提到了裁減的概念,實際裁減是一個較大的概念,為了減少需要繪製的頂點個數,而識別指定區域內或區域外的圖形部分的演算法都稱之為裁減。裁減演算法主要包括:視域剔除(View Frustum Culling)、背面剔除(Back-Face Culling)、遮擋剔除(Occlusing Culling)和視口裁減等。
處理三角形的過程被稱為Triangle Setup。到目前位置,我們得到了一堆在螢幕座標上的三角面片,這些面片是用於做光柵化的(Rasterizing)。
2.3 光柵化階段
2.3.1 Rasterization
光柵化:決定哪些畫素被集合圖元覆蓋的過程(Rasterization is the process of determining the set of pixels covered by a geometric primitive)。經過上面諸多座標轉換之後,現在我們得到了每個點的螢幕座標值(Screen coordinate),也知道我們需要繪製的圖元(點、線、面)。但此時還存在兩個問題,
問題一:點的螢幕座標值是浮點數,但畫素都是由整數點來表示的,如果確定螢幕座標值所對應的畫素?
問題二:在螢幕上需要繪製的有點、線、面,如何根據兩個已經確定位置的2個畫素點繪製一條線段,如果根據已經確定了位置的3個畫素點繪製一個三角形面片?
首先回答一下問題一,“繪製的位置只能接近兩指定端點間的實際線段位置,例如,一條線段的位置是(10.48,20.51),轉換為畫素位置則是(10,21)”(計算機圖形學(第二版)52頁)。
對於問題二涉及到具體的畫線演算法,以及區域圖元填充演算法。通常的畫線演算法有DDA演算法、Bresenham畫線演算法;區域圖元填充演算法有,掃描線多邊形填充演算法、邊界填充演算法等,具體請參閱《計算機圖形學(第二版)》第3章。
這個過程結束之後,頂點(vertex)以及繪製圖元(線、面)已經對應到畫素(pixel)。下面闡述的是“如何處理畫素,即:給畫素賦予顏色值”。
2.3.2 Pixel Operation
Pixel operation又稱為Raster Operation(在文獻【2】中是使用Raster Operation),是在更新幀快取之前,執行最後一系列針對每個片段的操作,其目的是:計算出每個畫素的顏色值。在這個階段,被遮擋面通過一個被稱為深度測試的過程而消除,這其中包含了很多種計算顏色的方法以及技術。Pixel operation包含哪些事情呢?
1:消除遮擋面
2:Texture operation,紋理操作,也就是根據畫素的紋理座標,查詢對應的紋理值;
3:Blending
混色,根據目前已經畫好的顏色,與正在計算的顏色的透明度(Alpha),混合為兩種顏色,作為新的顏色輸出。通常稱之為alpha混合技術。當在螢幕上繪製某個物體時,與每個畫素都相關聯的喲一個RGB顏色值和一個Z緩衝器深度值,另外一個稱為是alpha值,可以根據需要生成並存儲,用來描述給定畫素處的物體透明度。如果alpha值為1.0,則表示物體不透明;如果值為0,表示該物體是透明的,
從繪製管線得到一個RGBA,使用over操作符將該值與原畫素顏色值進行混合,公式如下:
4:Filtering,將正在算的顏色經過某種Filtering(濾波或者濾鏡)後輸出。可以理解為:經過一種數學運算後變成新的顏色值。
該階段之後,畫素的顏色值被寫入幀快取中。圖 5來自文獻【2】1.2.3,說明了畫素操作的流程: