LearnOpenGL——帧缓冲
帧缓冲
用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲。这些缓冲结合起来叫做帧缓冲(Framebuffer),它被储存在GPU内存中的某处。我们可以自己定义frame buffer来实现独特的效果。
一、创建一个帧缓冲
与VBO、VAO类似,我们需要定义一个Unsigned int 类型的fbo,使用glGenFramebuffers函数来创建帧缓冲对象FBO
unsigned int fbo;
glGenFramebuffers(1, &fbo);
我们创建FBO对象之后,需要使用glBindFramebuffer函数将它绑定为激活的帧缓冲,再经过一系列操作之后,解绑帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
......
glDeleteFramebuffers(1, &fbo);
在绑定到GL_FRAMEBUFFER目标之后,所有的读取和写入帧缓冲的操作将会影响当前绑定的帧缓冲。但是我们现在还不能使用这个帧缓冲(不完整),一个完整的帧缓冲需要满足以下的条件:
- 附加至少一个缓冲(颜色、深度或者模板缓冲)
- 至少一个颜色附件
- 所有附件都必须是完整的(保留了内存)
- 每个缓冲都应该有相同的样本数(sample)
我们需要为帧缓冲附加一个附件,附件是一个内存位置,作为帧缓冲的其中一个缓冲,可以想象为一张图片。附件有两种:纹理附件、渲染缓冲对象。见后面介绍。
我们可以使用glCheckFramebufferStatus函数来检查帧缓冲是否完整,如果它返回的是GL_FRAMEBUFFER_COMPLETE,帧缓冲就是完整的了。
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
检查资源完整之后所有的渲染操作将会渲染到当前绑定帧缓冲的附件中。此时帧缓冲还不是默认帧缓冲,所以不会有任何效果,我们可以通过“离屏渲染”——需要再次激活帧缓冲,绑定到0
glBindFramebuffer(GL_FRAMEBUFFER, 0);
纹理附件
当把一个纹理附加到帧缓冲的时候,所有的渲染指令将会写入到这个纹理中,就像它是一个普通的颜色/深度或模板缓冲一样。所有的渲染操作结果会被存在一个纹理图像中,在着色器中很方便用。
为帧缓冲创建一个纹理和普通创建纹理差不多。
主要区别是,我们将维度设置为屏幕大小(不是必须的)、没有真的图像纹理来填充(在glTexImage2D函数width和height是屏幕宽高、data处值为null);我们并不关心环绕方式或多级渐远纹理
unsigned int texture;
glGenTexture(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);
如果你想将你的屏幕渲染到一个更小或更大的纹理上,你需要(在渲染到你的帧缓冲之前)再次调用glViewport,使用纹理的新维度作为参数,否则只有一小部分的纹理或屏幕会被渲染到这个纹理上。
创建好纹理后,就可以附加到帧缓冲上。
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
- target:帧缓冲的目标(绘制、读取都有)
- attachment:附加的类型(此处为颜色附件)。如果是深度缓冲GL_DEPTH_ATTACHMENT,此时纹理格式和内部格式为GL_DEPTH_COMPONENT;若是模板缓冲GL_STENCIL_ATTACHMENT,纹理格式设置为GL_STENCIL_INDEX
- textarget:附加的纹理类型
- texture:附加的纹理本身
- level:mipmap级别,此处为0
也可以将深度和模板缓冲设置为单独的纹理,该纹理32位数值中24位为深度值,8位为模板信息。
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);
渲染缓冲对象附件
渲染缓冲对象(Renderbuffer Object)是在纹理之后引入到OpenGL中,和纹理图像一样,渲染缓冲对象是一个真正的缓冲,即一系列的字节、整数、像素等。渲染缓冲对象附加的好处是,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。
渲染缓冲对象 直接将所有的渲染数据储存到它的缓冲中,让它变为一个更快的可写储存介质。渲染缓冲对象通常都是只写的,不能通过纹理访问读取,可以使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素
创建一个渲染缓冲对象,然后再绑定激活
unsigned int rbo;
glGenRenderbuffers(1,&rbo);
glBindRenderbuffer(GL_RENDERBUFFER,rbo);
由于渲染缓冲对象通常都是只写的,它们会经常用于深度和模板附件,当我们不需要从这些缓冲中采样的时候,通常都会选择渲染缓冲对象,因为它会更优化一点。创建一个深度和模板渲染缓冲对象可以通过调用glRenderbufferStorage函数来完成:
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
最后一件事就是附加这个渲染缓冲对象
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
如果你不需要从一个缓冲中采样数据,那么对这个缓冲使用渲染缓冲对象会是明智的选择。如果你需要从缓冲中采样颜色或深度值等数据,那么你应该选择纹理附件。
二、渲染到纹理
我们将会将场景渲染到一个附加到帧缓冲对象上的颜色纹理中,之后将在一个横跨整个屏幕的四边形上绘制这个纹理。
先创建帧缓冲,并绑定
unsigned int framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
再生成纹理图像,将其作为颜色附件附加到帧缓冲上
// 生成纹理
unsigned int texColorBuffer;
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能够进行深度测试(如果你需要的话还有模板测试),所以我们还需要添加一个深度(和模板)附件到帧缓冲中。创建一个渲染缓冲对象,并将渲染缓冲对象附加到帧缓冲的深度和模板附件上:
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glBindRenderbuffer