Monday, 19 April 2010

Approximating Film with Tonemapping


Please note:

I now realise that a bunch of this post is a complete load of rubbish.

- The film approximation does have nice black compression. It's just subtle, and doesn't show up in the linear graph (doh!)


Recently, I finished up a big tutorial for HDR rendering techniques.
This tutorial covered Tone Mapping and Gamma Correction.

If you are not aware of Gamma Correction or Tone Mapping:
Gamma Correction lets you compensate for the human eye's non-linear perception of light. To put it simply, if you double the brightness of the pixel on screen, you actually more than quadruple the intensity of the light output by your monitor!

Tone Mapping is how you compress the huge visible range of light into something more suitable to display on a limited contrast monitor. It varies by game, and is often the most important factor in determining the 'look' of a game.

Originally, I had two Tone Mapping methods available in the tutorial: Exponential Exposure (my personal favorite) and the function (x / (x+1)), which is commonly used - including samples in the DirectX SDK.

Broadly speaking, these both produced very similar results - with exp producing a more saturated result. However, they both suffered from loss of saturation the brighter the image got.

Then one day a friend introduced me to the light response curve for Film.
He showed me the this image, which is the response curve of Kodak's high end motion picture film.
Also, he introduced me to the following approximation, which he said was a close match to the kodac film:

rgb = Math.Max(0, input - 0.004);
output = rgb*(0.5+6.2*rgb)/(0.06+rgb*(1.7+6.2*rgb));

It even approximated gamma correction. Amazing!
I quickly implemented it as a 3rd tone mapping option. The results were good, the curve maintained saturation and produced really clear blacks.

(I later discovered this function - and that graph - were both from Naughty Dog's excellent paper 'Uncharted 2: HDR Lighting')

For comparison, here are the different tone mapping graphs (with gamma correction):

x / (x + 1):

x / (x+1)

exponential exposure:

exponential exposure

film approximation:

film approximation

As can be seen, the film approximation is more 'punchy' than the previous two. However, looking closer, I was a bit disappointed:

Deep blacks of the film approximation:

Deep blacks of the film approximation

I was expecting something similar to the actual film response curve, with compressed blacks.
Now, perhaps I'm simply not using the right magic numbers - but unfortunately I cannot find the original source for the approximation.

I wasn't entirely happy with this, so I set about improving it.
I didn't want a cutoff to black, I wanted a nice transition into compressed blacks - similar to the film curve.

This is the revised approximation:

const float cutoff = 0.025;
rgb += (cutoff * 2 - rgb) * saturate(cutoff * 2 - rgb)
* (0.25f / cutoff) - cutoff;
output = rgb*(0.5+6.2*rgb)/(0.06+rgb*(1.7+6.2*rgb));

It looks more complex than it is.
Deep blacks of the film approximation, using black compression:

Deep blacks of the film approximation using black compression

That's much better!

The big upshot of this is that the cutoff value can be boosted significantly.
Here are a set of images comparing black compression with a boosted cutoff:

Exposure tone mapping:

Exposure Tone Mapping
Film Approximation
Film Approximation + Cutoff
Film Approximation + Black compression

Exponential Exposure Tone Mapping lacks saturation. The Film Approximation is much better, but still looks a bit desaturated in the shadows. Turning on the cutoff helps, but destroys detail in the deep blacks. Turning on black compression isn't as aggressive as the cutoff, but retains both saturation and deep black detail.

(Note, this demonstration is highly dependent on your monitor!)

No comments:

Post a Comment