Framebuffer中的亮度和颜色值的范围被限制在0.0和1.0之间。我们在场景中设置光和颜色的时,也只能这个范围取值。这么做大部分情况下是OK的,结果也还可以,但是当场景中有一块多光源,亮度总合超过1.0的区域时,结果是什么样的?答案是所有的亮度或颜色值超过1.0的片段被直接截断到1.0。这并不是我们想要的结果!
由于大量的片段亮度或颜色被直接截断到1.0,所以这些被截断的片段存的值都是白色值,得到的画面是看起来白白的一大块,很明显的丢失了大量的细节,看起来不真实。
这种问题的一种解决方式是降低光源强度并保证场景中没有区域亮度超过1.0,但这不是一个好的解决方式,因为这样场景的光源亮度被限制,自然而然真实度就会降低。好的解决方式是允许颜色值暂时超过1.0,输出时才变换它到0.0和1.0之间,这样做才不会丢失细节。
虽然显示器只能显示0.0到1.0范围的颜色,但光照计算时没有这个范围限制。我们可以使用高动态范围(HDR)来保存片段颜色超过1.0的值。HDR的颜色范围值可模拟从最亮的太阳直射到最暗的阴影,并且细节可见。
HDR最早用于拍摄,摄像师通过拍摄多张同一场景下不同曝光度的照片来合成最终的HDR图像,基于这些不同曝光度,使得HDR照片有大范围的细节被保留。举个例子,下面的图像展示了长曝光下获取了大量的场景细节,这些细节在短曝光下是拍摄不到的。
这和人眼的工作原理很像,当光线微弱或强烈时,人眼会自我调节来看清楚那些很黑或者很亮的区域,人眼的这种调节机制就好像人眼有一个根据场景亮度自动设置曝光度的滑条。
HDR渲染跟上述原理很相似。我们用精度更高的值来保存大范围的黑到亮的区间对应的颜色值,最后图像输出时把HDR值转换回低动态范围(LDR)[ 0.0, 1.0]范围。这个转换过程称为色调映射(tone mapping),而且目前已经有大量的tone mapping算法使得转换过程尽可能的保留更多的HDR细节。
实时渲染中的HDR不仅可以使用超出LDR的[0.0, 1.0]的值范围和保留更多的细节,而且还可以指定光源的真实亮度。比如,太阳比其他的东西比如闪电来得亮,那么可以指定太阳的亮度为10.0。这样我们可以在场景中设置更加真实的光照参数,而这些参数是没法在LDR渲染中用的,因为它们被直接截断到1.0。
因为显示器只能显示0.0到1.0范围的值,我们需要把当前的HDR颜色值转换回显示器的范围。简单的用平均算法不够好,因为会使得结果还是一片白的情况。我们可以采用其他的公式或者曲线来做HDR到LDR的转换,这个转换过程就是前面提到的 色调映射(tone mapping),它是HDR渲染的最后阶段。
为了实现HDR渲染,我们需要避免颜色值在片段着色处理时被截断。当framebuffer使用标准化的 fixed-point的颜色格式(比如 GL_RGB)作为colorbuffer的内置格式时,OpenGL会在保存颜色值到framebuffer前,自动截断颜色值到0.0 到 1.0。这个截断操作对大部分的颜色格式都会执行,floating point格式除外,这样它可以用来保存HDR值。
当framebuffer的colorbuffer的格式被设置为GL_RGB16F, GL_RGBA16F, GL_RGB32F 或GL_RGBA32F时,framebuffer便可以存储0.0 到 1.0范围外的颜色值,完美满足HDR渲染的要求。
创建浮点framebuffer只需要改变colorbuffer的内部格式参数:
OpenGL默认的framebuffer的颜色格式是8位表示一个颜色分量。 GL_RGB32F 或GL_RGBA32F格式的浮点framebuffer用32位表示一个颜色分量,内存占用是默认颜色格式的4倍。大部分情况下,32位精度过于高,确实没啥必要,GL_RGBA16F格式16位在实际应用中已经足够。
场景的颜色值包含可能超过1.0的任意值被放进浮点colorbuffer。这篇教程的简单演示场景中,用一个大型的拉长的盒子来模拟一个隧道,里面放置4个点光源,其中在隧道尽头放置的点光源非常亮。
渲染浮点framebuffer跟正常渲染framebuffer基本一样。区别是片段着色器不同。我们先暂时定义一个简单的pass-through片段着色器:
这里我们直接对浮点colorbuffer采样,并用于片段着色器的输出。这样会导致输出值被截断到0.0到1.0,尽管浮点颜色贴图的存值有些超过1.0。
很明显,隧道尽头的光源强度值被截断到1.0,因为大部分的区域是全白,在处理过程失去了超过1.0的值,因为我们直接把HDR转换到LDR。我们需要改进这个转换过程,使得转换到LDR后,保留更多的细节。我们称这个过程为色调映射tone mapping。
色调映射是把HDR浮点颜色值转换到最后的LDR[0.0, 1.0]颜色区间的过程,并尽量不丢失过多细节。
最简单的tone mapping算法是Reinhard tone mapping,我们把Reinhard tone mapping放到早版本的片段着色器中,并且做了伽马矫正(gamma correction)
Reinhard tone mapping算法使得明亮区域不再丢失细节,但它简单的反比例降低亮度值,使得黑的区域丢失了细节,降低了黑的区域的区分度。
这里可以重新看见隧道尽头木板的材质纹理。
另一个tone mapping的有趣应用是允许使用曝光参数。在前文HDR图像的介绍中,提到了不同的曝光度可得到更多的细节。如果我们场景中有白天夜晚循环天气系统,明智的做法是白天使用低曝光度,晚上使用高曝光度,就像人眼的自我适应。这个曝光参数我们可以调节以适应白天黑夜不同的的光照条件。
一种相对简单的exposure tone mapping算法如下:
这里我们定义了exposure uniform变量,初始值时1.0,并允许我们可以根据黑或亮来调节它。比如,高exposure 值使得黑的区域很明显的显示出更多的细节,低exposure 值使得黑的区域的细节大量丢失,但我们看到亮的区域更多的细节。见下图:
上面的图像很好的展示了HDR渲染的优势。通过调节曝光度我们获取了场景中不同的细节,这些在LDR渲染中是没法做得的。举隧道尽头为例,普通曝光度下木头纹理很难看见,但在低曝光度下木头纹理很清楚。
上述介绍的两种tone mapping算法只是大量tone mapping算法的小部分,不同的算法都有其优势和不足。有的算法关注特定的颜色或强度的处理,有的算法同时处理低和高曝光度来创建色彩和细节丰富的图像。还有一种称为自动曝光调节或自适应的技术用来模拟人眼的自我调节,它根据场景中上一帧的亮度来(慢慢的)调节曝光系统,黑的区域逐渐变得没那么黑,亮的区域逐渐变得没那么亮。
更进一步,HDR渲染使得很多有趣的画面效果变成可能和更加真实。这些效果中的一种就是bloom,我们下次再来讨论。
附原文地址,代码原文中有下载!
http://learnopengl.com/#!Advanced-Lighting/HDR