创建帧缓冲

unsigned int fbo;
glGenFramebuffers(1, &fbo); // 第一个参数为生成的帧缓冲个数,第二个参数为帧缓冲ID的指针

绑定帧缓冲

glBindFramebuffer(GL_FRAMEBUFFER, fbo);

此时绑定的fbo能读能写,长相十分英俊

我们也可以单独设置读取的fbo和写入的fbo

glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

帧缓冲需要满足的条件

通过执行上面的步骤,我们可以创建一个fbo,但是还不能使用它,因为一个完整的帧缓冲需要满足以下条件:

  1. 附加至少一个缓冲(颜色,深度,模板)
  2. 至少有一个Attachment(颜色附件)
  3. 所有附件都必须是完整的(有内存)
  4. 每个缓冲应该都有相同的样本数

我们可以使用glCheckFramebufferStatus函数来检查帧缓冲的状态,如果状态为GL_FRAMEBUFFER_COMPLETE,则说明帧缓冲满足条件,可以用了.

GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status == GL_FRAMEBUFFER_COMPLETE) {
// use the fbo
}

附加缓冲

附加的缓冲有两种类型:

  1. 材质
  2. 渲染缓冲

附加材质

unsigned int 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); // 定义纹理大小,格式,数据
//图象从纹理图象空间映射到帧缓冲图象空间(映射需要重新构造纹理图像,这样就会造成应用到多边形上的图像失真),这时就可用glTexParmeteri()函数来确定如何把纹理象素映射成像素.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 设置纹理缩小过滤方式,LINEAR表示线性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 设置纹理放大过滤方式,LINEAR表示线性插值
glBindTexture(GL_TEXTURE_2D, 0); // 解绑纹理

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);// 将纹理attach到帧缓冲对象上

附加渲染缓冲

unsigned int rbo;
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); // 将渲染缓冲attach到帧缓冲对象上

检查帧缓冲状态

我们可以使用glCheckFramebufferStatus函数来检查帧缓冲的状态,如果状态为GL_FRAMEBUFFER_COMPLETE,则说明帧缓冲满足条件,可以用了.

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
std::cout << "Framebuffer not complete!" << std::endl;
throw std::runtime_error("Framebuffer not complete!");
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);

利用帧缓冲实现后处理

步骤:

  1. 将渲染结果写入帧缓冲
  2. 绑定默认帧缓冲
  3. 将帧缓冲中的材质提出输入到片段着色器中
  4. 在片段着色器中实现后处理

那么首先我们需要创建一个四边形.

float quadVertices[] = { // vertex attributes for a quad that fills the entire screen in Normalized Device Coordinates.
// positions // texCoords
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,

-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
unsigned int quadVAO, quadVBO;
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));

我们为其编写最简单的Shader,实现一个反相操作.

#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

void main()
{
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
TexCoords = aTexCoords;
}
#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D screenTexture;

void main()
{
FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
}

接下来更改我们的渲染循环

glBindFramebuffer(GL_FRAMEBUFFER, fbo);
//渲染
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//设置清空屏幕使用的颜色,是一个状态设置函数
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清空颜色缓冲,是一个状态使用函数,获取状态后执行
model.Draw();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
screenShader.Use();
unsigned short textureLoc = glGetUniformLocation(screenShader.shaderProgramID, "screenTexture");
glUniform1i(textureLoc, GL_TEXTURE0);
glBindVertexArray(quadVAO);
glDisable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 6);
//交换缓冲,检查并调用事件回调函数
glfwSwapBuffers(window); //交换颜色缓冲
glfwPollEvents(); //检查是否触发事件(输入),更新窗口状态,调用对应回调函数

这样就可以得到反相的效果了.
反相