概述

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

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

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

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

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

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

初始化

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

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

每个函数都需要不同的参数。可以参考OpenGL API手册。边标志用于标记顶点是否在边界上。因此,如果glPolygonMode()为GL_LINE时,只有边具有边标记的那些边是可见的。

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()不准许在顶点数组中跳跃,你必须为每个面重复指定共用顶点。

glDrawArrays()具有3个参数。第一个参数为图元类型。第二个参数为数组的其实偏移位置。最后一个参数为传递给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(),你可以用单个glDrawArrays()调用替换36次的glVertex*()调用。然而,我们依旧需要重复指定共用顶点,因此数组中的顶点数量依旧为36个,而不是8个。glDrawElements()是降低数组中顶点数量的方法,因此,它准许向OpenGL传递更少的数据。

glDrawElements()

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

glDrawElements()需要4个参数。第一个参数为图元类型,第二个为索引数组数量,第三个位索引数组的数据类型,最后一个参数为索引数组地址。在此例中,参数分别为:GL_TRIANGLES、36、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

最新文章

  1. H5是什么,CSS3又是什么?
  2. JS && JSON
  3. wamp包--如何导出sql
  4. Oracle数据库的启动与停止
  5. mysql5.5主从配置
  6. NodeJS制作爬虫全过程
  7. Java基础知识强化35:String类之String的其他功能
  8. DelphiXE7如何调用Java Class,JAR等文件?
  9. java分布式服务框架Dubbo的介绍与使用
  10. MyBatis中多对多关系的映射和查询
  11. BZOJ_3585_mex && BZOJ_3339_Rmq Problem_莫队+分块
  12. jirba库的使用和好玩的词云
  13. Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:co
  14. MySql 查询银行卡号打码
  15. java-两个整数变量的交换-不需要定义第三方变量
  16. Pronunciation – The Definitive Guide to the Top 100 Words in American English
  17. servlet 高级知识之Listener
  18. 经典JS
  19. CSDN BI Flume
  20. 记一次androidd登陆页面的实现

热门文章

  1. 关于freeCAD
  2. java 终端练习
  3. ie11浏览器和chrome浏览器对于bgsound和background的一些区别
  4. 可拖拽的ListView
  5. 《C++primer》v5 第4章 表达式 读书笔记 习题答案
  6. 获取局域网中指定IP或是主机名称的所有文件夹及其搜索文件
  7. spring 3.0 应用springmvc 构造RESTful URL 详细讲解
  8. jquery中的DOM事件绑定与解绑
  9. Shell 编写准则
  10. 项目中 poi 导出 出现html特殊符号的实体 (已解决)