【OpenGL实践12】关于缓存区Framebuffer的运用

news2024/11/16 21:56:20

文章目录

  • 一、说明
  • 二、帧缓冲区
  • 三、创建新的帧缓冲区
  • 四、附属装饰
    • 4.1 纹理图像
    • 4.2 渲染缓冲区对象图像
  • 五、使用帧缓冲区
    • 5.1 后期处理
    • 5.2 更改代码
  • 六、后期处理效果
    • 6.1 色彩处理
    • 6.2 模糊
    • 6.3 Sobel算子
  • 七、结论
  • 练习

一、说明

关于FrameBuffer的使用,是OpenGL的高级使用方式,而这种新诞生的功能有些未知的技术需要逐步熟悉,本篇就是针对它的图像处理功能展开陈述。

二、帧缓冲区

在前面的章节中,我们了解了 OpenGL 提供的不同类型的缓冲区:颜色、深度和模板缓冲区。这些缓冲区像任何其他 OpenGL 对象一样占用视频内存,但到目前为止,除了在创建 OpenGL 上下文时指定像素格式之外,我们几乎无法控制它们。这种缓冲区组合称为默认帧缓冲区,正如您所见,帧缓冲区是内存中可以渲染的区域。如果您想获取渲染结果并对其进行一些附加操作(例如许多现代游戏中的后处理),该怎么办?

在本章中,我们将讨论帧缓冲区对象,这是创建额外的帧缓冲区以进行渲染的一种方法。帧缓冲区的伟大之处在于它们允许您将场景直接渲染到纹理,然后可以在其他渲染操作中使用该纹理。在讨论了帧缓冲区对象的工作原理之后,我将向您展示如何使用它们对上一章的场景进行后处理。

三、创建新的帧缓冲区

您需要的第一件事是一个帧缓冲区对象来管理新的帧缓冲区。

GLuint frameBuffer;
glGenFramebuffers(1, &frameBuffer);

此时您还不能使用此帧缓冲区,因为它还不完整。在以下情况下,帧缓冲区通常是完整的:

至少已附加一个缓冲区(例如颜色、深度、模板)
必须至少有一种颜色附件(OpenGL 4.1 及更早版本)
所有附件均已完成(例如,纹理附件需要保留内存)
所有附件必须具有相同数量的多重样本
您可以随时通过调用glCheckFramebufferStatus并检查它是否返回来检查帧缓冲区是否完整GL_FRAMEBUFFER_COMPLETE。其他返回值请参阅参考资料。您不必执行此检查,但验证通常是一件好事,就像检查着色器是否成功编译一样。

现在,让我们绑定帧缓冲区来使用它。

glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);

第一个参数指定帧缓冲区应附加到的目标。OpenGL 在这里区分了GL_DRAW_FRAMEBUFFER和GL_READ_FRAMEBUFFER。绑定到 read 的帧缓冲区用于 的调用glReadPixels,但由于这种区别在普通应用程序中相当罕见,因此您可以使用 将您的操作应用于两者GL_FRAMEBUFFER。

glDeleteFramebuffers(1, &frameBuffer);

完成后别忘了清理。

四、附属装饰

仅当已分配内存来存储结果时,您的帧缓冲区才能用作渲染目标。这是通过为每个缓冲区附加图像(颜色、深度、模板或深度和模板的组合)来完成的。有两种对象可以用作图像:纹理对象和渲染缓冲区对象。前者的优点是它们可以直接在着色器中使用,如前面的章节所示,但根据您的实现,渲染缓冲区对象可能会作为渲染目标进行更优化。

4.1 纹理图像

我们希望能够渲染一个场景,然后在另一个渲染操作中使用颜色缓冲区中的结果,因此纹理在这种情况下是理想的选择。创建纹理作为新帧缓冲区颜色缓冲区的图像与创建任何纹理一样简单。

GLuint 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);

该纹理与您之前见过的纹理之间的区别在于NULLdata 参数的值。这是有道理的,因为这次将通过渲染操作动态创建数据。由于这是颜色缓冲区的图像,因此format和internalformat参数受到更多限制。该format参数通常仅限于 或GL_RGB以及GL_RGBA颜色internalformat格式。

我在这里选择了默认的 RGB 内部格式,但您可以尝试更奇特的格式,例如GL_RGB10如果您想要 10 位颜色精度。我的应用程序的分辨率为 800 x 600 像素,因此我使这个新的颜色缓冲区与该分辨率相匹配。分辨率不必与默认帧缓冲区相匹配,但glViewport如果您决定改变,请不要忘记调用。

剩下的一件事是将图像附加到帧缓冲区。

glFramebufferTexture2D(
    GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0 );

第二个参数意味着您可以有多个颜色附件。片段着色器可以通过使用我们之前使用的函数将out变量链接到附件来向其中任何一个输出不同的数据。glBindFragDataLocation我们现在将坚持使用一种输出。最后一个参数指定图像应附加到的 mipmap 级别。 Mipmapping 没有任何用处,因为在使用颜色缓冲区图像进行后期处理时,它将以其原始大小进行渲染。

4.2 渲染缓冲区对象图像

由于我们使用深度和模板缓冲区来渲染可爱的旋转立方体,因此我们也必须创建它们。 OpenGL 允许您将它们组合成一张图像,因此我们必须再创建一张图像才能使用帧缓冲区。尽管我们可以通过创建另一个纹理来做到这一点,但将这些缓冲区存储在渲染缓冲区对象中会更有效,因为我们只对读取着色器中的颜色缓冲区感兴趣。

GLuint rboDepthStencil;
glGenRenderbuffers(1, &rboDepthStencil);
glBindRenderbuffer(GL_RENDERBUFFER, rboDepthStencil);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);

创建渲染缓冲区对象与创建纹理非常相似,不同之处在于该对象被设计用作图像而不是像纹理那样的通用数据缓冲区。我在这里选择了GL_DEPTH24_STENCIL8内部格式,它适合分别保存 24 位和 8 位精度的深度缓冲区和模板缓冲区。

glFramebufferRenderbuffer(
    GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rboDepthStencil  );

连接起来也很容易。您可以稍后通过调用删除该对象,就像删除任何其他对象一样glDeleteRenderbuffers。

五、使用帧缓冲区

选择帧缓冲区作为渲染目标非常容易,实际上只需一次调用即可完成。

glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
在此调用之后,所有渲染操作都将其结果存储在新创建的帧缓冲区的附件中。要切换回屏幕上可见的默认帧缓冲区,只需传递0.

glBindFramebuffer(GL_FRAMEBUFFER, 0);
请注意,虽然只有默认的帧缓冲区在屏幕上可见,但您可以读取当前与调用绑定的任何帧缓冲区,glReadPixels只要它不仅绑定到GL_DRAW_FRAMEBUFFER.

5.1 后期处理

在当今的游戏中,后处理效果似乎几乎与屏幕上渲染的实际场景一样重要,并且实际上可以使用不同的技术来实现一些令人惊叹的结果。实时图形中的后处理效果通常在片段着色器中实现,并将渲染的场景作为纹理形式的输入。帧缓冲区对象允许我们使用纹理来包含颜色缓冲区,因此我们可以使用它们为后处理效果准备输入。

要使用着色器为先前渲染到纹理的场景创建后处理效果,通常将其渲染为屏幕填充的 2D 矩形。这样,应用了效果的原始场景就会以其原始大小填充屏幕,就好像它首先被渲染到默认帧缓冲区一样。

当然,您可以利用帧缓冲区发挥创意,通过从不同角度多次渲染场景并将其显示在监视器或最终图像中的其他对象上,使用它们在游戏世界中执行从门户到摄像机的任何操作。这些用途更加具体,因此我将它们作为练习留给您。

5.2 更改代码

不幸的是,在这里逐步介绍对代码的更改有点困难,特别是如果您偏离了此处的示例代码。现在您已经了解了帧缓冲区是如何创建和绑定的,并且只要小心一点,您应该能够做到这一点。让我们全局地完成这里的步骤。

首先尝试创建帧缓冲区并检查它是否完整。尝试将其绑定为渲染目标,您会看到屏幕变黑,因为场景不再渲染到默认帧缓冲区。尝试更改场景的清晰颜色并读取它以glReadPixels检查场景是否正确渲染到新的帧缓冲区。
接下来,尝试创建一个新的着色器程序、顶点数组对象和顶点缓冲区对象,以 2D(而不是 3D)渲染事物。切换回默认帧缓冲区非常有用,这样可以轻松查看结果。您的 2D 着色器不需要变换矩阵。尝试以这种方式在 3D 旋转立方体场景前面渲染一个矩形。
最后,尝试将 3D 场景渲染到您创建的帧缓冲区,并将矩形渲染到默认帧缓冲区。现在尝试使用矩形中帧缓冲区的纹理来渲染场景。
我选择仅使用 2 个位置坐标和 2 个纹理坐标进行 2D 渲染。我的 2D 着色器如下所示:

#version 150 core
in vec2 position;
in vec2 texcoord;
out vec2 Texcoord;
void main()
{
    Texcoord = texcoord;
    gl_Position = vec4(position, 0.0, 1.0);
}
#version 150 core
in vec2 Texcoord;
out vec4 outColor;
uniform sampler2D texFramebuffer;
void main()
{
    outColor = texture(texFramebuffer, Texcoord);
}

使用此着色器,程序的输出应该与您了解帧缓冲区之前相同。渲染一帧大致如下所示:

// Bind our framebuffer and draw 3D scene (spinning cube)
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
glBindVertexArray(vaoCube);
glEnable(GL_DEPTH_TEST);
glUseProgram(sceneShaderProgram);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texKitten);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texPuppy);

// Draw cube scene here

// Bind default framebuffer and draw contents of our framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindVertexArray(vaoQuad);
glDisable(GL_DEPTH_TEST);
glUseProgram(screenShaderProgram);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texColorBuffer);

glDrawArrays(GL_TRIANGLES, 0, 6);

3D 和 2D 绘图操作都有自己的顶点数组(立方体与四边形)、着色器程序(3D 与 2D 后处理)和纹理。您可以看到,绑定颜色缓冲区纹理与绑定常规纹理一样简单。请注意,glBindTexture更改 OpenGL 状态之类的调用相对昂贵,因此请尝试将它们保持在最低限度。

我认为,无论我在这里解释程序的总体结构有多好,你们中的一些人只是喜欢看一些新的示例代码,也许还可以运行diff它以及上一章中的代码。

六、后期处理效果

我现在将讨论各种有趣的后处理效果、它们的工作原理以及它们的外观。

6.1 色彩处理

反转颜色是图像处理程序中常见的一个选项,但您也可以使用着色器自己完成!

由于颜色值是从0.0到 1.0的浮点值,因此反转通道就像计算一样简单1.0 - channel。如果对每个通道(红色、绿色、蓝色)执行此操作,您将得到反转的颜色。在片段着色器中,可以这样完成。

outColor = vec4(1.0, 1.0, 1.0, 1.0) - texture(texFramebuffer, Texcoord);

这也会影响 alpha 通道,但这并不重要,因为 alpha 混合默认情况下是禁用的。

在这里插入图片描述

通过计算每个通道的平均强度可以简单地实现颜色灰度化。

outColor = texture(texFramebuffer, Texcoord);
float avg = (outColor.r + outColor.g + outColor.b) / 3.0;
outColor = vec4(avg, avg, avg, 1.0);

这工作得很好,但人类对绿色最敏感,对蓝色最不敏感,因此更好的转换可以使用加权通道。

outColor = texture(texFramebuffer, Texcoord);
float avg = 0.2126 * outColor.r + 0.7152 * outColor.g + 0.0722 * outColor.b;
outColor = vec4(avg, avg, avg, 1.0);

6.2 模糊

有两种众所周知的模糊技术:框模糊和高斯模糊。后者会产生更高质量的结果,但前者更容易实现,并且仍然相当接近高斯模糊。

在这里插入图片描述

模糊是通过对像素周围的像素进行采样并计算平均颜色来完成的。

const float blurSizeH = 1.0 / 300.0;
const float blurSizeV = 1.0 / 200.0;
void main()
{
    vec4 sum = vec4(0.0);
    for (int x = -4; x <= 4; x++)
        for (int y = -4; y <= 4; y++)
            sum += texture(
                texFramebuffer,
                vec2(Texcoord.x + x * blurSizeH, Texcoord.y + y * blurSizeV)
            ) / 81.0;
    outColor = sum;
}

您可以看到总共采集了 81 个样本。您可以更改 X 轴和 Y 轴上的样本量来控制模糊量。这些blurSize变量用于确定每个样本之间的距离。较高的样本数和较低的样本距离会产生更好的近似值,但也会迅速降低性能,因此请尝试找到一个良好的平衡点。

6.3 Sobel算子

Sobel算子常用于边缘检测算法中,我们来看看它长什么样。

片段着色器如下所示:

vec4 top         = texture(texFramebuffer, vec2(Texcoord.x, Texcoord.y + 1.0 / 200.0));
vec4 bottom      = texture(texFramebuffer, vec2(Texcoord.x, Texcoord.y - 1.0 / 200.0));
vec4 left        = texture(texFramebuffer, vec2(Texcoord.x - 1.0 / 300.0, Texcoord.y));
vec4 right       = texture(texFramebuffer, vec2(Texcoord.x + 1.0 / 300.0, Texcoord.y));
vec4 topLeft     = texture(texFramebuffer, vec2(Texcoord.x - 1.0 / 300.0, Texcoord.y + 1.0 / 200.0));
vec4 topRight    = texture(texFramebuffer, vec2(Texcoord.x + 1.0 / 300.0, Texcoord.y + 1.0 / 200.0));
vec4 bottomLeft  = texture(texFramebuffer, vec2(Texcoord.x - 1.0 / 300.0, Texcoord.y - 1.0 / 200.0));
vec4 bottomRight = texture(texFramebuffer, vec2(Texcoord.x + 1.0 / 300.0, Texcoord.y - 1.0 / 200.0));
vec4 sx = -topLeft - 2 * left - bottomLeft + topRight   + 2 * right  + bottomRight;
vec4 sy = -topLeft - 2 * top  - topRight   + bottomLeft + 2 * bottom + bottomRight;
vec4 sobel = sqrt(sx * sx + sy * sy);
outColor = sobel;

就像模糊着色器一样,以有趣的方式采集并组合一些样本。您可以在其他地方阅读有关技术细节的更多信息。

七、结论

着色器的一个很酷的事情是,由于显卡具有巨大的并行处理能力,您可以实时地按像素操作图像。毫不奇怪,像 Photoshop 这样的新版本软件使用显卡来加速图像处理操作!还有许多更复杂的效果,例如 HDR、运动模糊和 SSAO(屏幕空间环境光遮挡),但这些效果比单个着色器涉及的工作量要多一些,因此它们超出了本章的范围。

练习

尝试通过添加另一个帧缓冲区来实现两次通过的高斯模糊效果。 (解决方案)
尝试在 3D 场景中添加一个面板,从不同角度显示该场景。 (解决方案)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1697917.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

spark实战:实现分区内求最大值,分区间求和以及获取日志文件固定日期的请求路径

spark实战&#xff1a;实现分区内求最大值&#xff0c;分区间求和以及获取日志文件固定日期的请求路径 Apache Spark是一个广泛使用的开源大数据处理框架&#xff0c;以其快速、易用和灵活的特点而受到开发者的青睐。在本文中&#xff0c;我们将通过两个具体的编程任务来展示S…

spiderfoot一键扫描IP信息(KALI工具系列九)

目录 1、KALI LINUX简介 2、spiderfoot工具简介 3、在KALI中使用spiderfoot 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、命令示例 4.1 web访问 4.2 扫描并进行DNS解析 4.3 全面扫描 5、总结 1、KALI LINUX简介 Kali Linux 是一个功能强大、多才多…

Spring Boot集成testcontainers快速入门Demo

1.什么是testcontainers&#xff1f; Testcontainers 是一个用于创建临时 Docker 容器进行单元测试的 Java 库。当我们想要避免使用实际服务器进行测试时&#xff0c;它非常有用。&#xff0c;官网介绍称支持50多种组件。​ 应用场景 数据访问层集成测试&#xff1a; 使用My…

掌握ASPICE标准:汽车软件测试工程师的专业发展路径

掌握ASPICE标准&#xff1a;汽车软件测试工程师的专业发展路径 文&#xff1a;领测老贺 随着新能源汽车在中国的蓬勃发展&#xff0c;智能驾驶技术的兴起&#xff0c;汽车测试工程师的角色变得愈发关键。这一变革带来了前所未有的挑战和机遇&#xff0c;要求测试工程师不仅要具…

Matlab中函数或变量 ‘eeglab‘ 无法识别

EEGLAB 没有安装或添加到 MATLAB 路径中&#xff1a; 确保已经安装了 EEGLAB&#xff0c;并且将其添加到 MATLAB 的路径中。您可以通过在 MATLAB 命令窗口中运行 which eeglab 来检查是否能够找到 EEGLAB。 EEGLAB 函数路径设置错误&#xff1a; 如果已经安装了 EEGLAB&#x…

Mac | Mac 移动硬盘无法分区问题

现象问题 电脑配置&#xff1a;MacBook Pro M1&#xff0c;系统 Sonoma Mac 系统新升级了 Sonoma&#xff0c;结果出现各种问题。外接屏幕居然不能旋转 90 &#xff0c;查了一下是Sonoma系统导致的&#xff0c;以及莫名发热的问题。想着要么回退一下系统算了&#xff0c;于是网…

Sql Sever删除数据库时提示数据库正在被使用,解决办法

报错解释&#xff1a; 当您尝试删除SQL Server中的某个对象&#xff08;如数据库、表等&#xff09;时&#xff0c;如果有程序或进程正在使用该对象&#xff0c;您可能会收到一个错误信息&#xff0c;提示该对象正被使用。这通常是因为还有一个或多个数据库连接仍然保持着对该…

使用libtorch加载YOLOv8生成的torchscript文件进行目标检测

在网上下载了60多幅包含西瓜和冬瓜的图像组成melon数据集&#xff0c;使用 LabelMe 工具进行标注&#xff0c;然后使用 labelme2yolov8 脚本将json文件转换成YOLOv8支持的.txt文件&#xff0c;并自动生成YOLOv8支持的目录结构&#xff0c;包括melon.yaml文件&#xff0c;其内容…

网络通信(二)

UDP通信 特点&#xff1a;无连不是先接、不可靠通信 不事先建立连接&#xff1b;发送端每次把要发送的数据&#xff08;限制在64KB内&#xff09;、接收端IP、等信息封装成一个数据包&#xff0c;发出去就不管了 java提供了一个java.net.DatagramSocket类来实现UDP通信 Dat…

20.SkyWalking

一.简介 SkyWalking用于应用性能监控、分布式链路跟踪、诊断&#xff1a; 参考连接如下&#xff1a; https://github.com/apache/skywalking https://skywalking.apache.org/docs/ 二.示例 通过官网连接进入下载页面&#xff1a;https://archive.apache.org/dist/skywalkin…

普通人转行程序员,最大的困难是找不到就业方向

来百度APP畅享高清图片 大家好&#xff0c;这里是程序员晚枫&#xff0c;小破站也叫这个名。 我自己是法学院毕业后&#xff0c;通过2年的努力才转行程序员成功的。[吃瓜R] 我发现对于一个外行来说&#xff0c;找不到一个适合自己的方向&#xff0c;光靠努力在一个新的行业里…

美团Java社招面试题真题,最新面试题

如何处理Java中的内存泄露&#xff1f; 1、识别泄露&#xff1a; 使用内存分析工具&#xff08;如Eclipse Memory Analyzer Tool、VisualVM&#xff09;来识别内存泄露的源头。 2、代码审查&#xff1a; 定期进行代码审查&#xff0c;关注静态集合类属性和监听器注册等常见内…

Leetcode算法题笔记(3)

目录 矩阵101. 生命游戏解法一解法二 栈102. 移掉 K 位数字解法一 103. 去除重复字母解法一 矩阵 101. 生命游戏 根据 百度百科 &#xff0c; 生命游戏 &#xff0c;简称为 生命 &#xff0c;是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子…

Redis简介与安装到python的调用

前言 本文只不对redis的具体用法做详细描述&#xff0c;做简单的介绍&#xff0c;安装&#xff0c;和python代码调用详细使用教程可查看一下网站 https://www.runoob.com/redis/redis-tutorial.html https://pypi.org/project/redis/ 官方原版: https://redis.io/ 中文官网:…

齿轮常见故障学习笔记

大家好&#xff0c;这期咱们聊一聊齿轮常见的失效形式&#xff0c;查阅了相关的资料&#xff0c;做个笔记分享给大家&#xff0c;共同学习。 介绍 齿轮故障可能以多种方式发生。如果在设计阶段本身就尽量防止这些故障的产生&#xff0c;则可以产生改更为优化的齿轮设计。齿轮…

Top期刊:针对论文Figure图片的7个改进建议

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 通过对来自细胞生物学、生理学和植物学领域的580篇论文&#xff0c;进行检查和归纳总结&#xff0c;来自德国德累斯顿工业大学的Helena Jambor及合作者&#xff0c;在PLOS Bio…

五分钟搭建一个Suno AI音乐站点

五分钟搭建一个Suno AI音乐站点 在这个数字化时代&#xff0c;人工智能技术正以惊人的速度改变着我们的生活方式和创造方式。音乐作为一种最直接、最感性的艺术形式&#xff0c;自然也成为了人工智能技术的应用场景之一。今天&#xff0c;我们将以Vue和Node.js为基础&#xff…

第12章-ADC采集电压和显示 基于STM32的ADC—电压采集(详细讲解+HAL库)

我们的智能小车用到了ADC测量电池电压的功能&#xff0c;这章节我们做一下。 我们的一篇在这里 第一篇 什么是ADC 百度百科介绍&#xff1a; 我们知道万用表 电压表可以测量电池&#xff0c;或者电路电压。那么我们是否可以通过单片机获得电压&#xff0c;方便我 们监控电池状…

WPF学习日常篇(一)--开发界面视图布局

接下来开始日常篇&#xff0c;我在主线篇&#xff08;正文&#xff09;中说过要介绍一下我的界面排布&#xff0c;科学的排布才更科学更有效率的进行敲代码和开发。日常篇中主要记录我的一些小想法和所考虑的一些细节。 一、主界面设置 主界面分为左右两部分&#xff0c;分为…

查分数组总结

文章目录 查分数组定义应用举例LeetCode 1109 题「[航班预订统计] 查分数组定义 差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减。 通过这个 diff 差分数组是可以反推出原始数组 nums 的&#xff0c;代码逻辑如下&#xff1a; int res[diff.size()]; // 根…