HDR values in 8 bit RGBA pixels
June 2, 2008A few days ago, I was standing in front of the same old problem with multiple render targets (MRT) in OpenGL (at least on NVidia Geforce 7 series): All buffers have to use the same bit depth. If you want to have a depth buffer, this is usually 32 bits. Now you can either rely on 8 bit per color channel (rgba*8 = 32) as in the good old days, or you have to find a work-around. One option is to increase the number of image buffers, using two buffers with two 16 bit float channels each. if that’s not possible, you might consider the following trick: Ward’s RGB encoding is basically a float format with a shared exponent. You rewrite your channels as mantissa*2^exponent, determine the largest exponent and scale each mantissa according to this exponent. This loses some resolution for the minor channels, but all in all it takes little bandwidth, covers a large range of values and is still computable in reasonable time:
vec4 HdrEncode(vec3 value)
{
value = value / 65536.0;
vec3 exponent = clamp(ceil(log2(value)), -128.0, 127.0);
float commonExponent = max(max(exponent.r, exponent.g), exponent.b);
float range = exp2(commonExponent);
vec3 mantissa = clamp(value / range, 0.0, 1.0);
return vec4(mantissa, (commonExponent + 128.0)/256.0);
}
vec3 HdrDecode(vec4 encoded)
{
float exponent = encoded.a * 256.0 - 128.0;
vec3 mantissa = encoded.rgb;
return exp2(exponent) * mantissa * 65536.0;
}
Note that this snippet uses a rather strange scaling factor of 65536.0. I had to add this to avoid a bug with exponents near 0, and since my values where all below 65536.0, I just avoided and ignored this. And since I was able to change my MRT code to avoid the depth buffer at all (and therefore avoid this custom HDR coded), I’m not going to fix this. However, I still think it might be useful.