>說明:跟著learnopengl的內(nèi)容學(xué)習(xí),不是純翻譯,只是自己整理記錄。>強(qiáng)烈推薦原文,無論是內(nèi)容還是排版。 [原文鏈接](http://learnopengl.com/#!Getting-started/OpenGL)本文地址:http://blog.csdn.net/aganlengzi/article/details/50354237
并不簡單的三角形繪制
在OpenGL的世界中,一切都是在三維空間中的,但是屏幕和窗口是二維的像素數(shù)組。所以O(shè)penGL的一大工作就是將三維坐標(biāo)轉(zhuǎn)換為適合屏幕顯示的二維像素。這個把三維坐標(biāo)轉(zhuǎn)換為二維坐標(biāo)的過程是由OpenGL的圖形渲染流水線來管理的。這個圖形渲染流水線分為兩大部分:首先是將三維坐標(biāo)轉(zhuǎn)換為二維坐標(biāo);其次是鍵二維坐標(biāo)轉(zhuǎn)換為真正的有顏色值的像素。在本次教程中,我們將會簡單地討論這個圖形渲染流水線以及我們應(yīng)該怎樣使用它來幫助我們創(chuàng)建一些酷炫的像素出來。
注意一個二維坐標(biāo)和一個像素點(diǎn)是不同的。一個二維坐標(biāo)是一個點(diǎn)在二維空間中的精確表示,但是一個二維的像素點(diǎn)是一個二維空間中的點(diǎn)在屏幕分辨率的限制下的一個近似表示。
圖形渲染流水線以一組三維坐標(biāo)為輸入,把它們轉(zhuǎn)換成屏幕上著色的二維像素點(diǎn)。圖形渲染流水線又可以分成許多步驟,每個步驟都是以前面步驟的輸出作為當(dāng)前步驟的輸入。每一個步驟都是專用的,它們具有特定的功能,這方便了并行執(zhí)行。因?yàn)檫@種并行特性,當(dāng)今顯卡基本上都包含成千上萬個小的處理核心,這些核心幫助我們在GPU圖形渲染流水線中的每個步驟中利用小的程序來快速處理數(shù)據(jù)。而這些在每個核心上面跑的小程序就叫做著色程序(shaders)。
在這些shader中,有一些是可以被開發(fā)者配置的,這些可配置的shader允許我們用自己寫的shader來替換默認(rèn)的shader。這給了我們對這個流水線的某些部分更細(xì)粒度的控制權(quán),因?yàn)樗鼈兪窃贕PU上運(yùn)行的,這或許也能夠幫助我們節(jié)省寶貴的CPU時間。shader是用GLSL(OpenGL Shading Language,簡稱GLSL)語言開發(fā)的,我們將在下個教程中了解更多關(guān)于GLSL的知識。
下面這幅圖展示的是對圖形渲染管線所有階段的一個抽象表示,其中藍(lán)色的部分代表我們可以注入自己的shader,應(yīng)該就是可以自己配置的意思。這里的圖借鑒了
如你所見,這個圖形渲染管線中包含了很多階段,每個階段完成從頂點(diǎn)數(shù)據(jù)項(xiàng)最終顯示像素點(diǎn)的一部分特定的工作。我們將會以一個簡化的方式簡短地解釋其中的每個階段,目的是讓你有一個對這個流水線工作方式的整體把握。
作為輸入,我們把數(shù)組中能構(gòu)成一個三角形的三個三維坐標(biāo)值(稱作頂點(diǎn)數(shù)據(jù),Vertex Data)傳遞進(jìn)這個流水線;頂點(diǎn)數(shù)據(jù)實(shí)際上就是所有頂點(diǎn)的集合,而每個頂點(diǎn)實(shí)際上就是每個三維坐標(biāo)系中表示這個頂點(diǎn)的數(shù)據(jù)。實(shí)際上,我們用于表示一個點(diǎn)的數(shù)據(jù)中可以包含我們想要包含的屬性,但是為了簡化起見,在本例中,我們假設(shè)每個頂點(diǎn)只包含這個頂點(diǎn)的三維坐標(biāo)和頂點(diǎn)的顏色值。
為了讓OpenGL知道你想用這些頂點(diǎn)數(shù)據(jù)或者顏色值繪制什么圖形,你需要指定你想用這些數(shù)據(jù)繪制的圖形類型:是需要用它們繪制一些獨(dú)立的點(diǎn),還是需要用它們繪制三角形,或者是用它們繪制一條長長的線?點(diǎn),三角形或者線,這些稱作圖元,是在任何繪制命令調(diào)用前需要告訴OpenGL的,也只有這樣,OpenGL才知道在下一個狀態(tài)用繪制命令和給定的數(shù)據(jù)繪制什么。指定的方式是通過前面說的狀態(tài)設(shè)置函數(shù)完成的,這在后面具體用到的時候會說明。而OpenGL支持的圖元類型永宏表示,比如GL_POINTS,GL_TRIANGLES和GL_LINE_STRIP。
好的,以上圖為例,假設(shè)我們已經(jīng)指定了要繪制三角形,并且已經(jīng)輸入了頂點(diǎn)數(shù)據(jù)(包含三個頂點(diǎn)的位置坐標(biāo)和顏色值),下面真正進(jìn)入圖形渲染流水線:
流水線的第一階段是頂點(diǎn)處理器,它以單獨(dú)的頂點(diǎn)(在本例中包含位置坐標(biāo)和顏色值)作為輸入,完成的主要功能是將頂點(diǎn)的三維坐標(biāo)轉(zhuǎn)換成另一種三維坐標(biāo)(后面具體會講到),還有就是對頂點(diǎn)的屬性做一些基本的處理。
圖元裝配階段,以所有頂點(diǎn)處理器處理過的的頂點(diǎn)為輸入(如果在前面指定的繪制的內(nèi)容是GL_POINTS的話,那么就以單個頂點(diǎn)作為輸入),生成一個圖元并且根據(jù)圖元的形狀放置所有的頂點(diǎn)。在本例中就是構(gòu)成一個三角形圖元,而且將這個三角形的各個頂點(diǎn)放到該放的位置。
圖元裝配的輸入作為幾何處理器的輸入。幾何shader以形成圖元的頂點(diǎn)幾何為輸入,它能夠生成新的頂點(diǎn)形成新的圖元(不僅限于前面指定的圖元,比如像本例中的三角形)。在本例中,它從給定的三角形(圖元裝配階段的輸出)中又生成了一個三角形。
幾何處理器的輸出被傳遞給光柵化階段作為輸入。光柵化階段完成圖元和最終要顯示屏幕的對應(yīng)像素之間的映射,它生成片段處理器用到的片段。在將這些片段輸出到片段處理器之前,裁剪被首先執(zhí)行。裁剪操作將所有超出顯示范圍的片段都去除,這樣可以提高性能。
在OpenGL中,一個片段就是OpenGL渲染一個像素點(diǎn)需要的所有數(shù)據(jù)。
片段處理器最主要的作用是計(jì)算像素點(diǎn)最終的顏色,這個階段也是所有高級OpenGL效果施展的地方。通常,片段中包含3D場景的數(shù)據(jù)(比如說光照、陰影和光照顏色等等),這些數(shù)據(jù)被用來計(jì)算出最終的像素顏色值。
在所有相關(guān)的顏色值都被確定后,最終的對象將會被傳遞到下一個階段,我們稱其為alpha通道測試和混合階段。這個階段檢查片段的深度值和模板值(我們后面會了解到),并且用他們來檢查這些生成的片段是否在其它對象的前面或者后面,如果在其它對象的后面,即被其它對象遮擋,那么這個片段就會被裁減掉。這個階段也會檢查alpha值(alpha值定義了一個對象的透明度)并且進(jìn)行對象的混合操作(根據(jù)透明度的不同生成不同的效果)。所以即使一個像素的顏色值是在片段處理器階段就生成的,但是到最終顯示的時候,還是有可能完全不同(因?yàn)樵谶@個階段還會和其它對象進(jìn)行相互作用,比如透明遮擋等等)。
如你所見,圖形渲染流水線是相當(dāng)復(fù)雜的,而且包含了很多可配置的部分(圖中藍(lán)色著色的階段)。但是,我們大部分只關(guān)心頂點(diǎn)和片段處理器。幾何處理器雖然是可選的,但是經(jīng)常被設(shè)置為默認(rèn)的。
在現(xiàn)代OpenGL中,我們需要自己至少定義一個頂點(diǎn)處理器(處理程序,shader)和一個片段處理器。因?yàn)樵贕PU中沒有默認(rèn)的頂點(diǎn)或者片段處理程序供我們選擇。基于此,通常開始學(xué)習(xí)現(xiàn)代OpenGL是非常困難的,因?yàn)閮H僅是渲染我們的第一個三角形都需要大量的相關(guān)知識。但是一旦你成功渲染了你的第一個三角形,你將會學(xué)到更多的OpenGL圖形編程知識。
下面我們就來渲染我們的第一個三角形吧~
##頂點(diǎn)輸入開始繪制之前我們首先要給OpenGL一些頂點(diǎn)數(shù)據(jù)。OpenGL是一個三維圖形庫,所以所有的坐標(biāo)都應(yīng)該是三維的,即包含x,y和z坐標(biāo)。OpenGL不會簡單地將你的三維坐標(biāo)轉(zhuǎn)換成屏幕上的二維像素。前面已經(jīng)提到過,OpenGL中的坐標(biāo)是標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系,即在x,y和z方向上都是-1到1之間的立方體。所有在這個標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系中的坐標(biāo)才是可以顯示在屏幕上的,而在這個標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系之外的坐標(biāo)都不可能顯示。因?yàn)槲覀兿胍秩疽粋€三角形。所以我們總共需要提供構(gòu)成這個三角形的三個點(diǎn)的三維坐標(biāo)值。我們利用一個GLfloat類型的數(shù)組定義他們在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系的可見區(qū)域。
GLfloat vertices[] = {-
0.5f , -
0.5f ,
0.0f ,
0.5f , -
0.5f ,
0.0f ,
0.0f ,
0.5f ,
0.0f
};
因?yàn)镺penGL在三維空間中進(jìn)行處理,但是我們希望渲染的是一個二維的三角形,所以我們將三個頂點(diǎn)的坐標(biāo)值中的z值全部都設(shè)置為0.0。這樣的能夠使三角形的深度之保持一致,看上去像一個二維圖形一樣。>####標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系 Normalized Device Coordinates (NDC)當(dāng)你的頂點(diǎn)坐標(biāo)在頂點(diǎn)處理器中處理過,它們就應(yīng)該在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系中。標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系是一個小的立方體空間中,這個立方體的三個維度上(x,y和z)都在-1到1之間。任何在這個范圍之外的坐標(biāo)都不會在屏幕上顯示。下圖中可見在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系統(tǒng)我們上面定義的三角形(先不考慮z軸,可以認(rèn)為z軸是垂直于紙面的)。
通常的屏幕坐標(biāo)系的原點(diǎn)是在屏幕的左上角上,而且y正軸是自原點(diǎn)垂直向下的。在標(biāo)準(zhǔn)化坐標(biāo)系中卻不同,其原點(diǎn)在正中,y軸垂直向上。最終你會希望你繪制的所有的對象的坐標(biāo)都在這個標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系之內(nèi),否則它們不會被顯示出來。你的標(biāo)準(zhǔn)化設(shè)備坐標(biāo)最終都會被轉(zhuǎn)換成屏幕坐標(biāo)系中的坐標(biāo)。這個轉(zhuǎn)化過程是基于在程序中你設(shè)置的glViewport參數(shù)來完成的。生成的屏幕坐標(biāo)系中的坐標(biāo)被轉(zhuǎn)換成片段并輸入到片段處理器。上面我們已經(jīng)完成了三角形頂點(diǎn)數(shù)據(jù)的定義,現(xiàn)在我們想要將這些數(shù)據(jù)作為圖形渲染流水線的第一階段的輸入,也就是頂點(diǎn)處理器的輸入。為此,我們需要在GPU中申請內(nèi)存來存儲這些頂點(diǎn)數(shù)據(jù)、告訴OpenGL應(yīng)該如何解釋這塊內(nèi)存并且指定應(yīng)該如何將這些數(shù)據(jù)發(fā)送到顯卡。之后頂點(diǎn)處理器就可以從內(nèi)存中處理我們指定數(shù)量的頂點(diǎn)了。我們利用所謂的頂點(diǎn)緩存對象(vertex buffer objects,簡稱VBO)來管理這塊內(nèi)存。VBO能夠在GPU的內(nèi)存中存儲大量的頂點(diǎn)。利用這種緩存對象的好處是我們可以一次就發(fā)送大批量的數(shù)據(jù)到顯卡,而不用每次之傳輸一個頂點(diǎn)。畢竟從CPU向顯卡中傳輸數(shù)據(jù)是非常慢的,所以我們總是找機(jī)會一次傳輸盡可能多的數(shù)據(jù)。一旦數(shù)據(jù)存儲在顯卡內(nèi)存中,頂點(diǎn)處理器對這些數(shù)據(jù)的訪問可以看成是瞬時的,這極大提升了頂點(diǎn)處理器的處理速度。VBO是我們在這個教程中遇到的第一個OpenGL對象。像OpenGL中的其它對象一樣,它有一個ID唯一的表示一個緩沖區(qū),所以我們可以像下面這樣用glGenBuffers創(chuàng)建一個VBO。
GLuint VBO;
glGenBuffers(1 , &VBO) ;
OpenGL的緩沖區(qū)對象有多種緩沖區(qū)類型,頂點(diǎn)緩存區(qū)對象的緩沖區(qū)類型是GL_ARRAY_BUFFER。我們通過下面的方式使用glBindBuffer將新生成的緩存區(qū)綁定到GL_ARRAY_BUFFER目標(biāo)類型。
glBindBuffer(GL_ARRAY_BUFFER, VBO) ;
此后,我們對任何緩沖區(qū)的調(diào)用(以GL_ARRAY_BUFFER為目標(biāo)類型),都會被用于當(dāng)前綁定的緩沖區(qū),即VBO。于是我們可以通過調(diào)用glBufferData函數(shù)來將之前定義的頂點(diǎn)數(shù)據(jù)拷貝到這個緩沖區(qū)內(nèi)存中:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) , vertices, GL_STATIC_DRAW) ;
glBufferData函數(shù)負(fù)責(zé)將用戶定義的數(shù)據(jù)拷貝到當(dāng)前綁定的緩沖區(qū)中,它的第一個參數(shù)是我們想要拷貝進(jìn)數(shù)據(jù)的緩沖區(qū)類型:在本例中頂點(diǎn)緩沖區(qū)對象當(dāng)前被綁定到了GL_ARRAY_BUFFER目標(biāo)類型。第二個參數(shù)指定了我們想要傳輸進(jìn)緩沖區(qū)的數(shù)據(jù)量大小(以字節(jié)為單位),即使用運(yùn)算符sizeof對我們定義的數(shù)組計(jì)算值。第三個參數(shù)指定我們想要傳輸?shù)臄?shù)據(jù)。第四個參數(shù)指定了我們想要讓顯卡怎樣來管理這些給定的數(shù)據(jù)(我感覺是高速顯卡我們可能怎樣操作這些數(shù)據(jù),在其進(jìn)行存儲的時候,為提高性能或者節(jié)省能耗而“心里有數(shù)”),它有三種形式:
GL_STATIC_DRAW :
這些數(shù)據(jù)基本上不會改變或者極少情況下會被改變。
GL_DYNAMIC_DRAW :
這些數(shù)據(jù)可能會經(jīng)常被改變。
GL_STREAM_DRAW :
這些數(shù)據(jù)在每次繪制的時候都會被改變。
三角形三個點(diǎn)的位置數(shù)據(jù)不會改變,在每次渲染的時候都保持在原來的位置,所以應(yīng)該被設(shè)置為GL_STATIC_DRAW。舉例來說,如果緩沖區(qū)中的數(shù)據(jù)會經(jīng)常改變,那么使用GL_DYNAMIC_DRAW或者GL_STREAM_DRAW參數(shù)將會讓顯卡將這些數(shù)據(jù)分配到能夠更快寫入的地方(以提高性能)。到目前,我們通過頂點(diǎn)緩沖區(qū)對象(VBO)將頂點(diǎn)數(shù)據(jù)存儲到了顯存中。接下來我們想要創(chuàng)建一個頂點(diǎn)處理程序和片段處理程序。>說明:頂點(diǎn)處理器聽上去像是GPU中的硬件名稱,這里之所以這么翻譯,是想和下面的頂點(diǎn)處理程序區(qū)別。實(shí)際上,頂點(diǎn)處理程序完成的就是上面圖形渲染流水線中頂點(diǎn)處理器完成的功能。這樣翻譯便于理解。實(shí)際上原文中上面的和下面的都叫做vertex shader。如果都翻譯成頂點(diǎn)處理程序,那么上面流水線的一個階段是頂點(diǎn)處理程序,怪怪的。所以這樣翻譯。##頂點(diǎn)處理程序 vertex shader頂點(diǎn)處理程序是我們可以編程的流水線中的一個部分。現(xiàn)代OpenGL要求我們,如果想要進(jìn)行渲染,至少要建立起頂點(diǎn)處理程序和片段處理程序。所以我們將會簡短介紹處理程序而且配置兩個非常簡單的shaders來繪制我們的第一個三角形,在下一個教程匯總將會討論關(guān)于shader的更多細(xì)節(jié)。我們首要做的是利用shader語言GLSL來寫我們的頂點(diǎn)處理程序并且編譯這個shader以便于我們可以再我們自己的程序中使用。下面我們將看到一個用GLSL寫的非常基本的vertex shader。
#version 330 core layout (location =
0 )
in vec3 position;
void main()
{
gl_Position =
vec4 (position.x, position.y, position.z,
1.0 );
}
正如你所見,GLSL和C類似。每個shader的開頭都會定義它的版本,330對應(yīng)著OpenGL3.3,420對應(yīng)著OpenGL4.2。我們還明確地聲明我們使用core-profile模式。接下來我們聲明了這個頂點(diǎn)渲染程序的頂點(diǎn)屬性輸入,以關(guān)鍵字in標(biāo)明的position。因?yàn)槟壳拔覀冎魂P(guān)心位置,所以只需要指定單獨(dú)這個頂點(diǎn)屬性作為輸入就夠了。GLSL中,有一個可以包含1到4個GLfloat類型的vector數(shù)據(jù)類型。因?yàn)槿切蔚拿總€頂點(diǎn)都是一個三維坐標(biāo),所以我們可以使用vec3類型的vector(vec3表示vector中含有3個GLfloat)來定義名稱為position的輸入。我們同時還通過layout關(guān)鍵字和location的值(本例設(shè)置為0)明確地指定這些輸入數(shù)據(jù)的位置。這在后面告訴GPU我們用的數(shù)據(jù)在哪兒的時候會用到。>這個程序可以這么理解:聲明類型為vec3的變量position,用關(guān)鍵字in指明這是此頂點(diǎn)處理器的輸入,并且用關(guān)鍵字layout(location = 0)指明輸入數(shù)據(jù)的索引號,便于后面查找。然后是函數(shù)體。>>矢量Vector在圖形編程中我們經(jīng)常使用數(shù)學(xué)中矢量的概念,因?yàn)槭噶靠梢院軆?yōu)雅地在任何維度內(nèi)表示對象的位置、方向和其他屬性(所有想要表示的都可以放到一個矢量中)。而且矢量具有很好的數(shù)學(xué)特定。GLSL中的矢量最多可以含有四個數(shù)值,而且可以通過與C結(jié)構(gòu)體中元素類似的訪問方式訪問,如vec.x,vec.y,vec.z和vec.w。它們分別代表了對象在空間中的每一個維度的表示。注意vec.w分量在表示三維空間中的位置時是不需要的。但是它用于稱作透視圖處理中。在后面的教程中應(yīng)該會對vector有更深入的講解。在程序中,設(shè)置頂點(diǎn)處理程序的輸出到在圖形渲染流水線中已經(jīng)定義好的gl_Position變量中。它是一個vec4類型的變量。這個gl_Position理解成是定點(diǎn)處理器和下一個階段圖元裝配的接口。因?yàn)樯厦嫖覀冊O(shè)置的輸入是3維矢量,我們需要把它轉(zhuǎn)化成4維矢量。妝花方式比較簡單,即利用vec4的構(gòu)造函數(shù)來生成四個分量已經(jīng)指定的一個vec4對象就好了。這里w分量設(shè)置為了1,具體原因后面會講到。目前這個頂點(diǎn)渲染程序應(yīng)該是可以想象到的最簡單的頂點(diǎn)處理程序了。因?yàn)樗鼘斎霂缀跏裁匆矝]有做,只是轉(zhuǎn)換了一下數(shù)據(jù)類型就作為結(jié)果輸出了。在真正的應(yīng)用程序中,一般輸入的數(shù)據(jù)不會(像本例中)已經(jīng)被標(biāo)準(zhǔn)化(所有的坐標(biāo)值都在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系中),所以定點(diǎn)處理程序可能首先需要先將這些坐標(biāo)轉(zhuǎn)化為OpenGL能夠處理的標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系。>這里需要說明一下,上面寫的定點(diǎn)處理程序(vertex shader)并不是放在單獨(dú)一個源文件編譯鏈接執(zhí)行的程序,它是一個shader程序。只是我們用到的圖形渲染流水線中的一個階段(頂點(diǎn)處理器)中用到的程序。所以它是被存儲在類似于C的字符數(shù)組中的。像下面這樣:
const GLchar* vertexShaderSource ="
# version 330 core
\n \
layout (location = 0) in vec3 position;
\n \
void main()
\n \
{ \n \
gl_Position = vec4(position.x, position.y, position.z, 1.0);
\n \
} \n \0";那么怎么將它組裝到我們的圖形渲染流水線中呢?首先編譯,然后組裝。接著向下看吧。##編譯shader我們已經(jīng)有了頂點(diǎn)渲染程序(像上面那樣存儲在了字符數(shù)組中),在使用的時候,我們需要在運(yùn)行時從它的源碼動態(tài)編譯它。為了編譯這個shader,我們需要先創(chuàng)建一個shader對象,同樣需要一個唯一的ID來標(biāo)識。像下面這樣通過GLuint來存儲ID,通過glCreateShader來創(chuàng)建shader對象:
GLuint vertexShader;
vertexShader =
glCreateShader(GL_VERTEX_SHADER);
需要注意的是,我們需要在調(diào)用glCreateShader的時候指定我們想要創(chuàng)建的shader的類型,因?yàn)槲覀儎?chuàng)建的是頂點(diǎn)處理程序,所以給的參數(shù)是GL_VERTEX_SHADER。接下來我們將上面寫的shader源碼和新創(chuàng)建的這個shader對象綁定。并且通過調(diào)用glCompileShader來編譯這個shader:
glShaderSource(vertexShader, 1 , &vertexShaderSource, NULL) ;
glCompileShader(vertexShader) ;
glShaderSource函數(shù)的第一個參數(shù)是一個shader對象,第二個參數(shù)指定傳遞的源碼數(shù)量,第三個參數(shù)是shader源碼字符數(shù)組的指針,第四個參數(shù)目前我們先不用管,直接設(shè)置為NULL就可以。>實(shí)際上完成上面的過程也就完成了一個shader的編譯,不管編譯哪個shader,其原理和做法都是相似的。但是總感覺不是那么放心,編譯成功沒有?錯在哪兒了?以下提供了可以檢查編譯結(jié)果的方法:
GLint success;
GLchar infoLog
[512] ;
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success) ;
即首先設(shè)置一個flag,即success變量,然后設(shè)置一個比較大的緩沖區(qū)來裝編譯結(jié)果輸出信息。最重要的是glGetShaderiv函數(shù),它幫助我們得到編譯結(jié)果信息。如果success為0,表示編譯出錯,這時我們應(yīng)該來獲取錯誤輸出信息,這通過glGetShaderInfoLog來完成:
if (
! success)
{glGetShaderInfoLog(vertexShader,
512 ,
NULL , infoLog);std
::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog
<< std
::endl ;
}
當(dāng)然如果編譯成功,就不會有報錯信息,也就是編譯成功了。## 片段處理程序 Fragment shader上面提到,為了渲染三角形,我們還需要提供片段處理程序。片段處理程序提供圖形渲染流水線中片段處理器完成的功能。它負(fù)責(zé)計(jì)算像素點(diǎn)的顏色值。簡化起見,我們的片段處理程序?yàn)樗械南袼囟伎偸禽敵鲆环N顏色——橙色。在計(jì)算機(jī)圖形中,顏色值是由四個值來表示的:分別是紅、綠、藍(lán)和alpha通道分量,通常簡寫為RGBA。在OpenGL和GLSL中,我們通過設(shè)置每種分量值(0.0-1.0)來定義一個顏色值。舉例來說,如果我們想要設(shè)置黃色,那么我們將紅綠兩個分量設(shè)置成1.0。由三種顏色分量我們可以得到16,000,000種顏色值。
#version 330 core out vec4 color;
void main()
{color = vec4(
1.0f ,
0.5f ,
0.2f ,
1.0f );
}
如上面的程序所示,片段渲染程序只輸出一個vec4類型的變量,也就是color,通過out關(guān)鍵字來標(biāo)識。程序的主體部分知識將這個輸出值賦值為橙色。這應(yīng)該也是一個非常簡單的片段處理器了。編譯片段處理程序的過程和編譯頂點(diǎn)處理程序的過程是十分相似的。只是在調(diào)用glCreateShader的時候指定的參數(shù)是GL_FRAGMENT_SHADER:
GLuint fragmentShader;
fragmentShader =
glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
>同樣,可以使用上面介紹的方法檢驗(yàn)我們的編譯是否成功。現(xiàn)在我們已經(jīng)準(zhǔn)備好了我們必須要提供的兩個shader,下面就是要將它們組裝到我們的圖形渲染流水線中(別忘了它們只是整個圖形渲染流水線中的兩個階段),以便于我們使用它來進(jìn)行渲染。## Shader program整個圖像渲染流水線可以看成是一個渲染程序,它由不同階段的shader構(gòu)建而成。OpenGL中對應(yīng)的概念是渲染程序?qū)ο?#xff08;shader program object),它是編譯好和連接到一起的不同階段的shader的整體。這里可以把它看成是可以裝配的流水線。為了使用剛剛編譯好的頂點(diǎn)和片段shader,我們需要把它們裝配到渲染程序?qū)ο笾胁⑶壹せ钏鼈儭_@樣我們才能夠在調(diào)用渲染指令的之后使用包含這些shaders的渲染程序?qū)ο髞礓秩疚覀兊膱D形。這個過程應(yīng)該是一個狀態(tài)設(shè)置過程,而調(diào)用渲染命令是狀態(tài)使用過程。如上面講到的,在圖形渲染流水線中,前面階段的輸出是后面階段的輸入。同理,在渲染程序?qū)ο笾?#xff0c;裝配不同的shader的時候也是這樣,將前面階段的shader的輸出作為后面階段shader的輸入,而且其它階段默認(rèn)已經(jīng)存在。理解成渲染程序?qū)ο髸臀覀兲幚磉@些就好了。創(chuàng)建一個渲染程序?qū)ο笫呛唵蔚?#xff1a;
GLuint shaderProgram;
shaderProgram =
glCreateProgram();
glCreateProgram創(chuàng)建了一個程序?qū)ο?#xff0c;而shaderProgram保存了其ID,現(xiàn)在我們將我們之前創(chuàng)建并編譯好的兩個shader通過調(diào)用glAttachShader和glLinkProgram裝配到這個渲染程序?qū)ο笾?#xff1a;
glAttachShader(shaderProgram, vertexShader) ;
glAttachShader(shaderProgram, fragmentShader) ;
glLinkProgram(shaderProgram) ;
好的,上面的過程就像是我們組裝了一條生產(chǎn)線,讓人激動的是,其中的兩個模塊使我們自己實(shí)現(xiàn)的。它幾乎可以開始生產(chǎn)了,而其產(chǎn)品將會是輸出到屏幕上的圖形。>我們可以像檢查shader程序是否編譯好一樣檢查這條生產(chǎn)線是否組裝好。只不過需要使用與之不同但是十分類似的函數(shù):
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {glGetProgramInfoLog(shaderProgram,
512 ,
NULL , infoLog);
...
}
怎么樣啟動這個生產(chǎn)線呢?我們首先需要告訴OpenGL我們想要激活這個渲染程序?qū)ο?#xff0c;這通過函數(shù)glUseProgram完成:
glUseProgram(shaderProgram) ;
這樣,在此之后我們調(diào)用的任何渲染指令,都會用這個渲染程序(這條生產(chǎn)線)來執(zhí)行。當(dāng)然不要忘記在將編譯好的shader裝配到渲染程序?qū)ο蠛髣h除它們,因?yàn)槲覀儾辉傩枰鼈?#xff1a;
glDeleteShader(vertexShader) ;
glDeleteShader(fragmentShader) ;
上面的過程相當(dāng)于我們已經(jīng)準(zhǔn)備好了生產(chǎn)產(chǎn)品的硬件條件。一條我們定制化(頂點(diǎn)和片段處理器都由我們創(chuàng)建)的生產(chǎn)線(渲染程序?qū)ο?#xff09;,而且我們已經(jīng)準(zhǔn)備好了原材料(頂點(diǎn)數(shù)據(jù))。我們似乎可以開工生產(chǎn)我們的產(chǎn)品(渲染我們的圖形)了。但是并沒有。OpenGL并不知道它應(yīng)該如何使用我們的原材料(數(shù)據(jù))。比如應(yīng)該怎樣取出和存入,怎樣將它們和頂點(diǎn)渲染程序中定義的輸入數(shù)據(jù)聯(lián)系起來。下面我們將告訴OpenGL應(yīng)該怎么使用這些數(shù)據(jù)。 ##設(shè)定頂點(diǎn)輸入方式 前面,我們寫的頂點(diǎn)處理程序只是設(shè)定了輸入的類型(vec3)和輸入后的索引(location=0),但是并沒有指明我們的頂點(diǎn)數(shù)據(jù)的輸入方式。我們的數(shù)組中一共有三個頂點(diǎn)9個數(shù)據(jù),是下標(biāo)為2的先輸進(jìn)去還是下標(biāo)為0的先輸進(jìn)去?實(shí)際上,在OpenGL中頂點(diǎn)定點(diǎn)渲染程序允許我們以多種方式指定類似的輸入方式,這提供了數(shù)據(jù)輸入的巨大靈活性,但是也意味著我們需要手工指定我們的頂點(diǎn)數(shù)據(jù)和頂點(diǎn)處理程序中的頂點(diǎn)屬性的對應(yīng)關(guān)系。即我們需要指定OpenGL在渲染前應(yīng)該如何解釋或理解這些頂點(diǎn)數(shù)據(jù)。我們的頂點(diǎn)緩沖區(qū)中的數(shù)據(jù)的個數(shù)如下圖所示:
位置坐標(biāo)值都是32位(4字節(jié))的浮點(diǎn)類型;每個位置由三個坐標(biāo)值構(gòu)成;在每組3個坐標(biāo)值之間沒有任何間隙,換句話說,數(shù)值在內(nèi)存中是連續(xù)緊密存放的;數(shù)據(jù)的第一個值位于緩沖區(qū)開始的位置。基于以上這些信息,我們可以通過glVertexAttribPointer函數(shù)來告訴OpenGL應(yīng)該如何解釋這些頂點(diǎn)數(shù)據(jù):
glVertexAttribPointer(0 , 3 , GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat) , (GLvoid*) 0);
glEnableVertexAttribArray(0 ) ;
glVertexAttribPointer函數(shù)的參數(shù)較多,我們逐一來看一下:第一個參數(shù):指定了我們想要配置哪個頂點(diǎn)屬性(頂點(diǎn)屬性是一個詞,這里可以理解成一個頂點(diǎn)屬性集合,即矢量)。還記得我們在頂點(diǎn)處理程序的開始處指定的輸入的位置頂點(diǎn)屬的location值嗎,就是這個實(shí)參0的含義。“l(fā)ayout (location = 0)“就限定了頂點(diǎn)屬性的位置是0,方便我們在這個地方使用的時候易于索引。第二個參數(shù)指定了頂點(diǎn)屬性的大小,因?yàn)槲覀冊O(shè)置的輸入是vec3類型的,所以這里設(shè)置為3,表示由3個數(shù)據(jù)構(gòu)成。第三個參數(shù)指定數(shù)據(jù)的類型,設(shè)置為GL_FLOAT。因?yàn)镚LSL中的vector中的數(shù)值類型是GLfloat。第四個參數(shù)指定我們是否需要將數(shù)據(jù)標(biāo)準(zhǔn)化,因?yàn)槲覀兊臄?shù)據(jù)在生成的時候就已經(jīng)標(biāo)準(zhǔn)化了,所以這里并不需要,設(shè)置為GL_FALSE。如果設(shè)置為GL_TRUE,所有不滿足數(shù)值大小范圍為[-1,1]的數(shù)值都會被首先標(biāo)準(zhǔn)化為標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系中的坐標(biāo)值。第五個參數(shù)指定了在連續(xù)頂點(diǎn)屬性集合之間的空隙——稱作步進(jìn)長度。我們的例子中每兩個頂點(diǎn)屬性之間相差三個GLfloat空間,所以設(shè)置為“3 * sizeof(GLfloat)“,實(shí)際上,因?yàn)檫@里數(shù)據(jù)都是緊密排列的,設(shè)置為0,OpenGL就會認(rèn)為頂點(diǎn)屬性之間沒有空隙,也是能夠正常解析的。最后一個參數(shù)將0轉(zhuǎn)換為GLvoid*類型,它指明了數(shù)據(jù)在緩沖區(qū)中的偏移。上面已經(jīng)說了,我們例子中的數(shù)據(jù)在緩沖區(qū)中的偏移是0,所以這里這么給實(shí)參。>還記得前面講的VBO嗎?實(shí)際上,上述頂點(diǎn)屬性的取得都要經(jīng)過VBO,因?yàn)閂BO是OpenGL和Memory之間的接口。那么在有多個VBO時,哪一個才是我們要取的呢?也就是說,如果我們設(shè)定的取數(shù)據(jù)的地方不是我們想象的,而是其他的VBO呢?實(shí)際上,在每次取數(shù)據(jù)的時候,程序能看到的VBO只有一個,也就是綁定到 GL_ARRAY_BUFFER目標(biāo)的那個VBO。那么,如果我們想要從其他VBO中取數(shù)據(jù)也是簡單的,只需要在取之前將含有我們想要數(shù)據(jù)的VBO綁定到 GL_ARRAY_BUFFER就好了。到目前為止,我們已經(jīng)指定好了OpenGL應(yīng)該怎樣解釋我們的原材料(頂點(diǎn)數(shù)據(jù))。我們還應(yīng)該通過上面所示的glEnableVertexAttribArray函數(shù)設(shè)置頂點(diǎn)屬性生效,因?yàn)轫旤c(diǎn)屬性默認(rèn)是disabled的,參數(shù)就是設(shè)置的location(為0)。OK,到這兒基本上所有該準(zhǔn)備的都已經(jīng)準(zhǔn)備完成了!我們首先利用VBO準(zhǔn)備好了頂點(diǎn)數(shù)據(jù),接著創(chuàng)建了兩個shader(頂點(diǎn)和片段),然后將它們裝配到當(dāng)前使用的渲染程序?qū)ο笾?#xff0c;最后我們告訴OpenLGL應(yīng)該如何解釋我們的數(shù)據(jù)。是時候繪制我們的圖形了。至此,我們知道了渲染圖形的整個流程大致是這么個樣子:
glBindBuffer(GL_ARRAY_BUFFER, VBO) ;
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) , vertices, GL_STATIC_DRAW) ;
glVertexAttribPointer(0 , 3 , GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat) , (GLvoid*) 0);
glEnableVertexAttribArray(0 ) ;
glUseProgram(shaderProgram) ;
someOpenGLFunctionThatDrawsOurTriangle() ;
在繪制之前,還有最后一步——更標(biāo)準(zhǔn)地繪制我們的三角形! 在每一次我們想要繪制一個對象的時候,這個流程都需要執(zhí)行一次。現(xiàn)在看上去可能不是那么多,但是,如果后面我們的繪制更加復(fù)雜的時候就會出現(xiàn)問題了。快速綁定合適的緩沖區(qū)對象和配置所有的頂點(diǎn)屬性變成一個龐雜的過程。要是能有一個對象將我們配置的所有的狀態(tài)都記錄下來,只要在使用的時候綁定這個對象就好了。這種對象就是下文要講到的頂點(diǎn)數(shù)組對象(Vertex Array Object,簡稱VAO)。##頂點(diǎn)數(shù)組對象 Vertex Array Object 頂點(diǎn)數(shù)組對象(VAO)可以像VBO類似方式綁定,隨后的對數(shù)組對象的調(diào)用都將被存儲到頂點(diǎn)數(shù)組對象中。這樣的好處是,在進(jìn)行頂點(diǎn)屬性指針配置的時候只需要調(diào)用一次必要的函數(shù),再次使用的時候,只需要綁定相關(guān)的VAO就可以了,因?yàn)閂AO已經(jīng)將這個配置全部記錄下來。這樣的話,在不同的對象繪制之間就簡化了配置的過程。因?yàn)槲覀冊O(shè)置要繪制對象的狀態(tài)設(shè)置都已經(jīng)存儲到了VAO中。OpenGL的core-profile模式要求我們使用VAO,這樣的話它能夠知道對我們的頂點(diǎn)輸入的具體操作。如果我們綁定VAO失敗,OpenGL很有可能停止運(yùn)行。一個頂點(diǎn)數(shù)組對象(VAO)存儲以下信息:對glEnableVertexAttribArray或者glDisableVertexAttribArray調(diào)用對頂點(diǎn)屬性的配置,即對glVertexAttribPointer的調(diào)用通過調(diào)用glVertexAttribPointer與頂點(diǎn)屬性關(guān)聯(lián)的VBO創(chuàng)建VAO的過程和創(chuàng)建VBO類似:
GLuint VAO;
glGenVertexArrays(1 , &VAO) ;
使用VAO時唯一要做的就是使用glBindVertexArray函數(shù)來綁定VAO。綁定之后我們應(yīng)該綁定或者配置相關(guān)的VBO和屬性指針,然后解綁這個VAO留作后用。在我們想要繪制一個對象的時候,我們只需要將包含我們想要的設(shè)置的VAO在繪制之前再次綁定就可以了。這個過程大概如下所示:
glBindVertexArray(VAO) ;
glBindBuffer(GL_ARRAY_BUFFER, VBO) ;
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) , vertices, GL_STATIC_DRAW) ;
glVertexAttribPointer(0 , 3 , GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat) , (GLvoid*) 0);
glEnableVertexAttribArray(0 ) ;
glBindVertexArray(0 ) ;
[...]
glUseProgram(shaderProgram) ;
glBindVertexArray(VAO) ;
someOpenGLFunctionThatDrawsOurTriangle() ;
glBindVertexArray(0 ) ;
>通常在每次配置之后將對象解綁是一個比較好的做法,因?yàn)檫@樣可以防止在其它地方對其無意之間的綁定。終于,所有的東西都已經(jīng)準(zhǔn)備好了,實(shí)際上在VAO講解之前就已經(jīng)好了,只不過我們對自己的要求比較高,要用更規(guī)范的方式來進(jìn)行我們圖形的繪制。實(shí)際上利用VAO的方式也的確方便我們后面的學(xué)習(xí)和理解。而且當(dāng)我們有很對對象或者很多VBO或者很多配置需要時常切換的時候,我們利用VAO可以大大提高工作效率。嗯,這是值得的!##期待已久的三角形!我們通過OpenGL提供的圖元繪制函數(shù)glDrawArrays(實(shí)際上還有其他,我們現(xiàn)在先選擇glDrawArrays)來繪制我們的對象。相關(guān)的VAO,VBO就是前面花了這么長時間準(zhǔn)備的:
glUseProgram(shaderProgram) ;
glBindVertexArray(VAO) ;
glDrawArrays(GL_TRIANGLES, 0 , 3 ) ;
glBindVertexArray(0 ) ;
glDrawArrays函數(shù)的第一個參數(shù)是OpenGL支持繪制的圖元類型的宏定義。GL_TRIANGLES代表三角形。 第二個參數(shù)指定了開始繪制的頂點(diǎn)數(shù)組下標(biāo),我們就讓它為0。最后一個參數(shù)指定了我們要繪制多少個點(diǎn),我們只有三個點(diǎn)。現(xiàn)在試著編譯我們的程序并且運(yùn)行吧,我已經(jīng)迫不及待了。我的運(yùn)行的結(jié)果是:
到目前為止,全部的代碼在這兒。
##元素緩沖對象 Element Buffer Objects除了上面介紹的利用glDrawArrays函數(shù)進(jìn)行圖形渲染的方式,實(shí)際上還有一種渲染方式,就是借助glDrawElements進(jìn)行圖形渲染。它和元素緩沖對象(Element Buffer Objects,簡稱EBO)是聯(lián)系在一起的。解釋元素緩沖對象(EBO)是如何工作的最好方式是給出一個例子:假設(shè)我們想要繪制一個矩形而不是三角形。我們可以利用兩個三角形(OpenGL主要是利用基本圖元三角形來完成復(fù)雜對象的繪制)來繪制一個矩形。按照上面講過的流程,首先是數(shù)據(jù)的產(chǎn)生:
GLfloat vertices[] = {
0.5f ,
0.5f ,
0.0f ,
0.5f , -
0.5f ,
0.0f , -
0.5f ,
0.5f ,
0.0f ,
0.5f , -
0.5f ,
0.0f , -
0.5f , -
0.5f ,
0.0f , -
0.5f ,
0.5f ,
0.0f
};
如你所見,兩個三角形之間是有所重合的:左上角和右下角的點(diǎn)被指定了兩次。相對于一個矩形的四個頂點(diǎn)來說,我們指定了六個點(diǎn)(其中有兩個是重合的),這相當(dāng)于多做了50%的工作!當(dāng)我們要繪制更為復(fù)雜的模型的時候這種情況還會更糟,因?yàn)樗鼈兛赡苡懈嗟闹睾稀J遣皇悄苡幸环N方法只需要存儲(矩形)模型的不同的點(diǎn),在繪制的時候只需要指定特定的繪制順序就能夠得到我們想要的圖形呢?在這種情況下,我們只需要存儲矩形的四個頂點(diǎn)(右上,右下,左上,左下),且每個頂點(diǎn)存儲一次,而且只需要在繪制的時候指定先繪制右上–右下–左上一個三角形,在繪制右下–左下–左上一個三角形就可以了。OpenGL會提供給我們這種方便的方式嗎?幸運(yùn)的是,元素緩沖對象(EBO)就是干這個事的!EBO是一個像VBO一樣的緩存區(qū),但是它存儲的是OpenGL需要繪制的頂點(diǎn)的索引(而不是坐標(biāo))。這種稱作為索引繪制的方法解決了上述的重復(fù)的問題。為了使用這種方法,我們需要首先設(shè)定頂點(diǎn)的坐標(biāo)值和我們期望OpenGL在繪制的時候的索引值,它們是兩個數(shù)組,如下所示:
GLfloat vertices[] = {
0.5f ,
0.5f ,
0.0f ,
0.5f , -
0.5f ,
0.0f , -
0.5f , -
0.5f ,
0.0f , -
0.5f ,
0.5f ,
0.0f
};
GLuint indices[] = {
0 ,
1 ,
3 ,
1 ,
2 ,
3
};
如代碼所示,我們僅僅在頂點(diǎn)坐標(biāo)的數(shù)組中指定了我們想要繪制的矩形的四個頂點(diǎn),而在索引數(shù)組中指定了在繪制每個三角形時使用的點(diǎn)。接下來我們來創(chuàng)建元素緩沖對象:
GLuint EBO;
glGenBuffers(1 , &EBO) ;
創(chuàng)建過程和VBO的創(chuàng)建過程是一致的,因?yàn)槎弑举|(zhì)上就是一塊緩存區(qū)。同樣像VBO一樣,可以使用glBindBuffer來指定EBO的緩沖區(qū)類型,可以使用glBufferData將索引數(shù)組的數(shù)據(jù)復(fù)制到這塊緩沖區(qū)中。同樣,和VBO綁定到GL_ARRAY_BUFFER目標(biāo)類似,我們將EBO綁定到GL_ELEMENT_ARRAY_BUFFER目標(biāo),以保證調(diào)用相關(guān)的函數(shù)的時候操作的是我們現(xiàn)在生成的這個索引數(shù)組:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO) ;
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices) , indices, GL_STATIC_DRAW) ;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO) ;
接下來,我們需要調(diào)用另一個繪制函數(shù)glDrawElements來完成這個矩形的繪制。調(diào)用glDrawElements表明我們想要按照我們當(dāng)前綁定的EBO中的索引值來繪制我們的圖形。如下所示:
glDrawElements(GL_TRIANGLES, 6 , GL_UNSIGNED_INT, 0 ) ;
glDrawElements函數(shù)的第一個參數(shù)指定了我們想要繪制的圖元類型,這里指定為GL_TRIANGLES,第二個參數(shù)是要繪制的元素個數(shù)。這里設(shè)置為6因?yàn)槲覀円L制兩個三角形(2*3=6個頂點(diǎn),就是索引數(shù)組中的六個頂點(diǎn))。第三個參數(shù)指定了索引的數(shù)據(jù)類型,這里設(shè)置的是無符號整型GL_UNSIGNED_INT,最后一個參數(shù)允許我們指定EBO中的一個偏移(或者在不用EBO的時候這個參數(shù)直接給一個索引數(shù)據(jù)名),這里我們給定的值是0。glDrawElements函數(shù)從當(dāng)前綁定到GL_ELEMENT_ARRAY_BUFFER目標(biāo)的EBO中取得索引值。這意味著我們在每次繪制對象的時候都需要綁定相應(yīng)的EBO到GL_ELEMENT_ARRAY_BUFFER,這看上去似乎又有些繁雜。恰好之前介紹過的VAO也同樣能夠幫助我們解決這個問題。一個頂點(diǎn)數(shù)組對象(VAO)也可以保留EBO的綁定信息(和VBO類似)。所以如果在綁定VAO之后進(jìn)行了EBO的綁定也會被VAO記錄下來,等到再次綁定VAO的時候,同樣相應(yīng)的EBO也就被綁定到了相應(yīng)的GL_ELEMENT_ARRAY_BUFFER。如下圖所示: >VAO在綁定目標(biāo)是GL_ELEMENT_ARRAY_BUFFER存儲glBindBuffer調(diào)用。這意味著它也存儲它的解綁調(diào)用,以確保你在解綁VAO之前不會解綁EBO,否則,它就沒有EBO來進(jìn)行配置了。利用EBO、VAO和glDrawElements的初始化和繪制代碼基本流程如下所示:
glBindVertexArray(VAO) ;
glBindBuffer(GL_ARRAY_BUFFER, VBO) ;
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) , vertices, GL_STATIC_DRAW) ;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO) ;
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices) , indices, GL_STATIC_DRAW) ;
glVertexAttribPointer(0 , 3 , GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat) , (GLvoid*) 0);
glEnableVertexAttribArray(0 ) ;
glBindVertexArray(0 ) ;
[...]
glUseProgram(shaderProgram) ;
glBindVertexArray(VAO) ;
glDrawElements(GL_TRIANGLES, 6 , GL_UNSIGNED_INT, 0 )
glBindVertexArray(0 ) ;
運(yùn)行上面的程序應(yīng)該得到如下所示的畫面。左邊的圖形看上去應(yīng)該是比較熟悉的填充模式,右邊的方式是用線框模式繪制的。線框的三角形顯示出這個矩形確實(shí)是由兩個三角形組成的。
線框模式和填充模式 以線框模式繪制三角形(或者其它圖元),需要利用狀態(tài)設(shè)置函數(shù)glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)來完成,其中第一個參數(shù)指定對要繪制的圖元的兩個面(OpenGL中的繪制對象都是有兩個面的,正面和反面,后面應(yīng)該會講到怎么區(qū)分這兩個面)都采用同樣的繪制模式,第二個參數(shù)指定以線框來繪制圖元。隨后的繪制命令都會以設(shè)定的線框模式來繪制圖形,知道我們將繪制模式再次通過glPolygonMode函數(shù)將繪制模式指定為填充模式。
錯誤是不可避免的,如果有錯誤,說明前面的某一步可能出問題了。同時,也代表理解上可能有點(diǎn)問題,當(dāng)然也有可能是我表述不清。。。。可以回過頭來檢查一下,到目前為止的多有代碼都在這兒。值得注意的是,代碼中為了和glDrawArrays繪制方式區(qū)別,以索引繪制的方式為其創(chuàng)建了另一份對應(yīng)的VBO,EBO和VAO,所以有多個VBO和VAO,這樣在切換的時候可以體會利用VAO進(jìn)行狀態(tài)設(shè)置保存的好處。
如果你按照上面的過程成功繪制了三角形或者矩形。你已經(jīng)挺過了學(xué)習(xí)現(xiàn)代OpenGL幾乎是最艱難的一段:繪制一個簡單的三角形。萬事開頭難嘛。實(shí)際上,這其中包含了很多相關(guān)的知識,如果沒有學(xué)過圖形學(xué)相關(guān)的內(nèi)容,看起來還是比較吃力的。如果有相關(guān)的圖形學(xué)基礎(chǔ),可以發(fā)現(xiàn),本次教程是對理論知識的一次小小實(shí)踐。充分地理解這個過程是十分必要的,也是后面繼續(xù)學(xué)習(xí)的基礎(chǔ)。一旦對這些概念和過程有了充分的了解,后面的內(nèi)容應(yīng)該就相對簡單一些了。
總結(jié)
以上是生活随笔 為你收集整理的【Modern OpenGL】第一个三角形 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。