LearnOpenGL——HDR、Bloom学习笔记

news2024/12/24 11:26:37

LearnOpenGL——HDR、Bloom学习笔记

  • HDR
    • 一、基本概念
    • 二、浮点帧缓冲 Floating Point Framebuffer
    • 三、色调映射 Tone Mapping
      • Reinhard色调映射
      • 曝光色调映射
  • Bloom
    • 一、提取亮色
    • 二、高斯模糊
    • 三、将两个纹理进行混合

HDR

一、基本概念

显示器被限制只能显示0.0-1.0的颜色,但是在光照方程中没有这个限制,通过将颜色超过1.0范围,我们将其称为HDR(High Dynamic Range, 高动态范围)。可以增加颜色表现力,亮的更亮,暗的更暗。
我们允许用HDR渲染来获取大范围的黑暗与明亮的场景细节,最后将所有HDR值转换成在[0.0, 1.0]范围的LDR(Low Dynamic Range,低动态范围)。将HDR转换到LDR的过程叫做色调映射(Tone Mapping)
在这里插入图片描述

二、浮点帧缓冲 Floating Point Framebuffer

我们想要将颜色信息存储的范围超过0.0-1.0范围。如果我们使用GL_RGB这样的格式创建图像时,OpenGL会自动将颜色值在存储到帧缓冲之前就约束到0.0-1.0之间。如果我们使用了浮点帧缓冲—— GL_RGB16F, GL_RGBA16F, GL_RGB32F 或者GL_RGBA32F,此时浮点帧缓冲可以存储超过0.0-1.0范围的浮点值,非常适合HDR渲染

可以直接在创建图像纹理时,使用 GL_RGB16F

glBindTexture(GL_TEXTURE_2D, colorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, 
	SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);

默认的帧缓冲默认一个颜色分量只占用8位,当使用32位每颜色分量的浮点帧缓冲时,就相当于要4倍的内存,所以除非非常高的精度,一般16够用

三、色调映射 Tone Mapping

色调映射(Tone Mapping)是一个损失很小的转换浮点颜色值至我们所需的LDR[0.0, 1.0]范围内的过程,通常会伴有特定的风格的色平衡(Stylistic Color Balance)。其中一个最简单的色调映射算法就是Reinhard色调映射。

Reinhard色调映射

这个算法平均地将所有亮度值分散到LDR上。我们将此算法应用到之前的片元着色器上,并增加一个Gamma矫正

void main()
{
	const float gamma = 2.2;
	vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
	// Reinhard色调映射
	vec3 mapped = hdrColor / (hdrColor + vec3(1.0));
	//Gamma矫正
	mapped = pow(mapped, vec3(1.0/gamma));
	color = vec4(mapped,1.0);
}

在这里插入图片描述

曝光色调映射

我们还可以引入曝光参数,如果我们有一个场景要展现日夜交替,我们当然会在白天使用低曝光,在夜间使用高曝光,就像人眼调节方式一样。

uniform float exposure;

void main()
{
	const float gamma = 2.2;
	vec3 hdrColor = texture(hdrBuffer,TexCoords).rgb;
	// 曝光色调映射
    vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure);
 	//Gamma矫正
 	mapped = pow(mapped,vec3(1.0/gamma));
	color = vec4(mapped,1.0); 
}

高曝光值会使隧道的黑暗部分显示更多的细节,然而低曝光值会显著减少黑暗区域的细节,但允许我们看到更多明亮区域的细节。

在这里插入图片描述

Bloom

光流,或发光效果,是通过一种叫做泛光(Bloom)的后期处理效果来实现的。
在这里插入图片描述
实现Bloom效果的基本步骤:提取HDR颜色缓冲以及图中明亮的部分,然后对明亮的部分进行模糊处理,然后将结果添加到原始HDR场景图像上面

在这里插入图片描述

一、提取亮色

第一步我们要从渲染出来的场景中提取两张图片。如果渲染两次场景,每次使用不同的着色器渲染到不同的帧缓冲中,这样开销很大。我们可以使用MRT(Multiple Render Targets,多渲染目标),这样就可以指定多个片元着色器的输出。我么可以通过MRT,在单独的一个渲染pass中提取两张图片。

在片元着色器的输出前,我们指定一个布局location标识符,这样我们便可控制一个片元着色器写入到哪个颜色缓冲:

layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 BrightColor; 

有多个颜色缓冲附加到了当前绑定的帧缓冲对象上时才可以使用多个片元着色器输出。我们在进行颜色附件的附加时使用 GL_COLOR_ATTACHMENT0 和 GL_COLOR_ATTACHMENT1

GLuint hdrFBO;
glGenFrameBuffer(1, &hdrFBO);
glBindFrameBuffer(GL_FRAMEBUFFER, hdrFBO);
GLuint colorBuffers[2];
glGenTextures(2, colorBuffers);
for(GLuint i = 0; i < 2; i++)
{
	glBindTexture(GL_TEXTURE_2D, colorBuffer[i]);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 
		SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glFramebufferTexture2D(GL_FRAMEBUFFER, 
		GL_COLOR_ATTACHMENT0 + i, 
		GL_TEXTURE_2D, colorBuffers[i], 0);
}

然后告知OpenGL我们正在通过glDrawBuffers渲染到多个颜色缓冲

GLuint attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(2, attachments);

在片元着色器中,在计算完光照后,然后传递给第一个变量FragColor,然后使用当前储存在FragColor的东西来决定它的亮度是否超过了阈值。我们通过FragColor.rgb, vec3(0.2126, 0.7152, 0.0722)的点乘来计算光照亮度

#version 330 core
layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 BrightColor;
[...]

void main()
{            
    [...] // first do normal lighting calculations and output results
    FragColor = vec4(lighting, 1.0f);
    // Check whether fragment output is higher than threshold, if so output as brightness color
    float brightness = dot(FragColor.rgb, vec3(0.2126, 0.7152, 0.0722));
    if(brightness > 1.0)
        BrightColor = vec4(FragColor.rgb, 1.0);
}

二、高斯模糊

高斯模糊基于高斯曲线,高斯曲线通常被描述为一个钟形曲线,中间的值达到最大化,随着距离的增加,两边的值不断减少。
在这里插入图片描述
以一个32×32的模糊kernel为例,高斯模糊可以分成两个部分:水平模糊和竖直模糊(32次+32次),这样可以避免二维一次性采样(32次×32次)的采样数过大。
这样的话需要我们对图像进行采样两次,帧缓冲是最好的办法。

高斯模糊的片元着色器

#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D image;
uniform bool horizontal;
uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);

void main()
{
	vec2 tex_offset = 1.0/textureSize(image,0);
	vec3 result = texture(image,TexCoords).rgb * weight[0];
	if(horizontal)
	{
		for(int i = 1; i<5; i++)
		{
			result += texture(image, TexCoords + 
				vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
			result += texture(image, TexCoords - 
				vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
		}
	}
	else
    {
        for(int i = 1; i < 5; ++i)
        {
            result += texture(image, TexCoords + 
            	vec2(0.0, tex_offset.y * i)).rgb * weight[i];
            result += texture(image, TexCoords - 
            	vec2(0.0, tex_offset.y * i)).rgb * weight[i];
        }
    }
    FragColor = vec4(result, 1.0);
}

然后我们为图像创建两个基本帧缓冲,每个只有一个颜色缓冲纹理

GLuint pingpongFBO[2];
GLuint pingpongBuffer[2];
glGenFramebuffers(2, pingpongFBO);
glGenTextures(2, pingpongBuffer);
for (GLuint i = 0; i < 2; i++)
{
    glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);
    glBindTexture(GL_TEXTURE_2D, pingpongBuffer[i]);
    glTexImage2D(
        GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL
    );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glFramebufferTexture2D(
        GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongBuffer[i], 0
    );
}

得到一个HDR纹理后,我们用提取出来的亮区纹理填充一个帧缓冲,然后对其模糊处理10次(5次垂直5次水平):

GLboolean horizontal = true, first_iteration = true;
GLuint amount = 10;
shaderBlur.Use();
for (GLuint i = 0; i < amount; i++)
{
    glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]); 
    glUniform1i(glGetUniformLocation(shaderBlur.Program, "horizontal"), horizontal);
    glBindTexture(
        GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongBuffers[!horizontal]
    ); 
    RenderQuad();
    horizontal = !horizontal;
    if (first_iteration)
        first_iteration = false;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
  • pingpongFBO是一个包含两个FBO的数组,一个用于水平模糊,一个用于垂直模糊。horizontal变量决定了使用哪一个FBO
  • 这行代码绑定了一个2D纹理,它是将要被模糊的图像。在第一次迭代时,它绑定的是colorBuffers[1](这是原始图像的纹理),在随后的迭代中,它绑定的是另一个FBO的颜色缓冲区(pingpongBuffers[!horizontal]),这样就可以在之前模糊的结果上继续模糊。

三、将两个纹理进行混合

最终的像素着色器,要注意的是我们要在应用色调映射之前添加泛光效果。这样添加的亮区的泛光,也会柔和转换为LDR,光照效果相对会更好。

#version 330 core
out vec4 FragColor;
in vec2 TexCoords;

uniform sampler2D scene;
uniform sampler2D bloomBlur;
uniform float exposure;

void main()
{             
    const float gamma = 2.2;
    vec3 hdrColor = texture(scene, TexCoords).rgb;      
    vec3 bloomColor = texture(bloomBlur, TexCoords).rgb;
    hdrColor += bloomColor; // additive blending
    // tone mapping
    vec3 result = vec3(1.0) - exp(-hdrColor * exposure);
    // also gamma correct while we're at it       
    result = pow(result, vec3(1.0 / gamma));
    FragColor = vec4(result, 1.0f);
}

在这里插入图片描述

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

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

相关文章

“AI+Security”系列第2期(四):AI/机器学习供应链攻击

近日&#xff0c;由安全极客、Wisemodel 社区和 InForSec 网络安全研究国际学术论坛联合主办的“AISecurity”系列第二期线上活动如期举行。此次活动的主题为“对抗&#xff01;大模型自身安全的攻防博弈”&#xff0c;旨在深入探讨和分析人工智能和机器学习领域中的安全问题。…

武汉流星汇聚:亚马逊返校季学习用品热销,精准布局助力卖家成功

随着夏日的余温逐渐消散&#xff0c;新学期的钟声悄然临近&#xff0c;家长与学生们纷纷投入到新学期的准备工作中&#xff0c;而亚马逊作为全球领先的电商平台&#xff0c;再次迎来了学习用品销售的火爆季节。数据显示&#xff0c;过去30天内&#xff0c;“back to school”&a…

罗德与施瓦茨(RS)RTP164、RTP134,RTP084,RTP064示波器

罗德示波器系列RTP164租赁RTP134收购RTP084/RTP064/RTP034 概述 数字示波器是数据采集&#xff0c;A/D转换&#xff0c;软件编程等一系列的技术制造出来的高性能示波器&#xff0c;是电子类学科设计、制造和维修产品过程中不可或缺的工具。R&SRTP164示波器可以提供16GHz的…

U盘安装Ubuntu24.04,乌邦图,UltralISO

文章目录 前言通过UltraISO&#xff0c;制作启动U盘下载镜像制作工具UltraISO(软碟通)下载ubuntu镜像文件制作启动U盘 安装ubuntu设置root密码&#xff0c;并登陆root 前言 在Ubuntu作为主流的linux系统&#xff0c;有时候使用VMware安装使用&#xff0c;总归有一定的性能损耗…

深度学习 --- VGG16各层feature map可视化(JupyterNotebook实战)

VGG16模块的可视化 VGG16简介&#xff1a; VGG是继AlexNet之后的后起之秀&#xff0c;相对于AlexNet他有如下特点&#xff1a; 1&#xff0c;更深的层数&#xff01;相对于仅有8层的AlexNet而言&#xff0c;VGG把层数增加到了16和19层。 2&#xff0c;更小的卷积核&#xff01;…

大数据-98 Spark 集群 Spark Streaming 基础概述 架构概念 执行流程 优缺点

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

C语言与Python的区别

一、言语类型Python是一种基于解说器的言语&#xff0c;解说器会逐行读取代码&#xff1b;首先将Python编译为字节码&#xff0c;然后由大型C程序解说&#xff1b;C是一种编译言语&#xff0c;完好的源代码将直接编译为机器代码&#xff0c;由CPU直接履行。 二、内存办理Python…

寄蜉蝣于天地,渺沧海之一粟

工具 1、Zulip 一个开源的群聊软件&#xff0c;有服务端和各种平台的客户端。 2、win-vind 这个工具为 Windows 系统提供 Vim 风格的快捷键&#xff0c;也可以把它当作 Windows 的快捷键设定器。 3、canvas-confetti 在网页上抛洒五彩纸屑的 JS 库。 4、WR.DO 一个开源的 W…

让老周都道歉的360手表,难道是AI模型的锅吗?

360集团创始人、董事长周鸿祎在微博发文&#xff0c;对360儿童手表出现错误问答一事致歉。周鸿祎表示&#xff0c;今天在网上看到视频反映我们某型号的儿童手表出现错误的问答&#xff0c;给用户带来不适&#xff0c;我们表示真诚的歉意。 经过快速检查&#xff0c;出现问题的这…

快手主站前端工程化探索:Gundam 脚手架在新春除夕项目中的实践与展望

一、背景与目标 1.1 背景 Gundam 作为快手主站前端的脚手架&#xff0c;成立于2022年底。当时主站前端的整体工程化建设&#xff0c;从开发准备阶段到开发、联调、测试、部署、运维整个全流程&#xff0c; 其中偏后链路的持续集成和持续部署、错误监控排障&#xff0c;依赖于…

中科服务器磁盘未断电状态被人拔插导致raid故障,安装系统找不到系统盘 修复raid再次安装系统成功

1&#xff0c;根据提示按del进入bios 直接回车 改成good状态保存&#xff08;多块盘的话重复此操作即可&#xff0c;直到让盘的状态显示good或者online&#xff09; 然后回到上级导入raid信息 raid信息导入 设置成yes&#xff0c;然后保存退出 然后他会自己同步数据&…

SSM健康生活博客小程序—计算机毕业设计源码23497

摘 要 本文设计了一种基于SSM框架的健康生活博客小程序&#xff0c;为人们提供了运动视频教学、博客信息分享&#xff0c;用户能够方便快捷地查看资讯、搜索健康方面的相关信息、还能发布个人生活博客等。健康生活博客小程序采取面对对象的开发模式进行软件的开发和硬体的架设&…

基于x86 平台opencv的图像采集和seetaface6的性别识别功能

目录 一、概述二、环境要求2.1 硬件环境2.2 软件环境三、开发流程3.1 编写测试3.2 配置资源文件3.2 验证功能一、概述 本文档是针对x86 平台opencv的图像采集和seetaface6的性别识别功能,opencv通过摄像头采集视频图像,将采集的视频图像送给seetaface6的性别识别模块从而实现…

未来城市的科技展望

未来城市&#xff0c;‌将是科技与人文深度融合的产物&#xff0c;‌展现出一个全方位智能化、‌绿色生态且可持续发展的全新面貌。‌随着物联网、‌人工智能等技术的飞速发展&#xff0c;‌未来城市的轮廓逐渐清晰&#xff0c;‌它将为我们带来前所未有的生活体验。‌ 在未来…

Linux驱动学习之点灯(六,利用平台设备总线)

平台设备总线 平台设备总线是内核虚拟的一条总线&#xff0c;早期没有设备树时&#xff0c;通过名字匹配设备信息&#xff0c;如今有设备树通过设备树里的complitable属性匹配&#xff0c;下图是平台设备总线的结构体描述。 much函数是完成信息匹配的&#xff0c; 总线就是使用…

Python 编程 之 tkinter : 导航栏与局部页面切换

import tkinter as tk class App: def __init__(self, root): self.root root self.root.title("导航栏与局部页面切换") self.root.geometry(800x500)self.root.minsize(width800, height300)# 创建导航栏 self.navbar tk.Frame(self.root, bggray) self.n…

TIM输出比较之PWM驱动直流电机应用案例

文章目录 前言一、应用案例演示二、电路接线图三、应用案例代码四、应用案例分析4.1 初始化PWM模块4.1.1 RCC开启时钟4.1.2 配置时基单元4.1.3 配置输出比较单元4.1.4 配置GPIO4.1.5 运行控制 4.2 PWM输出模块4.3 电机模块4.3.1 Motor初始化模块4.3.2 电机调速模块 4.4 主程序 …

0基础学习Python路径(21)Python NameSpaceScope

命名空间定义了在某个作用域内变量名和绑定值之间的对应关系&#xff0c;命名空间是键值对的集合&#xff0c;变量名与值是一一对应关系。作用域定义了命名空间中的变量能够在多大范围内起作用。 命名空间在 Python 解释器中是以字典的形式存在的&#xff0c;是以一种可以看得…

Linux Nvidia驱动一览

Unix Drivers | NVIDIAUnix Drivershttps://www.nvidia.cn/drivers/unix/