頂點數組
當我們有來自模型的大量數據的時候,使用顯示列表來對這些數據進行預編譯,需要遍歷這些頂點數據(一次一個頂點數據)把數據傳給OpenGL。依賴於頂點的數量,這會帶來潛在的性能損耗。而且這些數據不一定是靜態的,有可能在我們每次渲染的時候,我們需要對這些數據進行更改。這個時候就不適合使用顯示列表。
在OpenGl中,使用頂點數組能夠很好的解決這兩個問題。使用頂點數組,我們可以隨時進行預編譯或修改幾何圖形,然後一次性傳輸這些數據。基本的頂點數組幾乎和顯示列表一樣快,而且不要求數據是靜態的。
在OpenGL中使用頂點數組有4個基本的步驟:
為了演示這些步驟,修改之前的第九章的pointsprite的例子。我們使用頂點數組的方式替代掉glBegin/glEnd的方式。修改的代碼如下:
//畫小星星 glPointSize(7.0); glVertexPointer(2, GL_FLOAT, 0, &smallStars[0]); glDrawArrays(GL_POINTS, 0, SMALL_NUM); ///畫中等大小的星星 glPointSize(12.0); glVertexPointer(2, GL_FLOAT, 0, &mediumStars[0]); glDrawArrays(GL_POINTS, 0, MEDIUM_NUM); ////大星星 glPointSize(20.0); glVertexPointer(2, GL_FLOAT, 0, &largeStars[0]); glDrawArrays(GL_POINTS, 0, LARGE_NUM); glDisableClientState(GL_VERTEX_ARRAY);
首先我們需要把幾何圖形的數據組裝到數組中。在上面的例子中,在初始化的時候就組裝好數據,代碼如下:
//星星的坐標 M3DVector2f smallStars[SMALL_NUM]; M3DVector2f mediumStars[MEDIUM_NUM]; M3DVector2f largeStars[LARGE_NUM];
void SetupRC() { ... //隨機獲取星星的位置 for (int i = 0; i < SMALL_NUM; ++i) { smallStars[i][0] = (GLfloat)(rand() % SCREEN_X); smallStars[i][1] = (GLfloat)(rand() % SCREEN_Y); } for (int i = 0; i < MEDIUM_NUM; ++i) { mediumStars[i][0] = (GLfloat)(rand() % SCREEN_X); mediumStars[i][1] = (GLfloat)((rand() % SCREEN_Y) + 50); }
for (int i = 0; i < LARGE_NUM; ++i) { largeStars[i][0] = (GLfloat)(rand() % SCREEN_X); largeStars[i][1] = (GLfloat)(rand() % SCREEN_Y); } ... }
像OpenGL大多數的特性一樣,要使用頂點數組首先得啟用它。
//使用頂點數組
glEnableClientState(GL_VERTEX_ARRAY);
啟用和禁用的函數原型如下:
void glEnableClientState(GLenum array);
void glDisableClientState(GLenum array);
函數接受的參數值有:GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_SECONDARY_COLOR_ARRAY, GL_NORMAL_ARRAY, GL_FOG_CORRDINATE_ARRAY, GL_TEXTURE_COORD_ARRAY和GL_EDGE_FLAG_ARRAY。在上面的例子中我們只使用到了頂點數組。當然我們可以同時啟用多種類型的數組。
為什麼是使用glEnableClientState來啟用數組而不是像以前那樣用glEnable?因為OpenGL的設計時 client/server模式的。server服務器是圖形硬件,client客戶端是CPU和內存。對於PC來說,服務器就是顯卡,客戶端就是CPU和主存。
在我們啟用了頂點數組之後,我們需要告訴OpenGL數據在哪裡(內存中的位置)。在上面的例子中相應的代碼:
glVertexPointer(2, GL_FLOAT, 0, &smallStars[0]);
相應的有指定顏色數組,紋理坐標數組等等,列表如下:
//頂點 void glVertexPointer(GLint size, GLenum type, GLsizei stride, const void *pointer); //顏色 void glColorPointer(GLint size, GLenum type, GLsizei stride, const void *pointer); //紋理坐標 void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void *pointer); //輔助顏色 void glSecondaryColorPointer(GLint size, GLenum type, GLsizei stride, const void *pointer); //法線 void glNormalPointer(GLenum type, GLsizei stride, const void *pData); //霧坐標 void glFogCoordPointer(GLenum type, GLsizei stride, const void *pointer); //邊界 void glEdgeFlagPointer(GLenum type, GLsizei stride, const void *pointer);
上面同類型的參數意義都是一樣的。 其中第一個參數size是指一個頂點或顏色等所包含的元素的個數,例如頂點有(x,y), (x,y,z), (x,y,z,w)的形式。像法線,霧坐標, 邊界標記這幾個函數沒有size,因為它們的值一定是3(x,yz)的形式。
參數type指的是數據的類型,並不是所有的數據類型都可以被接受的。什麼類型的數組能接受的數據類型和元素個數如下:
stride參數指定了數據之間的間隔。例子中的情況是0,為我們的頂點數據是緊挨著的。如果我們一個數組中即包含了頂點數據和顏色數據(混合數組),那麼我們可以通過這個stride來區分。舉個例子:
GLfloat data[] = { 10.0f, 5.0f, 0.0f, //頂點數據 1.0f, 0.0f, 0.0f , //顏色數據 5.0f, 10.0f, 0.0f, //頂點數據 0.0f, 1.0f, 0.0f //顏色數據 } glVertexPointer(3, GL_FLOAT, 3, &data[0]); //此時頂點數據的間隔就是3 glColorPointer(3, GL_FLOAT, 3, &data[3]); //此時顏色數據的間隔也是3
對於多重紋理的情況,如果我們是使用glBegin/glEnd的方式,那可以通過glMultiTexCoord來指示為哪一個紋理指定坐標。如果使用頂點數組的方式,那麼我們可以在調用glTexCoordPointer之前調用:
glClientActiveTexture(GLenum texture);
其中texture是GL_TEXTURE0, GL_TEXTURE1等。來指定是哪一個紋理的坐標。
到此為止OpenGl已經知道我們數據的位置了,那麼我們可以用下面的代碼遍歷我們的數據:
glBegin(GL_POINTS); for(i = 0; i < SMALL_STARS; i++) glArrayElement(i); glEnd();
glArrayElement會從數組中提取相應的數據。假設我們已經啟用和設置好了頂點,顏色,紋理坐標數組。那麼上面的函數調用相當於:
glBegin(GL_POINTS); for (i = 0; i < SMALL_STARS; ++i) { glColor3fv(color[i]); glTexCoord3fv(texcoord[i]); glVertex3fv(vertex[i]); } glEnd();
當然OpenGL提供了一種更簡便快速的方法:
void glDrawArrays(GLenum mode, GLint first, GLint count);
其中mode指定了渲染的圖元模式GL_POINTS, GL_TRIANGLES等等。第二個參數first指定了頂點數組起始的下標,count指定了要使用的頂點的個數。在上面的例子中,渲染小星星的方式如下:
glDrawArrays(GL_POINTS, 0, SMALL_NUM);
這樣OpenGL的實現可以優化這些數據塊傳輸的過程,也節省了許多函數的調用。
頂點索引數組存儲的是頂點數組的索引(數組的下標)。這樣一來改變頂點遍歷的順序,其訪問順序是由一個單獨的索引數組指定的。二來頂點數組可以減少存儲頂點的數量,一些幾何圖形有許多的共享頂點,如果使用頂點索引數組的方式,這些共享的頂點就沒必要重復存儲在頂點數組中(許多情況下可以節省內存空間,節省傳輸的帶寬,也減少對內存的操作),也減少了變換的開銷。在理想的情況下,他們可能比顯示列表更快。
雖然三角形帶(GL_TRIANGLE_STRIPS)能夠共享頂點。但沒辦法避免兩個三角形帶所共享頂點的變換的開銷,因為每一個三角形帶都必須是獨立的。
下面舉一個簡單的例子。
一個立方體有6個面,每個面都是由4個頂點組成的正方形,6x4=24個頂點,其實有許多被正方形共享的頂點,不重復的頂點只有8個。但按照以往的方式使用glBegin(GL_UQADS)/glEnd,我們還是需要傳輸24個頂點(調用glVertex 24次)。如果我們使用頂點索引數組的方式,就只需要8個頂點就夠了,我們用索引指向這些頂點,索引數組中會有重復的值。圖示如下:
每個頂點有浮點數值組成的,但每個索引只是一個整數值。在頂點數少的情況下,並不會節省多少空間。比如這個立方體,雖然頂點數組少存了16個頂點,但是索引數組需要額外的24個整數值來存儲這些頂點的索引的。
代碼示例:
static GLfloat cube[]={-1.0f, -1.0f, -5.0f, //前面的正方形 1.0f, -1.0f,-5.0f, 1.0f, 1.0f, -5.0f, -1.0f, 1.0f, -5.0f, -1.0f, -1.0f, -10.0f,//背面的正方形 1.0f, -1.0f, -10.0f, 1.0f, 1.0f, -10.0f, -1.0f, 1.0f, -10.0f}; static GLubyte index[]={0, 1, 2, 3, //前面 0, 3, 7, 4, //左面 5, 6, 2, 1, //右面 7, 6, 5, 4, //後面 3, 2, 6, 7, //上面 1, 0, 4, 5 //地面 }; void SetupRC() { glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } void RenderScene() { glClear(GL_COLOR_BUFFER_BIT); glColor3f(0.0f, 0.0f, 1.0f); glPushMatrix(); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, cube); glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, index); glDisableClientState(GL_VERTEX_ARRAY); glPopMatrix(); glutSwapBuffers(); }
可以看到上面調用繪制的��數是
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, indexes);
第一個參數是圖元的模式,第二個是索引數組包含的值的個數,第三個參數索引數組值的類型,最後一個參數是索引數組的指針。還有其他相應的函數。
glDrawRangeElements 可以指定索引數組的起始和結束位置.
glInterleavedArrays可以使用混合數組。相關的函數請參考文檔。
OpenGL超級寶典 第4版 中文版PDF+英文版+源代碼 見 http://www.linuxidc.com/Linux/2013-10/91413.htm
OpenGL編程指南(原書第7版)中文掃描版PDF 下載 http://www.linuxidc.com/Linux/2012-08/67925.htm
OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm
Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm
OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm
Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm
如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm
更多《OpenGL超級寶典學習筆記》相關知識 見 http://www.linuxidc.com/search.aspx?where=nkey&keyword=34581