OpenGL顶点数组.docx
- 文档编号:17949030
- 上传时间:2023-08-05
- 格式:DOCX
- 页数:16
- 大小:27.52KB
OpenGL顶点数组.docx
《OpenGL顶点数组.docx》由会员分享,可在线阅读,更多相关《OpenGL顶点数组.docx(16页珍藏版)》请在冰点文库上搜索。
OpenGL顶点数组
OpenGL的顶点数组
关于OpenGL中的顶点数组
一个立方体有六个面,每个面是一个正方形,好,绘制六个正方形就可以了。
glBegin(GL_QUADS);
glVertex3f(...);
glVertex3f(...);
glVertex3f(...);
glVertex3f(...);
//...
glEnd();
为了绘制六个正方形,我们为每个正方形指定四个顶点,最终我们需要指定6*4=24个顶点。
但是我们知道,一个立方体其实总共只有八个顶点,要指定24次,就意味着每个顶点其实重复使用了三次,这样可不是好的现象。
最起码,像上面这样重复烦琐的代码,是很容易出错的。
稍有不慎,即使相同的顶点也可能被指定成不同的顶点了。
如果我们定义一个数组,把八个顶点都放到数组里,
然后每次指定顶点都使用指针,而不是使用直接的数
据,这样就避免了在指定顶点时考虑大量的数据,于
是减少了代码出错的可能性。
//将立方体的八个顶点保存到一个数组里面
staticconstGLfloatvertex_list[][3]={
-0.5f,-0.5f,-0.5f,
0.5f,-0.5f,-0.5f,
//...
};
//指定顶点时,用指针,而不用直接用具体的数据
glBegin(GL_QUADS);
glVertex3fv(vertex_list[0]);
glVertex3fv(vertex_list[2]);
glVertex3fv(vertex_list[3]);
glVertex3fv(vertex_list[1]);
//...
glEnd();
修改之后,虽然代码变长了,但是确实易读得多。
很
容易就看出第0,2,3,1这四个顶点构成一个正方形。
稍稍观察就可以发现,我们使用了大量的glVertex3fv函数,其实每一句都只有其中的顶点序号不一样,因此我们可以再定义一个序号数组,把所有的序号也放进去。
这样一来代码就更加简单了。
//将立方体的八个顶点保存到一个数组里面
staticconstGLfloatvertex_list[][3]={
-0.5f,-0.5f,-0.5f,
0.5f,-0.5f,-0.5f,
-0.5f,0.5f,-0.5f,
0.5f,0.5f,-0.5f,
-0.5f,-0.5f,0.5f,
0.5f,-0.5f,0.5f,
-0.5f,0.5f,0.5f,
0.5f,0.5f,0.5f,};
//将要使用的顶点的序号保存到一个数组里面
staticconstGLintindex_list[][4]={
0,2,3,1,
0,4,6,2,
0,1,5,4,
4,5,7,6,
1,3,7,5,
2,6,7,3,
};
inti,j;
//绘制的时候代码很简单
glBegin(GL_QUADS);for(i=0;i<6;++i)//有六个面,循环六次
for(j=0;j<4;++j)//每个面有四个顶点,循环四次
glVertex3fv(vertex_list[index_list[i][j]]);
glEnd();
这样,我们就得到一个比较成熟的绘制立方体的版本了。
它的数据和程序代码基本上是分开的,所有的顶点放到一个数组中,使用顶点的序号放到另一个数组中,而利用这两个数组来绘制立方体的代码则很简单。
关于顶点的序号,下面这个图片可以帮助理解。
正对我们的面,按逆时针顺序,背对我们的面,则按顺时针顺序,这样就得到了上面那个index_list数组。
为什么要按照顺时针逆时针的规则呢,因为这样做可以保证无论从哪个角度观察,看到的都是“正面”,而不是背面。
在计算光照时,正面和背面的处理可能是不同的,另外,剔除背面只绘制正面,可以提高程序的运行效率。
(关于正面、背面,以及剔除,参见第
三课,绘制几何图形的一些细节问题)
例如在绘制之前调用如下的代码:
glFrontFace(GL_CCW);glCullFace(GL_BACK);glEnable(GL_CULL_FACE);glPolygonMode(GL_FRONT_AND_BACK,
GL_LINE);
则绘制出来的图形就只有正面,并且只显示边线,不进行填充。
效果如图:
顶点数组
(提示:
顶点数组是OpenGL1.1所提供的功能)前面的方法中,我们将数据和代码分离开,看起来只要八个顶点就可以绘制一个立方体了。
但是实际上,循环还是执行了6*4=24次,也就是说虽然代码的结构清晰了不少,但是程序运行的效率,还是和最原始的那个方法一样。
减少函数的调用次数,是提高运行效率的方法之一。
于是我们想到了显示列表。
把绘制立方体的代码装到
一个显示列表中,以后只要调用这个显示列表即可。
这样看起来很不错,但是显示列表有一个缺点,那就
是一旦建立后不可再改。
如果我们要绘制的不是立方
体,而是一个能够走动的人物,因为人物走动时,四
肢的位置不断变化,几乎没有办法把所有的内容装到
一个显示列表中。
必须每种动作都使用单独的显示列
表,这样会导致大量的显示列表管理困难。
顶点数组是解决这个问题的一个方法。
使用顶点数组
的时候,也是像前面的方法一样,用一个数组保存所
有的顶点,用一个数组保存顶点的序号。
但最后绘制
的时候,不是编写循环语句逐个的指定顶点了,而是
通知OpenGL,“保存顶点的数组”和“保存顶点序号的
数组”所在的位置,由OpenGL自动的找到顶点,并
进行绘制。
下面的代码说明了顶点数组是如何使用的:
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,GL_FLOAT,0,vertex_list);
glDrawElements(GL_QUADS,24,GL_UNSIGNED_INT,index_list);
其中:
glEnableClientState(GL_VERTEX_ARRAY);表示启用顶点数组。
glVertexPointer(3,GL_FLOAT,0,vertex_list);指定顶点数组的位置,3表示每个顶点由三个量构成(x,y,z),GL_FLOAT表示每个量都是一个GLfloat类型的值。
第三个参数0,参见后面介绍“stride参数”。
最后的vertex_list指明了数组实际的位置。
glDrawElements(GL_QUADS,24,GL_UNSIGNED_INT,index_list);根据序号数组中的序号,查找到相应的顶点,并完成绘制。
GL_QUADS表示绘制的是四边形,24表示总共有24个顶点,GL_UNSIGNED_INT表示序号数组内每个序号都是一个GLuint类型的值,index_list指明了序号数组实际的位置。
上面三行代码代替了原来的循环。
可以看到,原来的glBegin/glEnd不再需要了,也不需要调用glVertex*系列函数来指定顶点,因此可以明显的减少函数调用次数。
另外,数组中的内容可以随时修改,比显示列表更加灵活。
详细一点的说明。
顶点数组实际上是多个数组,顶点坐标、纹理坐标、
法线向量、顶点颜色等等,顶点的每一个属性都可以
指定一个数组,然后用统一的序号来进行访问。
比如
序号3,就表示取得颜色数组的第3个元素作为颜色、
取得纹理坐标数组的第3个元素作为纹理坐标、取得
法线向量数组的第3个元素作为法线向量、取得顶点
坐标数组的第3个元素作为顶点坐标。
把所有的数据
综合起来,最终得到一个顶点。
可以用glEnableClientState/glDisableClientState单
独的开启和关闭每一种数组。
glEnableClientState(GL_VERTEX_ARRAY);glEnableClientState(GL_COLOR_ARRAY);glEnableClientState(GL_NORMAL_ARRAY);glEnableClientState(GL_TEXTURE_COORD_ARRAY);
用以下的函数来指定数组的位置:
glVertexPointer
glColorPointer
glNormalPointer
glTexCoordPointer
为什么不使用原来的glEnable/glDisable函数,而要
专门的规定一个
glEnableClientState/glDisableClientState函数呢,这跟OpenGL的工作机制有关。
OpenGL在设计时,认为可以将整个OpenGL系统分为两部分,一部分是客户端,它负责发送OpenGL命令。
一部分是服务端,它负责接收OpenGL命令并执行相应的操作。
对于个人计算机来说,可以将CPU、内存等硬件,以及用户编写的OpenGL程序看做客户端,而将OpenGL驱动程序、显示设备等看做服务端。
通常,所有的状态都是保存在服务端的,便于OpenGL使用。
例如,是否启用了纹理,服务端在绘制时经常需要知道这个状态,而我们编写的客户端OpenGL程序只在很少的时候需要知道这个状态。
所以将这个状态放在服务端是比较有利的。
但顶点数组的状态则不同。
我们指定顶点,实际上就是把顶点数据从客户端发送到服务端。
是否启用顶点数组,只是控制发送顶点数据的方式而已。
服务端只管接收顶点数据,而不必管顶点数据到底是用哪种方式指定的(可以直接使用glBegin/glEnd/glVertex*,也可以使用顶点数组)。
所以,服务端不需要知道顶点数组是否开启。
因此,顶点数组的状态放在客户端是比较合理的。
为了表示服务端状态和客户端状态的区别,服务端的
状态用glEnable/glDisable,客户端的状态则用
glEnableClientState/glDisableClientState。
stride参数。
顶点数组并不要求所有的数据都连续存放。
如果数据
没有连续存放,则指定数据之间的间隔即可。
例如:
我们使用一个struct来存放顶点中的数据。
注
意每个顶点除了坐标外,还有额外的数据(这里是一
个int类型的值)。
typedefstruct__point__{
GLfloatposition[3];
intid;
}Point;
Pointvertex_list[]={
-0.5f,-0.5f,-0.5f,1,
0.5f,-0.5f,-0.5f,2,
-0.5f,0.5f,-0.5f,3,
0.5f,0.5f,-0.5f,4,
-0.5f,-0.5f,0.5f,5,
0.5f,-0.5f,0.5f,6,
-0.5f,0.5f,0.5f,7,
0.5f,0.5f,0.5f,8,};
staticGLintindex_list[][4]={
0,2,3,1,
0,4,6,2,
0,1,5,4,
4,5,7,6,
1,3,7,5,
2,6,7,3,
};
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,GL_FLOAT,sizeof(Point),vertex_list);
glDrawElements(GL_QUADS,24,GL_UNSIGNED_INT,index_list);
注意最后三行代码,可以看到,几乎所有的地方都和
原来一样,只在glVertexPointer函数的第三个参数有
所不同。
这个参数就是stride,它表示“从一个数据的
开始到下一个数据的开始,所相隔的字节数”。
这里设置为sizeof(Point)就刚刚好。
如果设置为0,则表示
数据是紧密排列的,对于3个GLfloat的情况,数据
紧密排列时stride实际上为3*4=12。
混合数组。
如果需要同时使用颜色数组、顶点坐标数
组、纹理坐标数组、等等,有一种方式是把所有的数
据都混合起来,指定到同一个数组中。
这就是混合数
组。
GLfloatarr_c3f_v3f[]={
1,0,0,0,1,0,
0,1,0,1,0,0,
0,0,1,-1,0,0,
};
GLuintindex_list[]={0,1,2};glInterleavedArrays(GL_C3F_V3F,0,arr_c3f_v3f);
glDrawElements(GL_TRIANGLES,3,GL_UNSIGNED_INT,index_list);
glInterleavedArrays,可以设置混合数组。
这个函数
会自动调用glVertexPointer,glColorPointer等函数,
并且自动的开启或禁用相关的数组。
函数的第一个参数表示了混合数组的类型。
例如
GL_C3F_V3F表示:
三个浮点数作为颜色、三个浮点
数作为顶点坐标。
也可以有其它的格式,比如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,GL_T4F_C4F_N3F_V4F等等。
其中T表示纹理坐标,C表示颜色,N表示法
线向量,V表示顶点坐标。
再来说说顶点数组与显示列表的区别。
两者都可以明
显的减少函数的调用次数,但是还是各有优点的。
对于顶点数组,顶点数据是存放在内存中的,也就是
存放在客户端。
每次绘制的时候,需要把所有的顶点
数据从客户端(内存)发送到服务端(显示设备),
然后进行处理。
对于显示列表,顶点数据是放在显示
列表中的,显示列表本身又是存放在服务器端的,所
以不会重复的发送数据。
对于顶点数组,因为顶点数据放在内存中,所以可以
随时修改,每次绘制的时候都会把当前数组中的内容
作为顶点数据发送并进行绘制。
对于显示列表,数据
已经存放到服务器段,并且无法取出,所以无法修改。
也就是说,显示列表可以避免数据的重复发送,效率
会较高;顶点数组虽然会重复的发送数据,但由于数
据可以随时修改,灵活性较好。
顶点缓冲区对象
(提示:
顶点缓冲区对象是OpenGL1.5所提供的功能,但它在成为标准前是一个ARB扩展,可以通过GL_ARB_vertex_buffer_object扩展来使用这项功能。
前面已经讲过,ARB扩展的函数名称以字母ARB结尾,常量名称以字母_ARB结尾,而标准函数、常量则去掉了ARB字样。
很多的OpenGL实现同时支持vertexbufferobject的标准版本和ARB扩展版本。
我们这里以ARB扩展来讲述,因为目前绝大多数个人计算机都支持ARB扩展版本,但少数显卡仅支持OpenGL1.4,无法使用标准版本。
)
前面说到顶点数组和显示列表在绘制立方体时各有优劣,那么有没有办法将它们的优点集中到一起,并且尽可能的减少缺点呢,顶点缓冲区对象就是为了解决这个问题而诞生的。
它数据存放在服务端,同时也允许客户端灵活的修改,兼顾了运行效率和灵活性。
顶点缓冲区对象跟纹理对象有很多相似之处。
首先,分配一个缓冲区对象编号,然后,为对应编号的缓冲区对象指定数据,以后可以随时修改其中的数据。
下面的表格可以帮助类比理解。
纹理对象顶点缓冲区对象
分配编
号glGenTexturesglGenBuffersARB
绑定(指定为当前所使用的对
象)glBindTextureglBindBufferARB指定数
据glTexImage*glBufferDataA
RB
修改数
据glTexSubImage*glBufferSubDataARB
顶点数据和序号各自使用不同的缓冲区。
具体的说,
就是顶点数据放在GL_ARRAY_BUFFER_ARB类型
的缓冲区中,序号数据放在
GL_ELEMENT_ARRAY_BUFFER_ARB类型的缓冲
区中。
具体的情况可以用下面的代码来说明:
staticGLuintvertex_buffer;
staticGLuintindex_buffer;
//分配一个缓冲区,并将顶点数据指定到其中
glGenBuffersARB(1,&vertex_buffer);glBindBufferARB(GL_ARRAY_BUFFER_ARB,vertex_buffer);
glBufferDataARB(GL_ARRAY_BUFFER_ARB,
sizeof(vertex_list),vertex_list,GL_STATIC_DRAW_ARB);
//分配一个缓冲区,并将序号数据指定到其中
glGenBuffersARB(1,&index_buffer);glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,index_buffer);
glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB,
sizeof(index_list),index_list,
GL_STATIC_DRAW_ARB);
在指定缓冲区数据时,最后一个参数是关于性能的提
示。
一共有STREAM_DRAW,STREAM_READ,STREAM_COPY,STATIC_DRAW,STATIC_READ,STATIC_COPY,DYNAMIC_DRAW,
DYNAMIC_READ,DYNAMIC_COPY这九种。
每一
种都表示了使用频率和用途,OpenGL会根据这些提示进行一定程度的性能优化。
(提示仅仅是提示,不是硬性规定。
也就是说,即使使用了STREAM_DRAW,告诉OpenGL这段缓冲区数据一旦指定,以后不会修改,但实际上以后仍可修改,不过修改时可能有较大的性能代价)
当使用glBindBufferARB后,各种使用指针为参数的OpenGL函数,行为会发生变化。
以glColor3fv为例,通常,这个函数接受一个指针作为参数,从指针所指的位置取出连续的三个浮点数,作为当前的颜色。
但使用glBindBufferARB后,这个函数不再从指针所指的位置取数据。
函数会先把指针转化为整数,假设转化后结果为k,则会从当前缓冲区的第k个字节开始取数据。
特别一点,如果我们写glColor3fv(NULL);
因为NULL转化为整数后通常是零,所以从缓冲区的第0个字节开始取数据,也就是从缓冲区最开始的位置取数据。
这样一来,原来写的
glVertexPointer(3,GL_FLOAT,0,vertex_list);
glDrawElements(GL_QUADS,24,GL_UNSIGNED_INT,index_list);
在使用缓冲区对象后,就变成了
glVertexPointer(3,GL_FLOAT,0,NULL);
glDrawElements(GL_QUADS,24,GL_UNSIGNED_INT,NULL);
以下是完整的使用了顶点缓冲区对象的代码:
staticGLfloatvertex_list[][3]={
-0.5f,-0.5f,-0.5f,
0.5f,-0.5f,-0.5f,
-0.5f,0.5f,-0.5f,
0.5f,0.5f,-0.5f,
-0.5f,-0.5f,0.5f,
0.5f,-0.5f,0.5f,
-0.5f,0.5f,0.5f,
0.5f,0.5f,0.5f,};
staticGLintindex_list[][4]={
0,2,3,1,
0,4,6,2,
0,1,5,4,
4,5,7,6,
1,3,7,5,
2,6,7,3,
};
if(GLEE_ARB_vertex_buffer_object){
//如果支持顶点缓冲区对象
staticintisFirstCall=1;
staticGLuintvertex_buffer;
staticGLuintindex_buffer;
if(isFirstCall){
//第一次调用时,初始化缓冲区
isFirstCall=0;
//分配一个缓冲区,并将顶点数据指定到其中
glGenBuffersARB(1,&vertex_buffer);
glBindBufferARB(GL_ARRAY_BUFFER_ARB,
vertex_buffer);
glBufferDataARB(GL_ARRAY_BUFFER_AR
B,
sizeof(vertex_list),vertex_list,
GL_STATIC_DRAW_ARB);
//分配一个缓冲区,并将序号数据指定到其中
glGenBuffersARB(1,&index_buffer);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,index_buffer);
glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB,
sizeof(index_list),index_list,
GL_STATIC_DRAW_ARB);
}
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vertex_buffer);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,index_buffer);
//实际使用时与顶点数组非常相似,只是在指定
数组时不再指定实际的数组,改为指定NULL即可
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,GL_FLOAT,0,NULL);
glDrawElements(GL_QUADS,24,GL_UNSIGNED_INT,NULL);
}else{
//不支持顶点缓冲区对象
//使用顶点数组
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,GL_FLOAT,0,vertex_list);
glDrawElements(GL_QUADS,24,GL_UNSIGNED_INT,index_list);}
可以分配多个缓冲区对象,顶点坐标、颜色、纹理
坐标等数据,可以各自单独使用一个缓冲区。
每个缓冲区可以有不同的性
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- OpenGL 顶点 数组