當咱們有來自模型的大量數據的時候,使用顯示列表來對這些數據進行預編譯,須要遍歷這些頂點數據(一次一個頂點數據)把數據傳給OpenGL。依賴於頂點的數量,這會帶來潛在的性能損耗。並且這些數據不必定是靜態的,有可能在咱們每次渲染的時候,咱們須要對這些數據進行更改。這個時候就不適合使用顯示列表。 html
在OpenGl中,使用頂點數組可以很好的解決這兩個問題。使用頂點數組,咱們能夠隨時進行預編譯或修改幾何圖形,而後一次性傳輸這些數據。基本的頂點數組幾乎和顯示列表同樣快,並且不要求數據是靜態的。 數組
在OpenGL中使用頂點數組有4個基本的步驟: 服務器
爲了演示這些步驟,修改以前的第九章的pointsprite的例子。咱們使用頂點數組的方式替代掉glBegin/glEnd的方式。修改的代碼以下: ide
//畫小星星 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); spa
啓用和禁用的函數原型以下: .net
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可使用混合數組。相關的函數請參考文檔。