转载自:
OpenGL学习笔记(六)http://blog.csdn.net/afei198409/article/details/8267300
http://blog.csdn.net/afei198409/article/details/8267300
原文排版比较乱,我进行了整理以便阅读
缓冲区对象
许多 OpenGL 操作都向 OpenGL 发送一大块数据,例如向它传递需要处理的顶点数组数据。传输这种数据可能非常简单,例如把数据从系统的内存中复制到图形卡。但是,由于 OpenGL 是按照客户机-服务器模式设计的,在 OpenGL 需要数据的任何时候,都必须把数据从客户机内存传输到服务器。如果数据并没有修改,或者客户机和服务器位于不同的计算机(分布式渲染),数据的传输可能会比较缓慢,或者是冗余的。
OpenGL 1.5 版本增加了缓冲区对象(buffer object),允许应用程序地指定把哪些数据存储在图形服务器中。
当前版本的 OpenGL 使用了很多不同类型的缓冲区对象:
- 从 OpenGL 1.5 开始,数组中的顶点数据可以存储在服务器端缓冲区对象中。
- 在 OpenGL 2.1 中,加入了在缓冲区对象中存储像素数据(例如,纹理贴图或像素块(PBO))的支持。
- OpenGL 3.1 增加了统一缓冲对象(uniform buffer object,UBO)以存储成块的、用于着色器的统一变量数据。
一、创建缓冲区对象
任何非零的无符号整数都可以作为缓冲区对象的标识符使用。可以任意选择一个有代表性的值,也可以让 OpenGL 负责分配和管理这些标识符。两种做法区别是:让OpenGL分配标识符可以保证避免重复使用已被使用的缓冲区对象标识符,从而消除无意修改数据的风险。
为了让 OpenGL 分配缓冲区对象标识符可调用下面函数:
void glGenBuffers(GLsizei n,GLuint *buffers);
在 buffers 数组中返回 n 个当前未使用的缓冲区对象标识。注意在 buffers 数组中返回的标识名称并不需要是连续的整数。0 是一个保留的缓冲区对象名称,从来不会被 glGenBuffers() 作为缓冲区对象标识返回。
判断一个标识符是否是一个当前被使用的缓冲区对象标识符则调用:
GLboolean glIsBuffer(GLuint buffer);
如果 buffer 是一个已经绑定的缓冲区对象的名称,而且还没有删除则返回 GL_TRUE ,否则返回GL_FALSE 。
二、激活缓冲区对象
为了激活缓冲区对象首先需要将它绑定。绑定缓冲区对象表示选择未来的操作将影响哪个缓冲区对象。如果应用程序有多个缓冲区对象,就需要多次调用 glBindBuffer() 函数:一次用于初始化缓冲区对象以及它的数据,以后的调用要么选择用于渲染的缓冲区对象,要么对缓冲区对象的数据进行更新。
为了禁用缓冲区对象,可以用0作为缓冲区对象的标识符来调用 glBindBuffer() 函数。这将把OpenGL 切换为默认的不使用缓冲区对象的模式。
void glBindBuffer(GLenum target,GLuint buffer);
指定了当前的活动缓冲区对象。参数 target 必须设置为
- GL_ARRAY_BUFFER
- GL_ELEMENT_ARRAY_BUFFER
- GL_PIXEL_PACK_BUFFER、
- GL_PIXEL_UNPACK_BUFFER
- GL_COPY_READ_BUFFER
- GL_COPY_WRITE_BUFFER
- GL_TRANSFORM_FEEDBACK_BUFFER
- GL_UNIFORM_BUFFER
参数 buffer 指定了将要绑定的缓冲区对象。
glBindBuffer()完成 3 个任务之一:
- 当 buffer 是一个首次使用的非零无符号整数时,它就创建一个新的缓冲区对象,并把buffer分配给这个缓冲区对象,作为它的名称
- 当绑定到一个以前创建的缓冲区对象时,这个缓冲区对象便成为活动的缓冲区对象
- 当绑定到一个值为零的 buffer 时,OpenGL 就会停止使用缓冲区对象
三、用数据分配和初始化缓冲区对象
一旦绑定了一个缓冲区对象,就需要在服务器端分配存储空间。
void glBufferData(GLenum target,GLsizeiptr size,const GLvoid *data,GLenum usage);
分配 size 个存储单位(通常是字节)的 OpenGL 服务器内存,用于存储顶点数据或索引。以前所有与当前绑定对象相关联的数据都将删除。
参数 target 必须为
- GL_ARRAY_BUFFER (表示顶点数据)
- GL_ELEMENT_ARRAY_BUFFER (表示索引数据)
- GL_PIXEL_PACK_BUFFER(表示传递给OpenGL像素数据)
- GL_PIXEL_UNPACK_BUFFER(表示从OpenGL获取的像素数据)
- GL_COPY_READ_BUFFER 或 GL_COPY_WRITE_BUFFER(表示在缓冲区之间复制数据)
- GL_TEXTURE_BUFFER(表示作为纹理缓冲区存储的纹理数据)
- GL_TRANSFORM_FEEDBACK_BUFFER(表示执行一个变换反馈着色器的结果)
- GL_UNIFORM_BUFFER(表示统一变量值)
参数 size 是存储相关数据所需要的内存数量。该值通常是数据元素的个乘以它们各自的存储长度。
参数 data 是一个指向客户机内存的指针(用于初始化缓冲区对象),也可以是 NULL 。如果它传递的是一个有效的指针,size 个单位的存储空间就从客户机复制到服务器。如果传递的是 NULL ,这个函数将会保留 size 个单位的存储空间供以后使用,但不会对它进行初始化。
参数 usage 提供了一个提示,就是数据在分配之后将如何进行读取和写入。它有效值包括
- GL_STREAM_DRAW(流模式)
- GL_STREAM_READ(流模式)
- GL_STREAM_COPY(流模式)
- GL_STATIC_DRAW(静态模式)
- GL_STATIC_READ(静态模式)
- GL_STATIC_COPY(静态模式)
- GL_DYNAMIC_DRAW(动态模式)
- GL_DYNAMIC_READ(动态模式)
- GL_DYNAMIC_COPY(动态模式)
数据的读写模式
- 流模式:缓冲区对象中的数据只被指定一次,并且使用这些数据的频率较低【原作者理解有误,特做修正】
- 静态模式:缓冲区对象的数据只指定一次,但是使用这些数据的频率很高
- 动态模式:缓冲区对象的数据不仅常常需要进行更新,而且使用频率也非常高
注意,如果请求分配的内存数量超过了服务器能够分配的内存,glBufferData() 将返回 GL_OUT_OF_MEMORY 错误。如果 usage 并不是允许使用的值之一,则返回 GL_INVALID_VALUE 。
四、更新缓冲区对象的数据
有两种方法可以更新存储在缓冲区对象中的数据
方法一 :用提供的数据替换被绑定的缓冲区对象的一些数据子集
void glBufferSubData(GLenum target,GLintptr ofsset,GLsizeiptr size,const GLvoid *data);
用 data 指向的数据更新与 target 相关联的当前绑定缓冲区对象中从 offset (以字节为单位)开始的size 个字节数据。target 参数与 glBufferData 的 target 参数一致。
注意,如果 size 小于 0 或者 size+offset 大于缓冲区对象创建时所指定的大小,glBufferSubData() 将产生一个 GL_INVALID_VALUE 错误。
方法二:选择绑定的缓冲区对象,然后根据需要来写入新值(或简单地读取数据,这取决于内存的访问权限),就像对数组进行赋值一样
GLvoid * glMapBuffer(GLenum target,GLenum access);
返回一个指针,指向与 target 相关联的当前绑定缓冲区对象的数据存储。target 参数与 glBufferData的target 参数一致。参数 access 必须是 GL_READ_ONLY、GL_WRITE_ONLY 或GL_READ_WRITE 之一,表示客户可以对数据进行的操作。
注意,如果需要修改缓冲区中的大多数数据,这种方法很有用,但如果有一个很大的缓冲区并且只需要更新很小的一部分值,这种方法效率就很低。这时使用 glMapBufferRange() 效率更高。它允许只修改所需的范围内的数据值。
GLvoid * glMapBufferRange(GLenum target,GLintptr offset,GLsizeiptr length,GLbitfield access);
完成缓冲区对象的数据更新之后,可以调用 glUnmapBuffer() 取消对这个缓冲区的映射:
GLboolean glUnmapBuffer(GLenum target);
表示对当前绑定缓冲区对象的更新已经完成,并且这个缓冲区可以释放。
五、在缓冲区对象之间复制数据
如何把数据一个缓冲区对象复制到另一个缓冲区对象呢?
1)在 OpenGL 3.1 以前版本中,这个过程分两步:
- 把数据从缓冲区对象复制到应用程序的内存中
- 绑定到新的缓冲区对象,然后更新该缓冲区对象的数据。
2)在 OpenGL 3.1 中
glCopyBufferSubData() 用于复制数据,而不需要迫使数据在应用程序的内存中做短暂停留。
void glCopyBufferSubData(GLenum readbuffer,GLenum writebuffer,GLintptr readoffset,GLintptr writeoffset,GLsizeiptr size);
把数据从与 readbuffer 相关联的缓冲区对象复制到绑定到 writebuffer 的缓冲区对象。参数 readbuffer 和 writebuffer 与 glBufferData的target 参数一致。readoffset、writeoffset 为偏移量,size 为复制到数据的数量。
六、清除缓冲区对象
完成了对缓冲区对象的操作之后,可以释放它的资源,并使它的标识可以其他缓冲区对象使用。
void glDeleteBuffers(GLsizei n,const GLuint *buffers);
删除 n 个缓冲区对象,它们的标识名称就是 buffers 数组的元素。
注意,如果试图删除不存在的缓冲区对象或标识为 0 的缓冲区对象,该操作将被忽略,并不会产生错误。
七、示例
使用缓冲区对象存储顶点数组数据,并绘制
#define BUFFER_OFFSET(bytes) ((GLubyte *)NULL + (bytes))
GLuint buffers[2];
GLfloat vertices[][3] = { //包含顶点数据
{-1.0, -1.0, -1.0},
{1.0, -1.0, -1.0},
{1.0, 1.0, -1.0},
{-1.0, 1.0, -1.0},
{-1.0, -1.0, 1.0},
{1.0, -1.0, 1.0},
{1.0, 1.0, 1.0},
{-1.0, 1.0, 1.0}
};
GLbyte indices[][4] = { //包含索引数据
{0, 1, 2, 3},
{4, 7, 6, 5},
{3, 2, 6, 7},
{0, 3, 7, 4},
{1, 5, 6, 2}
};
glGenBuffers(2, buffers); //生成缓冲区对象标识符
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); //绑定顶点缓冲区对象
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //请求数据的存储空间并用指定数据进行初始化
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); //绑定索引缓冲区对象
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glEnableClientState(GL_VERTEX_ARRAY); //启用顶点数组
glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0)); //指定顶点数组数据
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0)); //根据索引绘图(注意:顶点数据和索引各自使用不同的缓冲区)
若没有使用缓冲区对象,则上面这段代码传进的偏移量
glVertexPointer(3,GL_FLOAT,0,BUFFER_OFFSET(0));
glDrawElements(GL_QUADS,24,GL_UNSIGNED_BYTE,BUFFER_OFFSET(0));
应该是这样改成实际的客户端数据指针
glVertexPointer(3,GL_FLOAT,0, vertices);
glDrawElements(GL_QUADS,24, indices);
使用缓冲区对象后,类似 glVertexPointer() 这种以指针为参数的 OpenGL 函数不再从指针所指位置取数据,函数会先把指针转化为整数,假设转化后的结果为 k ,则会从当前缓冲区的第 k 个字节开始取数据 (NULL 转化为整数后通常是 0 ,即缓冲区最开始的位置)。