【OpenGL】蓝宝书第八章——缓冲区对象:存储尽在掌握

前言

缓冲区对象概念允许应用程序方便地将数据从一个渲染管线移动到另一个渲染管线,以及从一个对象绑定到另一个对象,它可以在无需CPU介入情况下完成。帧缓冲区对象能真正让我们控制像素,而不受操作系统环境影响,例如离屏渲染等技巧。可以说片段着色器对哪些像素去哪里有了最终的控制权。

缓冲区

缓冲区能够保存顶点数据、像素数据、纹理数据、着色器处理的输入或不同着色器阶段的输出等。

缓冲区保存在GPU内存中,它们提供高速和高效的访问。在OpenGL有缓冲区对象之前,应用程序只有有限的选择可以在GPU中存储数据。在GPU中更新数据常常需要重新加载整个对象。在系统内存和GPU内存之间来回移动数据可能是一个缓慢的过程。

创建自己的缓冲区

1、创建一个缓冲区名字

GLuint pixBuffObjs[1];
glGenBuffers(1, pixBuffObjs);

2、缓冲区进行捆绑缓冲区对象捆绑点

名   称描   述
GL_ARRAY_BUFFER数组缓冲区存储颜色、位置、纹理坐标等顶点属性,或者其他自定义属性
GL_COPY_READ_BUFFER缓冲区用作通过glCopyBufferSubData进行复制的数据源
GL_COPY_WRITE_BUFFER缓冲区用作通过glCopyBufferSubData进行复制的目标
GL_ELEMENT_ARRAY_BUFFER索引数组缓冲区用于保存glDrawElements、glDrawRangeElements和glDrawElementsInstanced的索引
GL_PIXEL_PACK_BUFFERglReadPixels之类像素包装操作的目标缓冲区
GL_PIXEL_UNPACK_BUFFERglTexImage1D、glTexImage2D、glTexImage3D、glTexSubImage1D、glTexSubImage2D、glTexSubImage3D之类纹理更新函数的源缓冲区
GL_TEXTURE_BUFFER着色器可以通过纹理单元拾取来访问的缓冲区
GL_TRANSFORM_FEEDBACK_BUFFER变换反馈顶点着色器(transform feedback vertex shader)写入的缓冲区
GL_UNIFORM_BUFFER着色器能够访问的Uniform值

glBindBuffer(GL_PIXEL_PACK_BUFFER, pixBuffObjs[0]); //捆绑GL_PIXEL_PACK_BUFFER,它将作为glReadPixels函数读取到的像素进行存储的目标缓冲区。(即将读取出的内容存储到这个缓冲区)

glBindBuffer可以捆绑多个缓冲区到一个目标上,例如你有另一个缓冲区,也可以捆绑到GL_PIXEL_PACK_BUFFER。

glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); //再次调用它会解除捆绑,接着才能删除缓冲区
glDeleteBuffers(1, pixBuffObjs); //删除缓冲区

填充缓冲区

使用glBufferData方法可直接填充到缓冲区上。

glBindBuffer(GL_PIXEL_PACK_BUFFER, pixBuffObjs[0]);
glBufferData(GL_PIXEL_PACK_BUFFER, pixelDataSize, pixelData, GL_DYNAMIC_COPY);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

pixelDataSize是字节数,pixelData是字节数据(它也可以是NULL,然后通过其他方式填充),第四个参数是缓冲区对象使用方式

缓冲区使用方式描   述
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命令的输出来经常进行更新,并且经常用于绘制或复制到其他图像

看上表简单来理解就是分为三种形式的DRAW、READ、COPY,其中STREAM形式的可能有些许不同,但STATIC和DYNAMIC是相同的,即DRAW是从应用拷贝数据到缓冲区并用于绘制的;READ是从着色器流程输出到缓冲区,缓冲区是专门提供给应用程序读取的;COPY是着色器流程输出到缓冲区,缓冲区用于绘制或复制到其他图像的。其中STATIC是只进行一次填充缓冲区(即只更新一次),DYNAMIC是动态更新的(即一直更新),STREAM类似但READ和COPY有点区别,READ是着色器输出填充一次且用于绘制,COPY是着色器输出填充一次且不用于绘制和复制(那用来干嘛?是不是翻译错了)

在无法确定缓冲区用途时,可用GL_DYNAMIC_DRAW比较合适。

在二次使用glBufferData对同一个缓冲区进行设置新的形式时会将原始数据删除,若想仅仅更新一部分数据(但无法更新使用方式),可用如下方法:

void glBufferSubData(GLenum target, intptr offset, sizeiptr size, const void *data);

offset是填充开始位置偏移量,注意它是一个一维数组而已,size是字节数,data是数据,target是捆绑点类型(即glBufferData第一个参数一样)

注意:因为使用方式是与内存分配有关的,此时只是更新数据,所以并不允许更新部分数据同时还去更新它的使用方式。

像素缓冲区对象

在存储像素/纹理单元方面,像素缓冲区对象与纹理缓冲区对象非常相似。PBO是像素缓冲区对象缩写。只有捆绑到一个PBO缓冲区绑定点时才能成为真正的一个像素缓冲区对象。

第一个像素缓冲区对象绑定点是GL_PIXEL_PACK_BUFFER。当一个缓冲区对象捆绑到它时,会作为glReadPixels、glGetTexImage和glGetCompressedTexImage函数的读取缓冲区。一般而言这些函数其实是从一个帧缓冲区或纹理中抽取数据并将它们读回客户端内存中。但只要有一个缓冲区捆绑GL_PIXEL_PACK_BUFFER就不会读取回客户端内存了。

第二个PBO绑定点是GL_PIXEL_UNPACK_BUFFER。当一个缓冲区对象捆绑到它时,glTexImage*D、glTexSubImage*D、glCompressedTexImage*D和glCompressedTexSubImage*D这些操作会将数据存储到这个PBO缓冲区(它是GPU内存中的),而不是存储到客户端内存中(CPU内存)。

为什么需要使用PBO,因为能简化GPU操作,让GPU只需做数据复制进行初始化,等待复制完成,然后继续运行就行了,而缓冲区对象是需要经常访问、修改或更新像素数据的,例如如下例子:

① 流纹理更新 (GL_DYNAMIC_DRAW方式)
例如流视频应用,它是每一帧都对一个纹理进行更新,也许需要用户输入来改变它。PBO就允许应用程序改变纹理数据,而不需先下载然后再重新上传整个表面(即部分更新glBufferSubData)

*②渲染顶点数据
因为缓冲区对象是通用的数据存储,应用程序可以很容易地使用同一个缓冲区来达到不同的目的。例如,一个应用程序可以将顶点数据写入一个颜色缓冲区,然后再将这些数据复制到PBO。一旦完成,缓冲区就能够作为一个顶点缓冲区进行绑定,并且用来绘制新的几何图形。这种方式展示了灵活性,允许我们对新的顶点数据“染色”!(这里不太懂,用旧的顶点数据对新的顶点数据做操作?“染色”?)

③异步调用glReadPixels
可通过glReadPixels直接读取PBO的数据来进行操作,然后再填充回去,它不需要暂停GPU手头的活儿。甚至可以多次读取不同区域的像素并进行处理,再填充回去。而没有缓冲区对象是只能够等待GPU完成当前工作,才允许CPU去读取内容的。

像素缓冲区对象是一个很好的容器,可以暂时存储GPU本地像素数据。使用之前必须通过glBufferData进行分配空间,可以只分配而不填充,data填NULL即可。可以分配比实际使用更大的空间,但如果缓冲区频繁会改变大小,最好关闭对数据大小上限的设定,而不是频繁地对它进行重新设定。

PBO能解决大量纹理数据上传导致的卡顿问题,即它能以一种不拖延其他工作的方式在必要时提供纹理数据。也就是说,如果你上传到PBO,将不会引发卡顿,而是它会延时到合适的时机才真正加载而避免卡顿。(真的那么牛逼吗?)

从缓冲区中读取像素数据

能够从当前读取缓冲区的特定位置获取像素(即从屏幕获取像素)然后将它们复制到CPU内存中。

void* data = (void*)malloc(pixelDataSize);
glReadBuffer(GL_BACK_LEFT);
glReadPixels(0, 0, GetWidth(), GetHeight(), GL_RGB, GL_UNSIGNED_BYTE, pixelData);

以上代码会等GPU完成工作后才进行,并且可能对性能有所影响。下面介绍一种就是将缓冲区像素复制到PBO对象中。

glReadBuffer(GL_BACK_LEFT);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pixBuffObjs[0]);
glReadPixels(0, 0, GetWidth(), GetHeight(), GL_RBG, GL_UNSIGNED_BYTE, NULL);

它能避免性能问题,无需等GPU完成工作,而是直接就能进行这种拷贝操作,接下来就能用这个PBO来做点事情了。

使用PBO

PBO能带来性能的明显提升,接下来的一个实例会演示这种效率。

运动模糊(Motion blur)是一种特效,它有助于显示出一个场景中哪些对象是运动的,以及他们运动得有多快,简单来说就是残影的效果。它的实现方式有很多种,例如:将移动对象进行多次的偏移并采样混合,并结合深度缓冲区数据对更加接近照相机的对象应用模糊效果来加强运动效果。或者简单点的就是如下实例讲的,直接将前面帧的图像缓存到PBO,与当前帧图像混合来形成模糊效果。实例会将缓存前5帧图像进行混合,你当前可以不使用PBO方式,拷贝到CPU内存再去作为纹理数组传入着色器进行混合实现运动模糊。

案例地址:https://blog.csdn.net/qq_39574690/article/details/115598427

PBO简单来说就是提升了性能,对比传统方式,它不必等待GPU完成工作,而是直接进行缓存像素数据,然后直接提供给着色器使用,本例是缓存了最后5帧图像数据。

缓冲区对象

目前我们已经了解了GL_PIXEL_PACK_BUFFER和GL_PIXEL_UNPACK_BUFFER(即PBO像素缓冲区),接下来会介绍TBO(纹理缓冲区)即GL_TEXTURE_BUFFER。

TBO和纹理对象区别是大小限制宽松,它比普通纹理能大64倍,甚至几万倍,纹理缓冲区相当于一个临时纹理,它能直接填充着色过程中的任何数据,例如顶点数据、变换反馈等,也可以存储以前的数据进行使用。

TBO向着色器提供了大量对多种不同格式和类型的数据访问,允许着色器以通常是预留给CPU的方式进行操作。TBO也可以提供着色器额外的顶点数组数据,以实现一些运行时决策几何图形顶点效果有用途,为了实现这一点需要将TBO大小作为一个统一值传输到着色器中。

1、创建纹理缓冲区

GLuint texBO[1];
glGenBuffers(1, texBO);
glBindBuffer(GL_TEXTURE_BUFFER, texBO[0]);
glBufferData(GL_TEXTURE_BUFFER, sizeof(float)*count, fileData, GL_STATIC_DRAW);
glBindBuffer(GL_TEXTURE_BUFFER, 0);

2、第一个纹理单元的纹理对象捆绑纹理缓冲区,并使用第一个纹理缓冲区texBO[0]的内容进行填充纹理texBOTexture,着色器使用第一纹理单元。

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_BUFFER, texBOTexture);
glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, texBO[0]);

3、片段着色器使用它

uniform samplerBuffer lumCurveSampler;
void main(void){
   ... ...
   int offset = int(vColor.r * (1024-1));
   lumFactor.r = texelFetch(lumCurveSampler, offset).r;
}

samplerBuffer是纹理缓冲区的采样器类型,使用texelFetch函数采样,纹理坐标是一个整数值[0, 缓冲区大小-1] ,上图是只用了r分量来获取缓冲区的r分量,没有完整案例,后期看看能否补全。

TBO类似一种缓存杂七杂八的数据的一种用途,因为它不是纹理,所以能直接缓存着色器过程出现的任何中间值。

帧缓冲区对象(FBO),摆脱窗口的限制

默认上,屏幕就是个帧缓冲区。但OpenGL封装了帧缓冲区成为了一个对象提供给我们使用。帧缓冲区可以不受窗口大小限制,包含多个颜色缓冲区,捆绑一个纹理到FBO或保存其他确实有内存存储并且可以进行渲染的对象,例如纹理和渲染缓冲区,采用这种对象形式能够在保存OpenGl管线的输出同时还能将需要的状态和表面绑定在一起。

如何使用FBO

FBO创建、设置和绑定后,就和正常一样进行渲染图形,但输出的内容会放到FBO,而不是屏幕上显示了。(有点像Unity的RendererTexture渲染纹理?实际下面才讲到RBO 专门配合FBO的)

创建新的FBO

先创建FBO缓冲区名字(翻译是这样的,我理解就是个句柄吧。。)

GLuint fboName;
glGenFramebuffers(1, &fboName);

绑定到一个新的FBO来修改和使用它

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboName);

注意:同一时间只有一个FBO可绑定用来绘制,并且同一时间只有一个FBO可绑定用来进行读取。glBindFramebuffer函数第一个参数有两个选项,GL_DRAW_FRAMEBUFFER和GL_READ_FRAMEBUFFER,这意味着我们是可以进行对一个FBO进行读取,同时进行对另一个FBO进行写入的。

解除绑定,同时会把读写控制权交给默认FBO(即屏幕FBO)

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
//glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);

注意:只要上面两句代码其中一句执行了,都会将读写权利归还到默认FBO!

销毁FBO

在使用完FBO或者退出之前要进行销毁FBO

glDeleteFramebuffers(1, &fboName);

完整案例: 待补充

渲染缓冲区对象(RBO)

RBO是一种图像表面,它是专门为了绑定到FBO而设计的。一个RBO可以是一个颜色表面、模板表面或者深度/模板组合表面。我们可以为给定的FBO选择需要的任意RBO组合,甚至可一次性绘制多个颜色缓冲区给FBO。

创建RBO

glGenRenderbuffers(3, renderBufferNames); //3个颜色缓冲区

绑定RBO

glBindRenderbuffer(GL_RENDERBUFFER, renderBufferNames[0]);

设置RBO配置并分配内存(总共1个深度缓冲区和3个颜色缓冲区)

//其中颜色缓冲区的绑定和设置配置,分配内容 相关代码
glBindRenderbuffer(GL_RENDERBUFFER, renderBufferNames[0]);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, screenWidth, screenHeight);

//深度缓冲区的绑定RBO和设置配置,分配内容 相关代码
glBindRenderbuffer(GL_RENDERBUFFER, depthBufferName);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, screenWidth, screenHeight);

可见上面参数,第二是格式,第三第四是宽高值,宽高值可不必与屏幕一样,可通过查询glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE)来获取最大值,其设置的宽高值必须小于它即可。

可以用glRenderbufferStorageMultisample函数来创建多重采样的渲染缓冲区,这函数接收一个额外的采样变量,好处就是能像素显示在屏幕之前进行离屏多重采样。

RBO绑定到FBO上

FBO可以有多个捆绑点可进行提供绑定,即一个深度绑定点、一个模板绑定点以及多个颜色绑定点。可用glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS)来查询支持多少个颜色缓冲区绑定。在实例中只绑定了一个深度缓冲区和3个颜色缓冲区,在绑定RBO之前需确保FBO已经初始化完毕。

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboName);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBufferName);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBufferNames[0]);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, renderBufferNames[1]);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_RENDERBUFFER, renderBufferNames[2]);

可发现需先进行绑定FBO到GL_DRAW_FRAMEBUFFER(即准备绘制到FBO),然后开始绑定RBO到FBO同样是GL_DRAW_FRAMEBUFFER,说明是用于绘制FBO的。参数二是对应的FBO捆绑点类型,例如GL_DEPTH_ATTACHMENT对应深度捆绑点;参数三都是GL_RENDERBUFFER,代表第四个参数都是捆绑到RBO的缓冲区。

GL_STENCIL_ATTACHMENT是模板绑定点,GL_DEPTH_STENCIL_ATTACHMENT是深度模板绑定点,它允许将一个格式为GL_DEPTH_STENCIL的RBO绑定给这个深度模板绑定点。

注意:OpenGL不允许我们去改变默认FBO的绑定,也不能将默认FBO的其中一个捆绑点对应的RBO绑定到用户FBO上。

RBO大小

允许有不同颜色格式的渲染缓冲区绑定到同一个FBO上。允许不同大小的RBO绑定到FBO上,最终会渲染框会是最小的RBO大小。在多重FBO或多重缓冲区需要深度测试时,可创建一个深度缓冲区,并在所有FBO或渲染传递中使用它,在每次使用的间隔清除深度值,要保证深度格式RBO足够大,容纳最大的FBO配置,即共享深度缓冲区来节省内存开销。

绘制缓冲区

目前为止,已经将RBO绑定到一个FBO上了。

确保获取对渲染缓冲区的访问,有两个重要的步骤:①确保片段着色器设置正确。②确保输出被引导到正确的位置。

着色器输出

从着色器写入颜色输出的一种方法是写入名为gl_FragData[n]的内建输出中。案例会在片段着色器进行写入3个颜色缓冲区分别用正常颜色输出、灰度值、根据当前颜色rgb从纹理缓冲区采样的颜色值。(具体看完整例子代码)

缓冲区映射

在着色器写入输出颜色缓冲区数据后,这些gl_FragData[n]数据会输出到哪里?这就需要进行对它们进行route(路由)来指定输出到哪里,例如:

GLenum fboBuffs[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
glDrawBuffers(3, fboBuffers);

它与gl_FragData一一对应输出和输入。

使用完FBO之后要将这个映射恢复,注意要在解除FBO绑定后才进行,解除用户FBO后会自动恢复回默认FBO(不进行这步会出现GL报错)

GLenum windowBuff[] = { GL_FRONT_LEFT };
glDrawBuffers(1, windowBuff);

假如你不想将着色器的第三个输出映射到GL_COLOR_ATTACHMENT2,则可以用GL_NONE替换。不允许出现多个相同的映射值(除了GL_NONE)

可通过glGetIntegerv(GL_MAX_DRAW_BUFFERS)来查询最大映射数。

可调用glReadBuffer设置读取缓冲区映射,参数和glDrawBuffers一样。

帧缓冲区的完整性

与纹理的完整性类似,对以上所有的FBO初始化,捆绑RBO,着色器输出并映射到对应RBO完成之后,我们要先确保其完整才能真正开始使用FBO。

完整性有两种:绑定完整性和整个帧缓冲区的完整性。

绑定完整性

需保证如下五向完整才能称之为绑定完整。
① 没有任何图像关联到绑定的对象。
②绑定图像的宽度或高度为0.
③一个不能进行颜色渲染的格式被绑定到一个颜色绑定上。
④一个不能进行深度渲染的格式被绑定到一个深度绑定上。
⑤一个不能进行模板渲染的格式被绑定到一个模板绑定上。

帧缓冲区的整体完整性

①没有任何图像被绑定到帧缓冲区。
②glDrawBuffers被映射到一个没有任何图像进行绑定的FBO绑定。(其实①满足了,②必然满足啊,哇很烦不管啦)
③内部格式的组合不被支持

检查帧缓冲区

GLenum fboStatus = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);

若返回GL_FRAMEBUFFER_COMPLETE则正常,否则代表一些提示哪出了问题,导致不完整。

返回值描述
GL_FRAMEBUFFER_UNDEFINE当前绑定FBO为0,但不存在默认帧缓冲区
GL_FRAMEBUFFER_COMPLETE一个有用户定义的FBO被绑定并且是完整的,可以进行渲染
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT为了进行渲染而启用的某个缓冲区不完整
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT没有任何缓冲区被绑定到FBO
GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER没有任何缓冲区被绑定到为了进行渲染而启用的某个缓冲区绑定
GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER没有任何缓冲区被绑定到为了进行读取而启用的某个缓冲区绑定
GL_FRAMEBUFFER_UNSUPPORTED内部格式的组合不被支持
GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE采样数或者为所有缓冲区设置的TEXTURE_FIXED_SAMPLE_LOCATIONS值不匹配
GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS颜色绑定不都是层次纹理,或者没有都绑定到同一个目标
GLenum fboStatus = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
if(fboStatus != GL_FRAMEBUFFER_COMPLETE)
{
   switch(fboStatus)
   {
        case GL_FRAMEBUFFER_UNDEFINED:
            //没有窗口?
           break;
        case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
            //检查每个绑定的状态
            break;
        case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
            //将至少一个缓冲区绑定到FBO
            break;
        case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
            //检查所有通过glDrawBuffers启用的绑定在FBO中存在
            break;
        case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
            //检查所有通过glReadBuffer指定的缓冲区在FBO中都存在
            break;
        case GL_FRAMEBUFFER_UNSUPPORTED:
            //重新考虑用于绑定缓冲区的格式
            break;
        case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
            //确保每个绑定的采样数量相同
            break;
        case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
            //确保每个绑定的层次数量相同
            break;
   }
}

如果我们在一个不完整的FBO被绑定时试图执行任何从帧缓冲区进行读取或写入帧缓冲区的命令,都会抛出一个GL_INVALID_FRAMEBUFFER_OPERATION错误返回。用glGetError可获取到。

读取帧缓冲区也需要是完整的

与GL_DRAW_FRAMEBUFFER一样,GL_READ_FRAMEBUFFER同样需要保证完整性,因为只允许同一时间启动一个用户FBO进行读取或写入,所以确保其完整性就相对简单点。

在帧缓冲区中复制数据

void glBlitFramebuffer(GLint scrX0, GLint scrY0, GLint scrX1, GLint scrY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);

这个函数是从一个由glReadBuffer函数指定的读取缓冲区区域复制数据到一个由glDrawBuffers函数指定的绘制缓冲区区域,它是采用bit级图像传输或块传输的,它是一个源区域(scrX0,scrY0)为左下角(scrX1, srcY1)为右上角的区域复制到目标区域(dstX0, dstY0)为左下角(dstX1, dstY1)为右上角的区域。如果用户有1个FBO,它即捆绑读取也捆绑写入,那么可以从读取FBO获取部分像素数据,复制到写入FBO对应位置上。如果两个区域大小不一样会进行缩放。

蒙板参数(mask argument)可以是GL_DEPTH_BUFFER_BIT、GL_STENCIL_BUFFER_BIT或GL_COLOR_BUFFER_BIT中任意一个或全部(用'  | ' 来合并)

过滤类型(filter) 是GL_LINEAR 或 GL_NEAREST ,如果复制的是深度或模板数据必须是GL_LINEAR。下面只进行复制颜色数据,可使用线性过滤器。

GLint widht = 800;
GLint height = 600;
GLenum fboBuffs[] = { GL_COLOR_ATTACHMENT0 };
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboName);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fboName);
glDrawBuffers(1, fboBuffs);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(0, 0, width, height, (width*0.8), (height*0.8), width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);

假设FBO的RBO是800和600宽高值,那么此时的代码就是新生成了一个20%的副本图像在FBO右上角上,注意我们只复制了FBO的颜色缓冲区0的颜色值。注意:这里mask是只复制颜色的意思,至少也要填写GL_COLOR_BUFFER_BIT允许颜色值被复制,但你也可以全都写上,毕竟没有深度缓冲区和模板缓冲区的指定(话说怎么指定读取缓冲区 有多个缓冲区?类似绘制缓冲区一样)

发现它真的只有一个参数,难道是用 | 来合并的?待测

FBO综合运用

这个程序会将FBO、RBO、TBO等内容结合在一起运用了。

https://blog.csdn.net/qq_39574690/article/details/115605165

渲染到纹理

RBO本身存在的限制是必须捆绑到FBO上才有用,这意味着数据需要一个副本。但我们可以使用纹理直接捆绑到FBO绑定点上。
疑问:副本从何而来?是每次使用glTexBuffer时会从TBO拷贝出一个副本存入纹理对象?还是TBO本身存的就是副本纹理?(与下方谈及的纹理循环引用有关)

void glFramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
void glFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer);

target: GL_DRAW_FRAMEBUFFER /  GL_READ_FRAMEBUFFER

attachment: GL_DEPTH_ATTACHMENT、 GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENTn任意一个

textarget: 纹理类型, 对于立方体纹理来说,我们需要传递表面的目标(??)

texture: 纹理对象

level: 层级?

layer: 3D才有,表示使用的层次。

glGenFramebuffers(1, &fboName);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboName);

glGenTextures(1, &mirrorTexture);
glBindTexture(GL_TEXTURE_2D, mirrorTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 800, 800, 0, GL_RBGA, GL_FLOAT, NULL);

glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mirrorTexture, 0);

书籍谈及了纹理的循环引用问题,即用纹理作为FBO捆绑,那么可能出现从一个纹理获取纹理单元,然后将最终的着色结果写回同一个纹理,有可能覆盖到同一个位置上。这样就可能导致未定义的结果。(???这是说了啥)作为一般规则,建议是确保绑定到FBO的纹理和被写入的纹理不要绑定到任何纹理单元上。我寻思一个FBO捆绑一个纹理,它即写入又读取,读取自身纹理并将数据写入自身,好像也没毛病,只是在做无用功而已。

绑定到FBO的每个纹理状态也会影响FBO的完整性。一个纹理图像表面的大小和格式可以通过进行glTexImage2D之类的调用绑定到FBO的同时异步地进行修改。如果修改了一个作为渲染目标的纹理图像表面,那么就应该确保帧缓冲区仍然可以通过调用glCheckFramebufferStatus进行渲染。我们可以绑定一个纹理的任何Mip贴图层次,只要在绑定纹理时指定它即可。如果我们还计划为纹理贴图使用Mip贴图,那么还要为刚刚进行渲染的纹理生成Mip链的剩余部分(??)调用glGenerateMipmap即可完成。

https://blog.csdn.net/qq_39574690/article/details/115607098

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值