GPU Shader Tutorial Logo
GPU Shader Tutorial
This tutorial is currently a work in progress. Content may be added, updated, or removed at any time.

Shader Advanced - Color Banding and Dithering

An issue that can occur when drawing gradients is something called color banding. Let us look at a flat grayscale gradient going from black to white.

Example - Color Banded Gradient

Cannot run WebGL examples (not supported)

Fragment Shader Code:

1
2
3
4
5
6
7
8
uniform highp vec2 resolution;

void main() {
  highp vec2 coordinates = gl_FragCoord.xy / resolution;

  highp float fragmentColor = mix(0.05, 0.35, 1.0 - coordinates.y);
  gl_FragColor = vec4(vec3(fragmentColor), 1.0);
}

The issue may not be very obvious at first, so here's a screenshot of a portion of the image.

Color Banding Screenshot Normal

We can modify the light levels to make the issue more obvious.

Color Banding Screenshot Exaggerated

It appears that the image has bands of colors going across it. This shouldn't occur since the decrease of light from the bottom to the top should be gradual and smooth.

This issue is called color banding, and it is a problem because it makes a gradient of colors appear to be "chunky" rather than smooth, which is generally an undesirable effect.

We've previously discussed in the color part 2 chapter that computers are limited in what colors they have the ability to represent and show.

This issue occurs mainly because the colors that are required to show the colors transitioning smoothly just do not exist among the range of colors that can form an image and a monitor can show.

This means that if a computer wished to show a gradient of colors, there will be bands of colors forming instead of a smooth transition, since a computer cannot show the colors in between the two bands.

This issue can be illustrated in a simple way. Suppose we could only represent colors using a 1-bit value. This would mean a computer could only show two colors, black and white.

Actual Gradient

Source

Now, suppose the computer had to render a gradient of colors, going from white to black, as shown in the image above. Since the computer can only show black and white, all the shades of gray in between cannot be displayed by the computer.

Banded Gradient

This results in the computer showing a continues band of white, with a sudden transition to a band of black. This is a case of color banding, albeit an extreme one.

Dithering

The solution to this issue of not being able to represent intermediate colors is called dithering. The Wikipedia article on dithering, as well as the YouTube video by Computerphile on ordered dithering provide good explanations on what dithering is, but we'll also discuss it here.

Dithering is the process of introducing noise into an image. By introducing this noise in a certain way, you can overcome the limitations of the detail that can be shown by the computer and screen.

Below is an example showing how introducing noise can improve the detail of an image:

Wikipedia Dithering Example

Source

The image is just made from white and black pixels, but through the introduction of noise into the image, shades of gray can be seen within the image, although there is no actual gray present.

This is due to how colors interact with each other and affect how they appear to us, as discussed in the color part 2 chapter. This interactions blends the noise of the colors together to appear as one smooth band.

The noise does introduce a grainy effect, but the added detail in color is a high benefit, and in multiple cases this added detail offsets the issue of grain in the image.

The noise can be a lot harder to spot in certain issues where colors that form bands are a lot closer to each other in the color spectrum, like in our example of color banding above.

If we apply a noise to our image, it should cause dithering and allow for the colors to blend together.

Since the colors forming the bands are already pretty close in the color spectrum, the grainy effect introduced by dithering should be very hard to spot, making it ideal to fix the banding issue.

Example - Dithered Gradient

Cannot run WebGL examples (not supported)
Noise Granularity: 0.5 / 255.0

Below is a zoomed in part of the new dithered image.

Dithering Screenshot Normal

We can modify the light levels to see the introduced noise.

Dithering Screenshot Exaggerated

While with the color banding screenshots, the banding was noticeable even in the non-altered image (unless your display isn't calibrated well), with the dithered original screenshot there is no appearance of banding or even noise in the image.

The noise is only visible when modifying the light levels, but since the randomness spreads the colors of the bands, they easily blend together and look smooth to your eyes.

Fragment Shader Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uniform highp vec2 resolution;

const highp float NOISE_GRANULARITY = 0.5/255.0;

highp float random(highp vec2 coords) {
   return fract(sin(dot(coords.xy, vec2(12.9898,78.233))) * 43758.5453);
}

void main() {
  highp vec2 coordinates = gl_FragCoord.xy / resolution;

  highp float fragmentColor = mix(0.05, 0.35, 1.0 - coordinates.y);
  fragmentColor += mix(-NOISE_GRANULARITY, NOISE_GRANULARITY, random(coordinates));
  gl_FragColor = vec4(vec3(fragmentColor), 1.0);
}

A random number is generated for the fragment which needs to be noised. The granularity for the noise (stored in the constant NOISE_GRANULARITY) is set to a very small number (0.5/255.0) to ensure that any shift in color due to the noise would be minimal.

A random value was linearly interpolated using the GLSL function mix within this noise granularity range using the generated random number for the fragment and added to the fragment color, resulting in the color of the fragment being slightly modified.

Since the shift in color is random for every fragment, it results in in unordered/random dithering due to the randomness of the generated noise.

This process removes the appearence of color banding, as seen in the dithering screenshots, and allows for color gradients to appear much smoother than they really are.

To learn more about dithering in general and various dithering techniques out there, check out the HTTP 203 episode discussing it.

Summary

  • Color banding is an issue that can occur when the computer cannot simulate a transition between two colors since it cannot represent the intermediate colors between the two.
  • Dithering is a process of adding noise to an image to remove color pattern issues such as color banding. Dithering is of two types - ordered dithering, and unordered dithering.