当前位置: 首页 > ds >正文

【OpenGL】LearnOpenGL学习笔记16 - 帧缓冲(FBO)、渲染缓冲(RBO)

上接:https://blog.csdn.net/weixin_44506615/article/details/150696667?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl | https://github.com/Duo1J/LearnOpenGL

在这之前我们已经使用过了深度缓冲 (Depth Buffer)模板缓冲 (Stencil Buffer) 以及最初我们用来写入颜色值的颜色缓冲 (Color Buffer)
这一系列的缓冲结合起来就叫做帧缓冲 (Frame Buffer)

先来了解几个概念,最后我们会应用在 屏幕后处理 (Post Processing)

一、帧缓冲 (Frame Buffer)

GLFW为我们创建了一个默认帧缓冲,所以我们之前从未关注过它。接下来我们来自己创建一个帧缓冲并实现一些特殊的效果

类比之前,我们可以这样来创建和删除一个帧缓冲

unsigned int fbo;
glGenFramebuffers(1, &fbo);
glDeleteFramebuffers(1, &fbo);

绑定帧缓冲

// GL_FRAMEBUFFER,读取和写入都会影响
// GL_READ_FRAMEBUFFER 仅读取
// GL_DRAW_FRAMEBUFFER 仅写入
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// 绑定到默认的帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, 0);

帧缓冲创建并绑定好了,但是目前它还不完整,一个完整的帧缓冲需要满足以下条件 (这里的中文翻译不是很确切,最好记住英文名)

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

二、纹理附件 (Texture Attachment)

我们可以把一个纹理附加到帧缓冲,所有的渲染指令都将会写入到这个纹理中,我们之后在着色器中就可以很方便的使用它

创建纹理附件和创建普通纹理差不多

unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, screenWidth, screenHeight, 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);

接下来附加到帧缓冲上

// 附加帧缓冲目标
// 附件类型
// 纹理类型
// 纹理
// Mipmap级别
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

这里附件类型我们还可以附加:
深度缓冲 (GL_DEPTH_ATTACHMENT),纹理格式需要变成GL_DEPTH_COMPONENT
模板缓冲 (GL_STENCIL_ATTACHMENT),纹理格式需要变成GL_STENCIL_INDEX

同时我们也可以将深度和模板缓冲合二为一来节省资源,纹理的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 Attachment)

渲染缓冲对象 (Renderbuffer Object / RBO) 是在纹理之后引入OpenGL的,它会将数据存储为OpenGL原生的渲染格式,它是为离屏渲染 (渲染到一个不同的帧缓冲,而非默认帧缓冲) 到帧缓冲优化过的
它是一个更快的可写存储介质,不会做任何针对纹理格式的转换。通常不可读,但仍然可以使用 glReadPixels 来读取,这会从当前绑定的帧缓冲中读取,而非附件本身

创建渲染缓冲对象

unsigned int rbo;
glGenRenderbuffers(1, &rbo);

绑定

glBindRenderbuffer(GL_RENDERBUFFER, rbo);

由于RBO通常是只写的,所以经常用作深度和模板附件
创建一个深度和模板渲染缓冲对象

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, screenWidth, screenHeight);

附加到帧缓冲

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

四、屏幕后处理 (Post Processing)

接下来我们将上面的知识点应用到屏幕后处理上
屏幕后处理 就是将整个场景渲染到一张纹理上之后,再通过对该纹理的修改来创建一些效果,例如模糊、反色、泛光、暗角等等

接下来我们需要创建一个帧缓冲,然后创建并附加纹理附件和渲染缓冲对象附件,接着渲染整个场景到纹理附件上
然后在默认帧缓冲中绘制一个覆盖屏幕的矩形并采样纹理附件

创建帧缓冲、纹理附件、渲染缓冲附件并附加
Main.cpp

// 帧缓冲
unsigned int fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);// 纹理附件
unsigned int framebufferTex;
glGenTextures(1, &framebufferTex);
glBindTexture(GL_TEXTURE_2D, framebufferTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, screenWidth, screenHeight, 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);
// 附加纹理附件
// 参数1:帧缓冲的目标
// 参数2:附加的附件类型
// 参数3:附加的纹理类型
// 参数4:附加的纹理
// 参数5:mipmap级别
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, framebufferTex, 0);// 渲染缓冲附件
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, screenWidth, screenHeight);
// 附加渲染缓冲附件
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);// 检查帧缓冲完整性
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{std::cout << "[Error] Framebuffer is not complete" << std::endl;EXIT
}glBindFramebuffer(GL_FRAMEBUFFER, 0);

覆盖屏幕的矩形顶点数据和Shader
Main.cpp

// 屏幕Quad顶点
float screenQuadVertices[] = {// 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
};// 屏幕Quad顶点数据
unsigned int screenQuadVAO, screenQuadVBO;
glGenVertexArrays(1, &screenQuadVAO);
glGenBuffers(1, &screenQuadVBO);
glBindVertexArray(screenQuadVAO);
glBindBuffer(GL_ARRAY_BUFFER, screenQuadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(screenQuadVertices), &screenQuadVertices, 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
Shader screenShader("Shader/ScreenVertex.glsl", "Shader/ScreenFragment.glsl");

ScreenVertex.glsl 新建

#version 330 corelayout (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;
}

ScreenFragment.glsl 新建

#version 330 coreout vec4 FragColor;in vec2 TexCoords;uniform sampler2D screenTexture;void main()
{FragColor = texture(screenTexture, TexCoords);
}

接下来在主循环中进行绘制,先梳理一下流程

1、绑定我们创建的FBO
2、绘制场景
3、绑定默认的FBO
4、使用新创建的screenShader来绘制屏幕矩形和采样纹理附件
while (!glfwWindowShouldClose(window))
{// ...// 绑定我们创建的帧缓冲glBindFramebuffer(GL_FRAMEBUFFER, fbo);glClearColor(0.1f, 0.1f, 0.1f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);// 绘制场景// 绑定默认的帧缓冲glBindFramebuffer(GL_FRAMEBUFFER, 0);glClearColor(1.0f, 1.0f, 1.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// 绘制屏幕矩形screenShader.Use();screenShader.SetInt("screenTexture", 0);glBindVertexArray(screenQuadVAO);// 不需要深度测试glDisable(GL_DEPTH_TEST);glBindTexture(GL_TEXTURE_2D, framebufferTex);glDrawArrays(GL_TRIANGLES, 0, 6);glEnable(GL_DEPTH_TEST);glfwSwapBuffers(window);glfwPollEvents();
}

编译运行,顺利的话可以看见和之前一样的图像

启用 polygon模式 可以看见我们绘制的屏幕矩形

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

屏幕后处理
接下来就可以来实现几种屏幕后处理效果了

反色 (Inverse)

颜色取反 (1 - color)

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

反色

灰度 (Gray)

这里可以把rgb加起来除以3,但是人眼对rgb的感知不同,所以最好进行加权

void gray()
{ FragColor = texture(screenTexture, TexCoords);float average = 0.2126 * FragColor.r + 0.7152 * FragColor.g + 0.0722 * FragColor.b;FragColor = vec4(average, average, average, 1.0);
}

灰度

锐化 (Sharpen)

这里用到 卷积核 (Kernel)(算子) 矩阵,其作用是对目标位置以及周围一定范围内 (NxN 矩阵维度) 的纹理进行加权处理

const float offset = 1.0 / 300.0;  vec2 offsets[9] = vec2[](vec2(-offset,  offset), // 左上vec2( 0.0f,    offset), // 正上vec2( offset,  offset), // 右上vec2(-offset,  0.0f),   // 左vec2( 0.0f,    0.0f),   // 中vec2( offset,  0.0f),   // 右vec2(-offset, -offset), // 左下vec2( 0.0f,   -offset), // 正下vec2( offset, -offset)  // 右下
);float sharpen_kernel[9] = float[](-1, -1, -1,-1,  9, -1,-1, -1, -1
);void sharpen()
{vec3 sampleTex[9];for (int i = 0; i < 9; i++){sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));}vec3 col = vec3(0.0);for (int i = 0; i < 9; i++){col += sampleTex[i] * sharpen_kernel[i];}FragColor = vec4(col, 1.0);
}

锐化

模糊 (Blur)
vec2 blur_offsets[9] = vec2[](vec2(-1,  1),         // 左上vec2( 0.0f,    1),    // 正上vec2( 1,  1),         // 右上vec2(-1,  0.0f),      // 左vec2( 0.0f,    0.0f), // 中vec2( 1,  0.0f),      // 右vec2(-1, -1),         // 左下vec2( 0.0f,   -1),    // 正下vec2( 1, -1)          // 右下
);float blur_kernel[9] = float[](1.0 / 16, 2.0 / 16, 1.0 / 16,2.0 / 16, 4.0 / 16, 2.0 / 16,1.0 / 16, 2.0 / 16, 1.0 / 16  
);void blur()
{vec3 sampleTex[9];for (int i=0; i < 9; i++){sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));}vec3 col = vec3(0.0);for (int i = 0; i < 9; i++){col += sampleTex[i] * blur_kernel[i];}FragColor = vec4(col, 1.0);
}

模糊

边缘检测 (Edge Detection)
float edge_detection_kernel[9] = float[](1, 1, 1,1, -8, 1,1, 1, 1  
);void edge_detection()
{vec3 sampleTex[9];for (int i=0; i < 9; i++){sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));}vec3 col = vec3(0.0);for (int i = 0; i < 9; i++){col += sampleTex[i] * edge_detection_kernel[i];}FragColor = vec4(col, 1.0);
}

边缘检测

完整代码可在顶部git仓库中找到

http://www.xdnf.cn/news/19065.html

相关文章:

  • 【JUC】——线程池
  • 点评项目(Redis中间件)第一部分Redis基础
  • docker run 后报错/bin/bash: /bin/bash: cannot execute binary file总结
  • 边缘计算:一场由物理定律发起的“计算革命”
  • 预测模型及超参数:2.传统机器学习:PLS及其改进
  • HarmonyOS 高效数据存储全攻略:从本地优化到分布式实战
  • 从 GRIT 到 WebUI:Chromium 内置资源加载与前端展示的完整链路解析
  • AI Agent 发展趋势与架构演进
  • 稳敏双态融合架构--架构师的练就
  • 【MES】工业4.0智能制造数字化工厂(数字车间、MES、ERP)解决方案:智能工厂体系架构、系统集成以及智能设计、生产、管理、仓储物流等
  • uvloop深度实践:从原理到高性能异步应用实战
  • http请求能支持多大内容的请求
  • 通义万相音频驱动视频模型Wan2.2-S2V重磅开源
  • 安卓接入通义千问AI的实现记录
  • 欧盟《人工智能法案》生效一年主要实施进展概览(二)
  • React 组件命名规范:为什么必须大写首字母蛊傲
  • 【Datawhale之Happy-LLM】Encoder-only模型篇 task05精华~
  • 计算神经科学数学建模编程深度前沿方向研究(下)
  • 医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(一)
  • 卷积神经网络CNN
  • Xposed框架实战指南:从原理到你的第一个模块
  • 面试之JVM
  • Java并发编程深度解析:从互斥锁到StampedLock的高性能锁优化之路
  • 计算机视觉:从 “看见” 到 “理解”,解锁机器感知世界的密码
  • 嵌入式(day34) http协议
  • 快速了解卷积神经网络
  • 【软考论文】论DevOps及其应用
  • C#由Dictionary不正确释放造成的内存泄漏问题与GC代系
  • 红黑树下探玄机:C++ mapmultimap 的幕后之旅
  • Java大厂面试实录:从Spring Boot到Kubernetes的全链路技术突围