Color Representation and Treatment

The color is stored in the light and material pass using floating point render targets in both PC (FP16) and XBOX 360 (R10G10B10A2). Unfortunately using R10G10B10A2 could result in precision lost that it is very noticeable if the illumination is not carefully planned. This happens not only because the floating point values are stored using 7 bits for mantissa and 3 for exponent, also XNA resolves these render targets in FP16 textures and that means another transition and an extra possibility to lost some low frequency information.

Comparative between PC (up) and Xbox 360 (bottom). Color contrast is lost on Xbox because the HDRBlendable format works in FP10 on Xbox 360 instead of FP16 as on PC.

The engine also supports cube maps store in RGBM format (RGBK using PlayStation 3 nomenclature). This format provides a usable dynamic range stored in a conventional 8bit RGBA surface format and even permits conventional texture filtering. RGBM is not used for render targets because both platforms do not allow additive blending without using a ping pong technique, something that it is possible in the PlayStation 3's RSX.

Color information is operated in linear space so that illumination will be performed correctly. For that reason the engine performs a digamma operation every time a color or texture is read or stored (Gamma Space).

Finally the color is mapped to sRGB using one of several available tone mapping operators (Filmic, Hable, Drago, Reinhard, etc.) but previously an exposure adjustment (both automatic and manual) and a temporal adaptation process are performed. Overexposure could be simulated using a bloom filter.

Tone mapping options available on the post processing stage.

Moreover, it is possible to adjust color levels (as Photoshop) and apply up to two sRGB color mapping using lookup tables (color grading).



RGBM is an 8bit RGBA format, where Alpha is sacrificed to store a shared multiplier.

Decoding RGBM:

float3 DecodeRGBM(float4 rgbm)
    return rgbm.rgb * (rgbm.a * MaxRange);

Encoding RGBM:

float4 EncodeRGBM(float3 rgb)
    float maxRGB = max(rgb.x,max(rgb.g,rgb.b));
    float M =      maxRGB / MaxRange;
    M =            ceil(M * 255.0) / 255.0;
    return float4(rgb / (M * MaxRange), M);

Important: It’s best to convert the colors from linear to gamma space before encoding.

As far as quality, is difficult to see any banding. It also unsurprisingly handles very bright and saturated colors with the same level of quality. Storing your colors encoded means you cannot do any blending into the buffer. This rules out multi-pass additive lighting and transparency. You will have to use another buffer for transparent things such as particles. And because what is stored in RGB is basically still a color, you can apply multiply blending straight into a buffer stored as RGBM. Multiplying will never increase the range required to store the colors so this is a nondestructive operation. It is also mathematically correct so there are no worries as to whether it will get weird results.


Last edited Jan 22, 2013 at 7:28 PM by jischneider, version 13


No comments yet.