Vulkan_片元着色器特效5(泛光Bloom)

2023-11-14

本部分主要结合上一部分的Vulkan_片元着色器特效4(高动态范围HDR)来综合展示HDR+泛光场景,主要参照:LearnOpenGL中的Bloom章节
在这里插入图片描述

一、基本原理

Bloom使我们能够注意到一个明亮的物体真的有种明亮的感觉。泛光可以极大提升场景中的光照效果,并提供了极大的效果提升,尽管做到这一切只需一点改变。

Bloom和HDR结合使用效果很好。常见的一个误解是HDR和泛光是一样的,很多人认为两种技术是可以互换的。但是它们是两种不同的技术,用于各自不同的目的上。可以使用默认的8位精确度的帧缓冲,也可以在不使用泛光效果的时候,使用HDR。只不过在有了HDR之后再实现泛光就更简单了。

为实现泛光,我们像上一节那样渲染一个有光场景,提取出场景的HDR颜色缓冲以及只有这个场景明亮区域可见的图片。被提取的带有亮度的图片接着被模糊,结果被添加到HDR场景上面。

二、开发步骤

2.1 提取亮色

我们首先在HDR颜色缓冲(G-Buffer片元着色器)中,提取所有超出一定亮度的fragment,然后将其存储到附件1中(之前的HDR存储在附件0中):

...
layout (location = 0) out vec4 outColor0;//附件0:HDR
layout (location = 1) out vec4 outColor1;//附件1:Bloom
void main() 
{
	...
	// 曝光色调映射 填入附件0
	outColor0.rgb = vec3(1.0) - exp(-color.rgb * ubo.exposure);

	// 判断泛光阈值 填入附件1
	float bloom = dot(outColor0.rgb, vec3(0.2126, 0.7152, 0.0722));
	//阈值
	float threshold = 0.75;
    outColor1 = vec4((bloom > threshold) ? outColor0.rgb : vec3(0.0) , 1.0);
}

我们通过恰当地将其转为灰度的方式计算一个fragment的亮度,如果它超过了一定阈限,我们就把颜色输出到第二个颜色缓冲附件中,那里保存着所有高亮颜色。

经过上边的过滤,我们就可以有了两个颜色缓冲附件,一个正常场景的图像和一个提取出的亮区的图像;这些都在G-Buffer中处理得到的。
附件0:
在这里插入图片描述
附件1:
在这里插入图片描述

有了一个提取出的亮区图像,我们现在就要把这个图像进行模糊处理。我们可以使用帧缓冲教程后处理部分的那个简单的盒子过滤器,但不过我们最好还是使用一个更高级的更漂亮的模糊过滤器:高斯模糊(Gaussian blur)

2.2 高斯模糊

高斯模糊是一种图像空间效果,用于创建原始图像的柔和模糊版本。然后,可以通过更复杂的算法使用此图像来产生类似光晕,景深,热雾或模糊玻璃的效果。在本文中,我将介绍一种可以通过利用双线性纹理滤波来减少原始高斯模糊滤波器实现的性能的技术,及尽量少的的纹理查找数。参照可见线性采样的高效高斯模糊
图像空间高斯滤波器是一个NxN抽头卷积滤波器,它基于高斯函数对其覆盖范围内的像素进行加权:
在这里插入图片描述
使用从高斯函数获得的值对滤波器覆盖区的像素进行加权,从而提供模糊效果。高斯滤镜的空间表示(有时称为“钟形表面”)展示了足迹中各个像素对最终像素颜色的贡献程度。
在这里插入图片描述

为了获得更有效的算法,我们必须对高斯函数的一些好特性进行一些分析:

  • 二维高斯函数可以通过将两个一维高斯函数相乘来计算;

在这里插入图片描述
在这里插入图片描述

  • 分布为2σ的高斯函数等于分布为σ的两个高斯函数的乘积。

基于第一个属性,我们可以将二维高斯函数分成两个一维函数。在使用片段着色器的情况下,这意味着我们可以将高斯滤镜分为水平模糊滤镜和垂直模糊滤镜,在渲染后仍可获得准确的结果,这叫做两步高斯模糊。

这意味着我们如果对一个图像进行模糊处理,至少需要两步,最好使用帧缓冲对象做这件事。它的意思是,有一对儿帧缓冲,我们把另一个帧缓冲的颜色缓冲放进当前的帧缓冲的颜色缓冲中,使用不同的着色效果渲染指定的次数。基本上就是不断地切换帧缓冲和纹理去绘制。这样我们先在场景纹理的第一个缓冲中进行模糊,然后在把第一个帧缓冲的颜色缓冲放进第二个帧缓冲进行模糊,接着,将第二个帧缓冲的颜色缓冲放进第一个,循环往复。
在这里插入图片描述

在我们处理帧缓冲之前,先讨论高斯模糊的像素着色器:

#version 450

layout (binding = 0) uniform sampler2D samplerColor0;//HDR附件
layout (binding = 1) uniform sampler2D samplerColor1;//泛光附件

layout (location = 0) in vec2 inUV;

layout (location = 0) out vec4 outColor;

layout (constant_id = 0) const int dir = 0;

//learn OpenGL
void main()
{             
	const float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
    vec2 tex_offset = 1.0 / textureSize(samplerColor1, 0); // gets size of single texel
    vec3 result = texture(samplerColor1, inUV).rgb * weight[0]; // current fragment's contribution
    if(dir == 1)
    {
        for(int i = 1; i < 5; ++i)
        {
            result += texture(samplerColor1, inUV + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
            result += texture(samplerColor1, inUV - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
        }
    }
    else
    {
        for(int i = 1; i < 5; ++i)
        {
            result += texture(samplerColor1, inUV + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
            result += texture(samplerColor1, inUV - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
        }
    }
    outColor = vec4(result, 1.0);
}

//OpenGL Super bible
void main01(void)
{
	const float weights[] = float[](0.0024499299678342,0.0043538453346397,0.0073599963704157,0.0118349786570722,0.0181026699707781,
									0.0263392293891488,0.0364543006660986,0.0479932050577658,0.0601029809166942,0.0715974486241365,
									0.0811305381519717,0.0874493212267511,0.0896631113333857,0.0874493212267511,0.0811305381519717,
									0.0715974486241365,0.0601029809166942,0.0479932050577658,0.0364543006660986,0.0263392293891488,
									0.0181026699707781,0.0118349786570722,0.0073599963704157,0.0043538453346397,0.0024499299678342);

	const float blurScale = 0.003;
	const float blurStrength = 1.0;

	float ar = 1.0;
	// 垂直模糊通道的纵横比
	if (dir == 1)
	{
		vec2 ts = textureSize(samplerColor1, 0);
		ar = ts.y / ts.x;
	}

	vec2 P = inUV.yx - vec2(0, (weights.length() >> 1) * ar * blurScale);

	vec4 color = vec4(0.0);
	for (int i = 0; i < weights.length(); i++)
	{
		vec2 dv = vec2(0.0, i * blurScale) * ar;
		color += texture(samplerColor1, P + dv) * weights[i] * blurStrength;
	}

	outColor = color;
}


这里我们使用一个比较小的高斯权重做例子,每次我们用它来指定当前fragment的水平或垂直样本的特定权重。你会发现我们基本上是将模糊过滤器根据我们在创建管线时候用的特殊性常量dir设置的值分割为一个水平和一个垂直部分。通过用1.0除以纹理的大小(从textureSize得到一个vec2)得到一个纹理像素的实际大小,以此作为偏移距离的根据。
此时,在创建管线的时候,区分常量管线:

//第一遍模糊
uint32_t dir = 1;
specializationInfo = vks::initializers::specializationInfo(1, specializationMapEntries.data(), sizeof(dir), &dir);
shaderStages[1].pSpecializationInfo = &specializationInfo;
vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.bloom[0]);

//第二遍模糊
pipelineCreateInfo.renderPass = filterPass.renderPass;
dir = 0;
vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.bloom[1]);

之后在渲染的时候,我们在三个不同的渲染通道中分别渲染:

/*
	 第一次渲染通道:渲染场景到帧缓冲器
*/
...			
/*
	第二次渲染通道:第一次水平模糊
*/
...
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.bloom[1]);
vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
...
/*
	第三次渲染通道:应用第二次通道(水平模糊)进行竖直模糊及场景渲染
*/
...
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.bloom[0]);
vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
...

我们也可以从工具中看出此流程:
在这里插入图片描述
通过模糊处理后:
在这里插入图片描述
从图中我们可以看到,使用高斯模糊后高亮出所有像素都得到了相应的模糊处理。

2.3 泛光效果

从渲染工具流程上来看,我们可以看到:
在这里插入图片描述

34行的:vkCmdDraw,我们直接采样附件0:HDR场景;
36行的:vkCmdDraw,我们直接使用第一次模糊的泛光效果;

两者叠加后,可见如下效果:

在这里插入图片描述
除此之外,我们在泛光片元着色器中也保留了《OpenGL超级宝典》中的高斯模糊代码,可以实现效果更好的泛光表现,有兴趣的可以尝试。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vulkan_片元着色器特效5(泛光Bloom) 的相关文章

  • 为什么 GLSL 的算术函数在 iPad 上与在模拟器上产生如此不同的结果?

    我目前正在寻找在 iOS 设备上运行的 OpenGL ES 2 0 片段着色器代码中的一些错误 代码在模拟器中运行良好 但在 iPad 上它存在巨大问题 并且某些计算产生截然不同的结果 例如我有0 0在 iPad 上和4013 17在模拟器
  • 一般问题:着色语言/着色器是面向对象的吗?

    我目前正在接受学徒培训 其中一位培训师说 着色器是面向对象的 作为面向对象编程的示例 对我来说 这意味着 HLSL 和 GLSL 是面向对象的语言 我从来没有认为着色器是面向对象的 但现在当我看到这个时 https www khronos
  • 如何在C++中实现向量混合?

    struct vec2 union struct float x y struct float r g struct float s t vec2 vec2 float a float b x a y b struct vec3 union
  • 为什么我不能使用uniform1f而不是uniform4f来设置vec4制服?

    我通过以下方式逐步学习WebGL这本书 https sites google com site webglbook 我尝试通过使用缓冲区来绘制三个点 gl ARRAY BUFFER 而不是循环 正如我之前在本书的其他示例中所做的那样 var
  • 使用着色器创建模糊过滤器 - 从片段着色器访问相邻像素?

    我想使用 OpenGL ES 2 0 中的片段着色器创建模糊效果 我感兴趣的算法只是一个平均模糊 将所有相邻像素添加到我自己中并除以 9 进行标准化 但是我有两个问题 1 这是否需要我首先渲染到帧缓冲区 然后切换渲染目标 或者有更简单的方法
  • Three.js - 在自定义几何体上平滑兰伯特材质着色的问题

    我在 Three js 中创建了一个自定义几何体 现在 我想创建一个使用平滑阴影兰伯特材质的网格 使用循环 我创建了顶点数组 然后创建了面 然后我调用了 geometry computeCentroids geometry computeF
  • Vulkan:在多个命令缓冲区中排序图像内存屏障

    对于资源转换 您需要了解 之前 和 之后 VkImageLayout资源的 例如 在VkImageMemoryBarrier传递给vkCmdPipelineBarrier Vulkan 不保证命令缓冲区执行的任何顺序 除非 API 文档中明
  • glDrawBuffer(GL_NONE) 与 glColorMask 设置为全部 GL_FALSE

    glDrawBuffer GL NONE 和 glColorMask GL FALSE GL FALSE GL FALSE GL FALSE 有什么区别 两者只是丢弃对颜色缓冲区的任何绘制的另一种方式吗 还是有一些差异 首先也是最重要的 g
  • ShaderEffectItem 的奇怪 alpha 混合结果

    我正在尝试使用以下方法在 QML 项目上应用简单的 alpha 蒙版ShaderEffectItem 这是一个最小的 非 工作示例 我有一个从红到白的渐变作为背景 并且想要在其顶部绘制一个绿色的 200x200 正方形 该正方形的 alph
  • Vulkan:上传 3 通道图像到设备

    假设主机端有一个3通道图像 float或uint8 需要传输到设备图像 vkCmdCopyBufferToImage用于它 对于设备图像的格式 我看到两个选项 使用 R32G32B32A32 SFLOAT R8G8B8A8 SNORM 并将
  • 如何测量Vulkan管道的执行时间

    Summary 我希望能够测量 GPU 上运行整个图形管道所花费的时间 以毫秒为单位 目标 能够在优化代码之前 之后保存基准 下一步将是 mipmap 纹理 以查看改进 这在 OpenGL 中非常简单 但我是 Vulkan 新手 需要一些帮
  • OpenGL 将着色器附加到程序

    有没有办法访问附加到程序的着色器 也就是说 给定一个程序 我可以做类似的事情 vertexShader getVertexShaderFromProgram program 我想在验证我的程序的函数中记录着色器编译状态 但我只保留对程序的引
  • 对 VBO 中的特定三角形使用不同的纹理

    我有 9 个由三角形组成的四边形 如下所示 我在用着VBO存储有关它们的数据 它们的位置和纹理坐标 我的问题是 是否可以仅使用一个来使四边形 5 具有与其余四边形不同的纹理VBO and shader 绿色代表纹理 1 黄色代表纹理 2 到
  • Phong 着色问题

    我正在根据以下内容编写着色器冯模型 http en wikipedia org wiki Phong reflection model 我正在尝试实现这个方程 其中 n 是法线 l 是光线方向 v 是相机方向 r 是光反射 维基百科文章中更
  • GLSL - 计算表面法线

    我有一个用 GLSL 编写的简单顶点着色器 我想知道是否有人可以帮助我计算表面的法线 我正在 升级 一个平面 所以当前的灯光模型看起来 很奇怪 这是我当前的代码 varying vec4 oColor varying vec3 oEyeNo
  • Unity3D:在 AA 解析后绘制粒子以提高性能

    我正在尝试评估 MSAA 对 Unity 中含有大量粒子的场景的影响 为此 我需要 使用 8x MSAA 绘制场景中的所有非粒子对象 使用上一个通道中解析的深度缓冲区来渲染所有 将非遮挡粒子系统转移到较小的渲染目标上 将 2 的颜色缓冲区与
  • LibGDX - 着色器适用于桌面但不适用于 Android

    我编写了一个简单的程序 可以在 3D 环境中渲染球体 并根据球体周围的四个光源为其着色 当我在桌面上运行该程序时 它工作得很好 但在 Android 设备上 球体只是纯色的 下面是一些图片来说明我正在谈论的内容 gt Desktop gt
  • 使用 OpenGL 着色器进行数学计算 (C++)

    我有一个矩阵 例如 100x100 尺寸 我需要对每个元素进行计算 matrix i j tt 8 5例如 我有一个巨大的矩阵 我想使用 OpenGL 着色器来实现该算法 我想使用着色器 例如 uniform float val unifo
  • WebGL - 如何传递无符号字节顶点属性颜色值?

    我的顶点由具有以下结构的数组组成 Position colour float float float byte byte byte byte 传递顶点位置没有问题 gl bindBuffer gl ARRAY BUFFER this vbo
  • 将自己的结构传递到 opengl es 2.0 着色器中

    我想尝试 OpenGL ES 2 0 编程指南 一书中的照明示例 在着色器中他们制作了两种结构 struct directional light vec3 direction normalized light direction in ey

随机推荐

  • Tokenview X-ray功能:深入探索EVM系列浏览器的全新视角

    Tokenview作为一家领先的多链区块浏览器 为了进一步优化区块链用户的使用体验 我们推出了X ray 余额透视 功能 该功能将帮助您深入了解EVM系列浏览器上每个地址的交易过程 以一种直观 简洁的方式呈现地址的进出账情况 让您轻松掌握资
  • 技术实践干货:从零开始创建Node.js应用

    作为一个程序员 我们常常会有很多想法和创意 然后用技术实现出来 这是一个很有成就感的事情 在实践过程中 会发现很多想法都不能很好地落地 可能是技术 可能是团队氛围等等 于是就开始想着能够不能有一个框架去承载这些想法 其实在Node js这个
  • 解决宝塔面板打开不了登录界面问题或xshell界面显示的宝塔登录地址是空的的问题

    问题描述 某天重开电脑之后 在浏览器打开宝塔面板页面时 提示请使用正确的入口登录面版 如下图 解决方案 正常情况下把查看面板入口的命令给复制到xshell里面 他就会返回给你登录地址 用户名 密码 然后你复制地址重新登录就可以了 如果你在x
  • Android完全退出应用程序 ,【Android面试题】

    activity finish activityStack remove activity activity null 获得当前栈顶Activity public Activity currentActivity Activity acti
  • Vue自定义指令的使用详解

    自定义指令 vue官方提供了v text v for v model v if等常用的指令 还允许开发者自定义指令 在使用自定义指令前 须在自定义名称前加v 名称 私有自定义指令
  • android audio/linux alsa音频-硬件

    接着以前的文章继续写音频方面的分析 因为学得快忘得也快 如果不加以总结和记录 很快自己也不记得了 要完全了解一个音频器件 如ES8396 wm8998 首先得了解它的硬件原理 一般在嵌入式领域 音频的编解码芯片主要有两种 1 在单片机应用方
  • I/O接口

    I O接口 结构和作用 数据缓冲寄存器DBR 暂存即将输入输出的数据 主机和外设的速度匹配 状态 控制寄存器 命令字 CPU对设备发出的具体命令 状态字 设备的状态信息 供CPU检查 串 并转换机构 数据格式的转换 I O控制逻辑 根据命令
  • 网络QoS解决方案

    网络QoS解决方案 在网络带宽不足时 对网络流量做区别服务 优先传输那些重要的 要求网络延迟小的 如果丢弃会导致更大业务代价的数据 并对不同应用的数据做合理的带宽分配与控制 如果一定需要丢弃一些数据 则丢弃那些代价比较低的 这就是网络 Qo
  • pnpm install出现:ERR_PNPM_PEER_DEP_ISSUES Unmet peer dependencies

    使用 pnpm install 安装项目依赖时出现 ERR PNPM PEER DEP ISSUES Unmet peer dependencies 在 pnpm github issues 中找到相关解决方案 一 前言 完整日志 ERR
  • Git修改IP重新定位的方法

    进入已clone项目的 git文件夹 打开config文件 打开config 如图显示 修改url中的IP为192 168 6 102 然后保存 在项目上右击选择属性 R 然后选择Git 即可看到当前项目的跟踪远端网址 转载于 https
  • js vue上传文件判断文件格式 GIF JPG PNG

    根据文件识别头信息获取图片文件的类型 JPG 文件头标识 FF D8 文件尾标识 FF D9 PNG 文件头标识 8 bytes 89 50 4E 47 0D 0A 1A 0A GIF 文件头标识 6 bytes 47 49 46 38 3
  • 计算机二级试题及分值分布,计算机二级各部分分值分布

    计算机二级考试分选择题和操作题两大类 其中选择题10题 每题2分 一共20分 操作题分字处理题 电子表格题 演示文稿题三大类 其中字处理题30分 电子表格题30分 演示文稿题20分 共计80分 计算机二级各科目考试题型及分值 二级 MS O
  • java垃圾回收机制概述以及优缺点

    Java中的垃圾回收机制是自动内存管理的一部分 它负责在程序运行时自动回收不再使用的内存对象 以便为程序提供可用的内存空间 基于所谓的 垃圾收集器 它是Java虚拟机 JVM 的一部分 以下是Java垃圾回收机制的主要特点 1 对象生命周期
  • 教室管理系统(相关技术和设备:stm32、w5500、mqtt)

    背景 某学校对新建的实验楼有门禁管理需求 因此我们项目组借助KOB门锁 某宝销量较高的电吸锁和电插锁品牌 搭建了前端 微信小程序和网页 服务器 java服务器和mqtt服务器 单片机 基于stm32 用于控制电插锁 实现了一套完整的门禁管理
  • 关于RuoYi-Vue和ruoyi-vue-pro的基本使用理解

    文章目录 概要 前后端分离架构 技术栈 技术细节 小结 概要 提示 这里是本文概要 RuoYi Vue和ruoyi vue pro两个Web开源项目都是基于当下主流技术栈的前后端分离版本 后端采用SpringBoot多模块架构 前端使用Vu
  • 秋叶一键重装系统连接服务器失败,秋叶一键重装系统win7系统安装和使用DAEMONToolsLite的方法【图文教程】...

    DAEMON Tools Lite是一款虚拟光驱工具 装完不需启动即可用 是一个非常先进的模拟备份以及合并保护盘的软件 但是有部分win7秋叶系统用户还不知道要怎么安装和使用DAEMON Tools Lite 针对这个情况 小编这就给大家分
  • 保研日记v

    目录 个人情况 夏令营情况 预推免情况 希望能对学弟学妹们能有一定的参考价值 同样也是为了本科前三年画上一个句号 有问题可以直接留言哈 认识我的话可以直接小窗私戳我 即便困惑你的是很小的问题也希望大家能够勇敢的开口问 因为走了很多弯路 也在
  • 我优化了进度条,页面性能竟提高了70%

    前言 大家好 我是零一 最近我准备在组里进行代码串讲 所以我梳理了下项目之前的业务代码 在梳理的过程中 我看到了有个进度条组件写的非常好 这又想起我刚开始学前端时写的进度条的代码 跟这个比起来真的差距太大了 大部分的初学者应该都想不到 而且
  • 程序员常用的命令

    写在前面 你们好 我是小庄 很高兴能和你们一起学习常用命令 如果您对Java感兴趣的话可关注我的动态 写博文是一种习惯 在这过程中能够梳理和巩固知识 常用的Linux命令 cd 改变目录 cd 回退到上一级目录 直接cd进入默认目录 pwd
  • Vulkan_片元着色器特效5(泛光Bloom)

    本部分主要结合上一部分的Vulkan 片元着色器特效4 高动态范围HDR 来综合展示HDR 泛光场景 主要参照 LearnOpenGL中的Bloom章节 一 基本原理 Bloom使我们能够注意到一个明亮的物体真的有种明亮的感觉 泛光可以极大