第02章 GPU 圖形繪制管線(xiàn)
萬(wàn)事開(kāi)頭難,每門(mén)科學(xué)都是如此。
------ 馬克思
圖形繪制管線(xiàn)描述 GPU 渲染流程,即“給定視點(diǎn)、三維物體、光源、照明模式,和紋理等元素,如何繪制一幅二維圖像”。本章內(nèi)容涉及 GPU 的基本流程和實(shí)時(shí)繪制技術(shù)的根本原理,在這些知識(shí)點(diǎn)之上才能延申發(fā)展出基于 GPU 的各項(xiàng)技術(shù),所以本章的重要性怎么說(shuō)都不為過(guò)。欲登高而窮目,勿筑臺(tái)于浮沙!
本章首先討論整個(gè)繪制管線(xiàn)(不僅僅是 GPU 繪制)所包含的不同階段,然后對(duì)每個(gè)階段進(jìn)行獨(dú)立闡述,最后講解 GPU 上各類(lèi)緩沖器的相關(guān)知識(shí)點(diǎn)。
在《實(shí)時(shí)計(jì)算機(jī)圖形學(xué)》一書(shū)中,將圖形繪制管線(xiàn)分為三個(gè)階段:應(yīng)用程序階段、幾何階段、光柵化階段。
應(yīng)用程序階段,使用高級(jí)編程語(yǔ)言(C/C++、Java、OC等等)進(jìn)行開(kāi)發(fā),主要和 CPU、內(nèi)存打交道,諸如碰撞檢測(cè)、場(chǎng)景圖建立、空間八叉樹(shù)更新、視錐裁剪等經(jīng)典算法都在此階段執(zhí)行。在該階段的末端,幾何體數(shù)據(jù)(頂點(diǎn)坐標(biāo)、法向量、紋理坐標(biāo)、紋理等)通過(guò)數(shù)據(jù)總線(xiàn)傳送到圖形硬件(時(shí)間瓶頸);數(shù)據(jù)總線(xiàn)是一個(gè)可以共享的通道,用于在多個(gè)設(shè)備之間傳送數(shù)據(jù);端口是在兩個(gè)設(shè)備之間傳送數(shù)據(jù)的通道;帶寬用來(lái)描述端口或者總線(xiàn)上的吞吐量,可以用每秒字節(jié)(b/s)來(lái)度量,數(shù)據(jù)總線(xiàn)和端口(如加速圖形端口,Accelerated Graphic Port,AGP)將不同的功能模塊“粘接”在一起。由于端口和數(shù)據(jù)總線(xiàn)均具有數(shù)據(jù)傳輸能力,因此通常也將端口認(rèn)為是數(shù)據(jù)總線(xiàn)(《實(shí)時(shí)計(jì)算機(jī)圖形學(xué)》387頁(yè))
幾何階段,主要負(fù)責(zé)頂點(diǎn)坐標(biāo)變換、光照、裁剪、投影以及屏幕映射(《實(shí)時(shí)計(jì)算機(jī)圖形學(xué)》234頁(yè)),該階段基于 GPU 進(jìn)行運(yùn)算,在該階段的末端得到了經(jīng)過(guò)變換和投影之后的頂點(diǎn)坐標(biāo)、顏色、以及紋理坐標(biāo)(《實(shí)時(shí)計(jì)算機(jī)圖形學(xué)》10頁(yè))。
光柵化階段,基于幾何階段的輸出數(shù)據(jù),為像素(Pixel)正確配色,以便繪制完整圖像,該階段進(jìn)行的都是單個(gè)像素的操作,每個(gè)像素的信息存儲(chǔ)在顏色緩沖區(qū)(Color Buffer 或者 Frame Buffer)中。
值得注意的是:光照計(jì)算屬于幾何階段,因?yàn)楣庹沼?jì)算涉及視點(diǎn)、光源和物體的世界坐標(biāo),所以通常放在世界坐標(biāo)系中進(jìn)行計(jì)算;而霧化以及涉及物體透明度的計(jì)算屬于光柵化階段,因?yàn)樯鲜鰞煞N計(jì)算都需要深度值信息(Z 值),而深度值是幾何階段中計(jì)算,并傳遞到光柵化階段的。
下面具體闡述從幾何階段到光柵化階段的詳細(xì)過(guò)程。
2.1 幾何階段
在看這部分內(nèi)容之前我們先簡(jiǎn)單描述一下應(yīng)用程序階段主要做了些什么:
從名字上我們可以看出,這個(gè)階段是由我們的應(yīng)用主導(dǎo)的,因此通常由 CPU 負(fù)責(zé)實(shí)現(xiàn)。換句話(huà)說(shuō),我們這些開(kāi)發(fā)者具有這個(gè)階段的絕對(duì)控制權(quán)。
在這一階段中,開(kāi)發(fā)者有 3 個(gè)主要任務(wù):
首先,我們需要準(zhǔn)備好場(chǎng)景數(shù)據(jù),例如攝像機(jī)的位置、視錐體、場(chǎng)景中包含了哪些模型、使用了哪些光源等等;
其次,為了提高渲染性能,我們往往需要做一個(gè)粗粒度剔除(culling)工作,以把那些不可見(jiàn)的物體剔除出去,這樣就不需要再移交給幾何階段進(jìn)行處理;
最后,我們需要設(shè)置好每個(gè)模型的渲染狀態(tài)。這些渲染狀態(tài)包括但不限于它使用的材質(zhì)(漫反射顏色、高光反射顏色)、使用的紋理、使用的 Shader 等。這一階段最重要的輸出是渲染所需的幾何信息,即渲染圖元(rendering primitives)。通俗來(lái)講,渲染圖元可以是點(diǎn)、線(xiàn)、三角面等。這些渲染圖元會(huì)被傳遞到下一階段,也就是我們將要詳細(xì)講解的——幾何階段。
幾何階段的主要工作是“變換三維頂點(diǎn)坐標(biāo)”和“光照計(jì)算”,顯卡信息中通常會(huì)有一個(gè)標(biāo)示為“T&L”的硬件部分,所謂“T&L”即 Transform & Lighting。那么為什么要對(duì)三維頂點(diǎn)進(jìn)行坐標(biāo)空間變換?或者說(shuō),對(duì)三維頂點(diǎn)進(jìn)行坐標(biāo)空間變換有什么用?為了解釋這個(gè)問(wèn)題,我先引用一個(gè)段文獻(xiàn)【3】中的一段敘述:
Because, your application supplies the geomertric 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 proprams provide a way for you to program these tansformations yourself.
上述英文意思是:輸入到計(jì)算機(jī)中的是一系列三維坐標(biāo)點(diǎn),但是我們最終需要看到的是,從視點(diǎn)出發(fā)觀察到的特定點(diǎn)(這句話(huà)可以這樣理解,三維坐標(biāo)點(diǎn),要使之顯示在二維的屏幕上)。一般情況下,GPU 幫我們自動(dòng)完成了這個(gè)轉(zhuǎn)換?;?GPU 的頂點(diǎn)程序?yàn)殚_(kāi)發(fā)人員提供了控制頂點(diǎn)坐標(biāo)空間轉(zhuǎn)換的方法。
一定要牢記,顯示屏是二維的,GPU 所需要做的是將三維的數(shù)據(jù),繪制到二維屏幕上,并達(dá)到“躍然紙上”的效果。頂點(diǎn)變換中的每個(gè)過(guò)程都是為了這個(gè)目的而存在,為了讓二維的畫(huà)面看起具有三維立體感,為了讓二維的畫(huà)面看起來(lái)“躍然紙上”。
根據(jù)頂點(diǎn)坐標(biāo)變換的先后順序,主要有如下幾個(gè)坐標(biāo)空間,或者說(shuō)坐標(biāo)類(lèi)型:Object Space,模型坐標(biāo)空間;World Space,世界坐標(biāo)空間;Eye Space,觀察者坐標(biāo)空間;Clip and Project Space,屏幕坐標(biāo)空間。圖 3 表述了 GPU 的整個(gè)處理流程,其中茶色區(qū)域所展示的就是頂點(diǎn)坐標(biāo)空間的變換流程。大家從中只需得到一個(gè)大概的流程順序即可,下面將詳細(xì)闡述空間變換的每個(gè)獨(dú)立階段。

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】
上述語(yǔ)句表示了 Object Space 的兩層核心含義:其一,Object Space Coordinate 就是模型文件中的頂點(diǎn)值,這些值是在模型建模時(shí)得到的,例如,用 3DMAX 建立一個(gè)球體模型并導(dǎo)出為 .max 文件,這個(gè)文件中包含的數(shù)據(jù)就是 Object Space Coordinate;其二,Object Space Coordinate 于其他物體沒(méi)有任何參照關(guān)系,注意,這個(gè)概念非常重要,它是將 Object Space Coordinate 和 World Space Coordinate 區(qū)分開(kāi)來(lái)的關(guān)鍵。無(wú)論在現(xiàn)實(shí)世界,還是在計(jì)算機(jī)的虛擬空間中,物體都必須和一個(gè)固定坐標(biāo)原點(diǎn)進(jìn)行參照才能確定自己所在的位置,這是 World Space Coordinate 的實(shí)際意義所在。
毫無(wú)疑問(wèn),我們將一個(gè)模型導(dǎo)入計(jì)算機(jī)后,就應(yīng)該給它一個(gè)相對(duì)于坐標(biāo)原點(diǎn)的位置,那么這個(gè)位置就是 World Space Coordinate,從 Object Space Coordinate 到 World Space Coordinate 的變換過(guò)程由一個(gè)四階矩陣控制,通常稱(chēng)之為 World Matrix。
光照計(jì)算通常是在 World Space Coordinate(世界坐標(biāo)空間)中進(jìn)行的,這也符合人類(lèi)的生活常識(shí)。當(dāng)然,也就可以在 Eye Coordinate Space 中得到相同的光照效果,因?yàn)?,在同一個(gè)觀察空間中物體之間的相對(duì)關(guān)系是保持不變的。
需要高度注意的是:頂點(diǎn)法向量在模型文件中屬于 Object Space,在 GPU 的頂點(diǎn)程序中必須將法向量轉(zhuǎn)換到 World Space 中才能使用,如同必須將頂點(diǎn)坐標(biāo)從 Object Space 轉(zhuǎn)換到 World Space 中一樣,但兩者的轉(zhuǎn)換矩陣是不同的,準(zhǔn)確的說(shuō),法向量從 Object Space 到 World Space 的轉(zhuǎn)換矩陣是 World Matrix 的轉(zhuǎn)置矩陣的逆矩陣(許多人在頂點(diǎn)程序中會(huì)將兩者的轉(zhuǎn)換矩陣當(dāng)作同一個(gè),結(jié)果會(huì)出現(xiàn)難以查找的錯(cuò)誤)。(參閱潘李亮的 3D 變換中法向量變換矩陣的推導(dǎo)一文)
可以閱讀電子工業(yè)出版社的《計(jì)算機(jī)圖形學(xué)(第二版)》第11章,進(jìn)一步了解三維頂點(diǎn)變換具體的計(jì)算方法,如果對(duì)矩陣運(yùn)算感到陌生,則有必要復(fù)習(xí)一下線(xiàn)性代數(shù)。
2.1.2 從 World Space 到 Eye Space
每個(gè)人都是從各自的視點(diǎn)出發(fā)觀察這個(gè)世界,無(wú)論是主觀世界還是客觀世界。同樣,在計(jì)算機(jī)中每次只能從唯一的視角出發(fā)渲染物體。在游戲中,都會(huì)提供視點(diǎn)漫游的功能,屏幕顯示的內(nèi)容隨著視點(diǎn)的變化而變化。這是因?yàn)?GPU 將物體頂點(diǎn)坐標(biāo)從 World Space 轉(zhuǎn)換到了 Eye Space。
所謂 Eye Space,即以 Camera (視點(diǎn)或相機(jī))為原點(diǎn),由視線(xiàn)方向、視角和遠(yuǎn)近平面,共同組成一個(gè)梯形體的三維空間,稱(chēng)之為 Viewing Frustum(視錐),如圖 4 所示。近平面,是梯形體較小的矩形面,作為投影平面,遠(yuǎn)平面是梯形體較大的矩形,在這個(gè)梯形體中的所有頂點(diǎn)數(shù)據(jù)都是可見(jiàn)的,而超出這個(gè)梯形體之外的場(chǎng)景數(shù)據(jù),會(huì)被視點(diǎn)去除(Frustum Culling,也稱(chēng)之為視錐裁剪)。

2.1.3 從 Eye Space 到 Project and Clip Space
Once positions are in eye space, the next step is to determine what postions are actually viewable in the image you eventually intend trend.【3】
即:一旦頂點(diǎn)坐標(biāo)被轉(zhuǎn)換到 Eye Space 中,就需要判斷哪些點(diǎn)是視點(diǎn)可見(jiàn)的。位于 Viewing Frustum 梯形體以?xún)?nèi)的頂點(diǎn),被認(rèn)定為可見(jiàn),而超出這個(gè)梯形體以外的場(chǎng)景數(shù)據(jù),會(huì)被視點(diǎn)去除(Frustum Culling,也稱(chēng)之為視錐裁剪)。這一步通常稱(chēng)之為“Clip(裁剪)”,識(shí)別指定區(qū)域內(nèi)或區(qū)域外的圖形部分的過(guò)程稱(chēng)之為裁剪算法。
很多人在理解該步驟時(shí)存在一個(gè)混亂,即“不清楚裁剪與投影的關(guān)系和兩者發(fā)生的先后順序”,不少人覺(jué)得是“先裁剪再投影”,不過(guò)事實(shí)并非如此。因?yàn)樵诓灰?guī)則的梯形體(viewing frustum)中進(jìn)行裁剪并非易事,所以經(jīng)過(guò)圖形學(xué)前輩們的精心分析,裁剪被安排到一個(gè)單位立方體中進(jìn)行,該立方體的對(duì)角頂點(diǎn)分別是(-1,-1,1)和(1,1,1),通常稱(chēng)這個(gè)單位立方體為規(guī)范立方體(Canonical View Volume,CVV)(實(shí)時(shí)計(jì)算機(jī)圖形學(xué)第 9 頁(yè))。CVV 的近平面(梯形體較小的矩形面)的X、Y坐標(biāo)對(duì)應(yīng)屏幕像素坐標(biāo)(左下角是0、0),Z 坐標(biāo)則是代表畫(huà)面像素深度。
多邊形裁剪就是 CVV 中完成的。所以,從視點(diǎn)坐標(biāo)空間到屏幕坐標(biāo)空間(Screen Coordinate Space)事實(shí)上是由三步組成:
1. 用透視變換矩陣把頂點(diǎn)從視錐體中變換到裁剪空間的 CVV 中;
2. 在 CVV 進(jìn)行圖元裁剪;
3. 屏幕映射:將經(jīng)過(guò)前述過(guò)程得到的坐標(biāo)映射到屏幕坐標(biāo)系上。
在這里,我們尤其要注意第一個(gè)步驟,即把頂點(diǎn)從 Viewing Frustum 變換到 CVV 中,這個(gè)過(guò)程才是我們常說(shuō)或者聽(tīng)說(shuō)的“投影”。主要的投影方法有兩種:正投影(也稱(chēng)平行投影)和透視投影。由于透視投影更加符合人類(lèi)的視覺(jué)習(xí)慣,所以在附錄中會(huì)詳細(xì)講解透視投影矩陣的推導(dǎo)過(guò)程,有興趣的朋友可以查閱潘宏(網(wǎng)名 Twinsen)的“透視投影變換推導(dǎo)”一文。更詳細(xì)全面的投影算法可以進(jìn)一步閱讀《計(jì)算機(jī)圖形學(xué)(第二版)》第12章-第3節(jié)。
確定只有哦當(dāng)圖元完全或部分的存在于視錐內(nèi)部時(shí),才需要將其光柵化。當(dāng)一個(gè)圖元完全位于視體(此時(shí)視體以及變換為 CVV)內(nèi)部時(shí),它可以直接進(jìn)入下一個(gè)階段;完全在視體外部的圖元,將被剔除;對(duì)于部分位于視體內(nèi)的圖元進(jìn)行裁剪處理。詳細(xì)的裁剪算法可以進(jìn)一步閱讀《計(jì)算機(jī)圖形學(xué)(第二版)》第12章-第5節(jié)。
附 1:透視投影矩陣的推導(dǎo)過(guò)程,建議閱讀潘宏(網(wǎng)名 Teinsen)的“透視投影變換推導(dǎo)”一文。
附 2:視點(diǎn)去除,不但可以在 GPU 中進(jìn)行,也可以使用高級(jí)語(yǔ)言(C\C++)在 CPU 上實(shí)現(xiàn)。使用高級(jí)語(yǔ)言實(shí)現(xiàn)時(shí),如果一個(gè)場(chǎng)景實(shí)體完全不在視錐中,則該實(shí)體的網(wǎng)格數(shù)據(jù)不必傳入 GPU,如果一個(gè)場(chǎng)景實(shí)體部分或完全在視錐中,則該實(shí)體網(wǎng)格數(shù)據(jù)傳入 GPU 中。所以如果在高級(jí)語(yǔ)言中已經(jīng)進(jìn)行了視點(diǎn)去除,那么可以極大的減去 GPU 的負(fù)擔(dān)。使用 C++ 進(jìn)行視錐裁剪的算法可以參閱 OGRE (Object-Oriented Graphics Rendering Engine,面向?qū)ο蟮膱D形渲染引擎)的源碼。
2.2 Primitive Assembly && Triangle Setup
Primitive Assembly,圖元裝配,即將頂點(diǎn)根據(jù) Primitive(原始的連接關(guān)系),還原出網(wǎng)格結(jié)構(gòu)。網(wǎng)格由頂點(diǎn)和索引組成,在之前的流水線(xiàn)中是對(duì)頂點(diǎn)的處理,在這個(gè)階段是根據(jù)索引將頂點(diǎn)鏈接在一起,組成線(xiàn)、面單元。之后就是對(duì)超出屏幕外的三角形進(jìn)行裁剪,想象一下:一個(gè)三角形其中一個(gè)頂點(diǎn)在畫(huà)面外,另外兩個(gè)頂點(diǎn)在畫(huà)面內(nèi),這時(shí)我們?cè)谄聊簧峡吹降木褪且粋€(gè)四邊形。然后將該四邊形切成兩個(gè)小的三角形。
此處還有一個(gè)操作涉及到三角形的頂點(diǎn)順序(其實(shí)也就是三角形的法向量朝向),根據(jù)右手定則來(lái)決定三角面片的法向量,如果該法向量朝向視點(diǎn)(法向量與到視點(diǎn)的方向的點(diǎn)積為正),則該面是正面。一般是頂點(diǎn)按照逆時(shí)針排列。如果該面是反面,則進(jìn)行背面去除操作(Back-Face Culling)。在 OpenGL 中有專(zhuān)門(mén)的函數(shù) enable 和 disable 背面去除操作。所有的裁剪剔除計(jì)算都是為了減少需要繪制的頂點(diǎn)個(gè)數(shù)。
附:在 2.2 和 2.3 節(jié)都提到了裁剪的概念,實(shí)際裁剪是一個(gè)較大的概念,為了減少需要繪制的頂點(diǎn)個(gè)數(shù),而識(shí)別制定區(qū)域內(nèi)或區(qū)域外的圖形部分的算法都稱(chēng)之為裁剪。裁剪算法主要包括:視域剔除(View Frustum Culling)、背面剔除(Back-Face Culling)、遮擋剔除(Occlusing Culling)和市口裁剪等。
處理三角形的過(guò)程被稱(chēng)為 Triangle Setup。到目前為止,我們得到了一堆在屏幕坐標(biāo)上的三角面片,這些面片是用于做光柵化的(Rasterizing)。
2.3 光柵化階段
這一階段將會(huì)使用上個(gè)階段傳遞的數(shù)據(jù)來(lái)產(chǎn)生屏幕上的像素,并渲染出最終的圖像。這一階段也是在 GPU 上運(yùn)行。光柵化的任務(wù)主要是決定每個(gè)渲染圖元中的哪些像素應(yīng)該被繪制在屏幕上。它需要對(duì)上一個(gè)階段得到的逐頂點(diǎn)數(shù)據(jù)(例如紋理坐標(biāo)、頂點(diǎn)顏色等)進(jìn)行插值,然后再進(jìn)行逐像素處理。
2.3.1 Rasterization
光柵化:決定哪些像素被集合圖元覆蓋的過(guò)程(Rasterization is the process of determining the set of pixels covered by a geometric primitive)。經(jīng)過(guò)上面諸多坐標(biāo)轉(zhuǎn)換之后,現(xiàn)在我們得到了每個(gè)點(diǎn)的屏幕坐標(biāo)值(Sceen Coordinate),也知道我們需要繪制的圖元(點(diǎn)、線(xiàn)、面)。但此時(shí)還存在兩個(gè)問(wèn)題:
問(wèn)題一:點(diǎn)的屏幕坐標(biāo)值是浮點(diǎn)數(shù),但像素都是由整數(shù)點(diǎn)來(lái)表示的,如何確定屏幕坐標(biāo)值所對(duì)應(yīng)的像素?
問(wèn)題二:在屏幕上需要繪制有點(diǎn)、線(xiàn)、面,如何根據(jù)兩個(gè)已經(jīng)確定位置的2個(gè)像素點(diǎn)繪制一條線(xiàn)段,如何根據(jù)已經(jīng)確定了位置的 3 個(gè)像素點(diǎn)繪制一個(gè)三角形面片?
首先回答一下問(wèn)題一,“繪制的位置只能接近兩指定端點(diǎn)間的實(shí)際線(xiàn)段位置,例如,一條線(xiàn)段的位置是(10.48,20.51),轉(zhuǎn)換為像素位置則是(10,21)”(計(jì)算機(jī)圖形學(xué)(第二版)52頁(yè))。
對(duì)于問(wèn)題二涉及到具體的畫(huà)線(xiàn)算法,以及區(qū)域圖元填充算法。通常的畫(huà)線(xiàn)算法有 DDA 算法、Bresenham 畫(huà)線(xiàn)算法;區(qū)域圖元填充算法有,掃描線(xiàn)多邊形填充算法、邊界填充算法等,具體請(qǐng)參閱《計(jì)算機(jī)圖形學(xué)(第二版)》第3章。
這個(gè)過(guò)程結(jié)束之后,頂點(diǎn)(vertex)以及繪制圖元(線(xiàn)、面)已經(jīng)對(duì)應(yīng)到像素(pixel)。下面闡述的是“如何處理像素,即:給像素賦予顏色值”。
2.3.2 Pixel Operation
Pixel Operation 又稱(chēng)為 Raster Operation (在文獻(xiàn)【2】中是使用 Raster Operation),是在更新幀緩存之前,執(zhí)行最后一系列針對(duì)每個(gè)片段的操作,其目的是:計(jì)算出每個(gè)像素的顏色值。在這個(gè)階段,被遮擋面通過(guò)一個(gè)被稱(chēng)為深度測(cè)試的過(guò)程而消除,這其中包含了很多種計(jì)算顏色的方法以及技術(shù)。Pixel Operation 包含哪些事情呢?
1. 消除遮擋面;
2. Texture Operation,紋理操作,也就是根據(jù)像素的紋理坐標(biāo),查詢(xún)對(duì)應(yīng)的紋理值;
3. Blending;
混色,根據(jù)目前已經(jīng)畫(huà)好的顏色,與正在計(jì)算的顏色的透明度(Alpha),混合為兩種顏色,作為新的顏色輸出。通常稱(chēng)之為 alpha 混合技術(shù)。當(dāng)在屏幕上繪制某個(gè)物體時(shí),與每個(gè)像素都相關(guān)聯(lián)的有一個(gè) RGB 顏色值和一個(gè) Z 緩沖區(qū)深度值,另外一個(gè)稱(chēng)為 alpha 值,可以根據(jù)需要生成并存儲(chǔ),用來(lái)描述給定像素處的物體透明度。如果 alpha 值為 1.0,則表示物體不透明;如果值為 0,表示該物體是透明的。
從繪制管線(xiàn)得到一個(gè) RGBA,使用 over 操作符將該值與原像素顏色值進(jìn)行混合,公式如下:
newColor = alpha * alphaColor + (1 - alpha)* preColor 【over 操作符】
alpha 是透明度值,alphaColor 表示透明物體的顏色,preColor表示混合前像素的顏色值,newColor是最終計(jì)算得到的顏色值。Over 操作可以用于照片混合和物體合成繪制方面,這個(gè)過(guò)程稱(chēng)為合成(compositing)??梢月?lián)想一下,OGRE 中有一種技術(shù)稱(chēng)為 compositor(合成器)。
此外還需要提醒的一點(diǎn)是:為了在場(chǎng)景中繪制透明物體,通常需要對(duì)物體進(jìn)行排序。首先,繪制不透明的物體;然后,在不透明的物體的上方,對(duì)透明物體按照由后到前的順序進(jìn)行混合處理。如果按照任意順序進(jìn)行混合,那么會(huì)產(chǎn)生嚴(yán)重的失真。既然需要排序,那么就需要用到 Z Buffer。關(guān)于透明度、合成的相關(guān)知識(shí)點(diǎn),可以在《實(shí)時(shí)計(jì)算機(jī)圖形學(xué)(第二版)》第四章4.5節(jié)(59頁(yè))中得到更多詳盡的知識(shí)。
4. Filtering,將正在算的顏色經(jīng)過(guò)某種 Filtering (濾波或者濾鏡)后輸出。可以理解為:經(jīng)過(guò)一種數(shù)學(xué)運(yùn)算后變成新的顏色值。
該階段之后,像素的顏色值被寫(xiě)入幀緩存中。圖 5 來(lái)自文獻(xiàn)【2】1.2.3,說(shuō)明了像素操作的流程:

2.4 圖形硬件
這一節(jié)中主要闡述圖形硬件的相關(guān)知識(shí),主要包括 GPU 中數(shù)據(jù)的存放硬件,以及各類(lèi)緩沖區(qū)的具體含義和用途,如:Z Buffer(深度緩沖區(qū))、Stencil Buffer(模板緩沖區(qū))、Frame Buffer(幀緩沖區(qū))和 Color Buffer (顏色緩沖區(qū))。
2.4.1 GPU 內(nèi)存架構(gòu)
寄存器和內(nèi)存有什么區(qū)別?
從物理結(jié)構(gòu)而言,寄存器是 CPU 或 GPU 內(nèi)部的存儲(chǔ)單元,即寄存器是嵌入在 CPU 或者 GPU 中的,而內(nèi)存則可以獨(dú)立存在;從功能上而言,寄存器是有限存儲(chǔ)容量的高速存儲(chǔ)部件,用來(lái)暫存指令、數(shù)據(jù)和位址。Shader 編程是基于計(jì)算機(jī)圖形硬件的,這其中就包括 GPU 上的寄存器類(lèi)型,GLSL 和 HLSL 的著色虛擬機(jī)版本就是基于 GPU 的寄存器和指令集而區(qū)分的。

2.4.2 Z Buffer 與 Z 值
Z Buffer 應(yīng)該是大家最為熟悉的緩沖區(qū)類(lèi)型,又稱(chēng)為 Depth Buffer,即深度緩沖區(qū),其中存放的是視點(diǎn)到每個(gè)像素所對(duì)應(yīng)的空間點(diǎn)的距離衡量,稱(chēng)之為 Z 值或者深度值??梢?jiàn)物體的 Z 值范圍位于【0,1】區(qū)間,默認(rèn)情況下,最接近眼睛的頂點(diǎn)(近裁剪面上)其 Z 值為 0.0,離眼睛最遠(yuǎn)的頂點(diǎn)(遠(yuǎn)裁剪面上)其 Z 值為 1.0。使用 Z Buffer 可以用來(lái)判斷空間點(diǎn)的遮擋關(guān)系,著名的深度緩沖區(qū)算法(depth-buffer method,又稱(chēng) Z 緩沖區(qū)算法)就是對(duì)投影平面上每個(gè)像素所對(duì)應(yīng)的 Z 值進(jìn)行比較的。
Z 值并非真正的笛卡爾空間坐標(biāo)系中的歐幾德得距離(Euclidean distance),而是一種“頂點(diǎn)到視點(diǎn)距離”的相對(duì)度量。所謂相對(duì)度量,即這個(gè)值保留了與其他同類(lèi)型值得相對(duì)大小關(guān)系。在 Steve Baker 撰寫(xiě)的文章“Learning to love your Z-buffer”中將 GPU 對(duì) Z 值得計(jì)算公式描述為:
z_buffer_value = (1<<N)(az + b)/z
a = f/(f - n)
b = f * n/(n - f)
其中 f 表示視點(diǎn)到遠(yuǎn)裁剪面的空間距離,n 表示視點(diǎn)到近裁剪面的空間距離,z 表示視點(diǎn)到頂點(diǎn)的空間距離,N 表示 Z 值精度。
大多數(shù)人所忽略的是,Z Buffer 中存放的 Z 值不一定是線(xiàn)性變化的。在正投影中同一圖元相鄰像素的 Z 值是線(xiàn)性關(guān)系的,但在透視投影中卻不是的。在透視投影中這種關(guān)系是非線(xiàn)性的,而且非線(xiàn)性的成都隨著空間點(diǎn)到視點(diǎn)的距離增加而越發(fā)明顯。
當(dāng) 3D 圖形處理器將基礎(chǔ)圖元(點(diǎn)、線(xiàn)、面)渲染到屏幕上時(shí),需要以逐行掃描的方式進(jìn)行光柵化。圖元頂點(diǎn)位置信息是在應(yīng)用程序中指定的(頂點(diǎn)模型坐標(biāo)),然后通過(guò)一系列的過(guò)程變換到屏幕空間,但是圖元內(nèi)部點(diǎn)的屏幕坐標(biāo)必須由已知的頂點(diǎn)信息插值而來(lái)。例如,當(dāng)畫(huà)三角形的一條掃描線(xiàn)時(shí),掃描線(xiàn)上的每個(gè)像素的信息,是對(duì)掃描線(xiàn)左右端點(diǎn)處已知信息值進(jìn)行插值運(yùn)算得到的,所以?xún)?nèi)部點(diǎn)的 Z 值也是插值計(jì)算得到。同一圖元相鄰像素點(diǎn)是線(xiàn)性關(guān)系(像素點(diǎn)是均勻分布的,所以一定是線(xiàn)性關(guān)系),但對(duì)應(yīng)到空間線(xiàn)段上則存在非線(xiàn)性的情況,如圖 7 所示。所示:線(xiàn)段 AE 是某三角面片的兩個(gè)頂點(diǎn),投影到屏幕空間對(duì)應(yīng)到像素 1 和像素 5;光柵化時(shí),需要對(duì)像素 2、3、4 進(jìn)行屬性插值,從視點(diǎn)引射線(xiàn)到空間線(xiàn)段上的交點(diǎn)分別為 B、C、D。從圖中可以看出,點(diǎn) B、C、D 并不是均勻分布在空間線(xiàn)段上的,而且如果離視點(diǎn)越遠(yuǎn),這種差異就越發(fā)突出。即,投影面上相等的步長(zhǎng),在空間中對(duì)應(yīng)的步長(zhǎng)會(huì)隨著離視點(diǎn)距離的增加而邊長(zhǎng)。所以如果對(duì)內(nèi)部像素點(diǎn)的 Z 值進(jìn)行線(xiàn)性插值,得到的 Z 值并不能反映真實(shí)的空間點(diǎn)的深度關(guān)系。Z 值得不準(zhǔn)確性,會(huì)導(dǎo)致物體顯示順序的錯(cuò)亂,例如,在游戲中常會(huì)看到遠(yuǎn)處的一些面片相互交疊。
為了避免或減輕上述的情況,在設(shè)置視點(diǎn)相機(jī)元遠(yuǎn)裁剪面和近裁剪面時(shí),兩者的比值應(yīng)盡量小于 1000。要想解決這個(gè)問(wèn)題,最簡(jiǎn)單的方法是通過(guò)將近裁剪面遠(yuǎn)離眼睛來(lái)降低比值,不過(guò)這種方法的副作用是可能會(huì)將眼睛前的物體裁剪掉。

很多圖形硬件使用 16 位的 Z Buffer,另外的一些使用 24 位的 Z Buffer,還有一些很好的圖形硬件使用 32 位的。如果你擁有 32 位的 Z Buffer,則 Z 精度(Z-precision)對(duì)你不是一個(gè)問(wèn)題。但是如果你希望你的程序可以靈活的使用各種層次的圖形硬件,那么你就需要多思考一下。
Z 精度之所以重要,是因?yàn)?Z 值決定了物體之間的相互遮擋關(guān)系,如果沒(méi)有足夠的精度,則兩個(gè)相距很近的物體將會(huì)出現(xiàn)隨機(jī)遮擋的現(xiàn)象,這種現(xiàn)象通常稱(chēng)為“flimmering” 或 “Z-fighting”。
2.4.3 Stencil Buffer
A stencil buffer is an extra buffer, in addition to the color buffer and depth buffer found on modern computer graphics hardware. The buffer is per pixel, and works on integer values, ususlly with a depth of one byte per pixel. The depth buffer and stencil buffer often share the same area in the RAM of the graphics hardware.
Stencil buffer,中文翻譯為“模板緩沖區(qū)”,它是一個(gè)額外的 buffer,通常附加到 Z Buffer 中,例如:15 位的 Z Buffer 加上 1 位的 stencil buffer(總共 2 個(gè)字節(jié));或者 24 位的 Z Buffer 加上 8 位的 stencil buffer(總共四個(gè)字節(jié))。每個(gè)像素對(duì)應(yīng)一個(gè) stencil buffer(其實(shí)就是對(duì)應(yīng)一個(gè) Z Buffer)。 Z Buffer 和 stencil buffer 通常在顯存中共享同一片區(qū)域。Stencil buffer 對(duì)大部分人而言應(yīng)該比較陌生,這是一個(gè)用來(lái)“做記號(hào)”的 Buffer,例如:在一個(gè)像素的 stencil buffer 中存放 1,表示該像素對(duì)應(yīng)的空間點(diǎn)處于陰影體(shadow volume)中。
2.4.4 Frame Buffer
A framebuffer is a video output device that drives a video display a video display from a memory buffer containing a complete frame of data. The infomation in the buffer typically consists of color values for every pixel on the screen. Color values are commonly stored in 1-bit monochrome, 4-bit palettized, 8-bit palettized, 16-bit highcolor and 24-bit truecolor formats. An additional alpha channel is sometimes used to retain information about pixel transparency. The total amount of the memory required to drive the framebuffer depends on the resolution of the output signal, and on the color depth and palette size.
Frame buffer,稱(chēng)為幀緩沖區(qū),用于存放顯示輸出的數(shù)據(jù),這個(gè) Buffer 中的數(shù)據(jù)一般是像素顏色值。Frame buffer 有時(shí)也被認(rèn)為是 color buffer(顏色緩沖區(qū))和 Z buffer 的組合?在 WebMediaBrands 網(wǎng)站上摘錄了一段英文說(shuō)明,即 frame buffer 通常都是在顯卡上,但是有時(shí)顯卡會(huì)集成到主板上,所以這種情況下 frame buffer 被放在內(nèi)存區(qū)域(general main memory)。
Frame buffer:The portion of memory reserved for holding the complete bit-mapped image that is sent to the monitor. Typically the frame buffer is stored in the memory chios on the video adapter. In some instances, however, the video chipset is intergrated into the motherboard design, and the frame buffer is stored in general main memory.
2.5 本章小結(jié)
本章介紹了 GPU 圖形繪制管線(xiàn),并對(duì)相關(guān)的圖形硬件進(jìn)行了闡述。圖形繪制管線(xiàn)是 GPU 編程的基礎(chǔ),事實(shí)上頂點(diǎn)著色程序和片段著色程序正是按照?qǐng)D形繪制管線(xiàn)而劃分的。
國(guó)內(nèi)的 OpenGPU 網(wǎng)站上由一些資料可以進(jìn)一步閱讀,北京大學(xué)出版社出版的《實(shí)時(shí)計(jì)算機(jī)圖形學(xué)(第二版)》的第2章和第3章,以及 Blinn 的《A Trip Down the Graphics Pineline》在學(xué)習(xí)繪制管線(xiàn)方面呢也是非常好的資料。