Transformed colour model

One problem with the colours is that the RGB components range from 0 to 1. When using a shader to add light or shadow effects, special care has to be taken not to over shoot the 1.0 limit. It seems to be easier to start with “full lit colours”, and then reduce, instead of starting with non lit materials and add light.

I tried an idea to transform light from the range 0 to 1, to the range 0 to infinity. All manipulations were then done in that new range, and as a last thing, the range was transformed back to 0 to 1 again. I used the following simple formulas, for each colour:

transf.r = material.r/(1-material.r)
transf.g = material.g/(1-material.g)
transf.b = material.b/(1-material.b)

And the transformation back, after applying lighting effects, was:

fragColour.r = transf.r/(1+transf.r)
fragColour.g = transf.g/(1+transf.g)
fragColour.b = transf.b/(1+transf.b)

The way that lighting was done had to be changed a little, but I think the effect was interesting. There are some problems when a material has a colour channel that is saturated. It also seems like blending is best done in the original colour range. I am sure this has been investigated elsewhere, but couldn’t find any references.

Is this path a dead end?

Hmm, don’t know about the transformations you are doing, but it sounds like you are looking for high dynamic range (HDR) rendering - where one uses float color channels (not clamped) and then applies tone mapping to map back to the usual [0, 1] range for display.

Thanks for the references! Reading through them I see that what I am using, is reverse tone mapping to expand the range to HDR, apply lighting effects, and then normal tone mapping to map it back to [0,1]. Adding or multiplying to a normal colour channel gives the following result, as shown in Adding and multiplying.

When using the inverse tone mapped range, the result is instead as shown in Transformed colour.

The diagrams show what the changed colour will be, as a function of either adding 0.2 or multiplying with 1.2 to a colour in the range 0-1. There are some interesting differences between these.[ul][]A colour can no longer be saturated (as it would be on the right side in the first diagram). []The colour change is no longer linear. This is more natural in my opinion. If you light a lamp in full sunshine (in the real world), you may not notice the difference.[*]Adding the same constant to R, G and B that differs will produce a result that adds more to the darker channels. This will have the effect of making the reflection more white (or only grey). When used for specular glare, for example, I think it is less interesting to preserve the original colour balance.[/ul]I tried this technique in a game I am doing. The game contains sun light, lamps, ambient lights, specular glare, and other light effects. It was now possible to calibrate each of them, one at a time, and then simply combine all of them together, without any risk of saturated effects. I no longer have to estimate the worst case, to avoid saturation.

I use a simple exposure function in fragment shader to fit the lighting to be desired:

out_color.rgb = vec3(1.0) - exp(-exitant_radiance * lights.exposure.x);

Here out_color is the fragment shader output variable, exitant_radiance is the shader output color computed as usual without exposure (possibly going above 1.0), and light.exposure.x is the exposure value.

I see, that will indeed also transform the light from a high dynamic range to the 0-1 interval. I tested it, but got slightly worse performance, probably because of the exp function.

Btw, I realised my shader could be simplified into

fragColour = transf/(1+transf);

Although that had not further performance effect.