说起Tone Mapping,就不能不提HDR。HDR(High Dynamic Range, 高动态范围)是一种颜色范围,在传统的RGB中,每一个通道的颜色都是从属于[0,1]之间的浮点数范围,0代表纯黑,1代表纯白,超过了1仍然会归为白色;但是在现实光照方程中却没有这个限制,光的强度可以设置为任意大,那么就需要重新设定颜色的范围,这就是HDR。有了HDR,场景中亮的东西可以变得非常亮,暗的东西可以变得非常暗,而且充满细节。
之前提过的颜色值只能属于[0,1]之间的算法称作LDR(Low Dynamic Range,低动态范围),那么假设我们在计算中采用的是HDR,因为计算机屏幕只能显示[0,1]之间(也就是LDR)的颜色,因此在最终输出颜色的时候需要做一个从HDR到LDR的转换,具体的转换算法就是ToneMapping。
《LearnOpenGL》中提到了两种ToneMapping的算法,我们对此分别进行说明。
最简单的色调映射算法是Reinhard ToneMapping。它的实现非常简单,代码如下:
float3 ReinhardToneMapping(float3 color, float adapted_lum)
{
const float MIDDLE_GREY = 1;
color *= MIDDLE_GREY / adapted_lum;
return color / (1.0f + color);
}
MIDDLE_GREY是默认为灰的颜色,adapted_lum是统计的亮度(具体的计算可以参考Photographic Tone Reproduction for Digital Images这篇论文),我试了一下,它的函数图形如下(默认MIDDLE_GREY和adapted_lum都是1):
虽然它的做法简单粗暴,亮的变暗,暗的变亮,但是缺点就是把所有的颜色都往中间压缩,整体画面会显得灰暗,下图来自于使用该算法的一个场景:
后来CryEngine又提出了另一种ToneMapping方法,将亮的部分压平:
float3 CEToneMapping(float3 color, float adapted_lum)
{
return 1 - exp(-adapted_lum * color);
}
其中adapted_lum是曝光度,曝光度越高整体越亮,比Reinhard的方法更鲜艳,如下图所示:
以上就介绍这两种方法,后来人们还提出了更多效果更好的方法,可以参照这篇文章进一步地学习:https://zhuanlan.zhihu.com/p/21983679