由于monitor监视器的强度范围有限,明亮的光源和明亮的区域通常难以传达给观看者。 区分显示器上明亮光源的一种方法是让它们发光; 然后光线在光源周围流淌。 这有效地为观看者提供了这些光源或明亮区域非常明亮的错觉。

这种光晕或发光效果是通过称为 Bloom 的后处理效果实现的。 Bloom 使场景的所有明亮区域都具有类似发光的效果。 下面是一个有光和没有光的场景示例(图片由 Epic Games 提供):

Bloom 提供了关于物体亮度的明显视觉线索。当以一种微妙的方式完成时(有些游戏完全无法做到),Bloom 会显着增强场景的照明,并允许产生大量的戏剧效果。

Bloom 与 HDR 渲染结合使用效果最佳。一个常见的误解是 HDR 与 Bloom 相同,因为许多人可以互换使用这两个术语。然而,它们是用于不同目的的完全不同的技术。可以使用默认的 8 位精度帧缓冲区来实现 Bloom,就像可以在没有 Bloom 效果的情况下使用 HDR 一样。很简单,HDR 使 Bloom 实现起来更有效(我们稍后会看到)。

为了实现 Bloom,我们像往常一样渲染一个明亮的场景,并提取场景的 HDR 颜色缓冲区和只有其明亮区域可见的场景图像。然后对提取的亮度图像进行模糊处理,并将结果添加到原始 HDR 场景图像之上。

让我们一步一步地说明这个过程。我们渲染了一个充满 4 个明亮光源的场景,可视化为彩色立方体。彩色光立方的亮度值介于 1.5 和 15.0 之间。如果我们将其渲染到 HDR 颜色缓冲区,则场景如下所示:

我们采用这个 HDR color buffer texture 颜色缓冲纹理并提取所有超过一定亮度的片段。 这为我们提供了一个图像,它只显示亮色区域,因为它们的片段强度超过了某个阈值(光强超过某个值,才需要bloom)

然后我们采用这个阈值亮度纹理并模糊结果。 bloom效果的强度很大程度上取决于所使用的模糊滤镜范围强度

由此产生的模糊纹理是我们用来获得发光或流光效果的。 此模糊纹理添加到原始 HDR 场景纹理之上。 由于模糊过滤器的作用,明亮区域在宽度高度上都得到了扩展,因此场景的明亮区域看起来会发光或渗出光。

Bloom 本身并不是一项复杂的技术,但很难完全正确。 它的大部分视觉质量取决于用于模糊提取的亮度区域模糊过滤器的质量类型。 简单地调整模糊过滤器可以极大地改变布隆效果的质量。

遵循这些步骤为我们提供了 Bloom 后处理效果。 下图简要总结了实现 Bloom 所需的步骤:

第一步要求我们根据某个阈值提取场景的所有亮色。 让我们先深入研究一下。

1.Extracting bright color (提取亮色)

第一步要求我们从渲染场景中提取两个图像(一个正常,一个亮色)。 我们可以渲染场景两次,都用不同的着色器渲染到不同的帧缓冲区,但我们也可以使用一个巧妙的小技巧,称为Multiple Render Targets多渲染目标 (MRT),它允许我们指定多个片段着色器输出; 这使我们可以选择在单个渲染通道中提取前两个图像(一个正常,一个亮色,分别输出到两个fragment)。 通过在片段着色器的输出之前指定布局位置说明符,我们可以控制片段着色器写入哪个颜色缓冲区

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

这仅在我们实际上有多个缓冲区要写入时才有效。 作为使用多个片段着色器输出的要求,我们需要将多个颜色缓冲区附加到当前绑定的帧缓冲区对象。 您可能还记得在帧缓冲区一章中我们可以在将纹理链接为帧缓冲区的颜色缓冲区时指定颜色附件编号。 到目前为止,我们一直使用 GL_COLOR_ATTACHMENT0,但通过使用 GL_COLOR_ATTACHMENT1,我们可以将两个颜色缓冲区附加到帧缓冲区对象:

// set up floating point framebuffer to render scene to
unsigned int hdrFBO;
glGenFramebuffers(1, &hdrFBO);
glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
unsigned int colorBuffers[2];
glGenTextures(2, colorBuffers);
for (unsigned int i = 0; i < 2; i++)
{glBindTexture(GL_TEXTURE_2D, colorBuffers[i]);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, 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);// attach texture to framebuffer// 分别绑定不同的framebufferglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0);
}  

我们必须明确地告诉 OpenGL 我们正在通过 glDrawBuffers 渲染到多个颜色缓冲区。 默认情况下,OpenGL 仅渲染到帧缓冲区的第一个color attachment颜色附件,而忽略所有其他颜色附件。 我们可以通过传递我们希望在后续操作中呈现的color attachment颜色附件枚举数组来做到这一点:

//初始化一个2个元素的数组
unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
// 对这个数组进行绑定
glDrawBuffers(2, attachments);  

当渲染到这个帧缓冲区时,每当片段着色器使用布局位置说明符时,相应的颜色缓冲区用于渲染片段。 这很棒,因为这为我们节省了用于提取明亮区域的额外渲染通道,因为我们现在可以直接从要渲染的片段中提取它们

#version 330 core
layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 BrightColor;[...]void main()
{            [...] // first do normal lighting calculations and output resultsFragColor = vec4(lighting, 1.0);// check whether fragment output is higher than threshold, if so output as brightness color// 点积就是向量每个子项相乘,然后相加,vec3(0.2126, 0.7152, 0.0722)代表一个阈值float brightness = dot(FragColor.rgb, vec3(0.2126, 0.7152, 0.0722));// 看点积来的值,有没有大于1。0if(brightness > 1.0)BrightColor = vec4(FragColor.rgb, 1.0);elseBrightColor = vec4(0.0, 0.0, 0.0, 1.0);
}

在这里,我们首先照常计算光照,并将其传递给第一个片段着色器的输出变量 FragColor。然后我们使用当前存储在 FragColor 中的内容来确定其亮度是否超过某个阈值。我们首先通过适当地将片段转换为灰度来计算片段的亮度(通过取两个向量的点积,我们有效地将两个向量的每个单独分量相乘并将结果加在一起)。如果亮度超过某个阈值,我们将颜色输出到第二个颜色缓冲区。我们对光立方做同样的事情。

这也说明了为什么 Bloom 在 HDR 渲染中表现得非常好。因为我们在高动态范围内渲染,颜色值可以超过 1.0,这允许我们指定默认范围之外的亮度阈值,让我们更好地控制被认为是明亮的东西。如果没有 HDR,我们必须将阈值设置为低于 1.0,这仍然是可能的,但区域被认为是明亮的要快得多。这有时会导致发光效果变得过于突出(例如,想想白色的发光雪)。

有了这两个颜色缓冲区,我们就有了一个正常场景的图像,以及一个提取的明亮区域的图像;全部在单个渲染通道中生成。

有了提取的明亮区域的图像,我们现在需要模糊图像。 我们可以使用一个简单的 box filter 来做到这一点,就像我们在帧缓冲区一章的后处理部分所做的那样,但我们宁愿使用一个更高级(更好看)的模糊过滤器,称为Gaussian blur高斯模糊

2.Gaussian blur 高斯模糊

在后处理章节的模糊中,我们取了图像周围所有像素的平均值。 虽然它确实让我们很容易模糊,但它并没有给出最好的结果。 高斯模糊基于高斯曲线,该曲线通常被描述为钟形曲线,在靠近其中心的地方给出高值,随着距离的增加逐渐消失。 高斯曲线可以用不同的形式在数学上表示,但一般具有以下形状:

由于高斯曲线靠近其中心的区域更大,因此使用其值作为权重来模糊图像会产生更自然的结果,因为靠近的样本具有更高的优先级。 例如,如果我们在一个片段周围对一个 32x32 的盒子进行采样,离片段的距离越大,我们会使用越来越小的权重; 这提供了更好和更逼真的模糊,称为高斯模糊。

为了实现高斯模糊滤波器,我们需要一个二维权重框,我们可以从二维高斯曲线方程中获得它。 然而,这种方法的问题在于它很快就会变得非常重性能。 以 32 x 32 的模糊内核为例,这将需要我们为每个片段对纹理总共采样 1024 次!

对我们来说幸运的是,高斯方程有一个非常简洁的性质,它允许我们将二维方程分成两个较小的一维方程:一个描述水平权重,另一个描述垂直权重。 然后我们首先使用场景纹理上的水平权重进行水平模糊,然后在生成的纹理上进行垂直模糊。 由于这个属性,结果完全相同,但这次为我们节省了令人难以置信的性能,因为与 1024 个相比,我们现在只需要做 32 + 32 个样本! 这被称为 two-pass Gaussian blur二次高斯模糊

高斯模糊的片段着色器:

#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); // gets size of single texelvec3 result = texture(image, TexCoords).rgb * weight[0]; // current fragment's contribution//水平的采样一轮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);
}

在这里,我们采用相对较小的高斯权重样本,我们每个人都使用这些样本为当前片段周围的水平或垂直样本分配特定的权重。 您可以看到我们根据我们设置的水平均匀值将模糊过滤器分为水平和垂直部分。 我们将偏移距离基于纹理大小(来自纹理大小的 vec2)除以 1.0 获得的纹素的确切大小。

为了模糊图像,我们创建了两个基本的帧缓冲区,每个帧缓冲区都只有一个颜色缓冲区纹理:

unsigned int pingpongFBO[2];
unsigned int pingpongBuffer[2];
glGenFramebuffers(2, pingpongFBO);
glGenTextures(2, pingpongBuffer);
//创建两个帧缓冲区
for (unsigned int i = 0; i < 2; i++)
{glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);glBindTexture(GL_TEXTURE_2D, pingpongBuffer[i]);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, 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 次)

bool horizontal = true, first_iteration = true;
int amount = 10;
shaderBlur.use();
//对图像,水平糊一遍,垂直糊一遍,一共糊10遍,糊的越多,越模糊
for (unsigned int i = 0; i < amount; i++)
{glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]); shaderBlur.setInt("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); 

每次迭代我们根据我们想要水平模糊还是垂直模糊来绑定两个帧缓冲区之一,并将另一个帧缓冲区的颜色缓冲区绑定为要模糊的纹理。 第一次迭代我们专门绑定了我们想要模糊的纹理 (brightnessTexture),否则两个颜色缓冲区最终都会为空。 通过重复此过程 10 次,亮度图像最终会出现重复 5 次的完整高斯模糊。 这种结构允许我们根据需要随意模糊任何图像; 高斯模糊迭代次数越多,模糊越强

通过将提取的亮度纹理模糊 5 次,我们得到了场景所有明亮区域的适当模糊图像。

完成Bloom效果的最后一步是将这种模糊的亮度纹理与原始场景的HDR纹理结合起来。

3.Blending both textures (混合两种纹理)

有了场景的 HDR 纹理和场景的模糊亮度纹理,我们只需要将两者结合即可实现著名的 Bloom 或发光效果。 在最终的片段着色器中(与我们在 HDR 章节中使用的非常相似),我们将两种纹理相加混合:

#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;// 把bloom和hdr颜色相叠加hdrColor += bloomColor; // additive blending// tone mapping// hdr色调映射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.0);
}  

有趣的是,我们在应用色调映射之前添加了 Bloom 效果。 这样,bloom 的增加亮度也被柔和地转换到 LDR 范围,结果是具有更好的相对照明。

将这两种纹理加在一起,我们场景的所有明亮区域现在都获得了适当的发光效果:

 彩色立方体现在看起来更亮,并且作为发光物体提供更好的错觉。 这是一个相对简单的场景,因此这里的 Bloom 效果并不太令人印象深刻,但在光线充足的场景中,如果配置正确,它会产生显着的差异。

通过沿更大半径采集更多样本或重复模糊过滤器额外次数,我们可以改善模糊效果。 由于模糊的质量与 Bloom 效果的质量直接相关,因此改进模糊步骤可以产生显着的改进。 其中一些改进将模糊过滤器与不同大小的模糊kernels内核相结合,或使用多条高斯曲线来选择性地组合权重。 Kalogirou 和 Epic Games 的额外资源讨论了如何通过改善高斯模糊来显着改善 Bloom 效果。

Learn OpenGL 笔记6.8 Bloom(高动态范围)相关推荐

  1. Learn OpenGL 笔记6.7 HDR(高动态范围)

    默认情况下,亮度和颜色值在存储到帧缓冲区时被限制在 0.0 和 1.0 之间. 这个起初看似无害的声明让我们总是在这个范围内的某个地方指定光线和颜色值,试图让它们适应场景. 这工作正常并给出了不错的结 ...

  2. Learn OpenGL 笔记7.1 PBR Theory(physically based rendering基于物理的渲染 理论)

    PBR,或更通常称为基于物理的渲染,是一组渲染技术,它们或多或少基于与物理世界更接近的相同基础理论.由于基于物理的渲染旨在以物理上合理的方式模拟光线,因此与我们的原始光照算法(如 Phong 和 Bl ...

  3. Learn OpenGL 笔记7.3 PBR-IBL-Diffuse irradiance(Image based lighting-漫反射辐照度)

    IBL,或image based lighting基于图像的照明,是一组照明对象的技术,不是像前一章那样通过直接分析光,而是将周围环境视为一个 big light source大光源.这通常通过操纵立 ...

  4. Learn OpenGL 笔记5.11 Anti Aliasing(抗锯齿)

    这种清晰地看到边缘组成的像素结构的效果称为锯齿. 有很多称为抗锯齿技术的技术可以通过产生更平滑的边缘来对抗这种锯齿行为.(小时候打开一个新游戏,第一件事情就是把抗锯齿给关了,开抗锯齿太卡了) 起初,我 ...

  5. learn opengl 笔记 1.2

    1.GLFW GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口.它允许用户创建OpenGL上下文,定义窗口参数以及处理用户输入. 2.GLAD 由于OpenGL驱 ...

  6. Learn OpenGL 笔记7.4 PBR-Specular IBL(Image based lighting-特殊的基于图像的照明)

    在上一章中,我们通过预先计算辐照度贴图作为照明的间接漫反射部分,将 PBR 与基于图像的照明相结合. 在本章中,我们将关注反射方程的specular part镜面反射部分: 您会注意到 Cook-To ...

  7. Learn OpenGL 笔记6.9 Deferred Shading(延迟着色)

    到目前为止,我们进行照明的方式称为forward rendering前向渲染或forward shading前向着色.我们渲染对象,根据场景中的所有光源对其进行照明.我们为场景中的每个对象分别为每个对 ...

  8. Learn OpenGL 笔记6.5 Normal Mapping(法线贴图)

    我们通过在这些平面三角形上包裹 2D 纹理来增强真实感,隐藏多边形只是很小的平面三角形的事实. 从照明技术的角度来看,确定对象形状的唯一方法是通过其垂直法向量. 这种使用每片段法线与每表面法线相比的技 ...

  9. Learn OpenGL 笔记6.10 SSAO(Screen Space Ambient Occlusion屏幕空间环境光遮蔽)

    我们在基本照明一章中简要介绍了该主题:ambient lighting环境光. Ambient lighting环境光是一个固定的光常数,我们添加到场景的整体照明中以模拟光的scattering散射. ...

最新文章

  1. 影响网站各个页面权重高低的因素有哪些?
  2. Java连接FTP服务器并且实现对其文件的上传和下载
  3. 【内核驱动】 Kconfig简介
  4. HTML5语义化的理解
  5. 蓝桥杯 ADV-120算法提高 6-17复数四则运算
  6. easyui 时间段校验,开始时间小于结束时间,并且时间间隔不能超过30天
  7. 项目:聊天室思路(linux下实现,语言:C/C++)
  8. idea weblogic 部署慢_IDEA+weblogic部署运行项目
  9. 超强换元法,二重积分计算的利器(雅可比行列式超通俗讲解)
  10. @Qualifier注解 的理解和使用
  11. 【蓝牙】室内外定位技术-钛斗™星地融合定位系统
  12. swift野梦抄袭 taylor_如何看待蔡健雅新歌《半途》被指抄袭 Taylor Swift 的《Safe Sound》?...
  13. 猪猪猫Windows7 X86旗舰中文装机版V0911
  14. java 高德地图工具类
  15. 安卓近距离通信--蓝牙通信开发
  16. Python基础(适合初学-完整教程-学习时间一周左右-节约您的时间)
  17. C# 中的委托和事件[转自http://www.cnblogs.com/jimmyzhang/archive/2007/09/23/903360.html]
  18. Java程序:如何将句子(字符串)中的字母大小写转化
  19. 关于linux开机出现initramfs的解决
  20. 超算平台安装Anaconda和Tensorflow

热门文章

  1. 基于遗传算法的电动汽车有序充电优化调度 利用遗传算法对电动汽车有序充电进行优化;优化目标包括充电费用最低,电动汽车充到足够的电,负荷峰谷差最小
  2. 2022年【电商】测试优惠券如何编写测试用例?
  3. 红外接收二极管 -- 结电容和暗电流
  4. 为什么机器学习不是人工智能?
  5. Python函数实现“学生管理系统”案例
  6. java计算器or_java计算器
  7. 最全的NB-IoT芯片厂商、模组厂商信息
  8. 科普篇|什么是JBOD?
  9. 图片压缩免费-免费图片高清无损批量压缩
  10. “BiKi网格宝”——震荡行情收益利器