(自用)learnOpenGL学习总结-高级OpenGL-帧缓冲Framebuffers

news2024/11/16 6:02:21

我们在之前使用了很多缓冲了:颜色缓冲、深度缓冲、模板缓冲。这些缓冲结合起来叫做帧缓冲,

其实也能从名字理解,每一帧屏幕都需要不断更新画面,对应的缓冲也需要更新。

不过上面这些都是在默认的缓冲里面做的,现在我们可以自定义帧缓冲方式。

创建帧缓冲

和之前的VBO一样,我们生成VBO需要通过glGenBuffer,帧缓冲也一样

unsigned int fbo;
glGenFramebuffers(1, &fbo);//生成
glBindFramebuffer(GL_FRAMEBUFFER, fbo);//绑定

不过还不能用,一个完整的帧缓冲需要满足以下的条件:

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

 在完成了所有附加之后可以调用一个检查函数来检查是否完整。

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
  // 执行胜利的舞蹈

不过还记得我们有个默认的帧缓冲吗,我们自定义的叫做离屏渲染off-screen rendering,要保证所有的渲染操作在主窗口中有视觉效果,我们需要再次激活默认帧缓冲,将它绑定到0

glBindFramebuffer(GL_FRAMEBUFFER, 0);

。。。glDeleteFramebuffers(1, &fbo);

用完之后记得删除。 

添加一个纹理附件

和之前创建一个纹理差不多

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

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

这里注意,没有给纹理的data参数传东西。也就是分配了内存但是没有填充他。而填充纹理将会在我们渲染到帧缓冲中进行。同样注意我们并不关心环绕方式或多级渐远纹理,我们在大多数情况下都不会需要它们。

创建完最后一件事就是附加到帧缓冲上。

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

渲染缓冲对象附件

渲染缓冲对象(Renderbuffer Object)是在纹理之后引入到OpenGL中,作为一个可用的帧缓冲附件类型的,所以在过去纹理是唯一可用的附件。和纹理图像一样,渲染缓冲对象是一个真正的缓冲,即一系列的字节、整数、像素等。渲染缓冲对象附加的好处是,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。

渲染缓冲对象直接将所有的渲染数据储存到它的缓冲中,不会做任何针对纹理格式的转换,让它变为一个更快的可写储存介质。然而,渲染缓冲对象通常都是只写的,所以你不能读取它们(比如使用纹理访问)。当然你仍然还是能够使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素。因为它的数据已经是原生的格式了,当写入或者复制它的数据到其它缓冲中时是非常快的。所以,交换缓冲这样的操作在使用渲染缓冲对象时会非常快。我们在每个渲染迭代最后使用的glfwSwapBuffers,也可以通过渲染缓冲对象实现:只需要写入一个渲染缓冲图像,并在最后交换到另外一个渲染缓冲就可以了。渲染缓冲对象对这种操作非常完美。

创建一个渲染缓冲对象吧

unsigned int 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);//附加

渲染缓冲对象能为你的帧缓冲对象提供一些优化,但知道什么时候使用渲染缓冲对象,什么时候使用纹理是很重要的。通常的规则是,如果你不需要从一个缓冲中采样数据,那么对这个缓冲使用渲染缓冲对象会是明智的选择。如果你需要从缓冲中采样颜色或深度值等数据,那么你应该选择纹理附件。性能方面它不会产生非常大的影响的。

实战

        我们将会将场景渲染到一个附加到帧缓冲对象上的颜色纹理中,之后将在一个横跨整个屏幕的四边形上绘制这个纹理。这样视觉输出和没使用帧缓冲时是完全一样的,但这次是打印到了一个四边形上。

首先在main中创建framebuffer和rbo

    //生成并绑定
	unsigned int framebuffer;
	glGenFramebuffers(1, &framebuffer);
	glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

	//生成纹理并作为一个颜色附件加到帧缓冲上
	unsigned int 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);
	glBindTexture(GL_TEXTURE_2D, 0);//解绑
	//附加到帧缓冲对象上
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);

	//创建RBO 并且给他一个深度缓冲和模板缓冲
	unsigned int rbo;
	glGenRenderbuffers(1, &rbo);
	glBindRenderbuffer(GL_RENDERBUFFER, rbo);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,800,600);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);//为渲染缓冲对象分配了足够的内存之后,我们可以解绑这个渲染缓冲
	//最后把完成的rbo加到帧缓冲上
	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);

然后给我们的帧缓冲新写一套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 = texture(screenTexture,TexCoords);

}

给新场景绑上VAO

//screen quad VAO
	unsigned int quadVAO, quadVBO;

	glGenVertexArrays(1, &quadVAO);
	glBindVertexArray(quadVAO);

	glGenBuffers(1, &quadVBO);
	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)));

然后就是主体循环中,

while (!glfwWindowShouldClose(window))
	{
		//Process Input
		processInput(window);

		
		//第一阶段,渲染自己建立的fbo 
		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

		

		//Clear Screen
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);



		....画方块部分...



		//第二阶段渲染到默认的帧缓冲,绘制四边形
		glBindFramebuffer(GL_FRAMEBUFFER, 0);//恢复默认
		//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		glClearColor(1.0f, 1.0f, 0.0f, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		screenShader->use();
		glBindVertexArray(quadVAO);
		glBindTexture(GL_TEXTURE_2D, texColorBuffer);
		glDisable(GL_DEPTH_TEST);
		glUniform1i(glGetUniformLocation(screenShader->ID, "screenTexture"), 0);
		glDrawArrays(GL_TRIANGLES, 0, 6);
		glEnable(GL_DEPTH_TEST);




		//Clean up prepare for next render loop
		glfwSwapBuffers(window);
		glfwPollEvents();

		//Recording the time
		float currentFrame = glfwGetTime();
		deltaTime = currentFrame - lastFrame;
		lastFrame = currentFrame;
	}

当在画第二部分的时候开启线框模式

不开启的时候就是之前的场景。

要注意一些事情。第一,由于我们使用的每个帧缓冲都有它自己一套缓冲,我们希望设置合适的位,调用glClear,清除这些缓冲。第二,当绘制四边形时,我们将禁用深度测试,因为我们是在绘制一个简单的四边形,并不需要关系深度测试。在绘制普通场景的时候我们将会重新启用深度测试

所以这个有什么用处呢?因为我们能够以一个纹理图像的方式访问已渲染场景中的每个像素,我们可以在片段着色器中创建出非常有趣的效果。这些有趣效果统称为后期处理(Post-processing)效果。

后期处理

既然整个场景都被渲染到了一个纹理上,我们可以简单地通过修改纹理数据创建出一些非常有意思的效果。在这一部分中,我们将会向你展示一些流行的后期处理效果,并告诉你改如何使用创造力创建你自己的效果。

让我们先从最简单的后期处理效果开始。

反相

把screen片段着色器里面修改一下

	FragColor = vec4(vec3(1.0-texture(screenTexture,TexCoords)),1.0);

灰度 

void main()
{
    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);
}

 核效果

核效果其实就是类似与图像处理里面的卷积核效果,用一个3x3的方块去扫每一个像素点,然后把9个方块的像素rgb加权到中间的像素去。可以用来做模糊处理或者一些特性提取工作。

 在片段着色器中,我们首先为周围的纹理坐标创建了一个9个vec2偏移量的数组。偏移量是一个常量,你可以按照你的喜好自定义它。之后我们定义一个核,在这个例子中是一个锐化(Sharpen)核,它会采样周围的所有像素,锐化每个颜色值。最后,在采样时我们将每个偏移量加到当前纹理坐标上,获取需要采样的纹理,之后将这些纹理值乘以加权的核值,并将它们加到一起。

//核函数
	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 kernel[9] = float[](
        -1, -1, -1,
        -1,  9, -1,
        -1, -1, -1
    );

    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] * kernel[i];

    FragColor = vec4(col, 1.0);

 

模糊 

 修改核函数可以实现不同的效果

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

也可以把时间加入到效果中,创造出玩家醉酒时的效果,或者在主角没带眼镜的时候增加模糊。模糊也能够让我们来平滑颜色值

边缘检测

一些tips:

注意,核在对屏幕纹理的边缘进行采样的时候,由于还会对中心像素周围的8个像素进行采样,其实会取到纹理之外的像素。由于环绕方式默认是GL_REPEAT,所以在没有设置的情况下取到的是屏幕另一边的像素,而另一边的像素本不应该对中心像素产生影响,这就可能会在屏幕边缘产生很奇怪的条纹。为了消除这一问题,我们可以将屏幕纹理的环绕方式都设置为GL_CLAMP_TO_EDGE。这样子在取到纹理外的像素时,就能够重复边缘的像素来更精确地估计最终的值了。

我做了一些测试:当时默认的repeat的时候边缘是这样的

可以看到一些边缘处又奇怪的东西

改成clamp_to_edge 就没有了

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

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

相关文章

【ARM Trace32(劳特巴赫) 使用介绍 3.1 -- 不 attach core 直接访问 memory】

文章目录 背景介绍背景介绍 在使用 trace32 时在有些场景需要不 attach core 然后去读写 memory,比如在某些情况下 core 已经挂死连接不上了,这个时候需要dump内存,这个时候需要怎做呢? print "test for memory access directly";SYStem.OPTION WAITRESET OF…

【Java 数据结构】优先级队列(堆)

优先级队列&#xff08;堆&#xff09; 1. 优先级队列1.1 概念 2. 优先级队列的模拟实现2.1 堆的概念2.2 堆的存储方式2.3 堆的创建2.3.1 堆向下调整2.3.2 堆的创建2.3.3 建堆的时间复杂度 2.4 堆的插入与删除2.4.1 堆的插入2.4.2 堆的删除 2.5 用堆模拟实现优先级队列 3.常用…

streampark+flink一键整库或多表同步mysql到doris实战

streamparkflink一键整库或多表同步mysql到doris实战&#xff0c;此应用一旦推广起来&#xff0c;那么数据实时异构时&#xff0c;不仅可以减少对数据库的查询压力&#xff0c;还可以减少数据同步时的至少50%的成本&#xff0c;还可以减少30%的存储成本&#xff1b; streampar…

win11安装wsl作为linux子系统并当作服务器

wsl安装 打开控制面板&#xff0c;找到启用或关闭windows功能 开启windows虚拟机监控平台和适用于Linux的Windows子系统&#xff0c;重启电脑。 打开microsoft store搜索ubuntu&#xff0c;找到合适的版本下载安装 输入wsl -l如下所示&#xff0c;即为安装成功。 安装过程比较…

WebAssembly核心编程[1]:wasm模块实例化的N种方式

当我们在一个Web应用中使用WebAssembly&#xff0c;最终的目的要么是执行wasm模块的入口程序&#xff08;通过start指令指定的函数&#xff09;&#xff0c;要么是调用其导出的函数&#xff0c;这一切的前提需要创建一个通过WebAssembly.Instance对象表示的wasm模块实例(源代码…

京东广告算法架构体系建设--高性能计算方案最佳实践 | 京东零售广告技术团队

1、前言 推荐领域算法模型的在线推理是一个对高并发、高实时有较强要求的场景。算法最初是基于Wide & Deep相对简单的网络结构进行建模&#xff0c;容易满足高实时、高并发的推理性能要求。但随着广告模型效果优化进入深水区&#xff0c;基于Transformer用户行为序列和Att…

springboot137欢迪迈手机商城设计与开发

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

HBase介绍

一、HBase简介 1.1、HBase是什么 Google在200-2006发表了GFS、MapReduce、BigTable三篇 论文 &#xff0c;号称“三驾马车”&#xff0c;开启了大数据的时代。 GFS是Google File System&#xff0c;开源实现是HDFS&#xff08;Hadoop File System&#xff09;。 MapReduce…

全流程机器视觉工程开发(三)任务前瞻 - 从opencv的安装编译说起,到图像增强和分割

前言 最近开始做这个裂缝识别的任务了&#xff0c;大大小小的问题我已经摸得差不多了&#xff0c;然后关于识别任务和分割任务我现在也弄的差不多了。 现在开始做正式的业务&#xff0c;也就是我们说的裂缝识别的任务。作为前言&#xff0c;先来说说场景&#xff1a; 现在相…

初识webpack(一)概念、入口配置、输出配置、loader等

目录 (一)概念 webpack的依赖图 (二)webpack的基本使用 (三)webpack的配置文件 1.入口(entry)配置 2.输出(output)配置 (三)loader 1.css文件处理 (1)安装css-loader和style-loader (2)在webpack.config.js中配置loader 2.less文件处理 3.postcss的使用 (1)安装…

相片修复框架-GFPGAN

一 GFPGAN 介绍 GFPGAN 是一个由腾讯 ARC 团队开发的用于人脸图像生成和优化的 GAN 模型。在github可以找到开源的代码&#xff0c;它由两个主要模块组成&#xff1a; 退化移除模块 (U-Net)&#xff1a;用于从低分辨率、低质量的人脸图像中恢复出高质量的人脸图像。 生成式脸部…

软件工程知识梳理6-运行和维护

软件维护需要的工作量很大&#xff0c;大型软件的维护成本高达开发成本的4倍左右。所以&#xff0c;软件工程的主要目的就是要提高软件的可维护性&#xff0c;减少软件维护所需要的工作量&#xff0c;降低软件系统的总成本。 定义&#xff1a;软件已经交付使用之后&#xff0c;…

【爬虫专区】批量下载PDF (无反爬)

天命&#xff1a;只要没反爬&#xff0c;一切都简单 这次爬取的是绿盟的威胁情报的PDF 先看一下结构&#xff0c;很明显就是一个for循环渲染 burp抓包会发现第二次接口请求 接口请求一次就能获取到了所有的数据 然后一个循环批量下载数据即可&#xff0c;其实没啥难度的 imp…

腾讯云幻兽帕鲁Palworld服务器价格表,2024年2月最新

腾讯云幻兽帕鲁服务器价格32元起&#xff0c;4核16G12M配置32元1个月、96元3个月、156元6个月、312元一年&#xff0c;支持4-8个玩家&#xff1b;8核32G22M幻兽帕鲁服务器115元1个月、345元3个月&#xff0c;支持10到20人在线开黑。腾讯云百科txybk.com分享更多4核8G12M、16核6…

PVE 7.4-17 中开启vGPU显卡虚拟化

要为VM提供图形引擎&#xff0c;一般分为3种&#xff1a; 1、软件模拟图形-性能差 2、显卡直通-性能最好&#xff0c;一个虚拟机独享一个显卡 3、vGPU-性能好&#xff0c;多个虚拟机共享一个显卡 目前市面上的主流GPU厂家有intel、amd、nvidia&#xff0c;本篇文章主要介绍nvi…

Intercom与HelpLook:搭建知识库哪个更符合你的需求?

在当今信息化日益发展的时代&#xff0c;知识库作为一种集中存储和管理企业信息的重要工具&#xff0c;已经被越来越多的公司所采用。它有助于企业提升效率&#xff0c;改进服务质量&#xff0c;更好地与客户进行沟通。Intercom和HelpLook近两年在这个领域的讨论度不断变高&…

网络层 IP协议(1)

前置知识 主机:配有IP地址,但是不进行路由控制的设备 路由器:既配置了IP地址,又能进行路由控制的设备 节点:主机和路由器的总称 IP协议主要完成的任务就是 地址管理和路由选择 地址管理:使用一套地址体系,将网络设备的地址描述出来 路由选择:一个数据报如何从源地址到目的地址 …

摄影分享|基于Springboot的摄影分享网站设计与实现(源码+数据库+文档)

摄影分享网站目录 目录 基于Springboot的摄影分享网站设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、图片素材管理 3、视频素材管理 4、公告信息管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐…

excel 选中指定区域

问题 excel 选中指定区域 详细问题 笔者有一个excel数据集&#xff0c;数据量较大&#xff0c;如何快速选中指定区域 解决方案 步骤1、 点击起始单元格 确定单元格坐标&#xff08;建议直接CtrlC复制至剪贴板&#xff09; 具体操作入下图所示 步骤2、 点击结束单元格 …

数与抽象之自然数

自然数 “自然数的抽象与内在属性&#xff1a;图7中的纯粹五性” “自然”是数学家对我们所熟悉的1&#xff0c;2&#xff0c;3&#xff0c;4这样的数字所赋予的称呼。自然数是最基本的数学对象&#xff0c;但它们似乎并没有引导我们去抽象地思考。毕竟&#xff0c;单单一个数…