HDR & Bloom
一周没写博客了(感觉每天都在划水),OpenGL还差Deferred Shading和SSAO,以及PBR的内容没学,计划到下个月十号左右初步学完,然后进入实践环节(元旦前结束),至于图形学后面怎么学还没有正式确定(大概方向是进一步熟悉OpenGL,然后写个光栅化渲染器之类的,如果还有时间的话还能尝试尝试UE4,Unity之类的引擎)。
接着就是CSAPP了,目前规划是用3-4个月的时间去慢慢地学习(同时做lab,具体计划再看),从12月份开始,考虑到一月份之后回家期间进度会慢下来,所以时间可以适当放宽。数据结构和算法计划用一个月到一个半月的时间去重新学习。计算机网络方面准备下学期再开始。寒假还打算做的事情就是翻译(也许还可能建个网站),准备将Learn OpenGL CN的一些文章重新翻译一遍,然后再提PR。
好了,废话说到这里差不多就结束了。
本篇博客打算把学习HDR和Bloom过程中的一些问题给提出来。
首先是HDR(Height Dynamic Range)高动态范围。顾名思义,就是一个更大的颜色范围。
我们首先要知道,显示器被限制为只能显示颜色值为0.0 - 1.0,即使场景中有颜色值高于1.0的片段,最多也只会显示成1.0。这就使得细节损失了不少,很多超出颜色值1.0的片段也会显示(被约束)为1.0,表现为大片的白色。
对此,我们提出了一个解决方案,那就是将光照方程中的颜色值对应转换到区间[0.0, 1.0],从而防止损失细节(小细节多多少少还是会损失一点的)。
当帧缓冲(非默认帧缓冲)使用了一个标准化的定点格式(如GL_RGB)为它的颜色缓冲内部格式时,(渲染过后)OpenGL会在将这些值存入帧缓冲之前约束到[0.0, 1.0]中,因此我们首先需要将它的颜色缓冲的内部格式改为GL_RGB16F(GL_RGBA16F, GL_RGB32F, GL_RGBA32F),这样子帧缓冲就可以存储超过1.0的颜色值了。这种帧缓冲被称之为浮点帧缓冲。
从浮点帧缓冲得到未被限制的颜色值(存放在颜色附件里面)之后,我们需要将其用于默认帧缓冲中。但是不能直接用(直接用的话超过1.0的值会被约束为1.0),我们需要通过色彩映射(Tone Mapping)将浮点的HDR颜色值转化为LDR[0.0, 1.0]。色调映射算法这里就不说了,常用的有Reinhard映射。当然,记得gamma校正。
Reinhard映射使得我们不会在将HDR转化到LDR的时候亮度部分损失太多细节,但是对于暗部部分却不那么友好,暗部部分不那么精细,也不那么有区分度。
对此,我们可以考虑引入曝光(Exposure),高曝光值会使得黑暗的部分有更多的细节(同时亮度的细节减少了),而低曝光值会使得黑暗部分的细节减少,亮度的细节增加。
在摄影中,我们可以曝光多次来采取不同时候的颜色细节,合成之后不同等级的曝光使得极大部分的颜色值都具有细节。
接下来讲一下Bloom泛光(也称为Glow辉光)
泛光这一节说难也难,说简单也简单。主要是对于帧缓冲的应用,以及fragment shader的MRT(Multiple Render Targets)。
首先bloom用于一些光源(光照区域),它让光源发出光芒,从而营造出一种更加真实的效果。效果大致如下(图片来源LearnOpenGL)
泛光的实现过程大致如下:
【1】将HDR场景渲染到一个颜色附件中(姑且称之为“附件0”),并且将HDR场景提取出一定的亮度,放至另一个颜色附件中(以下称之为“附件1”)。
【2】将附件1进行模糊。
【3】让附件1的结果添加到附件0上,最后将输出至默认帧缓冲。
下面基于Learn OpenGL的相关章节做一下解读:
首先是颜色附件的问题,我们可以采取MRT技术,让一个fragment shader输出至一个帧缓冲(非默认)的两个纹理附件中,当然我们还需要显式地告知OpenGL我们正通过glDrawBuffers渲染至多个颜色缓冲中(附件0和附件1),默认情况下只会输出至第一个颜色缓冲中(附件0)。
得到附件0和附件1之后,我们需要对附件1进行模糊。这里采用的是两个帧缓冲,各有一个颜色附件的形式来进行交叉模糊(为了节省运算次数)。一开始我们需要将附件1作为纹理传入其中一个帧缓冲(实际上是传入shader fragment中进行操作),然后进行高斯模糊,之后得到的结果会存放于这个帧缓冲的颜色附件中;然后我们再将这个颜色附件传入另外一个帧缓冲中(同样是当作纹理的方式传入shader fragment),然后在这个颜色附件的基础上进行高斯模糊。如此交替下去即可。关于高斯模糊,可以参照知乎上的相关问题:高斯模糊的原理是什么。
最后将得到的模糊结果和原本的HDR混合(在默认帧缓冲的shader fragment中操作),输出至默认帧缓冲就行啦。
当时一开始看的时候,对于shader fragment的两个layout和两个帧缓冲十分不解。后面追根溯源,根据代码从结果一步步推导到初始的几个渲染过程,才明白了bloom的过程和原理。
以上。