Tuning tone mapping (saturation and contrast loss, etc)

sebbbi

Veteran
I have been tuning tone mapping for our fortcoming game lately. It's the first game I have deloped that has real tone mapping. In earlier games we have just used 2x or 4x dynamic range to get better bloom quality and a very simple linear tonemapping (to get some kind of eye iris simulation).

I have encountered some problems with the tonemapping algorithm I use, mostly color desaturation and losing the constrast of the dark areas and bright areas.

1. I calculate the average screen brightness like this:

For each pixel:
pixel.brightness = log(pixel.r * 0.2125 + pixel.g * 0.7154 + pixel.b * 0.0721);

Calculate average by downsampling (blending 4x4 pixels together) until the final result is one pixel:
averageBrightness = exp(pixel1x1Brightness);


2. Iris closing and opening is set to 1% per frame (60 fps):
interpolatedBrightness = averageBrightness * 0.01 + interpolatedBrightness * 0.99;

3. The tonemap multiplier is calculated like this:
middleGray = 0.12;
tonemapMultiplier = middleGray / (interpolatedBrightness + 0.001)

4. Final pixel color is tonemapped like this:
pixelColor.rgb *= tonemapMultiplier;
pixelColor.rgb /= (1.0 + pixelColor.rgb);

The "pixelColor.rgb /= (1.0 + pixelColor.rgb)" converts the color to [0,1] range. With this operation black colors stay black, pure white (1.0) colors become 0.5, double white colors (2.0) become 0.666, triple white (3.0) become 0.75, and infinite bright pixels become near 1.0.

The good thing about this conversion is that no color values are clamped. However the contrast and color saturation of the image is reduced and the full bit range is not used optimally (bright values are very rarely used). The resulting image looks pretty damp compared to non-tonemapped and linear tonemapped (clamped) versions.

I have noticed that this kind of color compression to [0,1] range is pretty much standard (in all the whitepapers I have read about tonemapping). Is there any better way to do the range compression? Or should I maybe implement some kind of contrast and saturation enhancement filters to combat this effect? What kind of solutions have you implemented on your games and applications?
 
I've always been a fan of photographic tonemapping:
result = 1.0 - exp2(-exposure * color.rgb);

This mimics the behavior of film in analog cameras.
 
I've always been a fan of photographic tonemapping:
result = 1.0 - exp2(-exposure * color.rgb);

This mimics the behavior of film in analog cameras.

Have to experiment with that formula tomorrow. It maps the whole color range to [0,1] area also. So there will be no color clamping using that formula either. However I suppose that will also cause some desaturation, as the RGB color channels are processed separately (the values of RGB channels get closer of each other in the processing making the resulting color more grey).

---

Currently I have implemented both a floating point render target version (16f and 10f) and a 8888 render target version of my hdr rendering pipeline. The 8888 pipeline calculates the tone mapping at the end of the deferred lighting shader (which calculates and outputs the final lighted pixels of one small screen tile), so no floating point render target is needed for full (32f-32f-32f-32f) floating point hdr result. In this method I also write the (non-tonemapped) log luminance of the pixels inside the alpha channel, and calculate the average screen brightness by downsampling those values by 4x4 until I reach 1x1 and read the value by the CPU. The CPU interpolates the exposure (1% each frame at 60 fps), and I use the calculated interpolated exposure in the shader (passed as a shader constant). High dynamic range bloom can be also calculated properly as the whole color range is compressed inside the [0,1] area (reverse tonemapping can be performed to get approx high dynamic range brightness from the compressed values).

With this method the post process step to do the tone mapping and the floating point render targets are not needed. However blending is a bit unrealistic as the system is blending together two post tonemapped images. This has not been a big issue for us, as we don't have that many transparencies (this was also one of the reasons why we chose to use a deferred renderer in the game).
 
Yeah, there will be some color desaturation from this formula as well. If you don't do any form of clipping there will always be some level of desaturation. The simplest solution is to just boost the saturation on the resulting image. I experimented a little with this and found that doing something like this at the end of the tonemapping shader worked nicely in my CustomResolve demo:

color = pow(color, 1.5);

I also tried using the standard s-curve (x^2*(3 - 2x)) but found the results a bit exaggerated. With a smoother "s" I guess the result might be fine. Not sure if it would be better than the pow curve though.
 
Back
Top