顶点数组
当我们有来自模型的大量数据的时候,使用显示列表来对这些数据进行预编译,需要遍历这些顶点数据(一次一个顶点数据)把数据传给OpenGL。依赖于顶点的数量,这会带来潜在的性能损耗。而且这些数据不一定是静态的,有可能在我们每次渲染的时候,我们需要对这些数据进行更改。这个时候就不适合使用显示列表。
在OpenGl中,使用顶点数组能够很好的解决这两个问题。使用顶点数组,我们可以随时进行预编译或修改几何图形,然后一次性传输这些数据。基本的顶点数组几乎和显示列表一样快,而且不要求数据是静态的。
在OpenGL中使用顶点数组有4个基本的步骤:
为了演示这些步骤,修改之前的第九章的pointsprite的例子。我们使用顶点数组的方式替代掉glBegin/glEnd的方式。修改的代码如下:
?

?
首先我们需要把几何图形的数据组装到数组中。在上面的例子中,在初始化的时候就组装好数据,代码如下:
?
像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]);
相应的有指定颜色数组,纹理坐标数组等等,列表如下:
上面同类型的参数意义都是一样的。 其中第一个参数size是指一个顶点或颜色等所包含的元素的个数,例如顶点有(x,y), (x,y,z), (x,y,z,w)的形式。像法线,雾坐标, 边界标记这几个函数没有size,因为它们的值一定是3(x,yz)的形式。
参数type指的是数据的类型,并不是所有的数据类型都可以被接受的。什么类型的数组能接受的数据类型和元素个数如下:

stride参数指定了数据之间的间隔。例子中的情况是0,为我们的顶点数据是紧挨着的。如果我们一个数组中即包含了顶点数据和颜色数据(混合数组),那么我们可以通过这个stride来区分。举个例子:
对于多重纹理的情况,如果我们是使用glBegin/glEnd的方式,那可以通过glMultiTexCoord来指示为哪一个纹理指定坐标。如果使用顶点数组的方式,那么我们可以在调用glTexCoordPointer之前调用:
glClientActiveTexture(GLenum texture);
其中texture是GL_TEXTURE0, GL_TEXTURE1等。来指定是哪一个纹理的坐标。
?
到此为止OpenGl已经知道我们数据的位置了,那么我们可以用下面的代码遍历我们的数据:
glArrayElement会从数组中提取相应的数据。假设我们已经启用和设置好了顶点,颜色,纹理坐标数组。那么上面的函数调用相当于:
当然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个整数值来存储这些顶点的索引的。
代码示例:

可以看到上面调用绘制的函数是
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, indexes);
第一个参数是图元的模式,第二个是索引数组包含的值的个数,第三个参数索引数组值的类型,最后一个参数是索引数组的指针。还有其他相应的函数。
glDrawRangeElements 可以指定索引数组的起始和结束位置.
glInterleavedArrays可以使用混合数组。相关的函数请参考文档。