原文地址http://www.songho.ca/opengl/gl_vertexarray.html
概要
你可以在一系列数组保存顶点信息,如顶点坐标,向量,纹理坐标,颜色信息,来代替立即模式下输入顶点信息的方法(即在glBegin()和glEnd()中指定顶点信息)。你可以使用数组索引找到数组中的元素,来画出几何体。仔细看下面使用立即模式下画立方体的代码。
glBegin(GL_TRIANGLES); // draw a cube with 12 triangles // front face ================= glVertex3fv(v0); // v0-v1-v2 glVertex3fv(v1); glVertex3fv(v2); glVertex3fv(v2); // v2-v3-v0 glVertex3fv(v3); glVertex3fv(v0); // right face ================= glVertex3fv(v0); // v0-v3-v4 glVertex3fv(v3); glVertex3fv(v4); glVertex3fv(v4); // v4-v5-v0 glVertex3fv(v5); glVertex3fv(v0); // top face =================== glVertex3fv(v0); // v0-v5-v6 glVertex3fv(v5); glVertex3fv(v6); glVertex3fv(v6); // v6-v1-v0 glVertex3fv(v1); glVertex3fv(v0); ... // draw other 3 faces glEnd();
每一个面要调用glVertex*()函数六次,来画出二个三角形。如前面的三角片v0-v1-v2和三角片v2-v2-v0。一个立方体有六个面,共要调用glVertex*()函数36次。如果你再指定法向量,纹理坐标和顶点对应的颜色,将增加调用OpenGL函数的次数。顶点v0被三个邻近的面共享。前面、右面、顶面。在立即模式下,你使用了此顶点6次,twice for each side as show in the code。使用顶点数组,可以减少函数调用次数和冗余的共享顶点。
有三种不同的OpenGL函数可使用顶点数组。glDrawArrays(),glDrawEelemnts()和glDrawRangeEelemnts().更好的方法是使用vertex buffer objects和display lists。
初始化OpenGL提供glEnableClientState()和glDisableClientState()函数来激活和禁止6种不同类型的数组。6个函数可指定这些数组的地址。OpenGL可在你的程序里访问这些数组。(me:即这些数组放在内存里,而不是显卡中)。GLfloat vertices[] = {...}; // 36 of vertex coords ... // activate and specify pointer to vertex array glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, vertices); // draw a cube glDrawArrays(GL_TRIANGLES, 0, 36); // deactivate vertex arrays after drawing glDisableClientState(GL_VERTEX_ARRAY);你可以使用一个glDrawArrays()代替36个glVertex*()。然而,我们仍要重复共享的顶点,这样在数组中定义的顶点数仍旧是36个而不是8个。glDrawElements()可以减少在数组中顶点的数量,这样可向OpenGL传递更少的数据。
glDrawElements()
glDrawElements()可以使用相关的索引数组,按索引访问顶点数组,来画出图像。它即可以减少函数的调用次数也可以减少顶点的传输量。OpenGL可能缓存最近的被处理的结果并重用它们,而不用多次发送同样的顶点数据到渲染管线中。GLfloat vertices[] = {...}; // 8 of vertex coords GLubyte indices[] = {0,1,2, 2,3,0, // 36 of indices 0,3,4, 4,5,0, 0,5,6, 6,1,0, 1,6,7, 7,2,1, 7,4,3, 3,2,7, 4,7,6, 6,5,4}; ... // activate and specify pointer to vertex array glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, vertices); // draw a cube glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indices); // deactivate vertex arrays after drawing glDisableClientState(GL_VERTEX_ARRAY);顶点坐标数组的大小现在为8,此为立方体没有冗余的顶点数。
在共享顶点上的不同的法向量
你应该思考的一件事是在共享顶点上的不同的法向量。如果相邻多边形的法向量有同一个顶点,但法向量不同,那么这些法向量应该和面一样多,一个面一个法向量。举个例子,v0顶点被前面,右面和顶面共享,但在v0顶点上的法向量却不能被共享。前面的法向量为n0,右面的法向量为n1,顶面的法向量为n2。这种情况下,在共享顶点的法向量是不一样的。顶点不能在顶点数组里定义一次。必须在顶点坐标数组里定义一个顶点多次,这样做是为了使顶点坐标数组中元素的数量与法向量数组的大小相同。一个立方体有24个顶点:6个面x4。参见示例 代码。glDrawRangeElements()
类似glDrawElements(), glDrawRangeElements()也可随机访问顶点数组。 然而,glDrawRangeElements()多了两个参数(start and end index) ,用来事先指定顶点的范围。通过添加范围约束,OpengGL可能事先得到顶点数据数组的受限大小,这可能提升性能。 在glDrawRangeElements中额外的参数是start和end index, OpenGL可以事先得到受限的顶点数组的大小:end - start + 1. 此值必须在start和end index之间。注意,晨不是所有在(start,end)范围内的的顶点都会都引用。But, if you specify a sparsely used range, it causes unnecessary process for many unused vertices in that range.GLfloat vertices[] = {...}; // 8 of vertex coords GLubyte indices[] = {0,1,2, 2,3,0, // first half (18 indices) 0,3,4, 4,5,0, 0,5,6, 6,1,0, 1,6,7, 7,2,1, // second half (18 indices) 7,4,3, 3,2,7, 4,7,6, 6,5,4}; ... // activate and specify pointer to vertex array glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, vertices); // draw first half, range is 6 - 0 + 1 = 7 vertices used glDrawRangeElements(GL_TRIANGLES, 0, 6, 18, GL_UNSIGNED_BYTE, indices); // draw second half, range is 7 - 1 + 1 = 7 vertices used glDrawRangeElements(GL_TRIANGLES, 1, 7, 18, GL_UNSIGNED_BYTE, indices+18); // deactivate vertex arrays after drawing glDisableClientState(GL_VERTEX_ARRAY);使用glGetIntegerv(),传入参数GL_MAX_ELEMENTS_VERTICES或GL_MAX_ELEMENTS_INDICES,你便可以查询顶点数组和索引数组的最大值。注意glDrawRangeElements()在OpenGL1.2及更高版本中有效。