oneMapping&ExposureAdjustment
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.
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.
return
float4(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:
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.
return
float4(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!
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.
return
float4(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.
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.
float
linReference = 0.18;
08.
float
logReference = 444;
09.
float
logGamma = 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.
float
FilmLutWidth = 256;
16.
float
Padding = .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.
return
float4(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.
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.
return
float4(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.
01.
float
A = 0.15;
02.
float
B = 0.50;
03.
float
C = 0.10;
04.
float
D = 0.20;
05.
float
E = 0.02;
06.
float
F = 0.30;
07.
float
W = 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.
float
ExposureBias = 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.
return
float4(retColor,1);
27.
}
Hopefully, that should clear up most of the ambiguity about these operators.