HDR&Filmic Tonemapping Operators

来源:互联网 发布:软件测试的含义 编辑:程序博客网 时间:2024/05/16 07:45

博主在unity5上的渲染结果,第一张片是母校图书馆 ^ _ ^



但是随着游戏业的画面水准开始向电影水准发展,就需要我们有更好的理解HDR,来进一步把游戏像电影画质推进,这也是近几年的GDC和siggraph上都有一线studio在推HDR相关的技术,比如naughty dog10年的filmic tone mapping,11年crytek在siggraph里面提到的physically based hdr等。
首先看一下概念:
  • dynamic range:reinhard的<tone mapping>论文中定义:一个场景中最高亮度与最低亮度的比是dynamic range。
  • low dynamic range : 之所以出现这种情况是图像存储介质(打印纸,照片,电脑屏幕等)精度有限造成的,导致在range上没法完全记录一个场景的亮度信息,只能记录有限的一部分,比如游戏里常见的在rgba8上渲染,每个channel大于1的部分就被截取到1了。
  • high dynamic range : 准确讲是high dynamic range imaging,是指一种图像技术,它能让图像表示一个比原有技术(之前的LowDynamicRange)更大(greater)的dynamic range
    • 这样就可以更加准确和真实的描绘一个场景
进一步说,hdr的存在在于图像存储与表达介质的精度有限,这也是为何游戏引擎大佬在提的都是CG和电影画质,而不是和现实一样的:因为存储介质在这里摆着呢,没法和现实一样,cg是上限。
实际应用中,我们也看到了,hdr除了在range上发力,在精度上也可以更好,比如描绘发暗的场景,可以把亮度矫正到可以充分显示暗部细节。


oneMapping&ExposureAdjustment

这里除了数学上正确的亮度计算以外,要考虑到人眼视网膜的特点,比如之前的gamma就是一个根据视网膜特点,重新分配亮度信息,来让人眼在有限的显示精度下获得最大的信息量。视网膜有两个典型特点,这里通过tone mapping和exposure adjustment来解决:
  • 自动根据亮度矫正明暗:让我们晚上看东西也能比较清楚,一开灯眼睛要矫正一会回来
  • 局部适应性:比较经典的图是:

这里我们遇到两个问题,各用两个方案解决:
  • 曝光率问题---解决:exposure adjustment。和照相时候曝光原理差不多,白天亮曝光就短一些,晚上曝光长一些,编程时候就是计算render target的平均亮度,然后矫正,这样沙漠的白天和丛林的夜晚都可以在游戏中的rgba8上有一个良好的体现。在hable的论文里,这个属于不同的场景之间的处理问题范畴。
  • 压缩的过程中不可避免的涉及到重新分配亮度值,怎么做来得到更好的尽可能不失真的画面这个解决方案就是tone mapping
实际中也有把这个exposure adjustment并到tone mapping中去,其实还是分开比较好,因为这些概念是从摄像技术中来的,曝光就是曝光,tone mapping就是tone mapping。
  • zone:存储空间的亮度阶这么一个概念,比如print只有11个zone
  • middle grey:中间的亮度
  • dynamic range:指场景中最高亮度与最低亮度的比值
    • 这是一个最学术派的定义,具体上摄像师一般会追求细节还可以明辨的range
  • key:描述整个场景亮度的数值
  • dodging and burning:把高亮度的东西亮度降低为dodging,把低亮度的部分加亮为burning







Filmic Tonemapping Operators








The most common questions that I get about my GDC talk have to do with the tonemapping operators. In particular, I’ve always found that when I read through presentations for code snippets that I always miss something. Those 2.2s can be tricky! So this post is a quick reference for various operators that I talked about. Also, I copied and pasted this code from my RenderMonkey scene so there may be typos.

All of these examples use this HDR image of Habib’s killer condo. Also, click any image for the high-res version.

First off, there is good old linear. All it does is read the linear data, do an exposure adjustment, and adjust for the monitor’s gamma of 2.2.

view sourceprint?
1.float4 ps_main( float2 texCoord  : TEXCOORD0 ) : COLOR
2.{
3.float3 texColor = tex2D(Texture0, texCoord );
4.texColor *= 16; // Hardcoded Exposure Adjustment
5.float3 retColor =pow(texColor,1/2.2);
6.returnfloat4(retColor,1);
7.}

Pretty simple. It looks like this. Click for high-res.

Don’t forget the pow(1/2.2). If you forget that step, it looks like this:

view sourceprint?
1.float4 ps_main( float2 texCoord  : TEXCOORD0 ) : COLOR
2.{
3.float3 texColor = tex2D(Texture0, texCoord );
4.texColor *= 16; // Hardcoded Exposure Adjustment
5.float3 retColor = texColor;
6.returnfloat4(retColor,1);
7.}

Next up is Reinhard. There are many variations, bu I’ll do the simplest which is 1/(1+x). A common variation is to only do it on luminance. Don’t forget the pow(1/2.2) at the end!

view sourceprint?
1.float4 ps_main( float2 texCoord  : TEXCOORD0 ) : COLOR
2.{
3.float3 texColor = tex2D(Texture0, texCoord );
4.texColor *= 16; // Hardcoded Exposure Adjustment
5.texColor = texColor/(1+texColor);
6.float3 retColor =pow(texColor,1/2.2);
7.returnfloat4(retColor,1);
8.}

Here it is with Haarm-Peter Duiker’s curve. This version is very similar to the Cineon node in Digital Fusion. The texture FilmLut refers tothis TGA file. No pow(1/2.2) necessary.

view sourceprint?
01.float4 ps_main( float2 texCoord  : TEXCOORD0 ) : COLOR
02.{
03.float3 texColor = tex2D(Texture0, texCoord );
04.texColor *= 16; // Hardcoded Exposure Adjustment
05. 
06.float3 ld = 0.002;
07.floatlinReference = 0.18;
08.floatlogReference = 444;
09.floatlogGamma = 0.45;
10. 
11.float3 LogColor;
12.LogColor.rgb = (log10(0.4*texColor.rgb/linReference)/ld*logGamma + logReference)/1023.f;
13.LogColor.rgb = saturate(LogColor.rgb);
14. 
15.floatFilmLutWidth = 256;
16.floatPadding = .5/FilmLutWidth;
17. 
18.//  apply response lookup and color grading for target display
19.float3 retColor;
20.retColor.r = tex2D(FilmLut, float2( lerp(Padding,1-Padding,LogColor.r), .5)).r;
21.retColor.g = tex2D(FilmLut, float2( lerp(Padding,1-Padding,LogColor.g), .5)).r;
22.retColor.b = tex2D(FilmLut, float2( lerp(Padding,1-Padding,LogColor.b), .5)).r;
23. 
24.returnfloat4(retColor,1);
25.}

Next up is the optimized formula by Jim Hejl and Richard Burgess-Dawson. I completely forgot about Richard in the GDC talk, but he shares the credit with Jim. Sorry Richard!! Note that you don’t need the pow(1/2.2) for this one either.

view sourceprint?
1.float4 ps_main( float2 texCoord  : TEXCOORD0 ) : COLOR
2.{
3.float3 texColor = tex2D(Texture0, texCoord );
4.texColor *= 16; // Hardcoded Exposure Adjustment
5.float3 x = max(0,texColor-0.004);
6.float3 retColor = (x*(6.2*x+.5))/(x*(6.2*x+1.7)+0.06);
7.returnfloat4(retColor,1);
8.}

Finally for the Uncharted 2 operator made by yours-truly. For this image I changed the defaults slightly for A and B.

Edit: Oops, in the previous version, I had the exposure bias outside the tonemapping function. Now it is fixed, where it is inside the tonemapping function.

view sourceprint?
01.floatA = 0.15;
02.floatB = 0.50;
03.floatC = 0.10;
04.floatD = 0.20;
05.floatE = 0.02;
06.floatF = 0.30;
07.floatW = 11.2;
08. 
09.float3 Uncharted2Tonemap(float3 x)
10.{
11.return((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
12.}
13. 
14.float4 ps_main( float2 texCoord  : TEXCOORD0 ) : COLOR
15.{
16.float3 texColor = tex2D(Texture0, texCoord );
17.texColor *= 16; // Hardcoded Exposure Adjustment
18. 
19.floatExposureBias = 2.0f;
20.float3 curr = Uncharted2Tonemap(ExposureBias*texColor);
21. 
22.float3 whiteScale = 1.0f/Uncharted2Tonemap(W);
23.float3 color = curr*whiteScale;
24. 
25.float3 retColor =pow(color,1/2.2);
26.returnfloat4(retColor,1);
27.}

Hopefully, that should clear up most of the ambiguity about these operators.


1 0