In my last game, we just put lum = min(a, max(b, lum)) before the tone-mapper, so that the average lum has limits. If it's 0, then it instead gets clamped at b, and if it's 1000000 it gets clamped at a. The artists could then play with a/b to find decent limits so that dark caves didn't end up looking like daylight.
I'm using the tone map from a DX sample based on Reinhard:
color.rgb *= MIDDLE_GRAY / ( luminance + 0.001f );
color.rgb *= ( 1.0f + vColor / LUM_WHITE );
color.rgb /= ( 1.0f + vColor );
I'm a bit confused with your code; you seem to have two colour inputs: color and vColor? What are they?
Despite my lack of understanding, we can probably break this out into two lines of math though. The first is your exposure, and the second is a the tonemapper. I find that trying to think about these two ideas separately makes it easier for me, but YMMV
C' = C * Grey / Lum
final = C' * ( 1 + V / White ) / ( 1 + V );
I'm calculating the average luminance of the scene and using that in the reinhard calculation.
Are you averaging the luminance values, or averaging log(luminance) values? The latter is known as the geometric mean (as opposed to the regular, arithmetic mean), and is usually used in these calculations.
With more thought throughout the day, I think that the MIDDLE_GRAY value used in the calculation shouldn't be a constant. But should be a variable based on the calculated average luminance.
In photography, that "MIDDLE_GRAY" value is sometimes called the "key", or a zone in the Ansel Adams system, or even "mood" because speaking artistically, it gives the final tone-mapped image a different mood, as to whether it's a day or night shot, etc... Technically, I think it's supposed to be the value that an 18% diffuse grey ball would have if it were present in your shot.