C++ OpenGL 帧缓冲(Framebuffer)理论与实现
OpenGL FBO理论基础
帧缓冲对象(FBO)是OpenGL中一种允许我们创建自定义渲染目标的对象。不同于默认的帧缓冲(直接渲染到屏幕),自定义帧缓冲让我们可以:
- 离屏渲染:将场景渲染到纹理而非屏幕
- 后期处理:对整个渲染场景应用特效
- 多重采样抗锯齿:提高图像质量
- 阴影映射:实现动态阴影
帧缓冲通常包含以下附件:
- 颜色附件:存储颜色信息的纹理或渲染缓冲
- 深度附件:存储深度信息
- 模板附件:存储模板信息
代码实现
下面是使用GLFW和GLAD实现帧缓冲的基本示例,包括创建一个简单的后处理效果:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
// 窗口尺寸
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
// 着色器程序
unsigned int shaderProgram;
unsigned int screenShaderProgram;
// 帧缓冲对象
unsigned int FBO;
unsigned int textureColorBuffer;
unsigned int RBO;
// 顶点着色器源码
const char *vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor;
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
}
)";
// 片段着色器源码
const char *fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main()
{
FragColor = vec4(ourColor, 1.0);
}
)";
// 屏幕顶点着色器源码
const char *screenVertexShaderSource = R"(
#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;
}
)";
// 屏幕片段着色器源码(包含一个简单的反相后处理效果)
const char *screenFragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D screenTexture;
void main()
{
vec3 col = texture(screenTexture, TexCoords).rgb;
// 反相效果
FragColor = vec4(1.0 - col, 1.0);
}
)";
// 编译着色器
unsigned int compileShader(const char* source, GLenum type) {
unsigned int shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
int success;
char infoLog[512];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
}
return shader;
}
// 创建着色器程序
unsigned int createShaderProgram(const char* vertexSource, const char* fragmentSource) {
unsigned int vertexShader = compileShader(vertexSource, GL_VERTEX_SHADER);
unsigned int fragmentShader = compileShader(fragmentSource, GL_FRAGMENT_SHADER);
unsigned int program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
int success;
char infoLog[512];
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return program;
}
// 初始化帧缓冲
void setupFramebuffer() {
// 创建帧缓冲对象
glGenFramebuffers(1, &FBO);
glBindFramebuffer(GL_FRAMEBUFFER, FBO);
// 创建颜色附件纹理
glGenTextures(1, &textureColorBuffer);
glBindTexture(GL_TEXTURE_2D, textureColorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 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, textureColorBuffer, 0);
// 创建渲染缓冲对象(深度和模板缓冲)
glGenRenderbuffers(1, &RBO);
glBindRenderbuffer(GL_RENDERBUFFER, RBO);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, RBO);
// 检查帧缓冲完整性
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
int main() {
// 初始化GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 创建窗口
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Framebuffer Example", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// 初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 创建着色器程序
shaderProgram = createShaderProgram(vertexShaderSource, fragmentShaderSource);
screenShaderProgram = createShaderProgram(screenVertexShaderSource, screenFragmentShaderSource);
// 设置帧缓冲
setupFramebuffer();
// 三角形顶点数据
float triangleVertices[] = {
// 位置 // 颜色
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f
};
// 屏幕四边形顶点数据
float quadVertices[] = {
// 位置 // 纹理坐标
-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
};
// 三角形VAO设置
unsigned int triangleVAO, triangleVBO;
glGenVertexArrays(1, &triangleVAO);
glGenBuffers(1, &triangleVBO);
glBindVertexArray(triangleVAO);
glBindBuffer(GL_ARRAY_BUFFER, triangleVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangleVertices), triangleVertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// 屏幕四边形VAO设置
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);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
// 设置屏幕着色器的采样器
glUseProgram(screenShaderProgram);
glUniform1i(glGetUniformLocation(screenShaderProgram, "screenTexture"), 0);
// 渲染循环
while (!glfwWindowShouldClose(window)) {
// 清空缓冲
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
// 第一阶段:渲染到帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, FBO);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 绘制三角形
glUseProgram(shaderProgram);
glBindVertexArray(triangleVAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// 第二阶段:渲染到默认帧缓冲,应用后处理效果
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT);
// 绘制屏幕四边形
glUseProgram(screenShaderProgram);
glBindVertexArray(quadVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureColorBuffer);
glDrawArrays(GL_TRIANGLES, 0, 6);
// 交换缓冲并检查事件
glfwSwapBuffers(window);
glfwPollEvents();
}
// 清理资源
glDeleteVertexArrays(1, &triangleVAO);
glDeleteBuffers(1, &triangleVBO);
glDeleteVertexArrays(1, &quadVAO);
glDeleteBuffers(1, &quadVBO);
glDeleteFramebuffers(1, &FBO);
glDeleteTextures(1, &textureColorBuffer);
glDeleteRenderbuffers(1, &RBO);
glDeleteProgram(shaderProgram);
glDeleteProgram(screenShaderProgram);
glfwTerminate();
return 0;
}
代码解析
这个示例演示了帧缓冲的基本实现,包括以下关键步骤:
-
帧缓冲创建:
- 生成帧缓冲对象
- 创建颜色附件作为纹理
- 创建渲染缓冲对象作为深度和模板附件
- 检查帧缓冲完整性
-
双通道渲染:
- 第一通道渲染彩色三角形到帧缓冲
- 第二通道渲染屏幕四边形,将帧缓冲的纹理应用反相效果
-
后处理效果:
- 示例中实现了一个简单的反相效果
- 可以扩展为更复杂的效果如模糊、锐化等
帧缓冲的应用场景
- 后期处理:实现模糊、锐化、HDR、颜色校正等效果
- 阴影映射:生成深度图用于阴影计算
- 环境反射/折射:创建环境贴图
- 延迟渲染:将光照计算延迟到可见片段
- 粒子效果:离屏渲染实现特殊效果
这个示例仅展示了基本用法,帧缓冲在实际应用中通常会结合更复杂的着色器和多重缓冲技术。在实际开发中需要根据具体的渲染需求调整帧缓冲的配置和附件类型。