实践项目:https://github.com/JackieLong/OpenGL/tree/main/project_framebuffer_test
由以下几个缓冲组成:
// *
// *第一步:创建,和其他OpenGL对象一样创建
// *
GLuint fbo;
glGenFrameBuffers(1, &fbo); // 创建FrameBuffer对象。
glBindFrameBuffer(GL_FRAMEBUFFER, fbo); // 读取帧缓冲操作都将作用在这个fbo上,一般就是用这个。 glBindFrameBuffer(GL_DRAW_FRAMEBUFFER, fbo); // 写操作(渲染、清除)作用在这个fbo上。 glBindFrameBuffer(GL_READ_FRAMEBUFFER, fbo); // 读操作作用在这个fbo上。
// * // *第二步:创建附件,并绑定到帧缓冲中,下面会讲 // * ……; // 创建附件并绑定到帧缓冲上。
// * // *第三步:检查帧缓冲完整性 // * if( glCheckFramebufferStatus( GL_FRAMEBUFFER ) != GL_FRAMEBUFFER_COMPLETE ) { cout << “ERROR::FRAMEBUFFER:: FRAMEBUFFER is not complete” << endl; }
// * // *第四步:使用 // * else { // do some junk }
// * // *第五步:结尾 // * glBindFramebuffer(GL_FRAMEBUFFER, 0); // 记得恢复成默认缓冲,这步不是必须,但是你得知道它的意义 // OpenGL只会从默认帧缓冲,读取显示画面。
glDeleteFrameBuffer(1, &fbo); // 如果不再使用了,别忘记删除,和其他openGL对象一样。
// *
// * 函数解释
// *
GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); // 检查帧缓冲是否完整
// result可能取值如下:
// GL_FRAMEBUFFER_COMPLETE: 帧缓冲是完整的,可以正常使用。
// GL_FRAMEBUFFER_UNDEFINED:
// if target is the default framebuffer, but the default framebuffer does not exist.
// GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
// if any of the framebuffer attachment points are framebuffer incomplete.
// GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
// if the framebuffer does not have at least one image attached to it.
// GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER
// if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color attachment
// point( s ) named by GL_DRAW_BUFFERi.
// GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER
// if GL_READ_BUFFER is not GL_NONE and the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is
// GL_NONE for the color attachment point named by GL_READ_BUFFER.
// GL_FRAMEBUFFER_UNSUPPORTED
// if the combination of internal formats of the attached images violates an implementation -
// dependent set of restrictions.
// GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE
// if the value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; if
// the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or , if the attached
// images are a mix of renderbuffers and textures, the value of GL_RENDERBUFFER_SAMPLES does not match
// the value of GL_TEXTURE_SAMPLES.
// also if the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not the same for all attached textures; or , if the attached images are a mix of renderbuffers and textures, the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached textures.
// GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS
// if any framebuffer attachment is layered, and any populated attachment
// is not layered, or if all populated color attachments are not from textures of the same target.
// 0
// an error is occurs.
离屏渲染(Off-screen Rendering):当前帧缓冲不是默认帧缓存,渲染指令会将结果输出到当前帧缓冲中,窗口不会有任何视觉输出。
```cpp
glBindFrameBuffer(GL_FRAMEBUFFER, 0); // 激活默认帧缓冲,窗口才会输出渲染结果。
二、附件
在完整性检查执行之前,我们需要给帧缓冲附加一个附件。
附件是一个内存位置,它能够作为帧缓冲的一个缓冲,可以将它想象为一个图像。当创建一个附件的时候我们有两个选项:
- 纹理类型
- 渲染缓冲对象(Renderbuffer Object)类型
附件使用过程如下,以纹理类型附件为例:
// ***********************************************
// ***********第一步:创建(纹理类型)附件
// ***********************************************
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture); // 暂时绑定为当前激活纹理
// 创建纹理,不同的附件类型(颜色也叫纹理、深度、模板),设置纹理的格式参数有不同,这里以创建纹理附件为例
// 本质上都是开辟一块内存,只不过内存格式有不同。
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, // 和创建纹理一样的参数
800, 600, // 设置为一个指定大小(比如屏幕大小)
0, // 一直为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);
// ***********************************************
// ***********第二步:绑定
// ***********************************************
// 例子:绑定一个颜色纹理附件
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
glBindTexture(GL_TEXTURE_2D, 0); // 恢复成默认绑定是好习惯。
glFrameBufferTexture2D
void glFramebufferTexture2D(GLenum target, GLenum attachment,GLenum textarget, GLuint texture, GLint level);
// 为target帧缓冲绑定纹理类型附件。
// target: 帧缓冲目标,GL_FRAMEBUFFER、GL_DRAW_FRAMEBUFFER、GL_READ_FRAMEBUFFER
// attachment: 我们想要附加的附件类型。
// GL_COLOR_ATTACHMENTi 颜色附件
// GL_DEPTH_ATTACHMENT 深度附件
// GL_STENCIL_ATTACHMENT 模板附件
// GL_DEPTH_STENCIL_ATTACHMENT 深度和模板在一个附件上。
// textarget: 希望附加的纹理类型,GL_TEXTURE_2D
// texture: 纹理id
// level: 多级渐远纹理的级别。我们将它保留为0。
纹理类型附件
颜色附件
不同纹理附件,创建过程基本相似,只不过参数不太一样。
GLuint texture;
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);
glBindTexture(GL_TEXTURE_2D, 0); // 好习惯
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
深度附件
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0,
GL_DEPTH_COMPONENT, // format
800, 600,
0,
GL_DEPTH_COMPONENT, // interal format
GL_UNSIGNED_INT, // 一个深度值大小
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_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture, 0);
模板附件
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0,
GL_STENCIL_INDEX, // format
800, 600,
0,
GL_STENCIL_INDEX, // interal format
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_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);
深度/模板附件
深度值和模板(一般)都保存在一个附件上。
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0,
GL_DEPTH24_STENCIL8, // format,24bit深度值,8bit模板值
800, 600,
0,
GL_DEPTH_STENCIL, // interal format
GL_UNSIGNED_INT_24_8, //
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_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);
渲染缓冲类型附件
RenderBuffer Object,在纹理之后引入。在处理离屏渲染上,渲染缓冲比纹理更高效。
渲染缓冲直接存储所有的渲染数据,不做任何纹理格式的转换,让它变为一个更快的可写储存介质。然而,渲染缓冲对象通常都是只写的,所以你不能读取它们(比如使用纹理访问)。当然你仍然还是能够使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素。
因为它的数据已经是原生的格式了,当写入或者复制它的数据到其它缓冲中时是非常快的。所以,交换缓冲这样的操作在使用渲染缓冲对象时会非常快。我们在每个渲染迭代最后使用的glfwSwapBuffers,也可以通过渲染缓冲对象实现。
渲染缓冲对象的创建和使用如下:
// ***********************************************
// ***********第一步:创建
// ***********************************************
GLuint rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo); // 暂时绑定为当前渲染缓冲对象。
// 创建深度和模板渲染缓冲对象:开辟内存
// 一般情况深度、模板缓冲都无需采样读取,因此非常适合实用渲染类型缓冲。
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600); // 下面有解释
// ***********************************************
// ***********第二步:绑定
// ***********************************************
// 绑定渲染缓冲对象到帧缓冲,这里是创建了一个深度模板渲染缓冲对象。下面有解释:
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
glBindRenderbuffer(GL_RENDERBUFFER, 0); // 好习惯
glRenderbufferStorage
// 开辟渲染缓冲内存,类比于glTexImage2D
void glRenderbufferStorage(
GLenum target, // 必须是GL_RENDERBUFFER
GLenum internalformat, // internal format,存储格式
GLsizei width, // 渲染缓冲的像素宽高。
GLsizei height);
glFrameBufferRenderbuffer
// 绑定渲染缓冲到帧缓冲
void glFramebufferRenderbuffer( GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
// target: 帧缓冲对象,可能值如下:
// GL_DRAW_FRAMEBUFFER
// GL_READ_FRAMEBUFFER
// GL_FRAMEBUFFER,等同于GL_DRAW_FRAMEBUFFER
// attachment: 我们想要附加的附件类型。
// GL_COLOR_ATTACHMENTi 颜色附件
// GL_DEPTH_ATTACHMENT 深度附件
// GL_STENCIL_ATTACHMENT 模板附件
// GL_DEPTH_STENCIL_ATTACHMENT 深度和模板在一个附件上。
// renderbuffertarget: 必须是GL_RENDERBUFFER
// renderbuffer: 渲染缓冲id,由glBindRenderbuffer得到
如何选择附件
如果不需要从缓冲中采样(读取)数据,选择渲染缓冲对象,比如深度值、模板值一般不会去读取。
如果需要从缓冲中采样颜色、深度值等,则选择纹理附件,性能方面它不会产生非常大的影响的。
三、帧缓冲例子
GLuint createFrameBuffer()
{
// ***************************************************
// ******* 第一步,创建帧缓冲
// ***************************************************
GLuint frameBuffer;
glGenFramebuffers( 1, &frameBuffer ); // 创建自己的帧缓冲
glBindFramebuffer( GL_FRAMEBUFFER, frameBuffer ); // 暂时绑定到当前帧缓冲(代替默认帧缓冲),目的是保证后续的附件绑定能生效,在最后别忘记恢复到默认的帧缓冲
// ***************************************************
// ******* 第二步,绑定颜色附件,创建为纹理类型,原因是后续我们需要进行采样(读取数据)。
// ***************************************************
glGenTextures( 1, &textureColorBuffer );
glBindTexture( GL_TEXTURE_2D, textureColorBuffer ); // 暂时绑定到当前激活纹理单元,目的是保证后面开辟纹理内存能生效,最后别忘了恢复成默认
// 开辟纹理内存
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, // 和纹理一样的参数类型
ScreenWidth, ScreenHeight, // 设置为一个指定大小(比如屏幕大小)
0, // 总是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, textureColorBuffer, 0 );
// ***************************************************
// ******* 第三步,绑定一个深度和模板附件(深度和模板数据都保存在一块内存中),使用的渲染缓冲类型,原因是我们只会写入数据,不进行采样读取,使用渲染缓冲,性能会非常好。
// ***************************************************
GLuint renderbuffer;
glGenRenderbuffers( 1, &renderbuffer );
glBindRenderbuffer( GL_RENDERBUFFER, renderbuffer ); // 暂时绑定,目的保证下面的开辟内存能生效
glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, ScreenWidth, ScreenHeight );
glBindRenderbuffer( GL_RENDERBUFFER, 0 ); // 恢复成默认绑定
glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderbuffer ); // 绑定渲染缓冲
// ***************************************************
// ******* 第四步:检查帧缓冲的完整性,是否所有必需的工作都做完了,绑定附件就是必须的。
// ***************************************************
if( glCheckFramebufferStatus( GL_FRAMEBUFFER ) != GL_FRAMEBUFFER_COMPLETE )
{
cout << "ERROR::FRAMEBUFFER:: FRAMEBUFFER is not complete" << endl;
}
glBindFramebuffer( GL_FRAMEBUFFER, 0 ); // 恢复到默认的帧缓冲,后续有需要,随时绑定使用。
return frameBuffer;
}