概述

作为在立即模式(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

OpenGL顶点数组的更多相关文章

  1. OpenGL中glVertex、显示列表(glCallList)、顶点数组(Vertex array)、VBO及VAO区别

    OpenGL中glVertex.显示列表(glCallList).顶点数组(Vertex array).VBO及VAO区别 1.glVertex 最原始的设置顶点方法,在glBegin和glEnd之间 ...

  2. openGL 提升渲染性能 之 顶点数组 VBO IBO VAO

    使用openGL图形库绘制,都需要通过openGL接口向图像显卡提交顶点数据,显卡根据提交的数据绘制出相应的图形. openGL绘制方式有:直接模式,显示列表,顶点数组,顶点索引. 直接模式:最简单, ...

  3. [转]OpenGL通过VBO实现顶点数组绘制顶点

    #include "stdlib.h" #include <OpenGL/glext.h> #include <GLUT/GLUT.h> #define B ...

  4. OpenGL:使用顶点数组法绘制正六面体

    在今天的opengl的课程以及实验中,我们学习了如何使用顶点数组的方法来绘制图形,但相信还有很多同学对它的实际使用方法不太了解,我们就用我们今天实验课上的实例来简单讲解一下 题目及要求 绘制一个正六面 ...

  5. OpenGL(十八) 顶点数组和抗锯齿(反走样)设置

    顶点数组函数可以在一个数组里包含大量的与顶点相关的数据,并且可以减少函数的调用.使用顶点数组需要先启用顶点数组功能,使用glEnableClientState函数启用顶点数组,参数可以是GL_VERT ...

  6. 3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(顶点数组对象)

    大部分OpenGL教程都会在一开始就讲解VAO,但是该教程的作者认为这是很不合理的,因为要理解它的作用需要建立在我们此前学过的知识基础上.因此直到教程已经进行了一大半,作者才引入VAO这个概念.在我看 ...

  7. OpenGL顶点缓冲区对象(VBO)

    转载 http://blog.csdn.net/dreamcs/article/details/7702701 创建VBO        GL_ARB_vertex_buffer_object 扩展可 ...

  8. OpenGL 顶点缓存对象

    顶点缓存对象(Vertex Buffer Object,简称 VBO),允许开发者根据情况把顶点数据放到显存中. 如果不用 VBO,用 glVertexPointer / glNormalPointe ...

  9. OpenGL顶点缓冲区对象

    [OpenGL顶点缓冲区对象] 显示列表可以快速简单地优化立即模式(glBegin/glEnd)的代码.在最坏的情况下,显示列表的命令被预编译存到命令缓冲区中,然后发送给图形硬件.在最好的情况下,是编 ...

随机推荐

  1. SharePoint Document Library中的"Versioning Settings"功能与CSOM的对应

     博客地址:http://blog.csdn.net/FoxDave SharePoint文档库关于版本的设置:"Versioning Settings",可以通过CSOM用L ...

  2. 存储过程详解与java调用(转)

    存储过程的一些基本语法: --------------创建存储过程----------------- CREATE PROC [ EDURE ] procedure_name [ ; number ] ...

  3. yate: windows下源码下载,配置,编译

    源码下载:使用svn下载checkout:http://voip.null.ro/svn/yate/trunk 配置:(本人使用的是vs2008,故下载的qt工具都是对应2008) 1. 下载并安装q ...

  4. mediawiki安装

    下载wiki,我用的mediawiki-1.23.6 版本. 服务器是window2008 r2 的. 下载xampp,或者wamp都可以,我的用的禅道安装的xampp,解压后直接放在htdocs目录 ...

  5. jsp取得绝对路径的方法(避免请求转发的方式导致路径错误)

    <%String path = request.getContextPath();String basePath = request.getScheme()+"://"+re ...

  6. python基础之模块

    模块,用一砣代码实现了某个功能的代码集合. 类似于函数式编程和面向过程编程,函数式编程则完成一个功能,其他代码用来调用即可,提供了代码的重用性和代码间的耦合. 而对于一个复杂的功能来,可能需要多个函数 ...

  7. Ubuntu14.04安装build-essential失败,包依赖问题如何解决?

    正在读取软件包列表... 完成 正在分析软件包的依赖关系树        正在读取状态信息... 完成        有一些软件包无法被安装.如果您用的是 unstable 发行版,这也许是 因为系统 ...

  8. 控制ClistCtrl的滚动的位置

    int nItem = m_listCtrl.GetTopIndex(); CRect rc; m_listCtrl.GetItemRect(nItem, rc, LVIR_BOUNDS); CSiz ...

  9. Pip install lxml centOSFailed building wheel for lxml

    转到虚拟环境目录:yum install libxslt-devel libxml2-devel yum install python-devel pip install lxml

  10. RabbitMQ在window的搭建

    RabbitMq window 搭建设置过程,网上有些说的不太明白,所以亲自操刀测试过程,参考了很多人的资料,多谢各位大神的宝贵资料第一步:装RabbitMq运行环境,类似一个虚拟机的东东 1.otp ...