現(xiàn)在在我們的"hello world"程序中,我們已經(jīng)加載了我們的緩沖和紋理,并且編譯和鏈接了我們的著色器程序。終于到最后一步了--讓我們來渲染我們的圖片。 渲染作業(yè)綜述 渲染可能需要很多的參數(shù)。除了所有的緩沖,紋理,著色器,以及它所涉及到的uniform參數(shù),還有許多的其它控制渲染作業(yè)的設(shè)置我沒提到。OpenGL的方法是將這些設(shè)置做成了一個狀態(tài)機(jī),而不是提供一個完整的帶所有標(biāo)記作為參數(shù)的"draw"函數(shù),或者一個需要你去填充各個域的結(jié)構(gòu)體。當(dāng)你使用glBindTexture,glBindBuffer以及類似的方法綁定一個對象的時候,你不僅是使這些對象可以修改,你還將它們綁定到了當(dāng)前渲染作業(yè)的狀態(tài)。并且有狀態(tài)操作函數(shù)可以設(shè)置當(dāng)前著色器,賦值到uniform參數(shù)和描述頂點(diǎn)數(shù)組的結(jié)構(gòu)。當(dāng)你最后使用glDrawElements將一個作業(yè)提交時,OpenGL取當(dāng)前狀態(tài)機(jī)的一個快照并將它添加到GPU的命令隊列,它將在當(dāng)GPU可用時被執(zhí)行。同時,你可以改變OpenGL狀態(tài)以及將更多任務(wù)加到隊列中,而不用等待之前的作業(yè)完成。一旦你將作業(yè)排隊完畢,你可以讓窗口系統(tǒng)"切換緩沖",這個操作將會等待所有的排隊作業(yè)完成然后將結(jié)果顯示在窗口中。 讓我們寫一些代碼設(shè)置渲染作業(yè)狀態(tài): 激活著色器程序并賦值uniform static void render(void) { glUseProgram(g_resources.program); 我們首先通過傳遞鏈接的程序?qū)ο蟮拿纸oglUseProgram來激活我們的著色器對象。一旦程序激活,我們可以開始對我們的uniform變量進(jìn)行賦值。如果你回憶下我們的片元著色器的代碼,我們需要給float fade_factor和一個叫做textures的sampler2D數(shù)組進(jìn)行賦值。 glUniform1f(g_resources.uniforms.fade_factor, g_resources.fade_factor); OpenGL提供了一組glUniform*函數(shù)用于給uniform變量賦值,其中每一個對應(yīng)GLSL程序中的一種uniform變量類型。這些函數(shù)都是glUniform{dim}{type}的形式,其中dim表示vector類型的大小(int或float的uniform是1,vec2是2,等等),type表示組元的類型:要么是i表示integer,要么是f表示float。我們的fade_factor uniform是一個簡單的float,因此我們通過調(diào)用glUniform1f給它賦值,傳入uniform的位置以及新的值作為參數(shù)。 glActiveTexture(GLTEXTURE0); glBindTexture(GLTEXTURE2D, gresources.textures[0]); glUniform1i(g_resources.uniforms.textures[0], 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, g_resources.textures[1]); glUniform1i(g_resources.uniforms.textures[1], 1); 將紋理賦值給samplers有一點(diǎn)點(diǎn)復(fù)雜。GPU只有數(shù)量有限的紋理單元可以提供給紋理數(shù)據(jù)給渲染作業(yè)。我們必須將我們的紋理對象綁定到這些紋理單元,然后將紋理單元的索引賦值給我們的sampler uniform變量,如果它們是int的話。我們綁定的GL_TEXTURE_*目標(biāo)名必須對應(yīng)于sampler uniform的類型。在這里,GLTEXTURE2D對應(yīng)于我們的textures變量使用的sample2D類型。glActiveTexture設(shè)置當(dāng)前活躍的紋理單元。glBindTexture其實(shí)是使用活躍紋理單元作為一個隱含參數(shù)(其它的紋理對象操作的函數(shù)像glTexParameteri和glTexImage2D也是操作綁定到當(dāng)前活躍的紋理單元的紋理)。一旦我們綁定紋理單元之后,我們可以使用glUniform1i對它的索引進(jìn)行賦值。 設(shè)置紋理數(shù)組 glBindBuffer(GL_ARRAY_BUFFER, g_resources.vertex_buffer); glVertexAttribPointer( g_resources.attributes.position, /* attribute */ 2, /* size */ GL_FLOAT, /* type */ GL_FALSE, /* normalized? */ sizeof(GLfloat)*2, /* stride */ (void*)0 /* array buffer offset */ ); glEnableVertexAttribArray(g_resources.attributes.position); 接下來,我們告訴OpenGL我們使用的紋理數(shù)組的格式。我們通過調(diào)用glVertexAttribPointer設(shè)置每一個頂點(diǎn)屬性格式,這個函數(shù)告訴OpenGL在渲染時從頂點(diǎn)數(shù)組中讀出屬性值。glVertexAttribPointer使用屬性位置,關(guān)聯(lián)的屬性變量的元素大小和類型(對于我們的position屬性,大小為2,類型為GLFLOAT),屬性值之間的字節(jié)數(shù)(稱為stride),以及當(dāng)前第一個屬性在當(dāng)前綁定的GLARRAY_BUFFER中的偏移作為參數(shù)。由于歷史原因,offset是作為一個指針傳遞的,但它實(shí)際上被當(dāng)作integer值使用,因此我們傳遞一個整形的0并傳換為void*類型。 ![]() 在我們這里,我們的頂點(diǎn)數(shù)組只由單個vec2 position屬性組成;如果我們有多個屬性值,屬性值可以是交錯的,像是一個結(jié)構(gòu)體數(shù)組,或者是分別存儲在不同的數(shù)組里。靈活的glVertexAttribPointer讓我們可以選擇這兩種情況中每個屬性如何選擇stride和offset去適應(yīng)它們的存儲布局;改變GLARRAYBUFFER綁定不影響由我們已經(jīng)設(shè)置過的屬性數(shù)組指針使用的緩沖。 (上面我沒有提到的normalized?參數(shù)是跟頂點(diǎn)數(shù)組中的整型的數(shù)組一起使用的。如果為true,元素將從它們的integer類型的范圍進(jìn)行映射,比如0-255用于unsigned byte,0.0-1.0用于符點(diǎn)數(shù),像圖片中的顏色組分。如果為false,它們的整型值將被保存。像我們這樣使用的已經(jīng)是符點(diǎn)數(shù)的元素,該參數(shù)沒有任何作用。) 提交渲染作業(yè) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_resources.element_buffer); glDrawElements( GL_TRIANGLE_STRIP, /* mode */ 4, /* count */ GL_UNSIGNED_SHORT, /* type */ (void*)0 /* element array buffer offset */ ); glDrawElements是設(shè)置繪圖管線動作為函數(shù)。我們告訴它我們使用哪種三種角組裝模式,使用多少頂點(diǎn)組裝三角形,我們元素數(shù)組的組成類型,以及當(dāng)前綁定的第一個要渲染的元素在GLELEMENTARRAY_BUFFER內(nèi)部的偏移,這也是一個實(shí)際上是integer的指針參數(shù)。它將獲取指向的元素數(shù)組的索引,將它們跟當(dāng)前綁定的著色器程序,uniform變量,紋理單元,我們剛剛設(shè)置的頂點(diǎn)屬性指針集合在一起,綁定成為一個渲染作業(yè),并將這個作業(yè)放到GPU隊列中。 清理工作 glDisableVertexAttribArray(g_resources.attributes.position); "Always leave things the way you found them",Bill Brasky曾經(jīng)建議過。OpenGL狀態(tài)機(jī)的缺點(diǎn)就是所有的綁和設(shè)置都是全局地持久的,即使調(diào)用glDrawElements之后。這意味著我們必須注意整個程序生命期中,我們的OpenGL代碼是怎樣和其它的OpenGL代碼交互的。盡管在這個程序中還沒有其它的OpenGL代碼與之交互,我們?nèi)匀粦?yīng)該養(yǎng)成一個好的習(xí)慣。尤其要注意頂點(diǎn)屬性:在涉及到多個著色器程序和多個頂點(diǎn)數(shù)組的復(fù)雜程序中,不正確地使用頂點(diǎn)屬性可能會造成glDrawElements去使用無效的GPU數(shù)據(jù),導(dǎo)致錯誤的輸出或者段錯誤。只在需要的時候去使用頂點(diǎn)數(shù)組是一個好習(xí)慣。這里,我們對position禁用頂點(diǎn)屬性。 你也可能會想,每次渲染時,我們重新綁定了所有相同的對象,設(shè)置了所有的相同的uniform值(除了fade_factor),并且重新激活了所有的同樣的頂點(diǎn)屬性。如果狀態(tài)設(shè)置在glDrawElements調(diào)用之間是持久的,從技術(shù)上講在進(jìn)入glutMainLoop之后,我們可以幾乎完全沒必要要每幀都進(jìn)行設(shè)置,并且每次渲染只更新混色因子并調(diào)用glDrawElements。但是,在你每次期望的時候都設(shè)置好狀態(tài),這是個好主意。 顯示我們完成的場景 glutSwapBuffers(); } 我們只有一個渲染作業(yè)需要等待,因此當(dāng)我們提交作業(yè)并清理之后,我們可以立即執(zhí)行同步。GLUT函數(shù)glutSwapBuffers等待所有的運(yùn)行中的作業(yè)完成,然后用我們的雙緩沖的framebuffer交換顏色緩沖,在下一幀時將當(dāng)前可見的緩沖移到要渲染的"后面",然后將我們剛剛渲染好的圖象推到前面,在我們的窗口中顯示新渲染好的場景。我們的渲染流程完成了! 讓場景動起來 static void update_fade_factor(void) { int milliseconds = glutGet(GLUT_ELAPSED_TIME); g_resources.fade_factor = sinf((float)milliseconds * 0.001f) * 0.5f + 0.5f; glutPostRedisplay(); } 為了讓圖片動起來,我們的glutIdleFunc回調(diào)函數(shù)不停地更新我們給fadefactor賦值的uniform。GLUT維護(hù)一個毫秒級的計時器,我們可以使用glutGet(GLUTELAPSED_TIME)訪問到;我們使用標(biāo)準(zhǔn)C語言的sinf函數(shù)來得到一個平滑的,周期性的0到1之前的數(shù)。每次我們更新混色因子,我們調(diào)用glutPostRedisplay,這會強(qiáng)制我們的渲染回調(diào)函數(shù)去執(zhí)行,更新窗口。 再次編譯運(yùn)行程序 這是我們最后一次編譯和運(yùn)行整個程序,使用所有我們的新的代碼。構(gòu)建和執(zhí)行的命令看起來很像上次我們構(gòu)建的空函數(shù)版本,但是這次,你將編譯真正的hello-gl.c和util.c源文件。如果你使用Makefiles,你可以這樣編譯默認(rèn)的目標(biāo): make -f Makefile.MacOSX # or Makefile.Unix or Makefile.Mingw nmake /f Nmakefile.Windows 一旦編譯后,程序假定它的圖片和著色器資源是在當(dāng)前目錄的,因此最好從包含可執(zhí)行文件,圖片,著色器代碼的目錄用命令行運(yùn)行它。最后我們終于可以曬一下我們的成果了: ![]() 結(jié)論 必須承認(rèn)從一個簡單的"hello world"已經(jīng)起了很遠(yuǎn)了。但是這里我們所創(chuàng)建的框架是非常靈活的;你可以替換成你自己的圖片并調(diào)整著色器代碼在圖片取樣之前對它們進(jìn)行變換或者進(jìn)一步處理,都不需要重新編譯C。下一章中,我們將繼續(xù)頂點(diǎn)著色器來展示基本的3D變換和投影。 如果你很感興趣,這個時候你也可以停下來,自己看一下OpenGL標(biāo)準(zhǔn),注意,OpenGL 2標(biāo)準(zhǔn)仍然包含了很多我沒有提到的過時的特性。我強(qiáng)烈推薦你看OpenGL 3.1之后的版本,一定要看看核心標(biāo)準(zhǔn)部分而不是為了兼容的部分。盡管OpenGL 3之后相對于OpenGL 2添加了很多新的特征,所有的OpenGL 2中的基本的API也仍然是新版本的基本部分。 OpenGL ES 2也是值得一看的。它大部分由我這里提到的OpenGL 2之后的一個子集;所有的我前面提到的OpenGL API也都是在OpenGL ES 2中的。OpenGL ES還對移動平臺添加了一些額外的特性,比如浮點(diǎn)數(shù)支持以及離線著色器編譯,這是桌面版標(biāo)準(zhǔn)中所有提供的。如果你想試一下OpenGL ES開發(fā),它是Android NDK和iPhone SDK的部分。在Windows下,Google的ANGLE項目還提供一個OpenGL ES2在DirectX上的實(shí)現(xiàn)。 |