帧缓冲

文章推薦指數: 80 %
投票人數:10人

和OpenGL中的其它对象一样,我们会使用一个叫做 glGenFramebuffers 的函数来创建一个帧缓冲对象(Framebuffer Object, FBO): unsigned int fbo; glGenFramebuffers(1, &fbo);. Togglenavigation LearnOpenGLCN 主页 目录 简介 入门 OpenGL 创建窗口 你好,窗口 你好,三角形 着色器 纹理 变换 坐标系统 摄像机 复习 光照 颜色 基础光照 材质 光照贴图 投光物 多光源 复习 模型加载 Assimp 网格 模型 高级OpenGL 深度测试 模板测试 混合 面剔除 帧缓冲 立方体贴图 高级数据 高级GLSL 几何着色器 实例化 抗锯齿 高级光照 高级光照 Gamma校正 阴影 阴影映射 点阴影 CSM 法线贴图 视差贴图 HDR 泛光 延迟着色法 SSAO PBR 理论 光照 IBL 漫反射辐照 镜面IBL 实战 调试 文本渲染 2D游戏 Breakout 准备工作 渲染精灵 关卡 碰撞 球 碰撞检测 碰撞处理 粒子 后期处理 道具 音效 渲染文本 结语 历史存档 代码仓库 搜索 上一节 下一节 GitHub 支持原作者 帧缓冲 创建一个帧缓冲 渲染到纹理 后期处理 反相 灰度 核效果 帧缓冲 原文 Framebuffers 作者 JoeyDeVries 翻译 Krasjet 校对 暂未校对 到目前为止,我们已经使用了很多屏幕缓冲了:用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲。

这些缓冲结合起来叫做帧缓冲(Framebuffer),它被储存在内存中。

OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲。

我们目前所做的所有操作都是在默认帧缓冲的渲染缓冲上进行的。

默认的帧缓冲是在你创建窗口的时候生成和配置的(GLFW帮我们做了这些)。

有了我们自己的帧缓冲,我们就能够有更多方式来渲染了。

你可能不能很快理解帧缓冲的应用,但渲染你的场景到不同的帧缓冲能够让我们在场景中加入类似镜子的东西,或者做出很酷的后期处理效果。

首先我们会讨论它是如何工作的,之后我们将来实现这些炫酷的后期处理效果。

创建一个帧缓冲 和OpenGL中的其它对象一样,我们会使用一个叫做glGenFramebuffers的函数来创建一个帧缓冲对象(FramebufferObject,FBO): unsignedintfbo; glGenFramebuffers(1,&fbo); 这种创建和使用对象的方式我们已经见过很多次了,所以它的使用函数也和其它的对象类似。

首先我们创建一个帧缓冲对象,将它绑定为激活的(Active)帧缓冲,做一些操作,之后解绑帧缓冲。

我们使用glBindFramebuffer来绑定帧缓冲。

glBindFramebuffer(GL_FRAMEBUFFER,fbo); 在绑定到GL_FRAMEBUFFER目标之后,所有的读取和写入帧缓冲的操作将会影响当前绑定的帧缓冲。

我们也可以使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER,将一个帧缓冲分别绑定到读取目标或写入目标。

绑定到GL_READ_FRAMEBUFFER的帧缓冲将会使用在所有像是glReadPixels的读取操作中,而绑定到GL_DRAW_FRAMEBUFFER的帧缓冲将会被用作渲染、清除等写入操作的目标。

大部分情况你都不需要区分它们,通常都会使用GL_FRAMEBUFFER,绑定到两个上。

不幸的是,我们现在还不能使用我们的帧缓冲,因为它还不完整(Complete),一个完整的帧缓冲需要满足以下的条件: 附加至少一个缓冲(颜色、深度或模板缓冲)。

至少有一个颜色附件(Attachment)。

所有的附件都必须是完整的(保留了内存)。

每个缓冲都应该有相同的样本数。

如果你不知道什么是样本,不要担心,我们将在之后的教程中讲到。

从上面的条件中可以知道,我们需要为帧缓冲创建一些附件,并将附件附加到帧缓冲上。

在完成所有的条件之后,我们可以以GL_FRAMEBUFFER为参数调用glCheckFramebufferStatus,检查帧缓冲是否完整。

它将会检测当前绑定的帧缓冲,并返回规范中这些值的其中之一。

如果它返回的是GL_FRAMEBUFFER_COMPLETE,帧缓冲就是完整的了。

if(glCheckFramebufferStatus(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) //执行胜利的舞蹈 之后所有的渲染操作将会渲染到当前绑定帧缓冲的附件中。

由于我们的帧缓冲不是默认帧缓冲,渲染指令将不会对窗口的视觉输出有任何影响。

出于这个原因,渲染到一个不同的帧缓冲被叫做离屏渲染(Off-screenRendering)。

要保证所有的渲染操作在主窗口中有视觉效果,我们需要再次激活默认帧缓冲,将它绑定到0。

glBindFramebuffer(GL_FRAMEBUFFER,0); 在完成所有的帧缓冲操作之后,不要忘记删除这个帧缓冲对象: glDeleteFramebuffers(1,&fbo); 在完整性检查执行之前,我们需要给帧缓冲附加一个附件。

附件是一个内存位置,它能够作为帧缓冲的一个缓冲,可以将它想象为一个图像。

当创建一个附件的时候我们有两个选项:纹理或渲染缓冲对象(RenderbufferObject)。

纹理附件 当把一个纹理附加到帧缓冲的时候,所有的渲染指令将会写入到这个纹理中,就像它是一个普通的颜色/深度或模板缓冲一样。

使用纹理的优点是,所有渲染操作的结果将会被储存在一个纹理图像中,我们之后可以在着色器中很方便地使用它。

为帧缓冲创建一个纹理和创建一个普通的纹理差不多: unsignedinttexture; 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); 主要的区别就是,我们将维度设置为了屏幕大小(尽管这不是必须的),并且我们给纹理的data参数传递了NULL。

对于这个纹理,我们仅仅分配了内存而没有填充它。

填充这个纹理将会在我们渲染到帧缓冲之后来进行。

同样注意我们并不关心环绕方式或多级渐远纹理,我们在大多数情况下都不会需要它们。

Important 如果你想将你的屏幕渲染到一个更小或更大的纹理上,你需要(在渲染到你的帧缓冲之前)再次调用glViewport,使用纹理的新维度作为参数,否则只有一小部分的纹理或屏幕会被渲染到这个纹理上。

现在我们已经创建好一个纹理了,要做的最后一件事就是将它附加到帧缓冲上了: glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texture,0); glFrameBufferTexture2D有以下的参数: target:帧缓冲的目标(绘制、读取或者两者皆有) attachment:我们想要附加的附件类型。

当前我们正在附加一个颜色附件。

注意最后的0意味着我们可以附加多个颜色附件。

我们将在之后的教程中提到。

textarget:你希望附加的纹理类型 texture:要附加的纹理本身 level:多级渐远纹理的级别。

我们将它保留为0。

除了颜色附件之外,我们还可以附加一个深度和模板缓冲纹理到帧缓冲对象中。

要附加深度缓冲的话,我们将附件类型设置为GL_DEPTH_ATTACHMENT。

注意纹理的格式(Format)和内部格式(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); 渲染缓冲对象附件 渲染缓冲对象(RenderbufferObject)是在纹理之后引入到OpenGL中,作为一个可用的帧缓冲附件类型的,所以在过去纹理是唯一可用的附件。

和纹理图像一样,渲染缓冲对象是一个真正的缓冲,即一系列的字节、整数、像素等。

渲染缓冲对象附加的好处是,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。

渲染缓冲对象直接将所有的渲染数据储存到它的缓冲中,不会做任何针对纹理格式的转换,让它变为一个更快的可写储存介质。

然而,渲染缓冲对象通常都是只写的,所以你不能读取它们(比如使用纹理访问)。

当然你仍然还是能够使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素。

因为它的数据已经是原生的格式了,当写入或者复制它的数据到其它缓冲中时是非常快的。

所以,交换缓冲这样的操作在使用渲染缓冲对象时会非常快。

我们在每个渲染迭代最后使用的glfwSwapBuffers,也可以通过渲染缓冲对象实现:只需要写入一个渲染缓冲图像,并在最后交换到另外一个渲染缓冲就可以了。

渲染缓冲对象对这种操作非常完美。

创建一个渲染缓冲对象的代码和帧缓冲的代码很类似: unsignedintrbo; glGenRenderbuffers(1,&rbo); 类似,我们需要绑定这个渲染缓冲对象,让之后所有的渲染缓冲操作影响当前的rbo: glBindRenderbuffer(GL_RENDERBUFFER,rbo); 由于渲染缓冲对象通常都是只写的,它们会经常用于深度和模板附件,因为大部分时间我们都不需要从深度和模板缓冲中读取值,只关心深度和模板测试。

我们需要深度和模板值用于测试,但不需要对它们进行采样,所以渲染缓冲对象非常适合它们。

当我们不需要从这些缓冲中采样的时候,通常都会选择渲染缓冲对象,因为它会更优化一点。

创建一个深度和模板渲染缓冲对象可以通过调用glRenderbufferStorage函数来完成: glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,800,600); 创建一个渲染缓冲对象和纹理对象类似,不同的是这个对象是专门被设计作为帧缓冲附件使用的,而不是纹理那样的通用数据缓冲(GeneralPurposeDataBuffer)。

这里我们选择GL_DEPTH24_STENCIL8作为内部格式,它封装了24位的深度和8位的模板缓冲。

最后一件事就是附加这个渲染缓冲对象: glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,rbo); 渲染缓冲对象能为你的帧缓冲对象提供一些优化,但知道什么时候使用渲染缓冲对象,什么时候使用纹理是很重要的。

通常的规则是,如果你不需要从一个缓冲中采样数据,那么对这个缓冲使用渲染缓冲对象会是明智的选择。

如果你需要从缓冲中采样颜色或深度值等数据,那么你应该选择纹理附件。

性能方面它不会产生非常大的影响的。

渲染到纹理 既然我们已经知道帧缓冲(大概)是怎么工作的了,是时候实践它们了。

我们将会将场景渲染到一个附加到帧缓冲对象上的颜色纹理中,之后将在一个横跨整个屏幕的四边形上绘制这个纹理。

这样视觉输出和没使用帧缓冲时是完全一样的,但这次是打印到了一个四边形上。

这为什么很有用呢?我们会在下一部分中知道原因。

首先要创建一个帧缓冲对象,并绑定它,这些都很直观: unsignedintframebuffer; glGenFramebuffers(1,&framebuffer); glBindFramebuffer(GL_FRAMEBUFFER,framebuffer); 接下来我们需要创建一个纹理图像,我们将它作为一个颜色附件附加到帧缓冲上。

我们将纹理的维度设置为窗口的宽度和高度,并且不初始化它的数据: //生成纹理 unsignedinttexColorBuffer; 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); //将它附加到当前绑定的帧缓冲对象 glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texColorBuffer,0); 我们还希望OpenGL能够进行深度测试(如果你需要的话还有模板测试),所以我们还需要添加一个深度(和模板)附件到帧缓冲中。

由于我们只希望采样颜色缓冲,而不是其它的缓冲,我们可以为它们创建一个渲染缓冲对象。

还记得当我们不需要采样缓冲的时候,渲染缓冲对象是更好的选择吗? 创建一个渲染缓冲对象不是非常复杂。

我们需要记住的唯一事情是,我们将它创建为一个深度和模板附件渲染缓冲对象。

我们将它的内部格式设置为GL_DEPTH24_STENCIL8,对我们来说这个精度已经足够了。

unsignedintrbo; 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) std::cout<



請為這篇文章評分?