OpenGL顶点数组

概述

做为在当即模式(glBegin()与glEnd()之间)下指定单个顶点数据的替代,你能够保存顶点数据在一组列表中,包括顶点位置、法线、纹理坐标与颜色信息。而且你能够经过索引数组解引用数组元素绘制选定的几何图元。html

看看下面的用当即模式绘制立方体的代码。数组

glBegin(GL_TRIANGLES);  // draw a cube with 12 triangles
    // 前面 =================
    glVertex3fv(v0);    // v0-v1-v2
    glVertex3fv(v1);
    glVertex3fv(v2);

    glVertex3fv(v2);    // v2-v3-v0
    glVertex3fv(v3);
    glVertex3fv(v0);

    // 右面 =================
    glVertex3fv(v0);    // v0-v3-v4
    glVertex3fv(v3);
    glVertex3fv(v4);

    glVertex3fv(v4);    // v4-v5-v0
    glVertex3fv(v5);
    glVertex3fv(v0);

    // 上面 ===================
    glVertex3fv(v0);    // v0-v5-v6
    glVertex3fv(v5);
    glVertex3fv(v6);

    glVertex3fv(v6);    // v6-v1-v0
    glVertex3fv(v1);
    glVertex3fv(v0);

    ...                 // 绘制其他3面

glEnd();
image

为构造每一个面的2个三角形,须要调用glVertex*()6次。例如,正面分为v0-v1-v2与v2-v3-v0两个三角形。一个立方体有6个面,所以glVertex*()的调用次数为36。若是你还需为相关顶点指定法线、纹理坐标与颜色,这增长对OpenGL函数的调用。缓存

另外一个须要注意的是:顶点“v0”被三个相邻的面共用:正面、右面与顶面。在当即模式下,你必须提供这个共用点6次,就像代码中那样每一个面2次。ide

使用顶点数字会下降函数调用次数及共用顶点的重复使用。所以,能够提供渲染效率。在此,解释3种不一样的使用顶点数组的OpenGL函数:glDrawArrays()、glDrawElements()与glDrawRangeElements()。然而,更好的方法是使用顶点缓存对象(VBO)与显示列表。函数

初始化

OpenGL提供glEnableClientState()与glDisableClientState()函数启用/禁用6中不一样类别的数组。此外,有6个函数用于指定数组的精确位置(地址),所以在你的应用程序中OpenGL能够访问这些数组。性能

  • glVertexPointer():指定顶点坐标数组指针
  • glNormalPointer():指定法线数组指针
  • glColorPointer():指定RGB颜色数组指针
  • glIndexPointer():指定索引颜色数组指针
  • glTexCoordPointer():指定纹理坐标数组指针
  • glEdgeFlagPointer():指定边标志数组指针

每一个函数都须要不一样的参数。能够参考OpenGL API手册。边标志用于标记顶点是否在边界上。所以,若是glPolygonMode()为GL_LINE时,只有边具备边标记的那些边是可见的。ui

glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid* pointer)

  1. size:顶点坐标数量,对于2D点为2,3D点为3。
  2. type:GL_FLOAT、GL_SHORT、GL_INT或GL_DOUBLE。
  3. stride:连续两个顶点间的字节偏移量(用于交叉数组)。
  4. pointer:顶点数组指针。

glNormalPointer(GLenum type, GLsizei stride, const GLvoid* pointer)

  1. type:GL_FLOAT、GL_SHORT、GL_INT或GL_DOUBLE。
  2. stride:连续两个法线间的字节偏移量(用于交叉数组)。
  3. pointer:法线数组指针。

注意,顶点数组保存在你的应用程序(系统内存),它在客户端。且处在服务端的OpenGL访问它们。这就是为何拥有顶点数组这些特殊命令的缘由,使用glEnableClientState()与glDisableClientState()而不是glEnable()与glDisable()。指针

glDrawArrays()

glDrawArrays()从开启的数组中顺序读取顶点数据。因为glDrawArray()不许许在顶点数组中跳跃,你必须为每一个面重复指定共用顶点。code

glDrawArrays()具备3个参数。第一个参数为图元类型。第二个参数为数组的其实偏移位置。最后一个参数为传递给OpenGL渲染管线的顶点数量。在上面绘制立方体的实例中,第一个参数为GL_TRIANGLES,第二个参数为0,即从数组开始读取。最后一个参数为36:立方体具备6个面,且每一个面须要绘制两个三角形的6个顶点,6×6=36。orm

GLfloat vertices[] = {...}; // 36顶点坐标
...
// 启用并指定顶点数组指针
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);

// 绘制立方体
glDrawArrays(GL_TRIANGLES, 0, 36);

// 在绘制以后禁用顶点数组
glDisableClientState(GL_VERTEX_ARRAY);

因为使用glDrawArrays(),你能够用单个glDrawArrays()调用替换36次的glVertex*()调用。然而,咱们依旧须要重复指定共用顶点,所以数组中的顶点数量依旧为36个,而不是8个。glDrawElements()是下降数组中顶点数量的方法,所以,它准许向OpenGL传递更少的数据。

glDrawElements()

glDrawElements()经过顶点数组相关的随机数组索引绘制图元序列。它下降函数调用次数与顶点传递数量。此外,OpenGL能够缓存最近处理过的顶点以及重用它们,而没必要向顶点变换管线重复发送相同的顶点。

glDrawElements()须要4个参数。第一个参数为图元类型,第二个为索引数组数量,第三个位索引数组的数据类型,最后一个参数为索引数组地址。在此例中,参数分别为:GL_TRIANGLES、3六、GL_UNSIGNED_BYTE与indices。

GLfloat vertices[] = {...};          // 8个顶点坐标
GLubyte indices[] = {0,1,2, 2,3,0,   // 36个索引
                     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};
...
// 启用并指定顶点数组指针
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);

// 绘制立方体
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indices);

// 绘制以后禁用顶点数组
glDisableClientState(GL_VERTEX_ARRAY);

如今,顶点数组的大小为8,这就是立方体的顶点数量,而没有任何重复。

注意,索引数组的数据类型为GLubyte,而不是GLuint或GLushort。为了下降索引数组的大小,它应该是可以容纳索引数量的最小数据类型,不然,因为索引数组过大可以引发性能降低。由于顶点数组包含8个顶点,GLubyte足够容纳全部索引值。

另外一个须要考虑的问题是共用顶点上的法向量。若是共用顶点的相邻多边形的法线各不相同,则法向量须要同面的数量同样多,每一个面一个法向量。

例如,顶点v0为正面、右面与顶面所共用,不过法线并不能共用。正面的法线为n0,右面的法线为n1,顶面的法线为n2。对于这种状况,法线并不与共用顶点相同,顶点就不可以仅在顶点数组中指定一次。为了匹配法向数组中元素的数量,顶点数组中必须屡次指定该顶点坐标。具备法线的典型立方体须要24个单一顶点:6个面×每面4个顶点。参考示例代码中的实际实现。

glDrawRangeElements()

glDrawElements()相似,glDrawRangeElements()也适用于随机访问顶点数组。不过glDrawRangeElements()有额外的2个参数(start与end索引)以指定须要读取的顶点范围。经过增长该范围限定,OpenGL可以在渲染以前仅获取限定数量的顶点数组,且可以提升性能。

glDrawRangeElements()中新增的参数为start与end索引,OpenGL从这些值(end-start+1)中获取限定数量的顶点。而且索引数组中的值必须位于start与end索引之间。注意,并非范围(start,end)之间的全部顶点都会被接引用。然而,若是你指定一个稀疏使用范围,会引发没必要要的对数组中未使用的那些顶点的处理。

GLfloat vertices[] = {...};          // 8个顶点坐标
GLubyte indices[] = {0,1,2, 2,3,0,   // 第一部分(18个索引)
                     0,3,4, 4,5,0,
                     0,5,6, 6,1,0,

                     1,6,7, 7,2,1,   // 第二部分(18个索引)
                     7,4,3, 3,2,7,
                     4,7,6, 6,5,4};
...
// 开启并指定顶点数组指针
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);

// 绘制第一部分, 范围为6 - 0 + 1 = 7用到的顶点
glDrawRangeElements(GL_TRIANGLES, 0, 6, 18, GL_UNSIGNED_BYTE, indices);

// 绘制第二部分, 范围为6 - 0 + 1 = 7用到的顶点
glDrawRangeElements(GL_TRIANGLES, 1, 7, 18, GL_UNSIGNED_BYTE, indices+18);

// 绘制以后禁用顶点数组
glDisableClientState(GL_VERTEX_ARRAY);

你能够经过使用GL_MAX_ELEMENTS_VERTICES与GL_MAX_ELEMENTS_INDICES调用glGetIntegerv()查询可获取顶点的最大数量与可引用索引的最大数量。

注意,glDrawRangeElements()在OpenGL 1.2或更高版本有效。

实例

该实例程序以4中不一样方式绘制立方体:当即模式、glDrawArrays()、glDrawElements()与glDrawRangeElements()。

  • draw1():以当即模式绘制立方体。
  • draw2():以glDrawArrays()绘制立方体。
  • draw3():以glDrawElements()绘制立方体。
  • draw4():以glDrawRangeElements()绘制立方体。
  • draw5():以glDrawElements()与间隔顶点数组方式绘制立方体。

下载源代码与二进制文件:vertexArray.zip

为了正确执行该程序,显卡必须支持OpenGL v1.2或更高。经过glinfo确认你的显卡驱动是否支持OpenGL v1.2或更高。

 

英文原文:http://www.songho.ca/opengl/gl_vertexarray.html

相关文章
相关标签/搜索