<div class="markdown_views"><!-- flowchart 箭头图标 勿删 --><svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path></svg><p>前段时间在VR项目里实现了PCF+VSM之后,为了让阳光照射的区域看起来亮一些,降低了阴影区域的亮度,明暗区域的对比度确实高了,但是室内就有些过于暗了,于是寻思着得把HDR模块加上了。</p>

一般我们说的HDR其实是代指,automatic exposure control + Tonemapping + Bloom, 先根据场景的一帧计算出平均亮度,如果偏暗就加亮一些,反之亦然,调整好亮度之后再调整灰度,让明部跟暗部保持更多的细节,最后对高光部分做个Bloom,看起来更真实。

在现实世界中 ,很亮的灯周围都会有一圈模糊的光晕,Bloom就是指这个光晕。我的HDR处理是在计算完光照跟阴影之后进行的,光照着色的输出为一张RGBA16F格式的纹理,注意这里不能再用RGBA8了,用这张纹理作为HDR的输入。

首先进行计算的是平均亮度,这里有两种计算方式,一是利用RTT的方式,渲染到一张一半长宽的纹理里去,循环这个操作,直到纹理只剩1x1像素,这个像素就是场景所有像素的平均了,这里要注意的一点是,RTT的时候,原纹理的过滤方式一定要设为Linear,这样渲染出来的半尺寸纹理才是原纹理的平均。方式二是利用ComputeShader,这个实现跟RTT类似,但是速度会快上一倍左右,因为VR程序非常非常考验性能,我选择的就是这种方式,这里重点说下这个。

第一个问题就是原纹理的尺寸不是2的N次幂,而且会根据用户设定变化,这种无规律的尺寸在ComputeShader里面处理起来比较麻烦,我们需要先把它规格化一下,一般的方案是RTT到一张长宽分别是 离原纹理的一半最近的2的N次幂的纹理上,比如1733*1733就应该规格化到 1024*1024的纹理上。尺寸规范化之后,就可以很方便的用ComputeShader计算平均亮度了,这里面有个技巧,就是可以利用GPU的并行操作进一步加快计算过程,具体参见这里:《AVERAGE LUMINANCE CALCULATION USING A COMPUTE SHADER》
使用sRGB计算亮度的公式是这个:

float lum = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
  • 1

这个公式其实是色彩空间转换的一部分,在CIE XYZ色彩空间中,Y分量代表的就是亮度,而完整的sRGB转换成CIE XYZ的公式是张这样的:

ComputeShader中的第一步,就是计算出这个亮度,然后再对这个亮度做平均。其实亮度并不是线性的,对非线性的值直接做平均,最后得到的平均值并不准确,可以通过ln(lum)把亮度转化成线性的,然后对最后的全场景平均值再用exp(lum)转化回来,结果会更精确,不过我实际测试的结果来说,做不做这一步并没有明显的区别。另外一点需要特别提出来的,是场景画面中央的部分应该具有高权重,而边缘应该低些,总权重保持为1.我是在场景计算成16*16的时候引入的权重,中央4*4个像素具有更高的权重。

上一步执行完毕之后,我们会拥有一张1*1像素的平均亮度纹理,这个值别读到CPU端来,因为读取会造成渲染循环阻塞,非常的影响性能。只需要在需要使用的时候,用采样器采样就行了,采样坐标注意要设为(0.5,0.5)。有了当前场景的平均亮度,我们要计算一下适配亮度,人眼适应光线变化是一个渐进的过程,所以我们不能单纯的根据当帧的平均亮度来决定场景的亮度,需要引入上一帧的平均亮度,在两个亮度之间做一个插值。计算公式如下:

 float adaptedLum = lastLum + (currentLum - lastLum) * (1.0-pow(0.98, 30*elapesdTime));
  • 1

其中,elapesdTime是上一帧的耗时, 30是个经验参数,希望亮度变化更快可以加大这个值。另外一个就是需要加上一句类似如下功能的代码防止极端情况发生:

 adaptedLum = clamp(adaptedLum, 0.3, 0.7);
  • 1

同样,0.3跟0.7是经验参数;有了适配亮度之后,就可以进行场景的亮度校正了,我们希望场景不过暗,也不过亮,也就是希望亮度刚好在0.5左右,因此我们可以利用如下公式来计算曝光度:

float exposure = 0.5 /  adaptedLum;
  • 1

公式内的0.5可以弄成一个传入参数,以便根据用户期望来调整场景的亮度。将输入纹理的每一个像素都乘上exposure,自动曝光也就完成了。一般在这个shader里面,还会同时进行ToneMapping,这个比较简单,业内用的最多的就是Filmic Tone Mapping,效果是很不错的,计算方式如下:

float3 F(float3 x)
{const float A = 0.15f;const float B = 0.50f;const float C = 0.10f;const float D = 0.20f;const float E = 0.02f;const float F = 0.30f;return ((x * (A*x + C*B) + D*E) / (x * (A*x + B) + D*F)) - E/F;
}float3 FilmicToneMapping(float3 color, float exposure)
{const float WHITE = 11.2f;return F(exposure * color) / F(WHITE);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

好了,只剩下Bloom了。传统的Bloom的流程是先利用场景的原始图片作为输入,提取出高光部分,然后做多次高斯模糊,再把模糊了的纹理叠加回去。这里有个示范:https://learnopengl.com/#!Advanced-Lighting/Bloom,这么做效果是很好的,不足之处就是性能影响有些大了,想要Bloom效果明显,需要以不同的Kernel尺寸进行多次高斯模糊,这个过程非常非常耗性能。在VR里面每一点性能都不能浪费,那么有没有效果不错,性能也好的方案呢? 有的:《How to do good bloom for HDR rendering》一文中,作者提到在以5×5, 11×11, 21×21, 41×41为Kernel的高斯模糊之后,Bloom才有了一个不错的效果,但是41*41的高斯模糊,想想都觉得可怕,于是提出了另一种方案,降低原图的分辨率,降低一倍原图分辨率就相当于Kernel加大一倍,由于分辨率降低,速度更是进一步提高,最后多张分辨率不一的图片叠加起来,达到的效果跟在原图上以不同Kernel模糊出来的效果是差不多的!

我这里以5*5为核心,sigma为4作为高斯模糊参数,对原始图片的1/2长宽的level0, 1/4的level1, 1/8的level2,1/16的level3,四层图片进行了模糊,然后用加法直接叠加在了原图上,实现了Bloom效果。最后这一步我有了些改进,直接加上去,高光区域的颜色会过饱和,看起来油油的,而且会导致亮部细节丢失,白茫茫一片什么也看不清。于是我引入了一个机制,亮度越高的像素,加入的Bloom分量越少。最后完美解决了这个问题,计算过程如下:

// vec3 sceneColor:做过自动曝光和ToneMapping之后的像素颜色
// vec3 bloomColor : Bloom颜色
float linsetp(float _min, float _max, float v)
{return clamp((v - _min) / (_max - _min), 0.0, 1.0);
}vec3 LUMINANCE_VECTOR = vec3(0.2126, 0.7152 , 0.0722);
float lum = dot(sceneColor, LUMINANCE_VECTOR);
sceneColor += bloomColor * (1 - linstep(0.4, 0.8, lum));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

高斯模糊的代码参见:《Fast and beautiful blur filter / shader recommendations?》中,JobLeonard 的回答,类似代码很多,毕竟高斯模糊是很通用的算法了。

我机器是I7-6700+GTX960的显卡,整个HDR过程耗时在1.6ms左右,场景的平均亮度我并不需要每帧都更新,每秒更新20次就足够了,就算是10次也没什么问题,这样的话亮度计算耗时基本可以忽略,进一步提高了性能。


原图↑


自动曝光+ToneMapping↑


HDR↑

从前往后数第二张桌子的Bloom效果最明显了,有种在发光的感觉,其实黑色环境里的灯的Bloom效果是最明显的,可惜外网就只有这个场景。

HDR (automatic exposure control + Tonemapping + Bloom)相关推荐

  1. Windows Vista for Developers——第四部分:用户帐号控制(User Account Control,UAC)

    Windows Vista for Developers--第四部分:用户帐号控制(User Account Control,UAC) 作者:Kenny Kerr 翻译:Dflying Chen 原文 ...

  2. 自动报加班系统(Automatic Baojiaban Xitong,ABX)

    加班系统一直是qs班的特色,但是每次都得输验证码不免有些麻烦,有时候还会发生忘报加班的情况...在一个月前我就嚷嚷着要把自动报加班程序写出来,今天花了几乎一天的时间(不知道之后节约的时间有没有这么多^ ...

  3. 模型预测控制(Model predictive control,MPC)

    模型预测控制( MPC ) 是一种先进的过程控制方法,用于在满足一组约束条件的同时控制过程.自 1980 年代以来,它一直在化工厂和炼油厂的加工工业中使用.近年来,它还被用于电力系统平衡模型[1]和电 ...

  4. 什么是控制反转(Inversion of Control)

    [2020.11 最后编辑] 要点:IoC是对框架特征的一般性描述:每一种框架用途不一,永远不要问某个框架"哪些方面的控制被反转了呢?"这种愚蠢的问题.JUnit是Java语言著名 ...

  5. Prism4文档翻译(第四章 第一部分) 转载bluesky234

    本节导读: 第四章介绍了模块化应用程序开发所必要的知识和需要注意的内容.而本部分通过讲述模块化应用程序开发所注意的必要概念,包括IModule接口,模块加载过程,模块列表,模块间通信,和依赖注入容器, ...

  6. [论文阅读] (11)ACE算法和暗通道先验图像去雾算法(Rizzi | 何恺明老师)

    <娜璋带你读论文>系列主要是督促自己阅读优秀论文及听取学术讲座,并分享给大家,希望您喜欢.由于作者的英文水平和学术能力不高,需要不断提升,所以还请大家批评指正,非常欢迎大家给我留言评论,学 ...

  7. 【计算机网络:自顶向下方法】期末复习总结(USTC 2020秋 zq班)

    计算机网络:自顶向下方法 第一章 计算机网络和因特网 1.1 什么是互联网 1. 从具体构成角度 网络:由节点和边组成 计算机网络:节点:主机及其上的应用程序,称为主机节点,□表示:路由器.交换机等网 ...

  8. 万字长文告诉新手如何学习Python图像处理(上篇完结 四十四) | 「Python」有奖征文

    该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子.图像增强技术.图像分割等,后期结合深度学习研究图像识别 ...

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

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

最新文章

  1. 北京智源人工智能研究院2020年博士后招收简章
  2. iptables的地址取反操作
  3. react state成员
  4. SQL 语句递归查询 With AS 查找所有子节点
  5. Bootstrap3 插件的原理
  6. Kubernetes的系统架构与设计理念
  7. springBoot方法上面添加@Transactional注解与类上面添加@Transactional注解的区别
  8. 空间计量模型_5种经典空间计量模型的回归命令、程序及原始数据:SAR模型、SDM模型、SAC模型、SEM模型及GSPRE模型...
  9. 重要数据 | 数据分类和分级概念解析
  10. 计算机病毒中的后门病毒,国家计算机病毒中心发现恶意后门程序新变种
  11. PS利用切片工具将一张大图裁剪成多个子图像并导出
  12. 服务器硬件配置应如何选择?
  13. AutoFill 自动填充生长动画插件
  14. 常用限流方案的设计和实现
  15. 日语基础复习 Day 11
  16. uva 1471 Defense Lines
  17. 设置窗口颜色保护眼睛(win7/xp)
  18. 新版Remix界面使用教程
  19. KendoUI —— 关于 KendoUI
  20. template 推导

热门文章

  1. oracle sqlplus 常用命令大全
  2. python str byte编码_Python3中内置类型bytes和str用法及byte和string之间各种编码转换 问题...
  3. c语言对空指针memcpy,C语言memcpy 断错误
  4. python类私有函数_python-面向对象-14-私有方法
  5. Spring Cloud Netflix Eureka 配置参数说明
  6. 我在51CTO微职位学软考——网络工程师
  7. 基于沙盒环境,安装python3.6
  8. BZOJ 4810 [Ynoi2017]由乃的玉米田 ——Bitset 莫队算法
  9. SVN库迁移整理方法总结
  10. U盘中的autorun.inf