加速渲染主要有两种方法:显示列表与顶点数组。
其中,显示列表是通过预编译方式加速,而顶点数组是通过优化实时编译方式加速。但是,不需要同时使用两种加速方式。如果把顶点数组放在预编译中,然后通过显示列表来显示,这样做所耗时间与直接使用顶点数组所耗时间基本相同。
使用顶点数组,也就是把所有点存储在一个数组中,然后将该数组的地址属性通知给OpenGL,然后使用顶点调用函数来调用所有的顶点。OpenGL会对调用进行优化。
其具体步骤如下:
1. 建立一个GLfloat型一位数组。比如要以三维方式显示一张6×7的图片上的每个点,那么就共有42个点,每个点含(x,y,z)共3个分量。所以该数组共有6×7×3=126个元素。
GLfloat vPoints[126];
然后为每个元素赋值。按照x0,y0,z0,x1,y1,z1……的方式进行顺序赋值。
2. 在场景绘制函数中,启用顶点数组:
glEnableClientState(GL_VERTEX_ARRAY);
3. 设置顶点数组的地址及属性
glVertexPointer(3, GL_FLOAT, 0, vPoints);
① 3表示顶点数组由3个成分组成(x,y,z);
② GL_FLOAT表示每个成分的数据类型都是GLfloat型;
③ 0表示每个数组元素间以字节为单位的空隙,一般为0;vPoints指定了数组的首地址。
4. 访问顶点数组
glBegin(GL_POINTS); for (int i = 0; i < 42; i++) glArrayElement(i); glEnd();
这样,就会将顶点以点的形式绘制出来。
glArrayElement(i);即可访问第i个元素的(x,y,z)坐标值。该函数用于单个元素的访问。
伪代码如下:
GLfloat vPoints[126];
对每个元素进行赋值
glEnableClientState(GL_VERTEX_ARRAY); //启用顶点数组 glVertexPointer(3, GL_FLOAT, 0, vPoints); //设置顶点数组属性 glBegin(GL_POINTS); for (int i = 0; i < 42; i++) glArrayElement(i); //访问顶点数组 glEnd(); glDisableClientState(GL_VERTEX_ARRAY); //禁用顶点数组
关于涉及到的几个函数:
1. glEnableClientState
在OpenGL使用客户/服务器模型。在个人电脑上,服务器指的是图形硬件,比如显卡等,而客户端指CPU和内存。也就是说,CPU和内存需要显卡为其提供服务。
OpenGL的绝大部分函数是使用客户/服务器模型,比如glEnable/glDisable。但是也有部分函数只针对客户端使用。
void glEnableClientState(GLenum array); //启用客户端功能 void glDisableClientState(GLenum array); //禁用客户端功能
以上两个函数仅针对客户端,用于启用/禁用指定功能。当
其中GLenum的取值为:
GL_VERTEX_ARRAY //顶点数组
GL_COLOR_ARRAY //颜色数组
GL_SECONDARY_COLOR_ARRAY
GL_NORMAL_ARRAY
GL_FOG_COORDINATE_ARRAY
GL_TEXURE_COORD_ARRAY
GL_EDGE_FLAG_ARRAY
在相关绘制结束后一定要调用glDisableClientState来禁用客户端功能,否则会影响到接下来的绘制。比如在GL_COLOR_ARRAY生效时,glColor3f是无效的。
客户端功能允许在一次绘制中打开/关闭多次。但不允许多次打开而不关闭。且一旦客户端功能打开,其相应的数组必须调用glVertexPointer来设置,否则会出错。
2. glVertexPointer
void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer);
该函数仅用于设置顶点数组的地址及属性。
GLint size:指定每个顶点的坐标分量个数,只可以为2、3、4中的一个,默认为4
GLenum type:指定顶点的数据类型,取值为:GL_BYTE, GL_SHORT,GL_FIXED,GL_FLOAT
GLsizei stride:指定每个数组元素的总大小,以字节为单位。默认为0,OpenGL会按照已设的size自行计算。但若使用混合数据,比如3个坐标分量后面跟着3个坐标颜色分量,那么该值就需要设置为6*sizeof(GLfloat),也就是元素1偏移6*sizeof(GLfloat)即为元素2。
在该例中,设为0或者设为3*sizeof(GLfloat)结果是相同的。因为若设为0,系统会按照size自行计算出3*sizeof(GLfloat)。
const GLvoid *pointer:指定顶点数组首地址
glVertexPointer对应于glEnableClientState(GL_VERTEX_ARRAY );用于启用顶点数组。同理地:
① glEnableClientState(GL_ COLOR _ARRAY )对应于:
void glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer);
② glEnableClientState(GL_SECONDARY_COLOR_ARRAY)对应于:
void glSecondaryColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer);
③ glEnableClientState(GL_NORMAL_ARRAY)对应于:
void glNormalPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer);
④ glEnableClientState(GL_TEXURE_COORD_ARRAY)对应于:
void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer);
3. glArrayElement
void glArrayElement(GLint index);
该函数用于访问所有已启用顶点数组,且只能访问单个元素。注意此时访问的是整个元素,不是元素的某一部分。比如元素是点,那么glArrayElement (k)就是访问序号为k的点,而不是序号为k的GLfloat组成部分。这是由顶点设置所造成的。
注意,可以同时启用多种顶点数组,且使用glArrayElement进行同时访问,来达到将不同顶点数组中的数据进行组合的效果。
例如:
glEnableClientState(GL_VERTEX_ARRAY); / 启用顶点数组 glEnableClientState(GL_COLOR_ARRAY); / 启用颜色数组 glVertexPointer(3, GL_FLOAT, 0, vertices); //设置顶点数组 glColorPointer(3, GL_FLOAT, 0, colors); //设置颜色数组 glBegin(GL_LINES); glArrayElement(0); //同时访问所有启用数组的0号元素 glArrayElement(1); //同时访问所有启用数组的1号元素 glEnd();
在上面例子中,同时启用了两个数组:顶点数组与颜色数组。当调用glArrayElement(0)时,会对两个启用的数组同时访问,从而既获取了顶点信息又获取了颜色信息。
也可以使用混合数组:
GLfloat data[] = { 1.0, 0.0, 0.0, 25.0,25.0, 1.0, 0.0, 0.0, 100.0,100.0, 0.0, 1.0, 0.0, 120.0,120.0, 0.0, 1.0, 0.0, 200.0,200.0 }; glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glColorPointer(3, GL_FLOAT, 5 * sizeof(GLfloat), &data[0]); glVertexPointer(2, GL_FLOAT, 5 * sizeof(GLfloat), &data[3]); glArrayElement(0);
在上面的data[]中,每一行都按(3个颜色分量,2个坐标分量)构成。所以:
glColorPointer(3, GL_FLOAT, 5 * sizeof(GLfloat), &data[0]);
表示每个颜色元素含3个GLfloat分量,每两个元素首地址之间偏移量为5*sizeof(GLfloat)。第0个颜色元素的首地址为&data[0]。
glVertexPointer(2, GL_FLOAT, 5 * sizeof(GLfloat), &data[3]);
表示个坐标元素含2个GLfloat分量,每两个元素首地址之间偏移量为5*sizeof(GLfloat)。第0个坐标元素的首地址为&data[3]。
glArrayElement(0);
表示调用第0个元素。也就是(1.0,0.0, 0.0, 25.0,25.0)。并且会按照颜色为(1.0,0.0, 0.0),坐标为(25.0,25.0)来解析。
4. glDrawArrays
void glDrawArrays(GLenum mode, GLint first, GLsizei count);
glArrayElement一次只能访问一个元素。有时候需要对一片数据区进行访问,那么可以使用glDrawArrays。这是一个绘制函数,且不需要在glBegin/glEnd之间调用。
GLenum mode:绘制方式,有以下参数:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN
GLint first:从数组缓存中的哪一个元素开始绘制,也就是第一个绘制顶点的序号,一般为0
GLsizei count:数组中需绘制顶点的数量。注意是顶点数量,而非GLfloat元素个数,也不是最后绘制顶点的序号
例:
points中共存储了0-98个GLfloat元素,每个顶点占用3个GLfloat元素,所以共有33个顶点。
glDrawArrays(GL_POINTS, 5, 20);//以点的形式将数组中5-20号元素(共计16个)绘制出来。绘制时使用的GLfloat为15-60号GLfloat元素 glDrawArrays(GL_POINTS, 0, 33);//全部绘制。注意后面两个参数
OpenGL会对数据块的传输进行优化,从而使其性能明显优于多次调用单独的顶点函数。
glDrawArrays会从glEnableClientState所启用的所有数组中查找对应的数据。因此,若调用glEnableClientState来启用了一个数组,那么就必须为该数组调用glVertexPointer等函数来指定数组属性。否则会出现非法访问内存。