帧缓冲 - LearnOpenGL-CN
文章推薦指數: 80 %
把这几种缓冲结合起来叫做帧缓冲(Framebuffer),它被储存于内存中。
OpenGL给了我们自己定义帧缓冲的自由,我们可以选择性的定义自己的颜色缓冲、深度和模板缓冲。
LearnOpenGL-CN
主页
简介
入门
OpenGL
创建窗口
你好,窗口
你好,三角形
着色器
纹理
变换
坐标系统
摄像机
复习
光照
颜色
光照基础
材质
光照贴图
投光物
多光源
复习
加载模型
Assimp
网格
模型
高级OpenGL
深度测试
模板测试
混合
面剔除
帧缓冲
帧缓冲
创建一个帧缓冲
后期处理
反相
灰度
Kerneleffects
练习
立方体贴图
高级数据
高级GLSL
几何着色器
实例化
抗锯齿
高级光照
高级光照
Gamma校正
阴影
阴影映射
点阴影
CSM
法线贴图
视差贴图
HDR
泛光
延迟着色法
SSAO
实战
调试
文字渲染
2D游戏
Breakout
准备工作
渲染精灵
粒子
LearnOpenGL-CN
Docs»
高级OpenGL»
帧缓冲
帧缓冲
原文
Framebuffers
作者
JoeyDeVries
翻译
Django
校对
Geequlim
到目前为止,我们使用了几种不同类型的屏幕缓冲:用于写入颜色值的颜色缓冲,用于写入深度信息的深度缓冲,以及允许我们基于一些条件丢弃指定片段的模板缓冲。
把这几种缓冲结合起来叫做帧缓冲(Framebuffer),它被储存于内存中。
OpenGL给了我们自己定义帧缓冲的自由,我们可以选择性的定义自己的颜色缓冲、深度和模板缓冲。
我们目前所做的渲染操作都是是在默认的帧缓冲之上进行的。
当你创建了你的窗口的时候默认帧缓冲就被创建和配置好了(GLFW为我们做了这件事)。
通过创建我们自己的帧缓冲我们能够获得一种额外的渲染方式。
你也许不能立刻理解应用程序的帧缓冲的含义,通过帧缓冲可以将你的场景渲染到一个不同的帧缓冲中,可以使我们能够在场景中创建镜子这样的效果,或者做出一些炫酷的特效。
首先我们会讨论它们是如何工作的,然后我们将利用帧缓冲来实现一些炫酷的效果。
创建一个帧缓冲
就像OpenGL中其他对象一样,我们可以使用一个叫做glGenFramebuffers的函数来创建一个帧缓冲对象(简称FBO):
GLuintfbo;
glGenFramebuffers(1,&fbo);
这种对象的创建和使用的方式我们已经见过不少了,因此它们的使用方式也和之前我们见过的其他对象的使用方式相似。
首先我们要创建一个帧缓冲对象,把它绑定到当前帧缓冲,做一些操作,然后解绑帧缓冲。
我们使用glBindFramebuffer来绑定帧缓冲:
glBindFramebuffer(GL_FRAMEBUFFER,fbo);
绑定到GL_FRAMEBUFFER目标后,接下来所有的读、写帧缓冲的操作都会影响到当前绑定的帧缓冲。
也可以把帧缓冲分开绑定到读或写目标上,分别使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER来做这件事。
如果绑定到了GL_READ_FRAMEBUFFER,就能执行所有读取操作,像glReadPixels这样的函数使用了;绑定到GL_DRAW_FRAMEBUFFER上,就允许进行渲染、清空和其他的写入操作。
大多数时候你不必分开用,通常把两个都绑定到GL_FRAMEBUFFER上就行。
很遗憾,现在我们还不能使用自己的帧缓冲,因为还没做完呢。
建构一个完整的帧缓冲必须满足以下条件:
我们必须往里面加入至少一个附件(颜色、深度、模板缓冲)。
其中至少有一个是颜色附件。
所有的附件都应该是已经完全做好的(已经存储在内存之中)。
每个缓冲都应该有同样数目的样本。
如果你不知道什么是样本也不用担心,我们会在后面的教程中讲到。
从上面的需求中你可以看到,我们需要为帧缓冲创建一些附件(Attachment),还需要把这些附件附加到帧缓冲上。
当我们做完所有上面提到的条件的时候我们就可以用glCheckFramebufferStatus带上GL_FRAMEBUFFER这个参数来检查是否真的成功做到了。
然后检查当前绑定的帧缓冲,返回了这些规范中的哪个值。
如果返回的是GL_FRAMEBUFFER_COMPLETE就对了:
if(glCheckFramebufferStatus(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE)
//Executevictorydance
后续所有渲染操作将渲染到当前绑定的帧缓冲的附加缓冲中,由于我们的帧缓冲不是默认的帧缓冲,渲染命令对窗口的视频输出不会产生任何影响。
出于这个原因,它被称为离屏渲染(off-screenrendering),就是渲染到一个另外的缓冲中。
为了让所有的渲染操作对主窗口产生影响我们必须通过绑定为0来使默认帧缓冲被激活:
glBindFramebuffer(GL_FRAMEBUFFER,0);
当我们做完所有帧缓冲操作,不要忘记删除帧缓冲对象:
glDeleteFramebuffers(1,&fbo);
现在在执行完成检测前,我们需要把一个或更多的附件附加到帧缓冲上。
一个附件就是一个内存地址,这个内存地址里面包含一个为帧缓冲准备的缓冲,它可以是个图像。
当创建一个附件的时候我们有两种方式可以采用:纹理或渲染缓冲(renderbuffer)对象。
纹理附件
当把一个纹理附加到帧缓冲上的时候,所有渲染命令会写入到纹理上,就像它是一个普通的颜色/深度或者模板缓冲一样。
使用纹理的好处是,所有渲染操作的结果都会被储存为一个纹理图像,这样我们就可以简单的在着色器中使用了。
创建一个帧缓冲的纹理和创建普通纹理差不多:
GLuinttexture;
glGenTextures(1,&texture);
glBindTexture(GL_TEXTURE_2D,texture);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,800,600,0,GL_RGB,GL_UNSIGNED_BYTE,NULL);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
这里主要的区别是我们把纹理的维度设置为屏幕大小(尽管不是必须的),我们还传递NULL作为纹理的data参数。
对于这个纹理,我们只分配内存,而不去填充它。
纹理填充会在渲染到帧缓冲的时候去做。
同样,要注意,我们不用关心环绕方式或者Mipmap,因为在大多数时候都不会需要它们的。
如果你打算把整个屏幕渲染到一个或大或小的纹理上,你需要用新的纹理的尺寸作为参数再次调用glViewport(要在渲染到你的帧缓冲之前做好),否则只有一小部分纹理或屏幕能够绘制到纹理上。
现在我们已经创建了一个纹理,最后一件要做的事情是把它附加到帧缓冲上:
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texture,0);
glFramebufferTexture2D函数需要传入下列参数:
target:我们所创建的帧缓冲类型的目标(绘制、读取或两者都有)。
attachment:我们所附加的附件的类型。
现在我们附加的是一个颜色附件。
需要注意,最后的那个0是暗示我们可以附加1个以上颜色的附件。
我们会在后面的教程中谈到。
textarget:你希望附加的纹理类型。
texture:附加的实际纹理。
level:Mipmaplevel。
我们设置为0。
除颜色附件以外,我们还可以附加一个深度和一个模板纹理到帧缓冲对象上。
为了附加一个深度缓冲,我们可以知道那个GL_DEPTH_ATTACHMENT作为附件类型。
记住,这时纹理格式和内部格式类型(internalformat)就成了GL_DEPTH_COMPONENT去反应深度缓冲的存储格式。
附加一个模板缓冲,你要使用GL_STENCIL_ATTACHMENT作为第二个参数,把纹理格式指定为GL_STENCIL_INDEX。
也可以同时附加一个深度缓冲和一个模板缓冲为一个单独的纹理。
这样纹理的每32位数值就包含了24位的深度信息和8位的模板信息。
为了把一个深度和模板缓冲附加到一个单独纹理上,我们使用GL_DEPTH_STENCIL_ATTACHMENT类型配置纹理格式以包含深度值和模板值的结合物。
下面是一个附加了深度和模板缓冲为单一纹理的例子:
glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH24_STENCIL8,800,600,0,GL_DEPTH_STENCIL,GL_UNSIGNED_INT_24_8,NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_TEXTURE_2D,texture,0);
缓冲对象附件
在介绍了帧缓冲的可行附件类型——纹理后,OpenGL引进了渲染缓冲对象(Renderbufferobjects),所以在过去那些美好时光里纹理是附件的唯一可用的类型。
和纹理图像一样,渲染缓冲对象也是一个缓冲,它可以是一堆字节、整数、像素或者其他东西。
渲染缓冲对象的一大优点是,它以OpenGL原生渲染格式储存它的数据,因此在离屏渲染到帧缓冲的时候,这些数据就相当于被优化过的了。
渲染缓冲对象将所有渲染数据直接储存到它们的缓冲里,而不会进行针对特定纹理格式的任何转换,这样它们就成了一种快速可写的存储介质了。
然而,渲染缓冲对象通常是只写的,不能修改它们(就像获取纹理,不能写入纹理一样)。
可以用glReadPixels函数去读取,函数返回一个当前绑定的帧缓冲的特定像素区域,而不是直接返回附件本身。
因为它们的数据已经是原生格式了,在写入或把它们的数据简单地到其他缓冲的时候非常快。
当使用渲染缓冲对象时,像切换缓冲这种操作变得异常高速。
我们在每个渲染迭代末尾使用的那个glfwSwapBuffers函数,同样以渲染缓冲对象实现:我们简单地写入到一个渲染缓冲图像,最后交换到另一个里。
渲染缓冲对象对于这种操作来说很完美。
创建一个渲染缓冲对象和创建帧缓冲代码差不多:
GLuintrbo;
glGenRenderbuffers(1,&rbo);
相似地,我们打算把渲染缓冲对象绑定,这样所有后续渲染缓冲操作都会影响到当前的渲染缓冲对象:
glBindRenderbuffer(GL_RENDERBUFFER,rbo);
由于渲染缓冲对象通常是只写的,它们经常作为深度和模板附件来使用,由于大多数时候,我们不需要从深度和模板缓冲中读取数据,但仍关心深度和模板测试。
我们就需要有深度和模板值提供给测试,但不需要对这些值进行采样(sample),所以深度缓冲对象是完全符合的。
当我们不去从这些缓冲中采样的时候,渲染缓冲对象通常很合适,因为它们等于是被优化过的。
调用glRenderbufferStorage函数可以创建一个深度和模板渲染缓冲对象:
glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,800,600);
创建一个渲染缓冲对象与创建纹理对象相似,不同之处在于这个对象是专门被设计用于图像的,而不是通用目的的数据缓冲,比如纹理。
这里我们选择GL_DEPTH24_STENCIL8作为内部格式,它同时代表24位的深度和8位的模板缓冲。
最后一件还要做的事情是把帧缓冲对象附加上:
glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,rbo);
在帧缓冲项目中,渲染缓冲对象可以提供一些优化,但更重要的是知道何时使用渲染缓冲对象,何时使用纹理。
通常的规则是,如果你永远都不需要从特定的缓冲中进行采样,渲染缓冲对象对特定缓冲是更明智的选择。
如果哪天需要从比如颜色或深度值这样的特定缓冲采样数据的话,你最好还是使用纹理附件。
从执行效率角度考虑,它不会对效率有太大影响。
渲染到纹理
现在我们知道了(一些)帧缓冲如何工作的,是时候把它们用起来了。
我们会把场景渲染到一个颜色纹理上,这个纹理附加到一个我们创建的帧缓冲上,然后把纹理绘制到一个简单的四边形上,这个四边形铺满整个屏幕。
输出的图像看似和没用帧缓冲一样,但是这次,它其实是直接打印到了一个单独的四边形上面。
为什么这很有用呢?下一部分我们会看到原因。
第一件要做的事情是创建一个帧缓冲对象,并绑定它,这比较明了:
GLuintframebuffer;
glGenFramebuffers(1,&framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER,framebuffer);
下一步我们创建一个纹理图像,这是我们将要附加到帧缓冲的颜色附件。
我们把纹理的尺寸设置为窗口的宽度和高度,并保持数据未初始化:
//Generatetexture
GLuinttexColorBuffer;
glGenTextures(1,&texColorBuffer);
glBindTexture(GL_TEXTURE_2D,texColorBuffer);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,800,600,0,GL_RGB,GL_UNSIGNED_BYTE,NULL);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glBindTexture(GL_TEXTURE_2D,0);
//Attachittocurrentlyboundframebufferobject
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texColorBuffer,0);
我们同样打算要让OpenGL确定可以进行深度测试(模板测试,如果你用的话)所以我们必须还要确保向帧缓冲中添加一个深度(和模板)附件。
由于我们只采样颜色缓冲,并不采样其他缓冲,我们可以创建一个渲染缓冲对象来达到这个目的。
记住,当你不打算从指定缓冲采样的的时候,它们是一个不错的选择。
创建一个渲染缓冲对象不太难。
唯一一件要记住的事情是,我们正在创建的是一个渲染缓冲对象的深度和模板附件。
我们把它的内部给事设置为GL_DEPTH24_STENCIL8,对于我们的目的来说这个精确度已经足够了。
GLuintrbo;
glGenRenderbuffers(1,&rbo);
glBindRenderbuffer(GL_RENDERBUFFER,rbo);
glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,800,600);
glBindRenderbuffer(GL_RENDERBUFFER,0);
我们为渲染缓冲对象分配了足够的内存空间以后,我们可以解绑渲染缓冲。
接着,在做好帧缓冲之前,还有最后一步,我们把渲染缓冲对象附加到帧缓冲的深度和模板附件上:
glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,rbo);
然后我们要检查帧缓冲是否真的做好了,如果没有,我们就打印一个错误消息。
if(glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE)
cout<
延伸文章資訊
- 1Framebuffers - LearnOpenGL
Renderbuffer objects were introduced to OpenGL after textures as a possible type of framebuffer a...
- 2opengl Tutorial => Basics of framebuffers
Framebuffer is a type of buffer which stores color values, depth and stencil information of pixel...
- 3OpenGL幀快取物件(FBO:Frame Buffer Object) - 程式人生
在OpenGL渲染管線中,幾何資料和紋理經過多次轉化和多次測試,最後以二維畫素的形式顯示在螢幕上。OpenGL管線的最終渲染目的地被稱作幀快取(framebuffer ...
- 4帧缓冲
和OpenGL中的其它对象一样,我们会使用一个叫做 glGenFramebuffers 的函数来创建一个帧缓冲对象(Framebuffer Object, FBO): unsigned int ...
- 5OpenGL學習腳印: 幀緩沖對象(Frame Buffer Object) - IT閱讀
FBO中包含兩種類型的附加圖像(framebuffer-attachable): 紋理圖像和RenderBuffer圖像(texture images and renderbuffer imag...