OpenGL系列教程之六:OpenGL顶点数组

相关主题:顶点缓冲区,显示列表

下载:vertexArray.zip, vertexArray2.zip

  • 概述
  • 初始化
  • glDrawArrays()
  • glDrawElements()
  • glDrawRangeElements()
  • 例子



概述

不像在立即模式(在glBegin()和glEnd()对之间)中指定单独的顶点数据 ,你可以存储顶点数据(顶点坐标,法向量,纹理坐标和颜色信息)在一系列数组中。你也可以利用数组的索引只取数组中的部分元素来绘制几何图元。
OpenGL系列教程之六:OpenGL顶点数组_第1张图片
看一下下面的代码中使用立即模式绘制一个立方体的例子。

每一个面需要调用6次glVertex*()来绘制两个三角形,例如,前表面有v0-v1-v2v2-v3-v0两个三角形。一个立方体有6个面,因此总共需要调用glVertex*()函数36次。如果你同时也指定法向量,纹理坐标和颜色给指定的顶点,也以同样的速度增加函数调用的次数。

你需要注意的另外一件事就是顶点“v0”被3个相邻的面共享:前表面,右表面和上表面。在立即模式中,你必须指定这个共享的顶点6次(每个面需要指定2次)。

glBegin(GL_TRIANGLES);  // 使用12个三角形绘制立方体

    // 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);

    ...                 // 绘制另外3个面

glEnd();

使用顶点数组减少了函数调用的次数和共享顶点的冗余使用。因此,你可以提高渲染的性能。现在将解释使用顶点数组的3个不同的OpenGL函数:glDrawArrays(),glDrawElements()glDrawRangeElements()。虽然更好的方式是使用顶点缓冲区对象(VBO)或显示列表。





初始化


OpenGL提供了glEnableClientState()和glDisableClientState()来激活或禁用6个不同类型的数组,还有6个函来操作这6个数组,因此,OpenGL可以在你的应用程序中访问这6个数组。

  • glVertexPointer():指向顶点坐标数组
  • glNormalPointer():指向法向量数组
  • glColorPointer():指向RGB颜色数组
  • glIndexPointer():指向索引颜色数组
  • glTexCoordPointer():指向纹理坐标数组
  • glEdgeFlagPointer():指向临界标示数组
每一个指向数组的函数需要不同的参数。具体需要哪些参数可以参考OpenGL API文档。临界标示数组用来标记顶点是否在边界上。所以唯一一中临界标示为真并且边界可见的情况是glPolygonMode()的值被设置成GL_LINE。

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

1.size:顶点的维数,2表示2维的点,3表示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()以连续地无跳跃的方式从顶点数组中读取数据。因为glDrawArrays不允许跳跃地访问顶点数组,你依然需要重复定义顶点。

glDrawArrays()需要三个参数。第一个参数是图元类型,第二个参数是第一个元素在数组中的偏移量,第三个参数是传递给OpenGL渲染的顶点数目。对于上面绘制立方体的例子而言,第一个参数是GL_TRIANGLES,第二个参数是0,表示是从数组中的首个元素开始的,最后一个参数是36:一个立方体有6个面,每个面需要6个顶点来绘制两个三角形,6*6=36。

GLfloat vertices[] = {...}; // 36 个顶点的坐标
...
// 激活顶点数组
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);

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

// 绘制完成后禁用顶点数组
glDisableClientState(GL_VERTEX_ARRAY);
通过使用glDrawArrays(),将36次glVertex*()函数调用替换成了一次glDrawArrays()。然而,我们依然需要重复共享顶点,因此顶点数组的大小依然是36而不是8。glDrawElements()函数是减少顶点数组中顶点数目的方法,它能传输更少的数据到OpenGL中。




glDrawElements()

glDrawElements()通过指定相关联的的数组索引在顶点数组中跳跃绘制一系列的图元。它同时减少了函数调用的次数和要传输的顶点数目。除此之外,OpenGL可能缓存最近被处理的顶点并且能重新使用它们而不需要多次将重复的数据发送到OpenGL的渲染管线中。

glDrawElements()需要4个参数。第一个参数指定图元类型,第二个参数指定索引数组的大小,第三个参数指定索引数组的类型,第4个参数指定索引数组。在本文的这个例子中,参数分别是是:GL_TRIANGLES, 36, GL_UNSIGNED_BYTE 和indices。

GLfloat vertices[] = {...};          // 8个顶点坐标
GLubyte indices[] = {0,1,2, 2,3,0,   // 索引数组
                     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被前表面,右表面和上表面共享,但是在v0处的法向量不能被共享。前表面的法向量是n0,右表面的法向量是n1,上表面的法向量是n2。对于这种情况,在共享顶点处的法向量不一样,在顶点数组中的顶点就不能只被定义一次。它必须被定义多次以匹配在法向量数组中的元素个数。一个典型的拥有合适的法向量的立方体需要24个顶点:6个面,每个面4个顶点。观察code中的实现。
OpenGL系列教程之六:OpenGL顶点数组_第2张图片





glDrawRangeElements()

和glDrawElements()比较类似,glDrawRangeElements()也可以在数组中跳跃访问。然而,glDrawRangeElements()还有两个额外的参数(开始和结束位置的索引)来指定顶点的范围。通过添加这个范围限制,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);

// 绘制后面一半的图形 7 - 1 + 1 = 7 个顶点被使用
glDrawRangeElements(GL_TRIANGLES, 1, 7, 18, GL_UNSIGNED_BYTE, indices+18);

// 禁用顶点数组
glDisableClientState(GL_VERTEX_ARRAY);


通过使用glGetIntegerv()和GL_MAX_ELEMENTS_VERTICES, GL_MAX_ELEMENTS_INDICES你可以找到能预取的最大的顶点数目和最大的索引数目。

注意glDrawRangeElements()只有在OpenGL 1.2版本或更高版本上才可以使用。





例子:


OpenGL系列教程之六:OpenGL顶点数组_第3张图片

这个例子程序使用4种不同的方式渲染一个立方体:立即模式,glDrawArrays(),glDrawElemennts(),glDrawRangeElements()。


  • draw1():使用立即模式绘制立方体
  • draw2():使用glDrawArrays()绘制立方体
  • draw3():使用glDrawElements()绘制立方体
  • draw4():使用glDrawRangeElements()绘制立方体
  • draw5():使用glDrawElements()和交错的顶点数组绘制立方体

下载源文件和可执行文件: vertexArray.zip

为了更好地运行这个程序,显卡需要支持OpenGL 1.2或者更高的版本。使用工具 glinfo来确保显卡支持OpenGL 1.2或更高的版本。

你可能感兴趣的:(OpenGL,openGL系列教程)