High Dynamic Range

    本文描述的是实时渲染领域内的HDR高动态范围渲染技术,此技术可粗略理解为在LDR表面呈现HDR场景。
    首先,DR动态范围是指某环境下某物理量最高值与最低值之比值。HDR全称High Dynamic Range,即高动态范围,通常指DR值达10的10次方以上。例如,室外太阳光的亮度与室内阴暗角落的地砖的亮度之比值可以达到10的10次方以上,这样的场景就可以称为具有高动态范围。
    既然有高动态范围,相应也有低动态范围LDR,最明显的例子就是我们的电脑显示器。显示器的DR(即对比度)一般不会超过10的4次方。
    如果不作任何处理,直接在LDR表面呈现HDR场景,可以预计,会出现过曝光或欠曝光情况,丢失场景细节,如图1。

High Dynamic Range_第1张图片
图1 直接HDR—>LDR映射导致丢失场景细节
那么,如何在一个LDR表面呈现HDR场景并且最大限度保留场景细节呢?从人类视觉系统可以找到这个问题的答案。
    人类视网膜是LDR表面,人类的视觉系统会根据场景的平均亮度,自动调整进入视网膜的光量(通过调整瞳孔大小实现),使视网膜尽量多地接收场景细节。人们根据这种机制,研制出机械镜头里的光圈。在摄影时,摄影师根据场景的平均亮度,调整光圈大小,保证捕捉足够的细节。例如,在较暗的环境下应使用大光圈,如下两图对比:
High Dynamic Range_第2张图片 
图2 ISO 800 f/8 1/10秒 无闪光 (此情况属于欠曝光)

High Dynamic Range_第3张图片 
图3 ISO 800 f/3.2 1/10秒 无闪光
很明显,光圈1/8的照片,只能看见字母,键盘的键帽已看不见,丢失细节,而光圈1/3.2的照片效果刚刚好,细节得到充分的保留。细心的朋友可能也发现,光圈1/3.2的照片数字键4、5、6略显模糊而光圈1/8的照片没有模糊。是的,这又是光圈的另一个效果,景深(Depth of Field或DoF)。实时渲染的DoF效果将在以后的文章中介绍。
    回到实时渲染HDR上,有没有类似机械镜头光圈这样的部件?没有,但有方法可以近似实现光圈效果,这个方法就是Tone Mapping(色调映射)。Tone Mapping详细信息可参阅《Photographic Tone Reproduction for Digital Images》这篇paper。下面将Tone Mapping精髓一一列出。
    Tone Mapping可以理解为以下步骤(例子):
1.有一HDR场景,一LDR表面(电脑显示器),如下:
High Dynamic Range_第4张图片 
图4
其中,本例中的HDR场景有大部分细节都是高亮度(d区)且不乏低亮度区域(a、b区)。如果直接在LDR表面呈现HDR场景,a、b区域将表现为全黑,而d区的大部分只能以全白形式呈现。如下两图对比(图片来源DirectX SDK):
High Dynamic Range_第5张图片 
图5 HDR直接映射到LDR表面,高亮度区域细节丢失,或者称之为过曝光

High Dynamic Range_第6张图片 
图6 这是期望的结果,最大限度保留细节
因为HDR场景大量细节集中在高亮的d区,所以我们应该尽量把d区细节呈现出来,即使是使用LDR表面呈现。接下来的工作就是要达到这个目的。
2.计算HDR场景的平均亮度。计算平均亮度使用以下公式:
Lavg = exp[ 1 / N( sum[ log( delta + L( x, y ) ) ] ) ]
其中L(x,y)是像素(x,y)的亮度(亮度可以通过RGB计算:Lum = 0.27R + 0.67G + 0.06B),delta是一个极小的值,防止L(x,y)为零,N是场景总像素。可以预计,本例的场景的平均亮度还是会集中在d区域。
3.根据HDR场景的平均亮度,进行HDR—>LDR映射。使用以下公式:
Cscaled( x, y ) = a * Chdr( x, y ) / Lavg       (1)
及Cdest( x, y ) = Cscaled( x, y ) / ( 1 + Cscaled( x, y ) )   (2)
其中,(1)式作用是根据Lavg对原有场景的亮度进行线性缩放。其中,Chdr是HDR场景像素的颜色,Cscaled是缩放后的颜色,Cdest是LDR对应HDR像素的颜色。关键参数a一般情况下设置为0.18。这里说一下a的由来,假设在LDR表面(电脑显示器)全黑为0,全白为1,在0和1之间有9个过渡灰度:
 
图7 LDR亮度过渡
中间(即第六格)格的亮度为0.18,把HDR场景的平均亮度映射到第六格,就可以保证在LDR表面保留最多的细节。经过线性缩放后,我们得到了一个(以LDR中等亮度为中心点的)可以投影到(LDR表面)0-1区间的亮度分布区间。其实这个步骤的作用就是使LDR中等亮度与HDR的平均亮度重合。最后把这个区间的所有颜色投影到0-1区间,这项工作就由公式(2)完成。
    值得注意的是,参数a可以根据实际情况设置,a值越高(范围0.0-1.0),LDR呈现出来的场景灰度越高,也就是越向白色(1.0)靠拢;另外,公式(2)称为Tone Mapping Operator,这个Operator可以有多种形式,一些Operator还能把高亮度细节重新恢复出来而不是白色一片,具体信息参阅《Photographic Tone Reproduction for Digital Images》。
    经过上述3步骤转换,在LDR表面就可以呈现HDR场景并且保留最大限度的细节,当然,仍然会丢失一些细节,就像在猛烈阳光的室外,你无法透过屋子的窗口窥探室内的情况。注意了,如果在这种情况下没有Tone Mapping(瞳孔缩小),呈现你眼前的可能是白茫茫一片,什么都看不见。我小时候就有过这样的经历,因为医生要检查眼底(视网膜),所以要扩大瞳孔,瞳孔扩大后,在室外看什么东西都很刺眼且看不清楚。
    完成Tone Mapping后,为了增强视觉效果,通常会加入Bloom特效,Bloom就是对场景有高光的像素进行模糊处理。另外,一些由镜头产生物理现象也可以加上,例如六角型或星星折射等。
    到目前为止的讨论都是以理论为主,以下简要说明在D3D下如何实现。
    首先,我们要渲染出HDR场景,才能进行HDR—>LDR映射。HDR场景需要使用浮点Render Target来渲染,一般是使用D3DFMT_A16B16G16R16F浮点格式。这是因为普通的A8R8G8B8整数格式无法记录大于1.0的值(0-255映射为0.0-1.0,除非进行数据压缩)。另外,场景的光源也有要求,亮度范围再也不是0.0-1.0,而是可以使用大于1.0的亮度值,这个值可以看作是光源实际物理亮度。
    得到HDR场景后(使用Render To Texture),就可以进行平均亮度计算。在实际应用中,为了提高效率,通常不会对HDR场景进行逐个像素计算,而是先把HDR场景downsample到比较小的尺寸(1/16),再进行平均亮度计算。downsample过程可以迭代几次,以减少计算量。
    得到平均亮度后,就可以进行HDR—>LDR映射。这个映射过程可以加入一些视觉特效:实现一个亮度渐变过程,模拟从高亮环境进入较暗环境(或相反情况)时人眼瞳孔变化。具体方法如下:记录连续两帧的Lavg,例如a帧(即前一帧)是高亮环境,b帧(当前帧)是较暗环境,得到Lavg[a] > Lavg[b],假如view point不移动,Lavg[b]将恒定且代表当前Lavg,这时,把b帧映射到LDR,在公式Cscaled( x, y ) = a * Chdr( x, y ) / Lavg的Lavg不直接使用Lavg[b],而是用Lt[b],Lt[b]=Lavg[a]+(Lavg[b]-Lavg[a])*delta,接着在渲染b+1帧是,Lt[b+1]=Lt[b]+(Lavg[b+1]-Lt[b])*delta,其中delta是小于1大于0的数。由于view point不动,所以Lavg[b+n]=Lavg[b+1]=Lavg[b]。使用这个方法,经过若干帧(如n帧)渲染后,Lt[n]会趋向与Lavg[b]相等,从而实现亮度过渡效果,改变delta的大小,可控制这个过渡效果的持续时间。
    完成HDR—>LDR映射后,可以加入Bloom或镜头折射效果,突出高亮部分。Bloom效果比较容易实现,把场景的高亮部分过滤出来后,加入模糊后再与最终画面混合就ok了。而镜头折射效果还真不好说,具体可参考DirectX SDK或者这里
    HDR渲染已是一个常用特效,本文目的是加深对HDR渲染的认识,希望本文对想了解HDR渲染的朋友有帮助。另外,我强烈推荐阅读《Photographic Tone Reproduction for Digital Images》这篇paper,读过的都懂的。另外,也可以参考DirectX SDK例子:HDRLighting以及这里

你可能感兴趣的:(dynamic)