缓冲区对象是一个强大的概念,它允许应用程序迅速方便地将数据从一个渲染管线移动到另一个渲染管线,以及从一个对象绑定到另一个对象。
缓冲区:缓冲区有很多不同的用途,它们能够保存顶点数据、像素数据、纹理数据、着色器处理的输入,或者不同阶段着色器阶段的输出。
缓冲区保存在GPU内存中,它们提供高速和高效的访问。在OpenGL有缓冲区对象之前,应用程序只有有限的选择可以在GPU中存储数据。另外,在GPU中更新数据常常需要重新加载整个对象。在系统内存和GPU内存之前来回移动数据可能是一个缓慢的过程。
创建缓冲区
我们可以使用glGenBuffers来为我们所需的任何数量的新缓冲区创建名称。实际缓冲区对象将在第一次使用时创建。
Gluint pixBufferObj[1];
glGenBuffers(1,pixBufferObjs);
当我们有了新的缓冲区名称,就可以对这些名称进行绑定来使用缓冲区。在OpenGL中有许多不同的绑定点,每个绑定点都允许我们为了不同的目的而使用某个缓冲区。我们可以将每个结合点或绑定点看作一个在同一时刻只能结合一个对象的槽。
名称 | 描述 |
GL_ARRAY_BUFFER | 数组缓冲区存储颜色、位置、纹理等顶点属性,或者其他自定义属性 |
GL_COPY_READ_BUFFER | 缓冲区用作通过glCopyBufferSubData进行复制的数据源 |
GL_COPY_WRITE_BUFFER | 缓冲区用作通过glCopyBufferSubData进行复制的目标 |
GL_ELEMENT_ARRAY_BUFFER | 索引缓冲区用于保存glDrawElements、glDrawRangeElements和glDrawElementsInstanced的索引 |
GL_PIXEL_PACK_BUFFER | glReadPixels之类像素包装操作的目标缓冲区 |
GL_PIXEL_UNPACK_BUFFER | glTexImage1D、glTexImage2D、glTexImage3D、glTexSubImage1D、glTexSubImage2D和glTexSubImage3D之类纹理更新函数的缓冲区 |
GL_TEXTURE_BUFFER | 着色器可以通过纹理单元拾取来访问的缓冲区 |
GL_TRANSFORM_FEEDBACK_BUFFER | 变换反馈顶点着色器(transform feedback vertex shader)写入的缓冲区 |
GL_UNIFORM_BUFFER | 着色器能够访问的UNiform值 |
要绑定一个缓冲区以备用,我们可以以这个缓冲区名称为参数,以上表列出的缓冲区为目标来调用glBindBuffer。我们将新的缓冲区绑定到像素包装缓冲区绑定点之后,就可以使用glReadPixels将像素数据复制到缓冲区中了。
glBindBuffer(GL_PIXEL_PACK_BUFFER,pixBuffer[0]);
要从一个绑定中对一个缓冲解除绑定,可以再次调用以0为缓冲区名称、目标与上述调用相同的glBuffer。我们还可以只是将另外一个合法的缓冲区绑定到同一个目标上。
当我们使用完一个缓冲区之后,这个缓冲区需要进行清除,在进行删除之前,我们要确保缓冲区没有被绑定到任何绑定点。
glDeleteBuffer(1,pixBufferObjs);
填充缓冲区
使用glBufferData函数来简单地将数据直接上传到任何类型的缓冲区中。
glBindBuffer(GL_PIXEL_PACK_BUFFER,pixBufferObjs[0]);
glBufferData(GL_PIXEL_PACK_BUFFER,0);
在调用glBufferData之前,我们必须将要使用的缓冲区进行绑定。对glBufferData使用的目标与我们为第一个参数绑定缓冲区时使用的目标相同。第二个参数是我们将要上传的数据大小,以字节(byte)为单位,而第三个参数则是将要被上传的数据本身。
如果我们想要分配一个特定大小的缓冲区却不需要立即对它进行填充,那么这个指针也可能是NULL。glBufferData的第4个参数用来告诉OpenGL我们打算如何使用缓冲区。
缓冲区使用方式 | 描述 |
GL_STREAM_DRAW | 缓冲区的内容将由应用程序进行一次设置,并且经常用于绘制 |
GL_STREAM_READ | 缓冲区的内容将作为一条OpenGL命令的输出来进行一次设置,并且经常用于绘制 |
GL_STREAM_COPY | 缓冲区的内容将作为一条OpenGL命令的输出来进行一次设置,并且不经常用于绘制或复制到其他图像 |
GL_STATIC_DRAW | 缓冲区的内容将有应用程序进行一次设置,并且经常用于绘制或复制到其他图像 |
GL_STATIC_READ | 缓冲区的内容将作为一条OpenGL命令的输出来进行一次设置,并且由应用程序进行多次查询 |
GL_STATIC_COPY | 缓冲区的内容将作为一条OpenGL命令的输出来进行一次设置,并且经常用于绘制或复制到其他图像 |
GL_DYNAMIC_DRAW | 缓冲区的内容将会经常由应用程序进行更新,并且经常用于绘制或复制到其他图像 |
GL_DYNAMIC_READ | 缓冲区的内容将作为OpenGL命令经常进行更新,并且由应用程序进行多次查询 |
GL_DYNAMIC_COPY | 缓冲区的内容将作为OpenGL命令的输出来进行更新,并且经常用于绘制或复制到其他图像 |
在我们不确定缓冲区的用途时,对于通常的缓冲区使用方式或条件来说,使用GL_DYNAMIC_DRAW是一个比较安全的值。我们总是可以再次调用glBufferData对缓冲区重新进行填充,还可以改变使用方式的提示。但是如果我们真的调用了lgBufferData,那么缓冲区中原来的所有数据都将被删除。
可以使用glBufferSubData对已经存在的缓冲区中的一部分进行更新,而不会导致缓冲区其他部分的内容变为无效。
void glBufferSubData(GLenum target,intptr offset,sizeiptr,const void *data);
glBufferSubData的大多爱护参数和glBufferData的相应参数相同。新的offset参数允许我们从除开头部分以外的其他位置开始更新。我们也不能改变缓冲区的使用方式,因为内存已经被分配了。
像素缓冲区对象(PBO)
在存储像素/纹理单元方面,像素缓冲区对象与纹理缓冲区对象非常相似,并且它们也都存储在GPU内存中。可以使用与其他缓冲区对象类型一样的方法访问和填充像素缓冲区对象。实际上,只有在绑定到一个PBO缓冲区绑定点时,一个缓冲区才真正成为一个像素缓冲区。
第一个像素缓冲区对象绑定点时GL_PIXEL_PACK_BUFFER。当一个像素缓冲区对象被绑定到这个目标上是,任何读取像素的OpenGL操作都会从像素缓冲区对象中获得它们的数据,这些擦偶哦在包括glReadPixels、glGetTexImage和glGetCompressedTexImage。通常这些擦欧总会从一个帧缓冲区或纹理中抽取数据,并将它们都会客户端内存中。当一个像素缓冲区对象被绑定到包装缓冲区时,像素数据在GPU内存中的像素缓冲区对象中的任务就结束了,而不会下载到客户端。
第二个PBO绑定点时GL_PIXEL_UNPACK_BUFFER。当一个像素缓冲区对象被绑定到这个目标时。任何绘制像素的OpenGL操作都会向一个绑定的像素缓冲区对象中放入它们的数据。
对初学者来说,任何从PBO中读取或写入PBO中的调用或任何缓冲区对象都用管线进行处理。这就意味着GPU不需要完成所有其他工作,之哟啊对数据复制进行初始化,等待复制完成,然后继续运行就可以了。
像素缓冲区对象是一个很好的容器,可以暂时存储GPU本地像素数据,但是需要注意,在使用它们之前需要为它们分配存储空间。和其他所有缓冲区对象一样,要调用glBufferData为这个缓冲区分配内存空间并用数据填充。但是没必要提供数据,为数据指针传递NULL可以简单地分配内存而不进行填充,如果我们试图填充存储空间之前不对它进行分配,那么OpenGL将抛出一个错误。
像素缓冲区经常用来存储来自一个渲染目标的2D图像、纹理或其他数据源。但是缓冲区对象本身是一维的,它们本质上没有宽度或者高度。在为2D图像分配存储空间时,我们可以只是将宽度与高度相乘,再与像素大小相乘。对于存储像素数据没有其他需要补充的;,但是缓冲区可以比戈丁的数据组所需的大小更大。
当我们打算为多种数据大小使相同的PBO,最好马上关闭对数据大小上限的设定,而不是频繁地对它进行重新设定。
纹理缓冲区对象
一个纹理包含两个主要组成成分:纹理采样状态和包含纹理值的数据缓冲区。纹理缓冲区也称为texBO或TBO,允许我们完成一些传统纹理不能完成的工作。首先,纹理缓冲区能够直接填充来自其他渲染结果(例如变换反馈、像素读取操作或顶点数据)的数据。这样就节省了不少时间,因为应用程序能够直接从着色器以前的渲染调用中获取像素数据。texBO的另一个特性是宽松的大小限制。纹理缓冲区与传统的一维纹理相似,但要更大。TexBO向着色器提供了大量对多种不同风格和类型的数据访问,允许着色器以通常是预留给CPU的方式进行操作。纹理缓冲区能够用来提供对片段着色器和顶点着色器中的顶点数组的访问。这在着色需要关于临近几何图形的信息以作出运行时决策和计算的情况下可能会非常有用。但是为了做到这一点,我们通常需要将texBO大小作为一个统一值传递到着色器中。
纹理缓冲区是作为普通的缓冲区来创建的,当它被绑定到一个纹理或者GL_TEXTURE_BUFFER绑定点时才会成为真正的纹理缓冲区。
glBindBuffer(gl_texture_buffer,texBo[0]);
glBufferData(GL_TEXTURE_BUFFER,xizeof(float)*count,fileData,GL_STATIC_DRAW);
但是texBO必须绑定到一个纹理单元上才能真正变得有用。要将一个texBO绑定到一个纹理上,可以调用glBuffer,但首先要确保要使用的纹理已经进行了绑定。
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_BUFFER,texBOTexture);
glTexBuffer(GL_TEXTURE_BUFFER,GL_R32F,texBO[0]);
纹理缓冲区对象操作与普通纹理看似很像,但还有一些重要的不同点。纹理缓冲区不能在着色器中用普通的采样器-也就是sampler1D和sampler2D进行访问。取而代之的是,我们必须用一个新的采样器-samplerBuffer。由于采样器类型不同,用来从纹理缓冲区中获取值的采样函数也不同。
帧缓冲对象
一个OpenGL窗口的表面长期以来一直被称作“帧缓冲区”。但是现在OPenGL将绘制缓存区到一个对象所需要的状态进行了封装,称为帧缓存区对象(FBO)。默认的帧缓冲区对象是与创建的OpenGL窗口相关联的,并且在一个新的环境被绑定时自动进行绑定。可以创建多个帧缓冲区对象,也叫做FBO,并且直接渲染一个FBO而不是窗口。使用这种离屏渲染,应用程序就可以执行许多不同种类的渲染算法了,例如阴影贴图、应用辐射着色,反射、后期处理和许多其他特效。另外,帧缓冲区对象并不受窗口大小的限制,它可以包含多个颜色缓冲区。甚至可以将纹理绑定到一个FBO上,这就意味着可以直接渲染到一个纹理中。
帧缓冲区根本不是缓冲区,并不存在与一个帧缓冲区对象相关联的真正内存存储空间。相反,帧缓冲区对象是一种容器,它可以保存其他确实有内存存储并且可以进行渲染的对象,例如纹理或渲染缓冲区。采用这种方式,帧缓冲区对象能够在保存OpenGL管线的输出时将需要的状态和表面绑定到一起。
使用FBO
先添加图像,才能渲染到一个FBO。一旦一个FBO被创建、设置和绑定,大多数OpenGL操作就将像是在渲染到一个窗口一样执行,但是输出结果将存储在绑定到FBO的图像中。
要先生成FBO缓冲区名称(可以同时生成任意数量的名称),才创建FBO。再绑定一个新的FBO来修改和使用它
GLuint fboName;
glGenFramebuffers(1,&fboName);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,fboName);
同一时间只有一个FBO可以绑定用来进行绘制,并且同一时间只有一个FBO可以绑定来进行读取。在绑定一个帧缓冲区时,glBindFramebuffer的第一个参数既可以是GL_DRAW_FRAMEBUFFER,也可以是GL_READ_FRAMEBUFFER。这就意味着我们可以使用一个帧缓冲区进行读取,而使用另一个不同帧缓冲区进行绘制。
使用完FBO或者在退出前进行清除时,要删除FBO
glDeleteFramebuffer(1,&fboName);