HDR Rendering – Tonemapping and Bloom
I have been experimenting with tone mapping from a long time. I tried many different ways of implementing tonemapping & bloom and now I have finalized this feature in the engine.
Tone mapping is a technique used in image processing and computer graphics to map one set of colors to another to approximate the appearance of high dynamic range images in a medium that has a more limited dynamic range.
I tried few different Global tone mapping operators with various ways to calculate scene luminance.I tried 3 major approaches (with a lot of variations in between) –
In the first approach, I calculated scene luminance by averaging log luminance values of HDR render target of the scene. I did this by rendering a screen space quad calculating log luminance values for the scene and then I used GenerateMips to average them automatically for me. I tried some different tone mapping operators including Reinhard, filmic curve and modified filmic curve (or uncharted2’s filmic curve). At the end modified filmic curve with the ability to modify the curve as need turned out to be the best approach for tone mapping operator.Problem with this method is any few extreme dark/bright spots in the scene can make the whole scene look brighter or darker beyond desired ranges.
I knew nowadays games use some image histogram based approach for advanced tone mapping for better results. So I went ahead and implemented histogram calculation algorithm using compute shaders. For tone mapping, I implemented Histogram Adjustment with a Linear Ceiling algorithm. The performance wasn’t very good and there were no direct options to control exposure, ignore some bright/dark spots, etc. Also implementing further parts of the algorithm (with more buckets) was making this sort of unsuitable realtime.
This is the method I wanted to use with histograms in the first place, but I was missing few pieces of the puzzle and with little more research over the internet figured it all out. So In this method Histogram is used to calculate the scene luminance with options to ignore some dark or bright pixels based on % (as explained here). Desired exposure is specified by user (I have also implemented a couple of ways to calculate exposure automatically). Once we have scene luminance and exposure values from eye adaptation I use filmic tone mapping to map values to LDR.
Here are render timings for all 3 methods as measured using RenderDoc on intel HD4600 for 720p –
- Method 1 – Reinhard: 1.33 ms Filmic Curve: 1.455 ms
- Method 2 – 1.7ms with 64 bins
- Method 3 – 2.2 ms
Final HDR Rendering Path
So the final version of HDR rendering path consists of following steps –
- Render Scene to FP16 render target
- Generate Histogram (64 bins) with options to ignore % of bright or dark pixels.
- Calculate scene exposure
- Eye Adaptation
- Calculate Bloom
- Tonemap using Filmic curve + Bloom
- Gamma correct and store results in LDR (Gamma correction is auto using sRGB render target)
You choose among few different options for both tone mapping operators and how to calculate exposure including automatic exposure calculation. All of this is easily configurable by modifying respective #define values in shader file.
Here’s a breakdown of timings on intel HD 4600 for 720p (full-size pass for both tone map and bloom) –
Some screen shots –
- High Dynamic Range Imaging, Second Edition
- Histogram Adjustment with a Linear Ceiling
- Histogram Calculation using CUDA
- Interactive Time-Dependent Tone Mapping Using Programmable Graphics Hardware