üOpenGL提供顶点数组函数
启用数组:
glEnableClientState(GLenum array);来启动指定的数组
参数可以为
GL_VERTEX_ARRAY
GL_COLOR_ARRAY
GL_SECOND_COLOR_ARRAY
GL_INDEX_ARRAY
GL_NORMAL_ARRAY
GL_FOG_COORDINATE_ARRAY
GL_TEXTURE_COORD_ARRAY
GL_GLAG_ARRAY
指定数组中的数据:
void glVertexPointer(GLint size,GLenum type,GLsize srtide,cinst GLvoid *pointer);
size 是每个顶点的坐标数目,必须是2,3,4
type 是指定数组每个坐标的数据类型(GL_FLOAT,GL_INT,GL_SHORT,GL_DOUBLE)
stride 是为两个相邻顶点之间的偏移量,单位为字节. stride为0 表明顶点是紧密存储在数组中
pointer是数组中第一个顶点的第一个坐标的内存地址
指定其他数组数据的函数类似:glColorPointer();
解除引用和渲染
对单个数组元素解除引用:
void glArrayElement(GLuint ith);
这个函数对单个数组元素解除引用,参数ith是要获得启用数组中数据的下标,如果同时指定了颜色及法线等信息,那么对于每个顶点只调用glArrayElement()一次,减少了函数调用的次数,
对一系列数组元素解除引用:
void glArrayElements(GLenum mode,GLsize count,GLenum type,void *index);
mode取值和glBegin()参数值相同,count为元素个数,type指出了数组index的数据类型:必须为GL_UNSIGNED_BYTE 、GL_UNSIGNED_SHOART 、GL_UNSIGNED_INT
对一系列相邻数组元素解除引用
void glDrawArrays(GLenum mode,GLint first,GLsize count);
在每个被启用的数组中,使用从first到first+count-1对应的数组元素,来构建一些列几何图元,参数mode指定构建哪种图元-同glBegin()中的参数一样
该函数的第一个参数用于指定绘制模式。关于绘制模式我们稍后会有详细的介绍。
第二个参数用于指定所允许的顶点的起始顶点。比如说,我们之前定义了5个顶点,但是我们在画正方形的时候想跳过第一个顶点,从第二个顶点开始画,那么索引就是1(起始索引值为0)。
第三个参数要绘制的顶点的个数。
static struct
{
GLubyte colours[4];
GLfloat vertices[3];
}vertexInfoList[] = {
{ {255, 255, 0, 255}, {1.0f, 1.0f, 0.0f} },
{ {255, 0, 0, 255}, {-0.5f, 0.5f, 0.0f} },
{ {0, 255, 0, 255}, {-0.5f, -0.5f, 0.0f} },
{ {0, 0, 255, 255}, {0.5f, 0.5f, 0.0f} },
{ {255, 0, 255, 255}, {0.5f, -0.5f, 0.0f} }
};
glClear(GL_COLOR_BUFFER_BIT);
glVertexPointer(3, GL_FLOAT, 16, vertexInfoList[0].vertices);
glColorPointer(4, GL_UNSIGNED_BYTE, 16, vertexInfoList[0].colours);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
GPU的一个特点是拥有大规模的线程以及超大的带宽。因此,我们用glBegin/glEnd对去一点点绘制图形的话对GPU而言是非常低效的。GPU喜欢做的是一口气把所有命令读上来,然后分派到不同的流处理器去执行。每条流处理器又有很多个线程。GPU的流水线比较长,做的工作也是比较特定的。为了方便GPU来处理主机端的数据,我们通常会把顶点信息以及其相应的颜色信息组成连续存放的数组形式。这样既有利于存储器访问,而且也利于GPU快速加载(比如通过内部的DMA)。
glVertexPointer函数定义了一组顶点数据的一个数组。其原型如下:
1void glVertexPointer( GLint size,
2 GLenum type,
3 GLsizei stride,
4 const GLvoid * pointer);
第一个参数指定了每个顶点的坐标分量个数。比如,我们这里一个顶点的坐标有三个分量,分别是:x,y,z。如果只有两个分量的话,z分量在处理时会被置0;如果有4个分量,那么就会增加w分量。w分量用于线性变换,这将是下一讲的话题。这个参数只能是2、3或4。
第二个参数用于指定顶点每个坐标分量的数据类型。可以是:GL_SHORT、GL_INT、GL_FLOAT或GL_DOUBLE。在OpenGL ES中没有GL_DOUBLE。
这里稍微讲一下顶点坐标分量的数据类型。每个顶点的所有坐标分量必须的数据类型必须是一致的,不能定义x分量时用int,然后定义y 的时候就用float。并且每个顶点的坐标分量类型必须相同。
在GPU中,其专门有快速浮点计算,因此提高GPU处理数据的速度往往会使用float类型,而不是int。不过即使是当前主流桌面计算机用的GPU,其对double类型的支持比较弱,因为double需要消耗2倍于float的带宽。所以就目前而言,不管是OpenGL还是OpenGL ES,往往使用GLfloat类型来定义顶点坐标,以使得做线性变换计算时能充分利用GPU的计算单元的性能。
第三个参数指定相邻两个顶点的偏移字节数。上面用的是:glVertexPointer(3, GL_FLOAT, 16, vertexInfoList[0].vertices);
因为第一个顶点的起始地址与第二个顶点的起始地址之间相差16个字节——即sizeof(vertexInfoList[0])。
第四个参数用来指定顶点数组的起始位置。
glColorPointer与glVertexPointer定义一样。它用来指定一组顶点颜色信息的数组。其原型如下:
1
2
3
4 |
void glColorPointer( GLint size,
GLenum type,
GLsizei stride,
const GLvoid * pointer);
|
第一个参数是每个颜色信息的分量个数,在OpenGL中可以是3或4;而OpenGL ES1.1中必须是4。如果是3个分量,则依次表示红(R)、绿(G)、蓝(B);如果是4个分量,那么前三个与前面的一样,第四个分量是alpha,表示透明度。
第二个参数用于指定颜色分量的数据类型,可以是:GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT、GL_FLOAT、GL_DOUBLE;不过在OpenGL ES1.1中,常常使用GL_UNSIGNED_BYTE或GL_FLOAT。
第三个和第四个参数与glVertexPointer一样。
通过指定顶点的坐标信息和颜色信息,后面就可以利用glDrawArrays进行绘制了。
glDrawElements来绘制图形。
glDrawElements用于通过一个用户自定义的索引数组来绘制图形。下面看看其原型:
1
2
3
4 |
void glDrawElements( GLenum mode,
GLsizei count,
GLenum type,
const GLvoid * indices);
|
第一个参数用于指定图元绘制模式;
第二个参数用于指定所要绘制顶点的个数;
第三个参数用于指定索引的数据类型;
第四个参数用于指定索引的起始地址。
这个函数可以很方便地用于绘制多个图形。首先,顶点信息往往比较大(由于包括了一个顶点的所有坐标分量信息以及颜色分量信息,甚至还有法线信息等),因此如果我们所要绘制的图形通过已指定的顶点就能绘制出,就无需重复地定义顶点,而只需要通过一个简短的索引数组就能解决问题。
其次,调用一条命令绘制图形往往比调多次效率要高,功耗要小。
在OpenGL中图元绘制模式有: GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS, 以及GL_POLYGON。
在OpenGL ES1.1中,图元绘制模式有:GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN以及GL_TRIANGLES。
下面我们将逐一介绍。
首先是GL_POINTS,这个模式是仅绘制顶点。当我们使用glDrawArrays时,顶点通过由glVertexPointer和glColorPointer所指定的顺序将依次被绘制出。
我们可以尝试一下:
1 |
glDrawArrays(GL_POINTS, 0, 4);
|
将上述代码替换掉原来的对glDrawArrays的调用。
GL_LINE_STRIP模式:
这个模式用于绘制线段带。比如现在有顶点v0, v1, v2, v3,那么绘制出的线段为带由三条线段构成,依次为:(v0, v1), (v1, v2), (v2, v3)。
1 |
glDrawArrays(GL_LINE_STRIP, 0, 4);
|
将上述代码替换掉原来的对glDrawArrays的调用,然后查看结果。
GL_LINE_LOOP模式:
这个模式与GL_LINE_STRIP模式一样,除了最后一个顶点与第一个顶点仍然连成一条线段。比如,有4个顶点:v0, v1, v2, v3,那么构成的线段带为:(v0, v1), (v1, v2), (v2, v3), (v3, v0),共4条线段。
1 |
glDrawArrays(GL_LINE_LOOP, 0, 4);
|
请将上述代码替换掉原来对glDrawArrays的调用,观察结果。
GL_LINES模式:
该模式是对两个顶点仅绘制一条线段,并且每个顶点在且在一条线段上,由该模式下所绘制出的线段组中,不会有相交的两条线段。比如有4个顶点:v0, v1, v2, v3,那么绘制出的线段为:(v0, v1), (v2, v3);如果只有三个顶点:(v0, v1, v2),那么将只有(v0, v1)一条线段。
1 |
glDrawArrays(GL_LINES, 0, 4);
|
将上述代码替换掉原来代码中对glDrawArrays的调用,并观察结果。然后将4改为3再观察结果
GL_TRIANGLE_STRIP模式:
这个模式就是示例代码中所采用的绘制模式。它是将前三个顶点构成一个三角形后,从第四个顶点开始由先前所构成的三角形的某一条边作为公共边然后构造后一个三角形。而构造后一个三角形的绘制顺序依赖于构造第一个三角形所采用的绘制顺序。
GL_TRIANGLE_FAN模式:
这个模式在构造三角形时始终以第一个顶点作为初始顶点,然后与后两个顶点构成三角形,因此构造顺序犹如打开一把折扇。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
static struct
{
GLubyte colours[4];
GLfloat vertices[3];
}vertexInfoList[] = {
{ {0, 255, 0, 255}, {-0.5f, -0.5f, -5.0f} },
{ {255, 0, 255, 255}, {0.5f, -0.5f, -3.0f} },
{ {0, 0, 255, 255}, {0.5f, 0.5f, -3.0f} },
{ {255, 0, 0, 255}, {-0.5f, 0.5f, -5.0f} }
};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glInterleavedArrays(GL_C4UB_V3F, 0, vertexInfoList);
glCullFace(GL_BACK);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
将上述代码替换掉1楼中Mac部分代码片段中的第26到44行,观察结果。
GL_TRIANGLES模式:
该模式与GL_LINES模式类似,取三个顶点构成一个独立的三角形,并且顶点数组中每个顶点只对应于一个三角形
ü使用buffer object
缓存对象
以前顶点数据数组都保存在客户端的内存中,而有时候理想保存使用频繁的客户端数据(例如顶点数组数据)的位置是高性能的服务器内存。GL缓存对象提供了一套机制,使客户端可以分配,初始化和渲染服务器端内存。
利用缓冲区对象储存顶点数据分为以下几个步骤.
①创建缓冲区对象.
void glGenBuffers(GLsizei n, GLuint * buffers);
该函数通过buffers返回n个未被使用的缓冲区表识符(u int).
GLboolean glIsBuffers(GLuint buffer);
确定buffer是否为已经被绑定的缓冲区表识符.
②绑定缓冲区对象.
void glBindBuffer(GLenum target, GLuint buffer);
target为GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY_BUFFER之一.此函数有三种不同的行为模式.
当buffer首次使用时,就创建一个缓冲区,并绑定buffer作为其表识符.当绑定到一个以前创建的缓冲区时,这个缓冲区变成为当前活动的对象.当绑定buffer值为0时,停止使用缓冲区对象.
③使用顶点数据分配初始化缓冲区对象.
void glBufferData(GLenum target, GLsizeiptr size,const GLvoid * data,GLenum usage);
target同上,size表示分配在显存当中的储存单位个数.data是一个指向内存的指针,用于初始化缓冲区对象.usage提示数据在分配之后如何进行度取和写入,根据所指定的值OpenGL实现可能会对其进行针对性的优化,它有以下几种可能的值:
GL_STREAM_DRAW,GL_STREAM_READ,GL_STREAM_COPY,
GL_STATIC_DRAW,GL_STATIC_READ,GL_STAIC_COPY,
GL_DYNAMIC_DRAW,GL_DYNAMIC_READ,GL_DYNAMIC_COPY.
Draw-数据作为顶点数据,用于渲染.
Read-数据从一个OpenGL缓冲区(桢缓冲区之类的)读取,并在程序中与渲染并不直接相关的各种计算过程中使用.Copy-数据从一个OpenGL缓冲区读取,然后作为顶点数据,用于渲染.
Stream-缓冲区的对象需要时常更新,但使用次数很少.
Static-只需要一次指定缓冲区对象中的数据,但使用次数很多.
Dynamic-数据不仅需要时常更新,使用次数也很多.
④更新缓冲区对象内的数据
更新数据有以下两种方式:
void glBufferSubData(GLenum target,GLintptr offset,GLsizeiptr size,const GLvoid * data);
target同上.由于顶点数组数据的所有格式在缓冲区对象内同样有效.所以顶点\颜色\法线等相关数据都可以放入缓冲区中,所以需要指定一个offset作为缓冲区对象中数据的偏移量.size为起始下标.data 指向更新的数据.
另一种:
GLvoid * glMapBuffer(GLenum target,GLenum access);
target同上.access为访问数据的方式,可以为以下几个值:GL_READ_ONLY,GL_WRITE,GL_READ_WRITE.
这个函数直接获得一个指向被绑定缓冲区内数据的指针,通过给值的方式读写缓冲区对象.在读写完毕之后调用GLboolean glUnmapBuffer(GLenum target);表示被绑定的缓冲区对象更新完成,并且可以释放.
⑤清除缓冲区对象
void glDeleteBuffers(GLsizei n,const GLuint * buffers);
删除n个缓冲区对象,由buffer表识符数组指定.
操作:
1static const GLfloat g_vertex_buffer_data[] = {
2 -0.5f, -0.5f, 0.0f,
3 0.5f, -0.5f, 0.0f,
4 0.0f, 0.5f, 0.0f,
5};
6
7void myDispaly(){
8 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
9 glClear(GL_COLOR_BUFFER_BIT);
10
11 // This will identify our vertex buffer
12 GLuint vertexbuffer;
13 // Generate 1 buffer, put the resulting identifier in vertexbuffer
14 glGenBuffers(1, &vertexbuffer);
15 // The following commands will talk about our 'vertexbuffer' buffer
16 glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
17 // Give our vertices to OpenGL
18 glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
19 // 1rst attribute buffer : vertices
20 glEnableVertexAttribArray(0);
21 glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
22 glVertexAttribPointer(
23 0, // attribute 0. No particular reason for 0, but must match the layout in the shader.
24 3, // size
25 GL_FLOAT, // type
26 GL_FALSE, // normalized?
27 0, // stride
28 (void*)0 // array buffer offset
29
30 );
31
32 // Draw the triangle !
33 glDrawArrays(GL_TRIANGLES, 0, 3); // Starting from vertex 0; 3 vertices total -> 1 triangle
34
35 glDisableVertexAttribArray(0);
36 glutSwapBuffers();
37
38}
glDrawArrays,glDrawElements - [OpenGL]