Colour banding is an artifact in computer graphics where the viewer can distinguish between colours that are meant to form a smooth gradient.
It was very common in the days when machines could only display a limited palette such as 64 or 256 colours and so the machine had to pick the closest matching colours when attempting a gradient.
But these days all games have a palette of 16.7 million colours – so why is it that colour banding still exists?
Colour banding occurs when the viewer can perceive the difference between two adjacent colour intensities, and so the first thing to recognise is the relationship between colour banding and gamma correction, which we talked about last month.
The human eye is much more sensitive to changes in dark colours than in lighter colours. So, while it wouldn’t perceive the difference between adjacent high intensities such as 226 and 227, it would perceive the difference between adjacent low intensities such as 8 and 9.
It’s for this reason that we apply a gamma ramp to our image that gives more colour resolution to darker intensities than lighter ones. So if you haven’t set up your render pipeline to be gamma correct – and remember the default set-up for DirectX and OpenGL is to be not gamma correct – then you’re not going to be giving enough resolution to the darker colours, and so these will cause banding.
A common way to see this issue is to inspect the vignette that a lot of games have these days. This is a technique that emulates old movie cameras by darkening down the corners of the screen. A vignette is normally applied in a full screen pass and procedurally fades the image to black in the corners.
If the render target hasn’t been set up to be gamma correct then you’ll notice that the vignette exhibits banding. There’ll be similar banding artifacts introduced when performing any algorithmic operation on the framebuffer, such as tone mapping colour correction.
The next most common cause of banding is the DXT compressed textures that the consoles use. DXT is very popular because it reduces the memory required for a texture down to at least a quarter of its original footprint. However, this comes at a visual cost, because it can be very lossy.
DXT compression works like this (thanks to Shawn Hargreaves for this description):
• Divide the image into 4x4 blocks
• For each block, find the two most important colours in it
• Store these two colours in 16 bit 5.6.5 format
• For each of the 16 texels in the block, store a two bit value indicating how far it lies between the two main colours.
The problem is the truncation that happens when converting to 5.6.5 format. The red and the blue values are losing three bits of information giving them only 32 possible intensities. The green fares slightly better with 64 intensities – it’s favoured with the extra bit because our eyes are more sensitive to it.
With such a big loss of precision it’s easy to see how colour banding is introduced. Sky domes can be among the worst offenders because they tend to take up a lot of screen acreage and have lots of subtle gradients. When these subtle gradients are quantized into 32 intensity levels in the blue channel the banding becomes obvious. One possible trick is to swap the blue and green channels in the texture so that the blue channel gets to have 64 intensity levels. Of course, you'll then need to swap the channels back in the pixel shader.
GIVING IT SOME TEXTURE
If the texture is made up of texels with low intensities then it’ll be particularly susceptible to banding because it’s the least significant bits of each colour channel that are truncated. One common technique is normalize these textures by brightening them up at export time by a scalar and then using that scalar to bring them down to their original intensity in the pixel shader.
This only works if the texture consists only of low intensity texels because the high intensity information will be lost. So sometimes the best solution is just to mark the worst offending textures to not be DXT compressed and to take the memory hit.
Finally, there’s alpha blending and transparency. When rendering alpha texels into the framebuffer, the effective colour resolution of the framebuffer pixel is reduced because the colour values are being multiplied by a fractional value representing the alpha. This can become very obvious when layering lots of low intensity alpha polygons on top of each other, for example when drawing smoke or dust. It doesn’t take many layers before banding starts to appear on these types of objects. Reducing overdraw helps in these scenarios as does introducing a dither pattern into the alpha’d texture.
A lot of developers ignore colour banding in the pressure of trying to make a game but these artifacts, combined with the gamma correct artifiacts from last month, add up to a serious reduction in visual quality that can be easily avoided.