HDR & Bloom
HDR
Screens, historically, have been SDR displays meaning they render RGB colors where each channel maps to an 8-bit value (0-255, normalized to 0.0-1.0 in shaders). Naturally, games would render colors between 0.0 and 1.0 : everything below 0 or above 1 would be clipped (i.e. there is no brighter white than (1, 1, 1)). This can be an issue since the values above 1.0 still represent useful information (e.g. this piece of the screen is bright because directly lit by a nearby light) but they are lost due to clipping and therefore they cannot be used by post processes (e.g. to generate bloom, chromatic aberrations, etc.). Additionally, this means that artists must be careful when tuning light intensities so they aren't too bright and saturate rendered colors.
The solution is simple: render to a high dynamic range (HDR) image, meaning to an image where each channel of each pixel can hold a decimal value well above 1, apply appropriate post processes on this high range of value (so nothing is lost), and finally bring everything back to SDR/RGB space (I think this last step can be skipped for some monitors? but I'm not there yet anyway). Once again, there is no better than LearnOpenGL to learn about ... HDR.
So I did this, made sure the temporary images I render the scene to were set to use HDR (3x 16 bit floats) instead of SDR (3x 8 bit fixed point), increased the brightness of my game's lights to take advantage of the increased range, and implemented a new Tonemap pass between scene rendering and anti-aliasing that takes care of bringing those high dynamic range colors back to RGB space.
Here is a very simplified overview of my rendering pipeline, in draw order left-to-right:

Bloom
In case you live under a rock, bloom is a post-process effect where bright pixels bleed onto darker pixels to imitate how eyes behave in bright environments. Bloom is traditionally achieved by extracting bright pixels (~1.0 red/green/blue) from an RGB image, blurring them, and adding the result onto the initial image. With HDR implemented, bloom is practically trivial to implement: there is no need to extract bright pixels anymore since, in HDR, values above 1 naturally identify the bright areas; and the higher the value, the more they dominate the blurred result. The rest is unchanged (blurring and mix with original image).
In my case I implemented bloom with a mip chain of textures: the image from the rendered scene is blurred and halved recursively for a few steps (using a special blurring kernel of 13 samples) then all those results are added together to generate the final blurred image. This gives an outcome comparable to the one we would get if we used a huge blurring kernel (many more than 13 samples per pixel) but for a fraction of the cost. It's borrowed from Call of Duty, but if you want to learn more you can read this froyok post, it's great!
Skipping image, see below for video displaying this week's outcome.
Bonus
Very invisible to the viewer, but I also revisited how I inspect the output of my many render passes. It's now much easier to use and extend as I add more render passes.
In the video:
- I show you step by step the blurring + downscale process (image gets pixelated) then the sum that combines the blurs (image gets brighter and less pixelated)
- A few controls over tonemapping and bloom (more incoming, it's a very simple tonemap I did for now)
- Poor driving skills
