1 前言
在正式開始講解OpenGL渲染管線之前椭员,首先介紹下模型是如何從3D空間渲染到2D屏幕的状您,下圖演示了一個甲殼蟲的渲染結(jié)果例驹。這個例子在后面的文章中會詳細介紹,并會給出完成源碼库继。
左上角的圖像是一個完整的3D甲殼蟲渲染結(jié)果,它是由左下角的模型窜醉,和右下角的紋理共同繪制得到的宪萄,我們在渲染3D模型時,第一步是將3D模型投影到屏幕上榨惰,即左下圖的結(jié)果拜英,然后根據(jù)每個頂點在右下角紋理圖像中對應的采樣坐標(原點為左下角,水平向右為x軸琅催,垂直向上為y軸居凶,它們?nèi)≈捣秶荹0, 1])繪制對應的顏色,就能得到左上角的完整圖像藤抡。
這里僅僅是希望讀者對3D圖像的渲染有一個基本的了解侠碧,完整的細節(jié)后續(xù)的章節(jié)會介紹。
2 概覽
在上一節(jié)中缠黍,我們對OpenGL已經(jīng)有了最簡單的了解弄兜,本小節(jié)將對OpenGL的圖形渲染管線進行簡單介紹,以期望加深讀者對整個3D圖形渲染的流程有更深一步的了解瓷式。仍然需要注意的是替饿,本文的目的并不是詳細的論述OpenGL每個特性的詳細細節(jié),這些工作是后續(xù)文章的任務贸典。
現(xiàn)在只需要了解一個模型的渲染是通過一個OpenGL程序完成的视卢,一個OpenGL由多個階段組成,它們串連成一個流水線廊驼,這被稱為OpenGL圖形渲染管道据过,其中有些階段是可以通過編程實現(xiàn)的惋砂,這個子程序被稱為著色器(Shader)。本篇文章的知識點如下蝶俱。
- OpenGL圖形渲染管道班利,3D圖形渲染流程。
- 獨立于圖形渲染管道之外的計算著色器榨呆。
3 頂點抓取階段
在OpenGL的圖形渲染管線中罗标,頂點著色器(VertexShader)是第一個可編程的步驟,但是它并不是管線的第一個步驟积蜻,管線的第一個步驟是頂點抓取(Vertex Fetching Stage)闯割,這是一個固定函數(shù),系統(tǒng)負責調(diào)用并給頂點著色器提供輸入數(shù)據(jù)竿拆。我們可以使用類似于glVertexAttrib*()
的函數(shù)來通知OpenGL在該階段的時候執(zhí)行某些特定的操作宙拉。當前僅僅需要了解到這里即可,后續(xù)文章會詳細介紹丙笋。
4 頂點著色器階段
4.1 著色器實現(xiàn)
頂點著色器(Vertex Shader)是OpenGL的圖形渲染管線中的第2個階段谢澈,這個階段是可以通過編程實現(xiàn)的,一個簡單的頂點著色器如下御板。它的作用是確定頂點的位置锥忿。
#version 410 core
// layout關鍵字和location關鍵字指定了該輸入的索引值為0,
// 后面使用glVertexAttrib4fv函數(shù)時使用相同的值就能完成數(shù)據(jù)通信
layout (location = 0) in vec4 offset;
void main() {
const vec4 vertices[3] = vec4[3](vec4( 0.25, -0.25, 0.5, 1.0),
vec4(-0.25, -0.25, 0.5, 1.0),
vec4( 0.25, 0.25, 0.5, 1.0));
gl_Position = vertices[gl_VertexID] + offset;
}
在上面的代碼中怠肋,首先我們定義了該著色器的版本是兼容至OpenGL4.1敬鬓,然后我們聲明了該著色器的輸入頂點屬性偏移量offset,需要注意的是單個頂點可能會存在多個屬性笙各,這些屬性可以組成一個數(shù)組钉答,為了能夠在外部正確為頂點屬性賦值,我們需要定義該屬性在數(shù)組中的索引杈抢,這里定義為0数尿。在基于CPU運行的程序中為該頂點屬性賦值方法如下。
GLfloat attrib[] = { 0.5f, 0.6f, 0.0f, 0.0f };
glVertexAttrib4fv(0, attrib);
Main函數(shù)是著色器執(zhí)行的入口春感,這里我們需要繪制一個三角形模型砌创,因此首先定義了頂點數(shù)據(jù),當然通常我們的頂點數(shù)據(jù)是通過緩存的方式存儲和讀取的鲫懒,這個在后面的文章中會介紹嫩实。由于本章節(jié)還未講到坐標系,因此先簡單的理解為屏幕中心為原點窥岩,水平向右為x軸正方向甲献,垂直向上為y軸正方向,垂直向屏幕內(nèi)為y軸正方向颂翼,它們的取值范圍為[-1, 1]晃洒,這也是OpenGL的坐標系之一標準設備坐標系(Normal Device Coordinate System)慨灭。另外3D空間中唯一點需要使用3個分量即可表示,這里使用了4個分量是因為投影變換的關系球及,這個也會在后面文章有詳細介紹醒第,這里只需要知道第四個分量為0表示一個向量愕宋,第四個分量為1表示一個點。
接下來我們需要在該著色器內(nèi)部確定頂點位置,在頂點著色器中OpenGL提供了一個輸出gl_Position
途乃,我們只需要對其賦值即可拜姿。gl_VertexID
也是OpenGL提供的一個內(nèi)置變量寸莫,它表示的是當前著色器執(zhí)行時的頂點索引尖滚,對于繪制一個三角形,頂點著色器會被調(diào)用3次庐氮,而該變量的值分別為1语稠、2和3。
4.2 不同階段間數(shù)據(jù)通信
正如定義了in
關鍵字用于在著色器中聲明輸入變量弄砍,圖形著色器語言(GLSL, Graphic Language Shader Language)還定義了out
關鍵字用于聲明輸出變量仙畦,任意一個用out
關鍵字定義在某個模塊中的變量,在其下一個模塊都能使用同樣的標識符通過in
關鍵字獲取音婶。本小節(jié)中我們只使用到了頂點著色器和片段著色器议泵,因此我們需要將頂點著色器中計算得到的數(shù)據(jù)傳入到片段著色器中,這里我們傳遞顏色變量桃熄。數(shù)據(jù)能夠以基本數(shù)據(jù)、和復雜結(jié)構(gòu)類型傳遞型奥。
4.2.1 傳遞簡單數(shù)據(jù)
簡單數(shù)據(jù)指的是如float瞳收,int和vec4等基礎類型的方式傳遞,其過程如下厢汹。
頂點著色器輸出變量vsColor
#version 410 core
layout (location = 0) in vec4 offset;
layout (location = 1) in vec4 color;
out vec4 vsColor;
void main() {
gl_Position = ...;
vsColor = color;
}
片段著色器輸入變量vsColor
#version 410 core
in vec4 vsColor;
out vec4 FragColor;
void main() {
FragColor = vsColor;
}
4.2.2 傳遞復雜數(shù)據(jù)結(jié)構(gòu)
GLSL允許在Shader之間以結(jié)構(gòu)體的方式傳遞數(shù)據(jù)螟深。這樣有兩個好處,第一:允許輸出變量和輸入變量名稱不同烫葬,只需要他們結(jié)構(gòu)的定義相同界弧。第二:允許用同一個變量傳遞多個數(shù)據(jù)。但是需要注意的是這種方式不允許在頂點著色器(Vertex Shader)的輸入變量搭综,和片段著色器(Fragment Shader)的輸出變量中使用垢箕。其傳遞過程如下
頂點著色器輸出變量vs_out
#version 410 core
layout (location = 0) in vec4 offset;
layout (location = 1) in vec4 color;
out VS_OUT {
vec4 color;
} vs_out;
void main() {
gl_Position = ...;
vs_out.color = color;
}
片段著色器輸入變量fs_in
#version 410 core
in VS_OUT {
vec4 color;
} fs_in;
out vec4 color;
void main() {
color = fs_in.color;
}
上面代碼的渲染結(jié)果如下。源碼傳送門
5 曲面細分階段
曲面細分階段(Tessellation Stage)位于頂點著色階段之后兑巾,是OpenGL圖形管道中的第3個階段条获,該階段將單個圖元分解為大量的較小的點、線和三角蒋歌,使這些小圖元能直接被光柵化器使用帅掘。曲面細分階由一個可編程曲面細分控制模塊(Tessellation Control Shader)委煤,一個固定模塊曲面細分引擎(Tessellation Engine)和曲面細分計算模塊(Tessellation Evaluation Shader)組成。
在曲面細分工作開始之前修档,我們需要在基于CPU運行的主程序中調(diào)用函數(shù)glPatchParameteri()
碧绞,并將參數(shù)之一pname
設置為GL_PATCH_VERTICES
,從而設置被細分的每個曲面吱窝,或者說是每個補丁(Patch)的控制點個數(shù)讥邻。不調(diào)用此函數(shù)時,默認設置為3癣诱。
當曲面細分階段被激活時计维,OpenGL會為每個控制點調(diào)用一次頂點著色器(Vertex Shader),另外控制點將被分組進行批量處理撕予,其每批處理的控制點大小和每個補丁(Patch)的控制點數(shù)量相同(即每個Patch是依序處理的)鲫惶。曲面細分控制模塊每批次生成的控制點數(shù)量可以改變,從而使得該模塊的輸入頂點數(shù)不同于輸出頂點數(shù)实抡。
5.1 曲面細分控制模塊
曲面細分控制模塊(Tessellation Control Shader)主要負責兩件事情:
1. 確定曲面的每條邊需要被分為多少份欠母,使得曲面細分引擎能夠正常工作。
2. 生成曲面細分引擎吆寨,和曲面細分計算著色器需要的數(shù)據(jù)赏淌,即將原始圖元的控制點做自定義修改后以數(shù)組的方式傳入后面的兩個模塊。
下面是一個簡單的曲面細分控制著色器的例子啄清。
#version 410 core
layout (vertices = 3) out;
void main(void) {
if (gl_InvocationID == 0) {
gl_TessLevelInner[0] = 5.0;
gl_TessLevelOuter[0] = 5.0;
gl_TessLevelOuter[1] = 5.0;
gl_TessLevelOuter[2] = 5.0;
}
gl_out[gl_InvocationID].gl_Position =
gl_in[gl_InvocationID].gl_Position+vec4(0.25,0,0,0.0);
}
在上面的代碼中六水,我們首先聲明了控制著色器每批次輸出的控制點數(shù)量layout (vertices = 3) out;
。
接下來使用到了一些OpenGL內(nèi)建變量為曲面細分的后續(xù)操作準備數(shù)據(jù)和參數(shù)辣卒。內(nèi)建變量(Built-in Variable)gl_InvocationID
表示當前曲面細分控制著色器在當前批次的調(diào)用索引掷贾,前面講過曲面細分是分批次進行的,每個批次處理一個曲面荣茫,而一個曲面執(zhí)行曲面細分控制著色器的次數(shù)和單個曲面的控制點數(shù)相同想帅。因此該參數(shù)可以理解為控制點的索引值。
內(nèi)建變量gl_TessLevelInner
和gl_TessLevelOuter
保存的是圖元每個內(nèi)邊和外邊的細分等級啡莉,它們是一個數(shù)組港准,對于三角形圖元的曲面細分而言,前者數(shù)組的容量為1咧欣,后者為3浅缸,具體細節(jié)在后續(xù)文章中會詳細介紹。它們僅需在第一次著色器調(diào)用時設置一次即可该押。
內(nèi)建變量gl_out
和gl_in
分別為保存輸出和輸入控制點的數(shù)組疗杉,前面講過輸入控制點的數(shù)組容量取決于我們要進行曲面細分的圖元類型,而輸出數(shù)組的容量取決于我們設置的輸出控制點數(shù)量。這里輸出控制點的數(shù)量可以小于輸入數(shù)組烟具。
5.2 曲面細分引擎
曲面細分引擎(Tessellation Engine)是一個固定函數(shù)梢什,它根據(jù)前一個模組傳入的細分因子將圖元組(Patches)細分為大量點、線和三角圖元朝聋。然后組成基礎圖元的頂點相對坐標被曲面細分計算著色器獲取嗡午,從而計算得到新的圖元,為光柵化階段準備數(shù)據(jù)冀痕。
5.3 曲面細分計算模塊
OpenGL會為每一個曲面細分引擎生成的頂點調(diào)用一次曲面細分計算著色器(Tessellation Evaluation Shader)荔睹。曲面細分等級越高,生成的頂點越多言蛇,曲面細分計算著色器的調(diào)用次數(shù)越多僻他,該模塊的耗時越長。我們需要注意這點腊尚,并且避免復雜的曲面細分計算函數(shù)吨拗。下面是一個簡單的曲面細分計算著色器。
#version 410 core
layout (triangles, equal_spacing, cw) in;
void main(void) {
gl_Position = (gl_TessCoord.x * gl_in[0].gl_Position +
gl_TessCoord.y * gl_in[1].gl_Position +
gl_TessCoord.z * gl_in[2].gl_Position);
}
在上面的代碼中婿斥,關鍵字layout
后是曲面細分計算著色器必須聲明的參數(shù)劝篷,其中triangles
表示接受的輸入圖元類型為三角形,equal_spacing
表示曲面細分的方式是等距離細分民宿,cw
表示頂點生成的方向是順時針娇妓,更多的知識在后續(xù)文章中會詳細說明。
另外曲面細分計算著色器接受來曲面細分控制著色器輸出的控制點數(shù)組gl_in[]
活鹰,并以此為基礎建立重心坐標系(Barycentric Coordinate System)哈恰,同時接收曲面細分引擎輸入的頂點重心坐標gl_TessCoord
,最終計算得到當前頂點在當前歐式坐標系(關于坐標系的知識在后續(xù)文章講解)中的絕對坐標志群。
在本實力中為了觀察程序執(zhí)行的效果蕊蝗,在渲染時調(diào)用glPolygonMode()
函數(shù)控制渲染模式,此例中使用GL_FRONT_AND_BACK
作為其參數(shù)face
表示背向和朝向觀察者的面都需要繪制赖舟,使用GL_LINE
作為參數(shù)mode
表示以線的方式繪制模型的輪廓。另外啟用曲面細分功能后夸楣,在繪制函數(shù)中的涂鴉類型也需要改為GL_PATCHES
宾抓。函數(shù)調(diào)用如下。
void glPolygonMode(GLenum face, GLenum mode);
glDrawArrays(GL_PATCHES, 0, 3);
上述示例的執(zhí)行結(jié)果如下圖豫喧。源碼傳送門
6 幾何著色器階段
幾何著色器(Geometry Shader)階段是管道前端的最后一個模組石洗,位于曲面細分模組和光柵化模組之間。當其被激活時紧显,每處理一個圖元讲衫,幾何著色器將會被調(diào)用一次,并接收組成該圖元的所有頂點數(shù)據(jù)。在眾多模組間涉兽,它具有獨特的性質(zhì)招驴。
1. 它可以通過兩個函數(shù)EmitVertex()
和EndPrimitive()
來直接改變在管道中流動的數(shù)據(jù)量。
2. 它可以改變圖元的類型枷畏,如將三角分解為頂點輸出别厘,或者將頂點組合為三角輸出。
下面是一個簡單的幾何著色器拥诡。
#version 410 core
layout (triangles) in;
layout (points, max_vertices = 3) out;
void main() {
for (int i = 0; i < gl_in.length(); i++) {
gl_Position = gl_in[i].gl_Position;
// Generate a vertex
EmitVertex();
}
// Composite vertext to geometry as designed and clear canvas
EndPrimitive();
}
在上面的代碼中触趴,首先通過關鍵字triangles
聲明了幾何著色器的輸入圖元為三角形,關鍵字points
聲明了輸出的圖元類型為點圖元渴肉,關鍵字max_vertices = 3
聲明了每個著色器的輸出頂點數(shù)為3冗懦。
在主函數(shù)中,計算出每個輸出頂點的坐標后仇祭,調(diào)用函數(shù)EmitVertex()
向畫布中輸出一個頂點披蕉。在著色器的末尾,OpenGL會自動調(diào)用EndPrimitive()
函數(shù)將當前緩存中的頂點組成我們定義的圖元并將其輸出前塔,隨后清空畫布嚣艇。在基于CPU的主程序中調(diào)用函數(shù)glPointSize()
設置像素大小為5后,上面的程序渲染結(jié)果如下华弓。源碼傳送門
7 光柵化階段
當管道前端(Vertex, Tessellation, and Geometry Shader)運行完成后食零,不可編程的光柵化階段(Rasterization Stage)執(zhí)行了一系列的任務將標準坐標系中的圖元轉(zhuǎn)化為顯示窗口內(nèi)的像素。在這一系列復雜的任務之中寂屏,第一步被稱為圖元裝配(Primitive Assembly)贰谣,它將頂點組裝成線、三角和點圖元迁霎。隨后OpenGL將會對其不可見部分進行剪切吱抚。最后可見圖元被發(fā)送到光柵化器(Rasterizer)。光柵化器將圖元分解為像素考廉,并將這些像素點發(fā)送給片段著色器進行進一步處理秘豹。
7.1 剪切
在講些剪切(Clipping)操作之前,先介紹下OpenGL渲染3D模型需要使用到的歐式幾何和投影幾何昌粤,如下圖既绕。
在上圖中,我們從3D建模師得到的原始模型文件中頂點坐標都是在模型空間中(Model Space)定義的涮坐,而多個模型確定相對位置需要一個共有的世界空間(World Space)凄贩。3D圖形渲染的原理簡單的說可以是透視投影,也就是設置一個觀察點(Eye)袱讹,建立相機空間(View Space)疲扎,確定能夠看到的近平面(Near Plane)和視野極限的遠平面(Far Plane),從觀察點發(fā)出的射線剛好穿越這兩個平面的邊界,在這個截錐體內(nèi)的模型才是可見的椒丧,其余部分都會被裁減壹甥,從而避免性能浪費。
在OpenGL中點的頂點坐標瓜挽,正如在各個著色器中使用的glPosition
變量都是由4個坐標組成的盹廷,它們被稱為齊次坐標(Homogeneous Coordinate)。在投影幾何中使用齊次坐標空間表示點比使用常規(guī)的笛卡爾空間(Cartesian Space)更簡單久橙。在將頂點從齊次坐標系轉(zhuǎn)化為笛卡爾坐標系的過程中俄占,OPenGL執(zhí)行了透視除法(Perspective Division),即將前三個元素(x.y.z)都除以第4個元素(w)淆衷。具體細節(jié)會在后面數(shù)學章節(jié)中詳細介紹缸榄。
經(jīng)過投影除法后,所有點都位于標準設備空間(Normalized Device Space)內(nèi)祝拯,其各分量的取值范圍為[-1.0, 1.0]甚带。在這個范圍內(nèi)的圖元部分都是可見的,在這個范圍外的部分都將會被丟棄佳头。對于每一個圖元鹰贵,如果圖元的所有頂點都在可視范圍內(nèi),這個圖元將直接輸出到光柵化器康嘉;如果圖元的所有頂點全部在可視范圍外碉输,整個圖元將會被丟棄;如果圖元部分頂點在可視范圍內(nèi)亭珍,將會執(zhí)行額外操作敷钾,將在下文講到剪切章節(jié)時解釋。
7.2 視口變換
經(jīng)過剪切后肄梨,所有的頂點都位于標準設備空間內(nèi)(Normalized Device Space)阻荒。但是我們在屏幕上顯示的窗口坐標空間,其距離單位為像素众羡,范圍從左下的(0侨赡,0)直至右上角的(w-1,h-1)粱侣。OpenGL執(zhí)行視口變換(Viewport Transformation)操作辆毡,通過平移和和縮放的操作將頂點從標準設備空間轉(zhuǎn)換到窗口坐標空間(Window Space)。其計算公式如下甜害。
公式中xw,yw球昨,zw為片段在窗口坐標系中的坐標尔店,xd,yd,zd為頂點在標準設備坐標系中的坐標嚣州。px鲫售,py為視口的寬和高,ox该肴,oy為視口的原點情竹,可以通過函數(shù)glViewport()
設置,n和f為近平面和遠平面的在窗口坐標系中的值匀哄,可以通過調(diào)用函數(shù)glDepthRange()
設置秦效。
7.3 面剔除
在三角形真正被處理之前,可能會經(jīng)過一個面剔除(Culling)階段涎嚼。該階段計算了三角形是面向觀察者還是背向觀察者阱州,同時確定三角形是否會被管道中下一個階段的程序處理。OpenGL通過front-facing和back-facing來表示三角形的朝向法梯,通常苔货,背向觀察者的三角形都會被丟棄,因為對于封閉的圖形立哑,這些表面都將被隱藏夜惭。但是需要注意在繪制有透明度的圖形時這些表面不能被丟棄。
在決定三維空間中的一個三角形在z軸上是背向觀察者或者是面向觀察者時铛绰,OpenGL采用計算其在xy投影三角有向面積的方式诈茧。一種計算有向面積的方式是計算任意兩條邊的交叉相乘差的和。公式如下至耻。
其中xiw和ywi 分別是第i個頂點的窗口坐標若皱,i⊙1是(i+1)對3求余,n為頂點的個數(shù)尘颓。如果結(jié)果a是正數(shù)走触,這個三角形圖元被認為是面向觀察者的,如果是負數(shù)則是背向觀察者疤苹,只有當三個頂點在同一直線上時互广,該值為0。
另外可以通過調(diào)用glFrontFace()
函數(shù)取得相反結(jié)果卧土,其默認設置是GL_CCW
(三角形頂點出現(xiàn)順序為逆時針的三角形圖元被認為是面向觀察者圖元)惫皱,當設置為GL_CW
表示三角形頂點出現(xiàn)順序為正時針的圖元是面向觀察者,此時OpenGL會默認為每個有向面積取負數(shù)尤莺。如下圖旅敷。
另外一種計算三角形有向面積的方式是通過行列式的方式計算。其公式如下颤霎,其中x0,y0,x1,y1,x2,y2分別代表頂點V0,V1,V2的坐標媳谁。A表示三角形的有向面積涂滴。
在計算出三角形的朝向狀態(tài)后,程序中通過glEnable(GL_CULL_FACE)
函數(shù)啟用面剔除功能晴音,默認會剔除背向觀察者的三角形圖元(back-facing)柔纵。可以通過函數(shù)glCullFace()
和參數(shù)GL_FRONT
, GL_BACK
, GL_FRONT_AND_BACK
設置需要剔除的三角形面朝向锤躁。
點圖元和線圖元沒有幾何面積搁料,因此面剔除階段將會忽略這些圖元。并且該階段三角形圖元的頂點順序和頂點著色器中頂點輸入順序系羞,以及曲面細分計算著色器中的頂點輸入順序相關郭计,在使用默認面剔除功能時,會剔除掉順時針頂點順序的三角形圖元觉啊。
7.4 光柵化
光柵化(Rasterization)模組會計算哪些片段(在此處可以理解為那些像素)在線或者三角圖元內(nèi)拣宏。大多數(shù)OpenGL系統(tǒng)在光柵化三角形時采用半空間(Half-space-based)計算方法,該方法能夠并行處理大量計算任務杠人。具體的計算方式是勋乾,OpenGL會為窗口坐標系中的三角形圖元限定一個邊界框,將在框里的每一個像素分別和三角形的三條邊比較嗡善,向量的叉乘(點向量分別和三條連續(xù)相連邊向量叉乘)和向量點乘(上一步三個向量分別兩兩點乘)確定點是否在三條邊的同一側(cè)辑莫。這種方式使得每個像素的計算都相對獨立,它只考慮三角形某一邊的兩個端點和片段自己的位置罩引,使得大規(guī)模并行計算得以進行各吨。只有同時在三條邊內(nèi)的片段才會被傳遞到片段的下一個模組。下圖簡單的演示了光柵化過程袁铐,其中綠色的片段表示經(jīng)過光柵化處理的到的片段揭蜒。
8 片段著色器階段
片段著色器(Fragment Shader)是圖形管道的最后一個可編程模組,它計算出每個片段的顏色并將其輸入到幀緩存中剔桨。這里是圖形管道中計算量最大的一個模組屉更。光柵化過程可能會為每個圖元生成甚至上百萬個片段。一個簡單的片段著色器如下洒缀。
#version 410 core
in vec4 vs_color;
out vec4 color;
void main(void) {
color = vs_color;
}
上面的片段著色器代碼非常簡單瑰谜,通常這里的邏輯會更復雜,它需要執(zhí)行與光照(Lighting)树绩、紋理貼圖(Applying Materials)和片段深度(Depth of the Fragment)相關的計算邏輯萨脑。
gl_FragCoord
是一個重要的輸入內(nèi)部變量,它包括了片段在窗口中的坐標饺饭。片段著色器同樣可以接受來自前一個著色器傳入的變量渤早,但是這里的輸入變量不同于管道中其他模組接收的輸入變量,該階段的輸入變量會在圖元的不同片段之間進行插值瘫俊。
示例程序渲染結(jié)果如下鹊杖。源碼傳送門
9 幀緩存操作階段
幀緩存操作(Framebuffer Operations)階段是OpenGL圖形渲染管道的最后一個階段提鸟,它負責計算屏幕上可見內(nèi)容,并且管理一塊內(nèi)存區(qū)域用于存儲每個像素點的數(shù)據(jù)仅淑。在大多數(shù)平臺上,幀緩存和操作系統(tǒng)(更準確的將是窗口系統(tǒng))管理的當前窗口(當前窗口占滿整個屏幕時即指代整個屏幕)相關聯(lián)胸哥。窗口系統(tǒng)提供了默認幀緩存涯竟,當需要進行離屏渲染時也可以提供指定的幀緩存。幀緩存會保存多個狀態(tài)空厌,例如確定片段著色器產(chǎn)生的數(shù)據(jù)應該寫入的位置庐船,以及數(shù)據(jù)的格式等。這些狀態(tài)保存在一個幀緩存對象(Framebuffer Object)中嘲更。
在片段著色器輸出像素數(shù)據(jù)和將其渲染到屏幕上之間還要經(jīng)過幾個操作來確定其是否應該被放入窗口中筐钟,其中每個操作都可以在程序中開啟或禁用。
9.1 剪切測試
第一個操作是剪切測試(scissor Test)赋朦,它負責測試片段是否位于程序定義的矩形框內(nèi)篓冲,矩形框內(nèi)的片段輸出到下一個任務,矩形框外的片段將會被丟棄宠哄。如下圖壹将。
9.2 模版測試
接下來是模板測試(Stencil Test),它通過一個模版來確定哪些像素應該被寫入幀緩存毛嫉,哪些像素應該被丟棄诽俯,具體方式是比較程序提供的參考值和模板緩存中對應的值。模板緩存為每個片段存儲一個值承粤。這些值沒有任何特定的語義暴区,它能被用于任何目的。模版緩存測試通過和失敗的條件辛臊,以及模版緩存內(nèi)部的值更新邏輯可以通過調(diào)用OpenGL的接口控制仙粱。
9.3 深度測試
接下來是深度測試(Depth Test),比較片段的z軸坐標分量和深度緩存中的對應的值浪讳,和模版緩存類似缰盏,深度緩存也會為每個片段保存一個值。深度緩存是幀緩存的另一部分淹遵,它包含了每個像素的景深(像素和觀察者的距離)信息透揣。通常景深緩存中存儲的數(shù)據(jù)取值范圍有近到遠為0~1。
在對窗口中同一個坐標點進行多次渲染時辐真,OpenGL會比較當前待處理片段的z軸坐標分量须尚,和已深度緩存中對應的景深數(shù)據(jù)崖堤。如果當前片段的對應景深數(shù)據(jù)更小密幔,當前片段就會替代原有片段。這個測試的邏輯可以調(diào)用OpenGL的函數(shù)設置撩轰。另外景深測試的結(jié)果同樣也會影響OpenGL對模板測試的處理結(jié)果胯甩。
9.4 混合和邏輯操作
最后需要根據(jù)片段的顏色值的格式(浮點型,標準化整形偎箫,或者整型)皆串,將它們向后傳遞到混合和邏輯操作模組(Blending or Logical Operation Stage)恶复。如果是小數(shù)或者標準化整型數(shù)據(jù),混合模式將會被啟用塔插。OpenGL提供大量的函數(shù)用于計算片段著色器輸出的結(jié)果和當前顏色緩存中值混合值拓哟,計算出的混合值將會被寫入到幀緩存中。如果幀緩存包含的是整型數(shù)據(jù)流纹,OpenGL將會對片段著色器的輸出值和當前幀緩存中對應值執(zhí)行AND漱凝、OR诸迟、XOR等邏輯操作,并將計算出新的值寫入到幀緩存中壁公。
10 計算著色器
前文分析了OpenGL圖形管道的各個階段绅项,但是OpenGL中還獨立于圖形管道之外運行著計算著色器(Compute Shaders)。它可以被認為是獨立于圖像管道外的單模組管道囊陡。
每次計算著色器的調(diào)用都被在單個工作單元中處理撞反,這個工作單元被稱為工作塊(work item),多個工作塊形成一個工作組(local workgroups)垛膝。這OpenGL的計算管道就依賴于這些工作組運行丁稀。除了一些表明當前計算著色器允許工作組大小的內(nèi)部變量线衫,計算著色器不含任何固定的輸出或者輸入變量惑折,其所有的計算結(jié)果都將寫入內(nèi)存中惨驶。一個簡單的計算著色器如下。
#version 410 core
layout (local_size_x = 32, local_size_y = 32) in;
void main(void) {
// Do nothing
}
計算著色器的編譯鏈接方法和頂點著色器的編譯鏈接方法相同屋确。上述代碼定義了一個簡單的計算著色器攻臀,其中規(guī)定了單個工作組的容量是32*32個工作塊纱昧。關于計算著色器也會在后面的章節(jié)中詳細介紹识脆。
11 總結(jié)
本文的主要目的是對OpenGL的圖形渲染管線進行簡單介紹,以期望加深讀者對整個3D圖形渲染的流程有更深一步的了解离例。并不是詳細的論述OpenGL每個特性的詳細細節(jié)纵东,這些工作是后續(xù)文章的任務偎球。
因此看到這里希望能夠?qū)penGL圖形渲染管道有一個大致的了解辑甜,并且知道獨立于圖形渲染管道之外還有計算著色器存在磷醋。