
第八章缓冲区对象的内容
这一章中主要介绍了OpenGL中的缓冲区的使用方法以及相应的好处。
1.使用PBO(像素缓冲区)
PBO的作用就是将像素信息存放在了GPU的相应的缓冲区之中。与原来相比这样做的好处在于可以通过DMA的方式来完成对像素数据的传输, 而这是一种异步传输的方式, 意味着CPU在调用完成相关的函数之后并不需要等待就可以得到相应的函数的返回结果。因此通过这种方式可以加快对图像的渲染速度。
使用PBO的方式:
(1). 通过glGenBuffers来完成对缓冲区对象的建立, 函数原型:
void glGenBuffers(GLsizei n, GLuint * buffers);
其中n为要建立的缓冲区的数量, 而buffers指定了一个存储生成的缓冲区的名字的数组。
(2). 通过glBindBuffer将建立的缓冲区对象与对应的绑定点绑定起来。函数原型: void glBindBuffer(GLenum target, GLuint buffer);
如果想要建立一个像素缓冲区, 这里只需把target指定为GL_PIXEL_PACK_BUFFER或者GL_PIXEL_UNPAKC_BUFFER
如果我们将buffer指定为0, 那么会解除与target对应的缓冲区的绑定。
(3). 通过glBufferData来完成对缓冲区中的填充。函数原型:
void glBufferData(GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage);
其中target与想要传输数据的缓冲区绑定时的target相同
size用来指定希望上传的数据的大小, 单位是字节
data是要上传的数据本身, 这里注意的是我们可以将函数调用中的data设成为NULL, 这样就只会为缓冲区分配相应大小的空间而并不会将数据填充。
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
通常来说使用GL_DYNAMIC_DRAW是安全的方式
除此之外,如果对使用glBufferData填充后的缓冲区再次调用glBufferData进行填充会将其中的所有的数据全部删除。但是我们可以调用glBufferSubData来完成对其中的部分的数据替换。
(4) .通过使用glDeleteBuffers的方法来对已建立的缓冲区对象进行删除,函数原型: void glDeleteBuffers(GLsizei n, GLuint *buffers);
下面对绑定了GL_PIXEL_PACK_BUFFER和GL_PIXEL_UNPACK_BUFFER这两个绑定点的对象进行说明:
绑定到GL_PIXEL_PACK_BUFFER上的缓冲区对象, 任何读取像素的操作都会将数据保存在这个缓冲区对象之中。比如glReadPixels、glGetTexImage、glGetCompressedTexImage操作。如果GL_PIXEL_PACK_BUFFER没有缓冲区对象与其绑定, 那么上述的操作应该是将结果存放在客户端的内存之中, 而在绑定之后便不会将数据再送入内存之中。
绑定到GL_PIXEL_UNPACK_BUFFER上的缓冲区对象, 任何的绘制图像的操作都会从这个缓冲区中读取数据。比如glTexImageXD, glTexSubImageXD, glCompressedTexImageXD, glCompressedTexSubImageXD。
使用PBO完成运动模糊的效果的实例:
原理就是将当前运动的图像保存到PBO中并且通过调用glTexImage2D来完成将图像加载到纹理单元中的操作。之后在着色器中将多个帧的图像叠加在一起来制造出模糊运动的效果。可以看到使用PBO方法和原始的内存存储的方式的效果:



可以看出图片上有着一定的差别, 而且使用PBO的情况下的帧数是原始使用内存完成同样处理的帧率的2倍左右。因此可以看到使用PBO方式的好处。
2.使用TBO(纹理缓冲区)
纹理由纹理采样状态和包含纹理值的数据缓冲区构成。纹理缓冲区中可以直接填充来自其它的渲染结果的数据, 从而可以节省一定的时间; 纹理缓冲区的大小限制很宽松, 并且我们可以通过对GL_MAX_TEXTURE_BUFFER_SIZE参数的查询来看到纹理缓冲区被允许的最大的大小。但是, 纹理缓冲区无法生成Mip贴图, 而且无法使用任何的过滤器。
使用纹理缓冲区的方法:其中生成使用的函数仍然是glGenBuffers, 绑定时仍然使用glBindBuffer函数进行, 但是绑定点选择的是GL_TEXTURE_BUFFER, 对纹理缓冲区中的数据的载入使用glBufferData来完成。删除纹理缓冲区的操作仍然是使用glDeleteBuffers函数完成。由此可见缓冲区的使用步骤存在很大的相似性, 而不同的地方在于缓冲区的绑定点, 不同的绑定点使对缓冲区的操作的函数发生改变。而我们只需将缓冲区视为一个在GPU中的存储数据的空间, 并且并不存在彼此间的类型的差别。
在使用纹理缓冲区时, 着色器中的相应的采样器不能使用sampler2D, sampler1D来完成, 而是要使用samplerBuffer来完成采样。同样的不能使用texture函数来完成读取, 而是要使用texelFetch函数来完成。
3.使用FBO和RBO(帧缓冲区和渲染缓冲区)
首先应该知道的是FBO并不是一个真正的缓冲区, 我们应该将FBO视为一个容器, 一个用来承载其它的图像的容器。使用FBO的方法与之前的缓冲区有些不同, 我们使用glGenFramebuffers来完成对帧缓冲区对象的生成, 使用glBindFramebuffer来完成对帧缓冲区对象与相应绑定点的绑定。这个函数的绑定点有GL_READ_FRAMEBUFFER和GL_DRAW_FRAMEBUFFER, 其中使用绘制函数对帧缓冲区进行绘制的操作都会将目标转至绑定到GL_DRAW_FRAMEBUFFER的帧缓冲区中, 默认情况下是与屏幕相关的帧缓冲区与GL_DRAW_FRAMEBUFFER绑定, 并且我们可以使用glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)来将绑定切换至默认的帧缓冲区。同样的绑定到GL_READ_FRAMEBUFFER上的帧缓冲区会使所有的读取帧缓冲区的内容的函数会以其为目标, 可以使用glBindFramebuffer(GL_READ_FRAMEBUFFER, 0)来使其切换至默认的帧缓冲区, 即与屏幕相关的帧缓冲区。使用glDeleteFramebuffes来完成对帧缓冲区对象的删除。因为帧缓冲区并不会为其分配GPU的存储空间, 因此我们不需要为帧缓冲区分配空间。
因为FBO只是一个承载图像的容器, 因此如果希望使用FBO, 就要向其中添加其它的图像。这里我们使用RBO来创建图像, 一个RBO可以是一个颜色表面, 一个模板表面, 一个深度表面, 或者是一个模板/深度表面。创建RBO的方法是glGenRenderbuffers, 用法与之前类似的函数的用法一致。绑定RBO的方法是glBindRenderbuffer, 唯一的合法绑定点是GL_RENDERBUFFER。而对RBO的大小的分配则是使用glRenderbufferStorage函数来完成的, 函数原型如下:
void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
其中target选择GL_RENDERBUFFER; internalformat指的是缓冲区中的存储的数据格式; width指的是缓冲区的宽度; height指的是缓冲区的高度。
在创建完成RBO之后, 为了使用我们需要将RBO与FBO关联起来, 这是通过glFramebufferRenderbuffer的方式来实现, 函数原型:
void glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
其中target指的是我们希望绑定的帧缓冲区, 这里通过指示绑定点来完成与对应的绑定点上的帧缓冲区绑定。比如如果我们将target参数设置为GL_DRAW_FRAMEBUFFER, 那么便会将RBO与当前绑定到GL_DRAW_FRAMEBUFFER上的帧缓冲区绑定在一起。
其中attachment参数用来指定我们希望将RBO绑定到FBO中的哪个位置上。一个FBO中有一个深度绑定点、一个模板绑定点、多个颜色绑定点来供我们使用。
renderbuffertarget参数始终是GL_RENDERBUFFER。
renderbuffer参数用来指定希望绑定到的RBO。
为了将颜色输出到多个缓冲区中, 在着色器中我们需要完成多重颜色输出的设置。在片段着色器中我们可以通过使用gl_FragData[n]来将颜色输出至第n个绑定点对应的RBO之中。而绑定点的顺序可以通过glDrawBuffers函数来完成设置。而如果使用的是默认的帧缓冲区, 或者着色器中采用的内建输出变量是gl_FragColor, 那么传递到glDrawBuffers中的所有的缓冲区的值都会是一致的。
使用glBlitFramebuffer函数可以实现将与GL_READ_FRAMEBUFFER绑定的缓冲区中数据和与GL_DRAW_FRAMEBUFFER绑定的缓冲区中的数据之间的交换。函数原型如下(图取自《OpenGL超级宝典》第五版 P254):

使用RBO来完成绘制的方式称为离屏渲染, 除此之外,我们可以直接渲染到纹理单元之中。使用这种方式我们需要将纹理单元与对应的帧缓冲区对象绑定在一起, 使用glFramebufferTextureXD可以实现, 函数原型如图所示(图取自《OpenGL超级宝典》 第五版 P255):

书中有一个关于使用RBO, FBO来完成镜面效果的实例, 原理是在镜面角度来观察并且将其中的图像保存在一个与帧缓冲区绑定的纹理单元之中。之后再利用这个纹理单元来对镜面进行渲染, 从而实现反射的效果。可以看到实现的效果如图所示:

对帧缓冲区的完整性的定义(图取自《OpenGL超级宝典》第五版 P246):
满足如下的条件中的一个的缓冲区是不完整的:
绑定完整性的要求:

帧缓冲区整体完整性的要求:

可以通过使用glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER)来对与GL_DRAW_FRAMEBUFFER绑定的帧缓冲区的完整性进行检查。这个函数会返回一个GLenum类型的值用来检测是否完整。
