本文为学习OpenGL的学习笔记,如有书写和理解错误还请大佬扶正;
一,纹理缓冲区
一个纹理包含两个主要组成部分,纹理采样状态和包含纹理值得数据缓冲区;
1,为什么使用纹理缓冲区?
纹理缓冲区也称texBO或TBO,允许我们完成一些传统纹理不能完成的工作,首先,纹理缓冲区能够直接填充来自其他渲染结果(例如变换反馈,像素读取操作或顶点数据)的数据。
TBO的另一个特性上宽松的大小限制,纹理缓冲区与传统一维纹理相似,但要更大,OpenGL规范所规定的纹理缓冲区大小的最大值比1D纹理大64倍,但是在某些实现中纹理缓冲区的大小可能要大几万倍。
2,纹理缓冲区的可以做什么?
TexBO向着色器提供了大量对多种不同格式和类型的数据访问,允许着色器以通常是预留给CPU的方式进行操作,纹理缓冲区能够用来提供对片段着色器和顶点着色器中的顶点数组的访问。这在着色器需要关于临近几何图形的信息以做出运行时的决策和计算的情况下会非常有用。
3,纹理缓冲区使用
纹理缓冲区上作为普通的缓冲区来创建的,当它被绑定到一个纹理或者GL_TEXTURE_BUFFER绑定点时会成为真正的纹理缓冲区。
glBindBuffer(GL_TEXTURE_BUFFER,texBO[0]);
glBufferData(GL_TEXTURE_BUFFER,Sizeof(float)*count,fileData,GL_STATIC_DRAW);
texBO必须绑定到一个纹理单元上才能真正变得有用,要将一个texBO绑定到一个纹理上可以调用glTexBuffer,但是首先确保要使用的纹理已经进行了绑定。
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_BUFFER,texBOTexture);
glTexBuffer(GL_TEXTURE_BUFFER,GL_R32F,texBO[0]);
纹理缓冲区不能在着色器中用普通的采样器也就是sampler1D和sampler2D进行访问,取而代之的是,samplerBuffer,因为采样器类型不同,用来从纹理缓冲区中获值得采样函数也不相同,可以使用texelFetch从纹理缓冲区进行读取。
当着色器在一个纹理缓冲区中进行查询时,必须使用一个基于整数的非标准化索引,例如texture传统的采样函数接受的坐标范围从0.0到1.0,但是texBO查询函数texelFetch则接受一个从0到缓冲区大小值得整数索引,如果纹理坐标已近进行了标准化,那么可以通过乘以texBO的大小值减去1的结果,然后在将得到的结果转化为整数方式转换成索引;
二,帧缓冲区
一个OpenGL窗口的表面长期以来一直被称作“帧缓冲区”,但是现在OpenGL将绘制帧缓冲区到一个对象所需的状态进行了封装,称为帧缓冲区对象(FBO)。
默认的帧缓冲区对象是与创建的OpenGL窗口相关联的,并且在一个新的环境绑定时自动进行绑定,我们可以创建多个帧缓冲区对象,也叫做FBO,并且直接渲染一个FBO而不是窗口,使用这种离屏渲染,应用程序就可以执行许多不同种类的渲染算法,例如阴影贴图,应用辐射着色,反射,后期处理和许多其他特效,另外帧缓冲区对象不受窗口大小的限制,可以包含多个颜色缓冲区,甚至可以将纹理绑定到一个FBO上,意味着可以直接渲染到一个纹理中。
虽然帧缓冲区名称中包含缓冲区字眼,但是它们根本不是缓冲区,并不纯在一个帧缓冲区对象相关联的真正内存储存空间,,相反,帧缓冲区对象是一种容器,可以保存其他确实有内存储存并且可以进行渲染的对象,例如纹理或渲染缓冲区,采用这种方式,帧缓冲区对象能够在保存OpenGL管线的输出时将需要的状态和表面绑定到一起。
1,使用帧缓冲区(FBO)
FBO只是一个图像对象的容器,所以需要先添加图像,才能渲染到一个FBO,一旦一个FBO被创建,设置和绑定,大多数OpenGL的操作就像是在渲染到一个窗口一样执行,但是输出结果将储存在绑定到的FBO图像中。
创建FBO,首先要生成FBO缓冲区名称,可以同时生成任意数量的名称。
GLuint fboName;
glGenFramebuffers(1,&fboName);
然后在绑定一个新的FBO修改和使用;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,fboName);
同一时间只有一个FBO可以绑定用来进行绘制,并且同一时间只有一个FBO可以绑定来进行读取,在绑定一个帧缓冲时,glBindFramebuffer的第一个参数既可以是GL_DRAW_FRAMEBUFFER,也可以是GL_READ_FRAMEBUFFER,代表着既可以使用一个帧缓冲区进行读取,而使用另一个不同的缓冲区进行绘制。
将名称0绑定到任意一个FBO目标,都会将当前缓冲区解除绑定,并再次绑定到默认的FBO,一旦默认的FBO被绑定,那么读取和写入就都会再次绑定到了窗口的帧缓存区。
在使用完FBO,或者在退出前进行清除时,要删除FBO;
glDeleteFramebuffers(1,&fboName);
2,渲染缓冲区对象
在创建完FBO以后,我们需要将一些东西放到FBO中以供使用,渲染缓冲区对象,简称RBO,是一种图像表面,专门为了绑定到FBO而设计的,一个渲染缓冲对象可以是一个颜色表面,模板表面或者深度模板组合,可以为给定的FBO挑选任意的RBO组合,甚至可以一次性绘制很多个颜色缓冲区,创建RBO和创建FBO以及大多数其他OpenGL对象非常类似。
和FBO类似,RBO需要先绑定才能修改,渲染缓冲区的唯一合法目标是GL_RENDERBUFFER。
RBO已经绑定,需要分配支持RBO的内存空间,RBO在创建时是没有办法初始储存的,没有储存,就没有任何东西可渲染。
首先,要确定应用程序需要什么,然后选择合适的格式,使其与缓冲区的用途相匹配,大多数合法的纹理格式同时也是合法的渲染缓冲格式,另外,可以创建包含一个模板格式的渲染缓冲区存储,纹理可以有一个DEPTH_STENCIL组合格式,不只是一个模板格式。
glBindRenderbuffer(GL_RENDERBUFFER,renderBufferNames[0]);
glRendebufferStorage(GL_RENDERBUFFER,GL_RGBA8,screenWidth,screenHeight);
glBindRenderbuffer(GL_RENDERBUFFER,depthBufferName);
glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT32,screenWidth,screenHeight);
在上面的代码中,RBO储存被分配了与示例程序窗口相同的大小,但渲染缓冲区的大小不必与窗口相同,可以通过调用以GL_MAX_RENDERBUFFER_SIZE为参数的glGetIntegerv来查询OpenGL实现所支持的最大维度,宽度和高度值必须小于这个最大值,创建储存的唯一有效目标是GL_RENDERBUFFER.
还可以使用一个类似的函数 glRenderbufferStorageMultisample来创建多重采样的渲染缓冲区储存,这个函数接受一个附加的采样变量,这样做的好处是,可以在任何像素显示在屏幕上之前进行离屏多重采样。
绑定RBO
一旦为FBO创建了所有的渲染表面,就到了将他们衔接起来的时候,一个帧缓存区有多个绑定点可以进行绑定,一个深度绑定点,一个模板绑定点,以及多个颜色绑定点,可以使用glGetIntegerv查询GL_MAX_COLOR_ATTACHMENTS,来查出一次可以绑定多少颜色缓冲区,在试图绑定一个渲染缓冲区之前,要确保FBO已经被绑定。
在下列代码中,一次使用了一个深度缓冲区和3个颜色缓冲区
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,depthBufferName);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_RENDERBUFFER,depthBufferName[0]);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT1,GL_RENDERBUFFER,depthBufferName[1]);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT2,GL_RENDERBUFFER,depthBufferName[2]);
第一个参数可以是GL_DRAW_FRAMEBUFFER或GL_READ_FRAMEBUFFER,这取决于我们将FBO绑定到了哪里,然后在指定绑定点(第二个参数)。第三个参数总是GL_RENDERBUFFER,而最后一个参数则是将要使用的着色器缓冲区名称。
如果以0为名称调用了glFramebufferRenderbuffer,那么无论绑定到当前FBO指定的绑定点是何种缓冲区,他都将被解除绑定,有一个特殊的绑定点GL_DEPTH_STENCIL_ATTACHMENT,它允许我们将同一个缓冲区同时绑定到深度绑定点和模板绑定点,要使用这个绑定点,需要创建一个带有内部GL_DEPTH_STENCIL格式的RBO。
RBO大小
可以将不同颜色格式的缓冲区绑定到同一个帧缓存区上,甚至可以将不同大小的RBO绑定到同一个帧缓冲区上,如果RBO确实大小不同,那么只能渲染到一个大小为最小缓冲区的矩形上。
3,绘制缓冲区
要获得对渲染缓冲区的访问,,有两个重要的步骤。
- 第一步,确保片段着色器设置正确;
- 第二步,确保输出被引导到正确的位置;
着色器输出
为了将颜色缓冲区输出到多个缓冲区,着色器必须配置为写入多重颜色输出,从着色写入颜色输出的一种方法是写入到名为gl_FragData[n]的内建输出中,但不能在同一个着色器中使用gl_FragData[n]和gl_FragColor,n的值是着色器的输出索引。
缓冲区映射
OpenGL允许一个应用程序通过为每个缓冲区指定颜色绑定来将着色器输出映射到不同的FBO缓冲区。
默认的行为是单独的颜色输出将被发送到颜色绑定0,如果不通知OpenGL如何处理着色器输出,那么只有第一个输出被路由通过。可以通过调用glDrawBuffers来对着色器输出 进行路由,这将覆盖以前所有的映射。
GLenum fboBuffers[] = {GL_COLOR_ATTACHMENT0,GL_COLOR_ATTACHMENT1,GL_COLOR_ATTACHMENT2};
glDrawBuffers(3,fboBuffers);
第二个参数是指向一个GLenum数组指针,这个数组指定了着色器输出索引值将要路由到的颜色绑定,数组的索引会被传递到着色器输出索引对应的glDrawBuffers,大多数情况只想要一个一对一的映射,其中着色器输出的索引与颜色绑定的偏移量相等。
仍要调用glDrawBuffers之前确保FBO已经被绑定,如果在一个由用户创建的FBO被绑定的情况下使用glDrawBuffers,那么合法的缓冲区目标为从GL_COLOR_ATTACHMENT0到“1”减去最大值的结果,或者为GL_NONE。但是如果默认的FBO被绑定,那么可以使用与窗口相关联的颜色缓冲区名称,最普遍的是GL_BACK_LEFT。请注意,无论使用的FBO是何种类型,在数组中除GL_NONE以外的值最多只能使用一次,如果默认帧缓冲区被绑定,或者着色器程序写入到gl_FragColor,那么我们传递到glDrawBuffers的所有缓冲区都会获得相同的颜色,不要忘记在使用完FBO之后恢复绘制缓冲区的设置,否则将会产生GL错误。
GLenum windowBuffer[] = {GL_FRONT_LEFT};
glDrawBuffers(1,windowBuff);
关于缓冲区 GL_BACK_LEFT参考文章:
www.khronos.org/opengl/wiki/Default_Framebuffer
可以通过glDrawBuffers设置一个映射数量的限制值,可以通过调用以GL_MAX_DRAW_BUFFERS为参数的glGetIntegerv查询支持的最大映射数。
使用glDrawBuffers来选择着色器将要写入的缓冲区对于读取缓冲区绑定来说没有任何影响,可以通过调用glReadBuffer设置读取缓冲区,其值与glDrawBuffers中使用相同的值。
4,帧缓冲区的完整性
一个FBO的每个绑定点都必须符合某种被认为是完整的标准,如果任意一个绑定点是不完整的,那么整个帧缓冲区也将是不完整的,部分导致绑定不完整的情况如下。
- 没有任何图像关联到绑定对象
- 绑定图像的宽度或者高度为0
- 一个不能进行颜色渲染的格式被绑定到一个颜色绑定上。
- 一个不能进行深度渲染的格式被绑定到一个深度绑定上。
- 一个不能进行模板渲染的格式被绑定到一个深度绑定上。
帧缓冲区的整体完整性
不只是每个绑定点都必须有效并且符合特定标准,帧缓冲区对象作为一个整体也必须是完整的,默认的帧缓冲区(如果存在的话)总是完整的,整个帧缓冲区不完整的情况如下。
- 没有任何图像被绑定到帧缓冲区。
- glDrawBuffers被映射到一个没有任何图像进行绑定的FBO绑定。
- 内部格式的组合不被支持。
检查帧缓冲区
当我们认为已经完成了FBO设置时,可以调用:
GLenum fboStatus = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
来检查它是否完整。
如果glCheckFramebufferStatus返回GL_FRAMEBUFFER_COMPLETE,那么一切正常,可以使用FBO了,返回值glCheckFramebufferStatus则会提示哪里出了问题,导致帧缓冲区不完整。
同时读取帧缓冲区也需要是完整的,这样才能进行读取,同一时间只能启用一个读取缓冲区,这样确保一个FBO对于读取操作是完整的就相对简单一些。
在帧缓冲区中复制数据
传统上图形API允许一个应用程序将像素或者缓冲区数据读回到系统内存中,也提供了将它们回屏幕的方法,当这些方法起作用时,它们需要从GPU中将数据复制到CPU内存,然后再返回来将它复制回去。这是非常低效的,现在有一种方法,使用blit命令可以将像素数据从一点移动到另外一个点。blit是一个术语,它代表直接,有效的bit级数据/内存复制。执行这些复制是简单的,使用的函数如下。
虽然这个函数的名称里包含“blit”字眼,但是它不只是能够完成简单的逐位复制而已,实际上,它更像一种自动的纹理操作,复制的源是通过调用glReadBuffer而指定的读取缓冲区,而复制的区域则是由以(srcX0,secY0)和(srcX1,secY1)为顶点的矩形所定义的区域,类似地,复制的目标是通过调用glDrawBuffer而指定的当前绘制缓冲区,复制到的区域则是由以( dstX0, dxtY0)和( dstX1, dxtY1)为顶点的矩形所定义的区域。因为源和目标区域的矩形不需要是同样大小的,所以可以使用这个函数对被复制的像素进行缩放,如果我们将读取和绘制缓冲区设置为同一个FBO,并将同一个FBO绑定到了GL_DRAW_FRAMEBUFFER绑定和GL_READ_FRAMEBUFFER绑定,甚至可以将数据从一个缓冲区的一部分复制到另外一个缓冲区。
蒙版参数(mask argument)可以是GL_DEPTH_BUFFER_BIT,GL_STENCIL_BUFFER_BIT或GL_COLOR_BUFFER_BIT中的任何一个或者全部,过滤器可以是GL_LINEAR或GL_NEAREST,但是如果要复制深度或模板数据时则必须是GL_LINEAR。这些过滤器的行为和进行渲染时是一样的,如果只复制颜色数据,可以使用线性过滤器。
FBO渲染到纹理
也可以将一个纹理直接绑定到一个FBO绑定点上,因为各种纹理的情况不尽相同,所以有3个入口点用来将纹理绑定到一个帧缓冲区绑定点上。
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);
目标既可以是GL_DRAW_FRAMEBUFFER,也可以是GL_READ_FRAMEBUFFER,这与渲染缓冲区的情况相同,第二个参数指定FBO绑定点,可以是GL_DEPTH_ATTACHMENT,GL_STENCIL_ATTACHMENT,也可以是各个GL_COLOR_ATTACHMENTn 值中的任意一个,这一点也与缓冲区情况类似,对于大多数纹理,第三个参数是相应的纹理类型,但对于立方体贴图来说,需要传递表面目标,接下来要给出纹理的名称,然后是纹理要绑定到的Mip贴图层次,对于glFramebufferTexture3D,我们还必须指定3D纹理将使用的层次,一维纹理只能通过glFramebufferTexture1D进行绑定,而glFramebufferTexture3D则只能用于三维纹理,在二维纹理,矩形纹理和 立方体贴图纹理中则使用glFramebufferTexture2D。
利用上面的知识,重新书写渲染逻辑;
效果参考:
代码参考: