My hdr code consists of 2 parts, of which the first is most relevant to this discussion:
1. Exposure control: Adapting the brightness of stars in the scene according to the dynamic range (total scene brightness range). Exposure is varied exponentially with time to simulate the gradual contraction/expansion of the human iris.
2. Bloom (glare): Applying a shiny blur around spots with a brightness above a certain threshold. I used a "poor man's" method which could run on old graphics chips without shaders but this method was slow and can be considered completely obsolete now.
#1 is almost entirely implemented inside the Renderer::draw() method.
Here is the logic:
1. Find the maximum magnitude of any objects within the scene
Here we ignore the magnitude of any planet beneath us, but this could be changed to simulate light pollution, etc.
2. Compute an eclipse between eye <-> closest object <-> brightest star to prevent a bright shadowed star from inflating the scene's dynamic range. To reduce computational load, a single ray is cast between the eye and the star to compute the shadow, so this only works well for spherical blockers (but luckily works with most planets and moons) and single-star systems (works nicely in our solar system, but maybe not so for others).
3. Compute the exposure, which decays exponentially with time according to some not very scientifically accurate but aesthetically-pleasing parameters (of course, more physically accurate models could be substituted):
float exposureNow = 1.f / (1.f+exp((faintestMag - saturationMag + DEFAULT_EXPOSURE)/2.f));
exposure = exposurePrev + (exposureNow - exposurePrev) * (1.f - exp(-1.f/(15.f * EXPOSURE_HALFLIFE)));
4. Scale the brightness of stars inversely with the exposure.
As the exposure control code is designed to take advantage of existing code in Renderer::draw() that loops through the list of objects in the scene, #1 and #2 are actually done after #3 and #4, but this is ok since the exposure varies with time and is updated every frame and so is only off by 1 frame or ~1/30 seconds.
The advantage of this method is that the computational requirements are quite modest as there is already code to loop through objects and code to find the brightest one can be inserted easily. Also, brightness of stars is already scaled by a brightness factor and the exposure control just hooks into this.
It is better I think than the traditional way of exposure/tone mapping which is to render a floating point texture and use pixel shaders, because my way can scale even with large screen resolutions.
It works quite well, but I did have some problems with the brightness of stars on the surfaces of planets. The brightness would sometimes not vary smoothly as the planet rotated into/out of darkness. I suspect a numerical precision issue. It could be solved with some more careful analysis.