`

Win32 OpenGL编程(5)顶点数组详细介绍

 
阅读更多

Win32 OpenGL编程(5) 顶点数组详细介绍

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

讨论新闻组及文件

Technorati 标签: 顶点数组 ,OpenGL ,Win32 ,C++ ,图形 ,编程

提要

本文主要讲解了OpenGL中使用顶点数组来绘制图形的需求及方法,

需求

按照前面已经讲过的内容来说,简单的2D图形绘制已经没有问题了,并且,很多东西其实已经涉及到3D,但是会发现,复杂图形的绘制时,glBegin和glEnd对中指定顶点的函数调用会非常多,glVertex函数调用的开销会过大。(虽然在C/C++中函数调用开销其实已经比较小)在3D图形绘制时,此问题更加严重,想想,一个四边形,4个顶点,一个四方体,就有8个顶点了,但是按照我们目前描述平面的方式来描述就是需要指定6 * 4 = 24个顶点。这正是OpenGL作为高性能图像接口应该极力避免的事情,事实上,OpenGL当然不会一直要求我们用如此低效的方式来完成任务,我们有新的高效的工具——顶点数组。事实上,在强调高效和尽量减少API的OpenGL ES中,甚至根本就没有glBegin,glEnd和glVertex*等API。

顶点数组相对来说是OpenGL中用于提高效率的手段,不是核心概念。(虽然OpenGL ES中完全以顶点数组绘制图元,但是事实上OpenGL 1.1版中才加入了定点数组)顶点数组不能使我们绘制出以前以前绘制不了的图元,但是却实实在在的提高了我们绘制图形的效率,但是事实上,对于高性能图像处理来说,提高效率是如此的重要,因为效率的提升从本质上来说没有增加我们绘制更多图形的能力(即不用提高效率手段也能绘制出相同图形),但是,随着效率的提高,我们却能在同样的机器上绘制更多的图元,那么,意味着我们可以更加丰富和逼真地表现图像,这不正是图形绘制所追求的目标吗?

概念

有的东西说简单就简单,说复杂就复杂,顶点数组的概念如此的简单,如其名,就是将原来由一系列的glVertex*指定的顶点放在一个数组中,(其意思就是顶点数据组成的数组)由顶点数组处理函数一次性指定。事实上,不仅如此,其一次性指定的不仅仅是数组,还可以是其他系列的东西,比如表面发现,RGBA颜色,辅助颜色等。

使用方式初步

使用方式,编程手段的使用方式最佳讲解手段肯定是程序,我们以一个例子来讲解使用方式。

glVertexArray:

   1:
 //OpenGL初始化开始
   2:
 void
 SceneInit(int
 w,int
 h)
   3:
 {
   4:
     glClearColor(0.0, 0.0, 0.0, 0.0);
   5:
     // 启用顶点数组
   6:
     glEnableClientState(GL_VERTEX_ARRAY);
   7:
     
   8:
     // 顶点数组数据
   9:
     static
 GLfloat fVertices[] = {    -0.5, -0.5,
  10:
                                      0.5, -0.5,
  11:
                                      0.5,  0.5,
  12:
                                     -0.5,  0.5};
  13:
 
  14:
     glVertexPointer(2, GL_FLOAT, 0, fVertices);
  15:
 }
  16:
 
  17:
 //这里进行所有的绘图工作
  18:
 void
 SceneShow(GLvoid)        
  19:
 {
  20:
     glClear(GL_COLOR_BUFFER_BIT);    // 清空颜色缓冲区
  21:
     glColor4f(1.0, 1.0, 1.0, 1.0);
  22:
 
  23:
     glBegin(GL_QUADS);    
  24:
     glArrayElement(0);
  25:
     glArrayElement(1);
  26:
     glArrayElement(2);
  27:
     glArrayElement(3);
  28:
     glEnd();
  29:
 
  30:
     // 上述函数调用与下面的效果一样
  31:
     //glBegin(GL_QUADS);    
  32:
     //glVertex3f(-0.5, -0.5, 0.0);
  33:
     //glVertex3f(0.5, -0.5, 0.0);
  34:
     //glVertex3f(0.5, 0.5, 0.0);
  35:
     //glVertex3f(-0.5, 0.5, 0.0);
  36:
     //glEnd();
  37:
 
  38:
     glFlush();
  39:
 }  

为节省篇幅仅贴出关键片段,完整源代码见我博客源代码的2009-10-18/glVertexArray/ 目录,获取方式见文章最后关于获取博客完整源代码的说明。

上述例子的效果与以前SimpleRectangle的效果完全一样,请参考本OpenGL系列文章3,4,(以下简称XO3,XO4^^),截图如以前一样。

分步骤说明一下上述源代码的含义:

glEnableClientState(GL_VERTEX_ARRAY);

用于表示启用顶点数组,此函数还可以搭配其他参数用于指定其他数组

OpenGL Reference Manual

glEnableClientState — enable or disable client-side capability
void glEnableClientState( GLenum cap);
Parameters:Specifies the capability to enable.
Symbolic constants
GL_COLOR_ARRAY,
GL_EDGE_FLAG_ARRAY,
GL_FOG_COORD_ARRAY,
GL_INDEX_ARRAY,
GL_NORMAL_ARRAY,
GL_SECONDARY_COLOR_ARRAY,
GL_TEXTURE_COORD_ARRAY, and
GL_VERTEX_ARRAY
are accepted.

此例中仅仅使用了GL_VERTEX_ARRAY一种用法。

glVertexPointer也是关键函数,用于指定顶点数组的数据。

OpenGL Reference Manual

glVertexPointer — define an array of vertex data
C Specification
void glVertexPointer( GLint size,
GLenum type,
GLsizei stride,
const GLvoid * pointer);
Parameters

size

Specifies the number of coordinates per vertex. Must be 2, 3, or
4. The initial value is 4.
type

Specifies the data type of each coordinate in the array.
Symbolic constants
GL_SHORT,
GL_INT,
GL_FLOAT,
or GL_DOUBLE
are accepted. The initial value is GL_FLOAT.
stride

Specifies the byte offset between consecutive
vertices. If stride is 0, the vertices are understood to be tightly packed in
the array. The initial value
is 0.
pointer

Specifies a pointer to the first coordinate of the first vertex in the
array. The initial value is 0.

此例中,数据以2个位一组,类型都是GL_FLOAT,跨度为0表示数据紧密排列,指针自然指向刚才分配的顶点数组数据。

glArrayElement用于指定顶点数组函数具体的顶点,放在glBegin与glEnd之间,产生glVertex*一样的效果,即一次指定一个顶点。

OpenGL Reference Manual

glArrayElement — render a vertex using the specified vertex array element
C Specification
void glArrayElement( GLint i);
Parameters

i

Specifies an index into the enabled vertex data arrays.

上例中分别制定了0,1,2,3共4个顶点,与以前在SimpleRectangle中指定的4个顶点一样,注意取数据的方式,我们上面通过glVertexPointer制定数据的时候说明了是2个数据一组,(如同缩进所示)那么这里指定的0,1,2,3其实分别是从数组的0,2,4,6个数据开始取,每次取2个,意思就是此处glArrayElement指定的并不是顶点数组数据实际对应的C语言数组索引而是按照glVertexPointer指定分组分组后的分组索引。

附带颜色的使用

上述内容就已经包含了定点数组的核心内容,但是我们无法发现使用顶点数组的任何好处,甚至发现更加复杂,函数调用更多了。-_-!下面通过一个附带颜色的例子,加深理解顶点数组使用方式,并可以发现此时其初步减少函数调用次数的作用。

   1:
 //OpenGL初始化开始
   2:
 void
 SceneInit(int
 w,int
 h)
   3:
 {
   4:
     glClearColor(0.0, 0.0, 0.0, 0.0);
   5:
     // 启用顶点数组
   6:
     glEnableClientState(GL_VERTEX_ARRAY);
   7:
 
   8:
     // 颜色数组也需要启用
   9:
     glEnableClientState(GL_COLOR_ARRAY);
  10:
     
  11:
     // 默认就是此参数,可忽略,为了明确说明特意指定
  12:
     glShadeModel(GL_SHADE_MODEL);
  13:
 
  14:
     // 顶点数组数据
  15:
     static
 GLfloat fVertices[] = {    -0.5, -0.5,
  16:
                                      0.5, -0.5,
  17:
                                      0.5,  0.5,
  18:
                                     -0.5,  0.5};
  19:
 
  20:
     // 颜色数组
  21:
     static
 GLfloat fColor[] = { 1.0, 0.0, 0.0,
  22:
                                 0.0, 1.0, 0.0,
  23:
                                 0.0, 0.0, 1.0,
  24:
                                 1.0, 1.0, 1.0};
  25:
 
  26:
     // 指定顶点数组数据
  27:
     glVertexPointer(2, GL_FLOAT, 0, fVertices);
  28:
 
  29:
     // 制定颜色数组
  30:
     glColorPointer(3, GL_FLOAT, 0, fColor);
  31:
 
  32:
 }
  33:
 
  34:
 //这里进行所有的绘图工作
  35:
 void
 SceneShow(GLvoid)        
  36:
 {
  37:
     glClear(GL_COLOR_BUFFER_BIT);    // 清空颜色缓冲区
  38:
 
  39:
     glPushMatrix();
  40:
     glTranslatef(-0.5, 0, 0);
  41:
     glBegin(GL_QUADS);    
  42:
     glArrayElement(0);
  43:
     glArrayElement(1);
  44:
     glArrayElement(2);
  45:
     glArrayElement(3);
  46:
     glEnd();
  47:
     glPopMatrix();
  48:
 
  49:
     // 上述函数调用与下面的效果一样
  50:
     glPushMatrix();
  51:
     glTranslatef(0.5, 0.0, 0.0);
  52:
     glBegin(GL_QUADS);    
  53:
     glColor3f(1.0, 0.0, 0.0);
  54:
     glVertex3f(-0.5, -0.5, 0.0);
  55:
 
  56:
     glColor3f(0.0, 1.0, 0.0);
  57:
     glVertex3f(0.5, -0.5, 0.0);
  58:
 
  59:
     glColor3f(0.0, 0.0, 1.0);
  60:
     glVertex3f(0.5, 0.5, 0.0);
  61:
 
  62:
     glColor3f(1.0, 1.0, 1.0);
  63:
     glVertex3f(-0.5, 0.5, 0.0);
  64:
     glEnd();
  65:
     glPopMatrix();
  66:
 
  67:
     glFlush();
  68:
 }  

为节省篇幅仅贴出关键片段,完整源代码见我博客源代码的2009-10-18/glVertexArrayWithColor/ 目录,获取方式见文章最后关于获取博客完整源代码的说明。

注释已经详细说明了大部分新添内容的意义,此例子为加强对比,将用顶点数组绘制的图形移到了屏幕的左侧,而用glVertex*绘制的图形放在了屏幕的右侧。同样的效果,在此例中由于glArrayElement同时指定了顶点和颜色,已经显示出了节省函数调用的作用,何况顶点数组还可以同时指定辅助颜色,法线向量,雾坐标等等东西,此示例程序运行效果如下图所示。

20091018glVertexArrayWithColor

更进一步,强大的glDrawElements

glArrayElement的作用我们已经看到了,但是对于的数据仍然一个一个通过glArrayElement指定也不符合OpenGL高效的性格特点,OpenGL提供了一系列更加高效的函数以完成对效率要求苛刻的任务,我们将逐一介绍,会发现,使用难度越来越高,适用范围越来越窄,但是函数调用越来越少,功能越来越强大,事实上,函数调用少并不是OpenGL这样设计的唯一理由,越是这样同时处理多个数据的接口,因为一个接口掌握的信息越多,那么也就越能更多的对其进行优化,此是题外话。

glDrawElements是首先要讲到的。

OpenGL Reference Manual :

glDrawElements — render primitives from array data
C Specification
void glDrawElements( GLenum mode,
GLsizei count,
GLenum type,
const GLvoid * indices);
Parameters

mode

Specifies what kind of primitives to render.
Symbolic constants
GL_POINTS,
GL_LINE_STRIP,
GL_LINE_LOOP,
GL_LINES,
GL_TRIANGLE_STRIP,
GL_TRIANGLE_FAN,
GL_TRIANGLES,
GL_QUAD_STRIP,
GL_QUADS,
and GL_POLYGON are accepted.
count

Specifies the number of elements to be rendered.
type

Specifies the type of the values in indices. Must be one of
GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or
GL_UNSIGNED_INT.
indices

Specifies a pointer to the location where the indices are stored.

在《OpenGL Programming Guide 》中对其效果的描述为:

   1:
 glBegin(mode)
   2:
 for
 ( i = 0; i < count; i++)  
   3:
     glArrayElement(indices[i]);
   4:
 glEnd();

理解了glArrayElement,那么理解glDrawElemets也就不难了。甚至因为此函数作用之大,有了它,我们连glBegin,glEnd都不需要了(OpenGL ES 2.0中就将上述函数都省了,只留下了glDrawElements)

下面看一个例子 。

   1:
 //这里进行所有的绘图工作
   2:
 void
 SceneShow(GLvoid)        
   3:
 {
   4:
     glClear(GL_COLOR_BUFFER_BIT);    // 清空颜色缓冲区
   5:
 
   6:
     static
 GLubyte byRectIndices[] = { 0, 1, 2, 3};
   7:
     glPushMatrix();
   8:
     glTranslatef(-0.5, 0, 0);
   9:
     glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, byRectIndices);
  10:
     glPopMatrix();
  11:
 
  12:
     // 上述函数调用与下面的效果一样
  13:
     glPushMatrix();
  14:
     glTranslatef(0.5, 0, 0);
  15:
 
  16:
     glBegin(GL_QUADS);    
  17:
         glArrayElement(0);
  18:
         glArrayElement(1);
  19:
         glArrayElement(2);
  20:
         glArrayElement(3);
  21:
     glEnd();
  22:
     glPopMatrix();
  23:
 
  24:
     glFlush();
  25:
 }  

为节省篇幅仅贴出关键片段,完整源代码见我博客源代码的2009-10-18/glRectWithDrawElements/目录,获取方式见文章最后关于获取博客完整源代码的说明。

看了这个例子以后,真会感叹强中自有强中手啊。。。。。glArrayElement击败了glVertex*,但是glDrawElements又比glArrayElement更高。glDrawElements使用的时候将一连串glArrayElement需要使用的数据放在一个数组中(此例中是byRectIndices),然后通过一个函数调用一次指定。注意啊,glDrawElements自带glBegin及glEnd效果,不再需要它们。(事实上,在OpenGL中命名中带Draw的一般都不需要glBegin和glEnd)程序运行的效果与glVertexArrayWithColor例子运行效果相同。

更强大的glMultiDrawElements

以前有句话说牛逼是无止尽的,OpenGL函数的强大也是一样。glDrawElements是目前看过的OpenGL函数中最强大的顶点指定函数了,但是glMultiDrawElements如其名,可以一个顶两->N个glDrawElemetns。

OpenGL Reference Manual :

glMultiDrawElements — render multiple sets of primitives by specifying indices of array data elements
C Specification
void glMultiDrawElements( GLenum mode,
const GLsizei * count,
GLenum type,
const GLvoid ** indices,
GLsizei primcount);
Parameters

mode

Specifies what kind of primitives to render.
Symbolic constants
GL_POINTS,
GL_LINE_STRIP,
GL_LINE_LOOP,
GL_LINES,
GL_TRIANGLE_STRIP,
GL_TRIANGLE_FAN,
GL_TRIANGLES,
GL_QUAD_STRIP,
GL_QUADS,
and GL_POLYGON are accepted.
count

Points to an array of the elements counts.
type

Specifies the type of the values in indices. Must be one of
GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or
GL_UNSIGNED_INT.
indices

Specifies a pointer to the location where the indices are stored.
primcount

Specifies the size of the count array.

在《OpenGL Programming Guide 》中,对其进行的程序语言描述如下:

   1:
 for
 (i = 0; i < primcount; i++) 
   2:
 {
   3:
     if
 ( count[i] > 0)
   4:
         glDrawElements(mode, count[i], type, indices[i]);
   5:
 }

还是看个例子,但是因为上面那个矩形的例子都已经无法承载(^^)glMultiDrawElements的强大了,我们换了个更加复杂一些的例子,用4个三角形拼出来的矩形。顶点数组数据也很简单,只需要增加一个中心点就行了。

   1:
 // OpenGL需要的头文件
   2:
 #include
 <GL/glew.h>
   3:
 #include
 <GL/wglew.h>
   4:
 
   5:
 //定义程序链接时所需要调用的OpenGL程序库,简化工程配置
   6:
 #pragma
 comment( lib, "opengl32.lib"
 ) 
   7:
 #pragma
 comment( lib, "glu32.lib"
 )  
   8:
 #pragma
 comment( lib, "glew32.lib"
 )  
   9:
 #pragma
 comment( lib, "glew32s.lib"
 ) 
  10:
 
  11:
 //OpenGL初始化开始
  12:
 void
 SceneInit(int
 w,int
 h)
  13:
 {
  14:
     GLenum err = glewInit();
  15:
     if
(err != GLEW_OK)
  16:
     {
  17:
         MessageBox(NULL, _T("Error"
), _T("Glew init failed."
), MB_OK);
  18:
         exit(-1);
  19:
     }
  20:
 
  21:
     glClearColor(0.0, 0.0, 0.0, 0.0);
  22:
     // 启用顶点数组
  23:
     glEnableClientState(GL_VERTEX_ARRAY);
  24:
 
  25:
     // 颜色数组也需要启用
  26:
     glEnableClientState(GL_COLOR_ARRAY);
  27:
     
  28:
     // 默认就是此参数,可忽略,为了明确说明特意指定
  29:
     glShadeModel(GL_SHADE_MODEL);
  30:
 
  31:
     // 顶点数组数据
  32:
     static
 GLfloat fVertices[] = {    -0.5, -0.5,
  33:
                                      0.5, -0.5,
  34:
                                      0.5,  0.5,
  35:
                                     -0.5,  0.5,
  36:
                                      0.0,  0.0};    // 添加的原点
  37:
 
  38:
     // 颜色数组
  39:
     static
 GLfloat fColor[] = { 1.0, 0.0, 0.0,
  40:
                                 0.0, 1.0, 0.0,
  41:
                                 0.0, 0.0, 1.0,
  42:
                                 0.0, 0.0, 0.0,
  43:
                                 1.0, 1.0, 1.0};        // 原点颜色为白色
  44:
 
  45:
     // 指定顶点数组数据
  46:
     glVertexPointer(2, GL_FLOAT, 0, fVertices);
  47:
 
  48:
     // 制定颜色数组
  49:
     glColorPointer(3, GL_FLOAT, 0, fColor);
  50:
 
  51:
 }
  52:
 
  53:
 //这里进行所有的绘图工作
  54:
 void
 SceneShow(GLvoid)        
  55:
 {
  56:
     glClear(GL_COLOR_BUFFER_BIT);    // 清空颜色缓冲区
  57:
 
  58:
     static
 GLubyte byTopIndices[] = { 2, 3, 4};
  59:
     static
 GLubyte byLeftIndices[] = { 3, 0, 4};
  60:
     static
 GLubyte byBottomIndices[] = { 0, 1, 4};
  61:
     static
 GLubyte byRightIndices[] = { 1, 2, 4};
  62:
 
  63:
     static
 GLsizei iCounts[] = { 3, 3, 3, 3};
  64:
     static
 GLvoid *indices[] = { byTopIndices, byLeftIndices, byBottomIndices, byRightIndices};
  65:
     glPushMatrix();
  66:
     glTranslatef(-0.5, 0, 0);
  67:
     glMultiDrawElements(GL_TRIANGLES, (GLsizei*)iCounts, GL_UNSIGNED_BYTE, (const
 GLvoid**)indices, 4);
  68:
 
  69:
     glPopMatrix();
  70:
 
  71:
 
  72:
     // 上述函数调用与下面的效果一样
  73:
     glPushMatrix();
  74:
     glTranslatef(0.5, 0, 0);
  75:
     glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, byTopIndices);
  76:
     glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, byLeftIndices);
  77:
     glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, byBottomIndices);
  78:
     glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, byRightIndices);
  79:
     glPopMatrix();
  80:
 
  81:
     glFlush();
  82:
 }  

为节省篇幅仅贴出关键片段,完整源代码见我博客源代码的2009-10-18/glRectWithMultiDrawElements/ 目录,获取方式见文章最后关于获取博客完整源代码的说明。 运行效果见下图,由于颜色的搭配加上渐变效果,感觉就像是两个从上向下看的金字塔^^

image3

为了方便顶点排列顺序的理解和上例中参数的含义,我特意画了张草图:

image10

此例中首先需要说明的是,因为glMultiDrawElements函数是OpenGL 1.4版本引进的,而Windows仅仅实现了1.1,所以在Windows下必须需要进行额外处理才能使用了,我使用的是glew库,详细情况见XO2,其中详细说明了怎么获取Windows中缺失的OpenGL函数。glMultiDrawElements函数仅仅通过最后一个参数(此例中为4)表示指定4次glDrawElements调用,其他的参数与glDrawElements函数意义一致,仅仅是第2,4参数变成数组形式,这是种非常有意思又很容易理解和记忆的参数排列方式,也是值得一学的。

序列顶点数组函数

还是为了优化考虑,在对于排好序的顶点序列而言,可以通过接口指明这一点以允许OpenGL实现进一步的优化,OpenGL中对应的函数为glDrawArrays和glMultiDrawArrays,分别于上述glDrawElements和glMultiDrawElements函数对应,使用上由于有顶点数组按顺序这个优先条件在,参数更加简单一些,需在需要额外的索引数组,(前面例子中的Indice类数组)只需要指明起点和数量就行。

OpenGL Reference Manual

glDrawArrays — render primitives from array data
C Specification
void glDrawArrays( GLenum mode,
GLint first,
GLsizei count);
Parameters

mode

Specifies what kind of primitives to render.
Symbolic constants
GL_POINTS,
GL_LINE_STRIP,
GL_LINE_LOOP,
GL_LINES,
GL_TRIANGLE_STRIP,
GL_TRIANGLE_FAN,
GL_TRIANGLES,
GL_QUAD_STRIP,
GL_QUADS,
and GL_POLYGON are accepted.
first

Specifies the starting index in the enabled arrays.
count

Specifies the number of indices to be rendered.

glMultiDrawArrays — render multiple sets of primitives from array data
C Specification
void glMultiDrawArrays( GLenum mode,
GLint * first,
GLsizei * count,
GLsizei primcount);
Parameters

mode

Specifies what kind of primitives to render.
Symbolic constants
GL_POINTS,
GL_LINE_STRIP,
GL_LINE_LOOP,
GL_LINES,
GL_TRIANGLE_STRIP,
GL_TRIANGLE_FAN,
GL_TRIANGLES,
GL_QUAD_STRIP,
GL_QUADS,
and GL_POLYGON are accepted.
first

Points to an array of starting indices in the enabled arrays.
count

Points to an array of the number of indices to be rendered.
primcount

Specifies the size of the first and count

下面是个例子

   1:
 //OpenGL初始化开始
   2:
 void
 SceneInit(int
 w,int
 h)
   3:
 {
   4:
     GLenum err = glewInit();
   5:
     if
(err != GLEW_OK)
   6:
     {
   7:
         MessageBox(NULL, _T("Error"
), _T("Glew init failed."
), MB_OK);
   8:
         exit(-1);
   9:
     }
  10:
 
  11:
     glClearColor(0.0, 0.0, 0.0, 0.0);
  12:
     // 启用顶点数组
  13:
     glEnableClientState(GL_VERTEX_ARRAY);
  14:
 
  15:
     // 颜色数组也需要启用
  16:
     glEnableClientState(GL_COLOR_ARRAY);
  17:
     
  18:
     // 默认就是此参数,可忽略,为了明确说明特意指定
  19:
     glShadeModel(GL_SHADE_MODEL);
  20:
 
  21:
     // 顶点数组数据
  22:
     static
 GLfloat fVertices[] = {    -0.5, -0.5,
  23:
                                      0.5, -0.5,
  24:
                                      0.5,  0.5,
  25:
                                     -0.5,  0.5,
  26:
                                      0.0,  0.0,     // 添加的原点
  27:
                                     -0.5, -0.5}; // 添加的第一个点
  28:
 
  29:
     // 颜色数组
  30:
     static
 GLfloat fColor[] = { 1.0, 0.0, 0.0,
  31:
                                 0.0, 1.0, 0.0,
  32:
                                 0.0, 0.0, 1.0,
  33:
                                 0.0, 0.0, 0.0,
  34:
                                 1.0, 1.0, 1.0,    // 原点颜色为白色
  35:
                                 1.0, 0.0, 0.0};    // 回归添加第一个点为红色
  36:
 
  37:
     // 指定顶点数组数据
  38:
     glVertexPointer(2, GL_FLOAT, 0, fVertices);
  39:
 
  40:
     // 制定颜色数组
  41:
     glColorPointer(3, GL_FLOAT, 0, fColor);
  42:
 
  43:
 }
  44:
 
  45:
 //这里进行所有的绘图工作
  46:
 void
 SceneShow(GLvoid)        
  47:
 {
  48:
     glClear(GL_COLOR_BUFFER_BIT);    // 清空颜色缓冲区
  49:
 
  50:
     static
 GLubyte byTopIndices[] = { 2, 3, 4};
  51:
     static
 GLubyte byLeftIndices[] = { 3, 0, 4};
  52:
     static
 GLubyte byBottomIndices[] = { 0, 1, 4};
  53:
     static
 GLubyte byRightIndices[] = { 1, 2, 4};
  54:
 
  55:
     static
 GLsizei iCounts[] = { 3, 3, 3, 3};
  56:
     static
 GLvoid *indices[] = { byTopIndices, byLeftIndices, byBottomIndices, byRightIndices};
  57:
 
  58:
     static
 GLint iFirsts[] = { 0, 2, 3};
  59:
 
  60:
     // 左上角的图形展示了glDrawArrays的效果,glDrawArray连续的绘制0到4索引指定顶点的图形
  61:
     glPushMatrix();
  62:
     glTranslatef(-0.5, 0.0, 0.0);
  63:
     glDrawArrays(GL_QUADS, 0, 4);
  64:
     glPopMatrix();
  65:
 
  66:
     // 左下角的图展示了glMultiDrawArrays调用的效果
  67:
 
  68:
     glPushMatrix();
  69:
     glTranslatef(0.5, 0.0, 0.0);
  70:
     glMultiDrawArrays(GL_TRIANGLES, iFirsts, iCounts, 3);
  71:
     glPopMatrix();
  72:
 
  73:
     glFlush();
  74:
 }  

为节省篇幅仅贴出关键片段,完整源代码见我博客源代码的2009-10-18/glRectWithArrayDraw 目录,获取方式见文章最后关于获取博客完整源代码的说明。

按上面例子中的顶点排序,我们没有办法再组织处一个完整的矩形了,这里我通过{0,1,2},{2,3,4}绘制出了3/4,然后补上了一个第一个点坐标通过绘制{3,4,5},完成完整的矩形。记住array函数的按顺序特点就很好理解了。程序运行效果如下图,注意实现的不同导致与前面例子中的效果差异。

image13

范围限定顶点数组函数

事实上除了上述函数,OpenGL还有一组为了优化而存在的指定索引范围的函数,(非Multi组的函数都有,在有些扩展中也有Multi的Range函数)以Range加入上述函数名中的方式命名,使用方式与类似函数一致,仅仅是加入了两个参数表示索引的范围,以方便OpenGL的实现优化,如下例,就是glDrawElements对应的范围函数glDrawRangeElements的例子,其他函数也类似,这里不再详述。

   1:
 glPushMatrix();
   2:
 glTranslatef(0.5, 0, 0);
   3:
 glDrawRangeElements(GL_TRIANGLES, 0, 4, 3, GL_UNSIGNED_BYTE, byTopIndices);
   4:
 glDrawRangeElements(GL_TRIANGLES, 0, 4, 3, GL_UNSIGNED_BYTE, byLeftIndices);
   5:
 glDrawRangeElements(GL_TRIANGLES, 0, 4, 3, GL_UNSIGNED_BYTE, byBottomIndices);
   6:
 glDrawRangeElements(GL_TRIANGLES, 0, 4, 3, GL_UNSIGNED_BYTE, byRightIndices);
   7:
 glPopMatrix();

OpenGL Reference Manual

glDrawRangeElements — render primitives from array data
C Specification
void glDrawRangeElements( GLenum mode,
GLuint start,
GLuint end,
GLsizei count,
GLenum type,
const GLvoid * indices);
Parameters

mode

Specifies what kind of primitives to render.
Symbolic constants
GL_POINTS,
GL_LINE_STRIP,
GL_LINE_LOOP,
GL_LINES,
GL_TRIANGLE_STRIP,
GL_TRIANGLE_FAN,
GL_TRIANGLES,
GL_QUAD_STRIP,
GL_QUADS,
and GL_POLYGON are accepted.
start

Specifies the minimum array index contained in indices.
end

Specifies the maximum array index contained in indices.
count

Specifies the number of elements to be rendered.
type

Specifies the type of the values in indices. Must be one of
GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or
GL_UNSIGNED_INT.
indices

Specifies a pointer to the location where the indices are stored.

混合数组

原来的数组都是指定顶点的指定顶点,指定颜色的指定颜色,事实上OpenGL允许将顶点数组及颜色数组混用,即所谓的混合数组。

前面glVertexPointer中的第三个参数跨距就是用来指定这种情况的数据的,事实上,我们还有更简单(更复杂?)的办法,那就是使用glInterleavedArray函数以指定数据,需要注意的是glInterleavedArray与glVertexPointer等函数一样,仅仅是指定数据而已,我们还得通过Draw一组的函数去绘制图形。并且,个人感觉,glInterleavedArrays的参数那个复杂啊。。。。《OpenGL Programming Guide 》),中甚至需要通过一张极为复杂的表来表示。这里我也不想做过多解释了,其参数之复杂也不是一下解释的清楚的。建议还是参考《OpenGL Programming Guide 》)一书Table 2-5 : (continued) Variables that Direct glInterleavedArrays()的这个表比较好。

OpenGL Reference Manual

glInterleavedArrays — simultaneously specify and enable several interleaved arrays
C Specification
void glInterleavedArrays( GLenum format,
GLsizei stride,
const GLvoid * pointer);
Parameters

format

Specifies the type of array to enable. Symbolic constants
GL_V2F,
GL_V3F,
GL_C4UB_V2F,
GL_C4UB_V3F,
GL_C3F_V3F,
GL_N3F_V3F,
GL_C4F_N3F_V3F,
GL_T2F_V3F,
GL_T4F_V4F,
GL_T2F_C4UB_V3F,
GL_T2F_C3F_V3F,
GL_T2F_N3F_V3F,
GL_T2F_C4F_N3F_V3F,
and
GL_T4F_C4F_N3F_V4F
are accepted.
stride

Specifies the offset in bytes between each aggregate array element.

我这里有两个例子,分别使用了glVertexPointer,glColorPointer制定跨距的形式和glInterleavedArrays的形式,运行效果一致。

   1:
 //OpenGL初始化开始
   2:
 void
 SceneInit(int
 w,int
 h)
   3:
 {
   4:
     GLenum err = glewInit();
   5:
     if
(err != GLEW_OK)
   6:
     {
   7:
         MessageBox(NULL, _T("Error"
), _T("Glew init failed."
), MB_OK);
   8:
         exit(-1);
   9:
     }
  10:
 
  11:
     glEnableClientState(GL_VERTEX_ARRAY);
  12:
     glEnableClientState(GL_COLOR_ARRAY);
  13:
 
  14:
     // 前三列是颜色数组,后三列是顶点数组数据,
  15:
     static
 GLfloat fVertices[] = {    1.0, 0.0, 0.0, -0.5, -0.5, 0.0,  
  16:
                                     0.0, 1.0, 0.0,  0.5, -0.5, 0.0,  
  17:
                                     0.0, 0.0, 1.0,  0.5,  0.5, 0.0,  
  18:
                                     0.0, 0.0, 0.0, -0.5,  0.5, 0.0,  
  19:
                                     1.0, 1.0, 1.0,  0.0,  0.0, 0.0 };
  20:
 
  21:
     glVertexPointer(3, GL_FLOAT, 6 * sizeof
(GL_FLOAT), &fVertices[3]);
  22:
     glColorPointer(3, GL_FLOAT, 6 * sizeof
(GL_FLOAT), fVertices);
  23:
 }

为了更好分辨区别,我将上面例子中被替换掉功能的函数注释掉然后保留。

   1:
 void
 SceneInit(int
 w,int
 h)
   2:
 {
   3:
     GLenum err = glewInit();
   4:
     if
(err != GLEW_OK)
   5:
     {
   6:
         MessageBox(NULL, _T("Error"
), _T("Glew init failed."
), MB_OK);
   7:
         exit(-1);
   8:
     }
   9:
 
  10:
     //glEnableClientState(GL_VERTEX_ARRAY);
  11:
     //glEnableClientState(GL_COLOR_ARRAY);
  12:
 
  13:
     // 前三列是颜色数组,后三列是顶点数组数据,
  14:
     static
 GLfloat fVertices[] = {    1.0, 0.0, 0.0, -0.5, -0.5, 0.0,  
  15:
                                     0.0, 1.0, 0.0,  0.5, -0.5, 0.0,  
  16:
                                     0.0, 0.0, 1.0,  0.5,  0.5, 0.0,  
  17:
                                     0.0, 0.0, 0.0, -0.5,  0.5, 0.0,  
  18:
                                     1.0, 1.0, 1.0,  0.0,  0.0, 0.0 };
  19:
 
  20:
     //glVertexPointer(3, GL_FLOAT, 6 * sizeof(GL_FLOAT), &fVertices[3]);
  21:
     //glColorPointer(3, GL_FLOAT, 6 * sizeof(GL_FLOAT), fVertices);
  22:
     glInterleavedArrays(GL_C3F_V3F, 6 * sizeof
(GL_FLOAT), fVertices);
  23:
 }

为节省篇幅仅贴出关键片段,完整源代码见我博客源代码的2009-10-18/glInterleavedArrays/目录,获取方式见文章最后关于获取博客完整源代码的说明。

可见glInterleavedArrays功能是很强大的,甚至能够管理状态的开启,可以预见,在大规模程序中,需要切换状态较多时,此函数对于效率的提升作用还是会较多的(其实也较有限),但是此函数实在复杂-_-!使用时甚至需要通过查表来寻找参数,晕倒。

小结

到此为止,OpenGL顶点数组相关的用法算是基本讲完了,虽然顶点数组不是个OpenGL中的核心概念,但是对于效率的提升来说是很有价值的,所以OpenGL提供了一系列一个比一个更有效率的函数,在程序规模扩大后,不仅效率会获得提升,事实上将数据按照OpenGL的规范存在数组中对于编程也会带来很大的便利,相对于用数组加循环的方式来调用glVertex*会更加的容易理解和维护,毕竟,代码量的减少在很多时候那是工程容易维护的王道。

参考资料

1. 《OpenGL Reference Manual 》,OpenGL参考手册

2. 《OpenGL 编程指南》(《OpenGL Programming Guide 》),Dave Shreiner,Mason Woo,Jackie Neider,Tom Davis 著,徐波译,机械工业出版社

3. 《Nehe OpenGL Tutorials》,Nehe著,在http://nehe.gamedev.net/ 上可以找到教程及相关的代码下载,(有PDF版本教程下载)Nehe自己还做了一个面向对象的框架,作为演示程序来说,这样的框架非常合适。也有中文版 ,各取所需吧。

4. 《OpenGL入门学习》 ,eastcowboy著,这是我在网上找到的一个比较好的教程,较为完善,而且非常通俗。这是第一篇的地址:http://bbs.pfan.cn/post-184355.html

本系列下一篇 《Win32 OpenGL编程(6) 踏入3D世界

本OpenGL系列其他文章

1. Win32 OpenGL 编程(1)Win32下的OpenGL编程必须步骤

2. 《Win32 OpenGL编程(2) 寻找缺失的OpenGL函数

3. 《Win32 OpenGL编程(3) 基本图元(点,直线,多边形)的绘制

4. 《Win32 OpenGL编程(4) 2D图形基础(颜色及坐标体系进阶知识)

完整源代码获取说明

由于篇幅限制,本文一般仅贴出代码的主要关心的部分,代码带工程(或者makefile)完整版(如果有的话)都能用Mercurial在Google Code中下载。文章以博文发表的日期分目录存放,请直接使用Mercurial克隆下库:

https://blog-sample-code.jtianling.googlecode.com/hg/

Mercurial使用方法见《分布式的,新一代版本控制系统Mercurial的介绍及简要入门

要是仅仅想浏览全部代码也可以直接到google code上去看,在下面的地址:

http://code.google.com/p/jtianling/source/browse?repo=blog-sample-code

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

分享到:
评论

相关推荐

    OpenGL Win32 OpenGL Win32 OpenGL Win32

    4. **资源管理**:OpenGL对象如顶点数组、纹理、着色器等,需要通过Win32 API的辅助函数来创建、绑定和管理。例如,`wglGetProcAddress`用于获取扩展函数的地址,这些扩展函数可能不包含在标准OpenGL库中。 5. **...

    OpenGL编程指南(红宝书)源码

    这些代码可以帮助读者理解和实践书中讲解的概念,例如顶点数组、着色器、纹理映射、光照模型等。 3. **VS2008**:Visual Studio 2008是Microsoft开发的一个集成开发环境(IDE),支持多种编程语言,包括C++。作者...

    3D赛车游戏源码_OpenGL_Win32

    【3D赛车游戏源码】基于OpenGL和Win32 API的3D赛车游戏是一个学习图形编程和游戏开发的宝贵资源。对于初学者来说,通过分析和理解这些源代码,可以深入掌握3D图形渲染和游戏逻辑的核心概念。 1. **OpenGL**:OpenGL...

    opengl二维图形转换为三维图形

    5. 绘制3D形状:使用OpenGL的3D绘图函数,如`glBegin(GL_TRIANGLES)`和`glVertex3f`,定义多边形的顶点。 6. 渲染:最后,调用`glutSwapBuffers`来呈现结果。 在提供的`threedbmp`文件中,可能包含了一个或多个...

    基于Win32的面向对象的OpenGL构架

    4. **OpenGL对象**:比如顶点数组、纹理、着色器等,可以被封装为类,如`CVBO`(顶点缓冲对象)、`CTexture`(纹理对象)、`CGlShader`(着色器对象)。这样,它们的创建、绑定、使用和销毁都可以通过类的方法来完成...

    OPENGL编程入门

    随着深入学习,可以逐步掌握更多高级特性,如顶点数组、纹理映射、光照模型、深度测试、着色器等,从而实现更复杂的3D图形效果。此外,学习OpenGL的过程中,可以参考官方网站和其他在线资源,如教程、示例代码和论坛...

    exam1_flag.rar_NEHE_OpenGL MFC_mfc opengl_nehe MFC_pudn NeHe Win

    在NeHe教程中,通常会通过实际的图形效果来教授各种OpenGL技术,如纹理映射、光照、顶点数组等。 在实现NeHe教程的MFC版本时,开发者需要考虑如何将NeHe教程中的Win32 API调用转换为MFC的消息处理机制。这可能涉及...

    opengl纹理贴图源码

    5. **设置顶点数组**:VA模式下,我们使用`glVertexArrayPointer`和`glEnableVertexAttribArray`来定义顶点数组和顶点属性指针。这些顶点属性包括位置、颜色、法线向量以及纹理坐标。 6. **绘制几何体**:在VA模式...

    利用VC++和openGL实现魔方

    在VC++中创建一个新的Win32控制台应用项目,然后在项目属性中配置OpenGL所需的库链接,如`opengl32.lib`和`glu32.lib`。同时,为了处理键盘事件,还需要链接`user32.lib`库。 接下来,我们要定义魔方的结构。每个...

    OpenGL VS2012环境搭建以及第一个OpenGL例子(WIN7 64位系统)

    5. **编写代码**: "FirstOpenGL"可能是一个简单的OpenGL示例程序,通常会包括初始化GLFW窗口,加载GLEW,然后创建一个顶点数组对象,设置顶点着色器和片段着色器,最后绘制三角形等基本形状。代码会包含`glfwInit`、...

    win opengl glsl draw water

    最后,“triangle.cpp”和“triangle.h”可能包含了基本的OpenGL绘图功能,比如初始化GL上下文,设置视口,处理窗口事件,以及设置顶点数组和顶点缓冲对象(VAO、VBO、EBO)等。 总结来说,实现“win OpenGL glsl ...

    opengl 绘制三角形

    OpenGL的学习涉及很多方面,包括顶点坐标系统、顶点数组、纹理映射、光照、着色器等。通过这个简单的三角形和正方形绘制,你可以开始理解OpenGL的基本工作流程,并逐步深入到更复杂的3D图形编程中。在实践中,你可能...

    OpenGL的Nate Robinson教程

    学习如何设置顶点数组、如何初始化OpenGL上下文、如何使用GLUT或FreeGLUT库创建窗口、如何绘制基本形状、如何处理输入事件,以及如何实现更复杂的图形效果,如深度测试、混合模式和着色器编程。 在OpenGL中,顶点...

    OpenGL 的 shader language 例子

    6. **数据传递**:在CPU和GPU之间传递数据是通过顶点数组、纹理等实现的。例如,可以使用glVertexAttribPointer函数设置顶点数据,glBindTexture函数绑定纹理。 7. **绘制调用**:OpenGL的绘制调用(如glDrawArrays...

    OpenGL实现实时三维地形动态显示

    在VC++环境中,我们可以使用MFC(Microsoft Foundation Classes)或者Win32 API来创建OpenGL上下文,并与OpenGL库进行交互。开发者首先需要设置一个OpenGL窗口,然后在该窗口中初始化OpenGL环境,包括设置视口、投影...

    Visual C++ 2005下OpenGL应用实例

    OpenGL编程的核心是顶点数组、着色器和渲染管线。在Visual C++ 2005中,你可以使用OpenGL的固定功能管线,但现代的做法是使用可编程管线,包括顶点着色器和片段着色器。这些着色器语言通常是GLSL(OpenGL Shading ...

    opengl山间小筑场景

    4. **顶点数组和顶点缓冲对象**:高效地存储和传递顶点数据到GPU。 5. **纹理映射**:利用SOIL库加载山景图片作为纹理,贴合到几何模型表面。 6. **光照和阴影**:模拟光源,通过计算光照方向和表面法线,实现更...

    旋转的球opengl实现

    OpenGL中的球体通常由许多三角面片组成,这些面片可以使用顶点数组或者立即模式下的顶点指令定义。在我们的例子中,可以使用`glBegin(GL_TRIANGLES)`和`glEnd()`来开始和结束一个顶点序列,中间用`glVertex3f(x, y, ...

    opengl星云代码

    OpenGL是一种强大的图形编程接口,...为了进一步理解代码,需要打开文件查看具体的实现细节,如顶点数组、纹理处理、颜色计算等。同时,可能还需要学习和理解OpenGL的管线模型以及VC++中与图形编程相关的API使用方法。

Global site tag (gtag.js) - Google Analytics