Dithering – Reverse Engineering of the Rendering Technique

Inspired by a post about Uncharted 4 development, I decided to write a short post about dither algorithm and one of its uses in game development, precisely to render the opaque object as semi-transparent.

What is Dither? Dither per Wikipedia :

Dither is an intentionally applied form of noise used to randomize quantization error, preventing large-scale patterns such as color banding in images.

For our purposes, we need to go step further and look at Ordered Dithering, which is opposite to standard dithering algorithm (random) has pre-determined threshold map.


Ordered dithering is an image dithering algorithm. It is commonly used by programs that need to provide continuous image of higher colors on a display of less color depth.

In this example, I am going to use Bayer Matrix as a threshold map :

Screen Shot 2017-10-01 at 2.23.29 PM.png

For the purpose of this post, I did a small exercise and similarly to Ming-Lun, wrote small algorithm generating power of 2 Bayer matrices and visualized results in Unity.

With threshold map ready, time to simulate transparency by rendering opaque object using fragment shader which is discarding pixels based on current opacity level.

By using a DitheringValue function :

float DitheringValue(int cordX, int cordY) {
int x = cordX % _ArraySide;
int y = cordY % _ArraySide;
float result = _Array[(x + y * _ArraySide)] / _ArrayLength;
return result;

We wrap current pixels coordinates to be within Bayer matrix size and do the lookup to pull precalculated threshold value. Then we divide it by the length of the array giving us a value between 0 and 1.

With that value ready, we do want to determine if a pixel should be discarded or not. We can use discard function available in HLSL, but that would require from us to introduce not necessary if statement. Instead, we can use clip which will discard every pixel if value provided is going to be less than 0;

clip(DitheringValue(i.uv.y * _Resolution, i.uv.x * _Resolution) - _Transparency);

_Transparency is a value defining the opacity level of the material.

The final results look like this :


It shows object going from 0 opacity to 1 and back to 0.

An Image below shows two planes in front of a cube. Top Left (green) plane is using standard semi-transparent material set to 0.5f opacity, Bottom Right (blue) plane is using an opaque material with dithering algorithm set to 0.5f opacity.

Screen Shot 2017-09-30 at 12.22.37 AM

As you can see results are not the same. There are several issues visible at first glance:

  • Repeating tiling effect
  • Colors don't mix as in the green plane case

These problems can be addressed by improving the threshold map generating algorithm (i.e., by using Robert A. Ulichney’s void and cluster method). I will try to show how these can enhance dithering in part 2, and in the meantime, if you are interested, code for this experiment is available here :

Dithering on GitHub

Thanks to Ming-Lun (Uncharted 4 Dithering) for the work over Uncharted 4 and a post about dithering which inspired me to do a quick experiment on my own. For more resources, I encourage you to have a look at Libcaca study: the science behind color ASCII art. With a little bit more time, I will prepare Part 2 in which I plan to describe pros and cons of using dithering in more detail as well as improved algorithm to generate better threshold map.


Next Part :

  • Performance Considerations
  • Optimization (Avoiding Tile Artifacts)


Update [ 04.11.2017 ]

While playing recently released Super Mario Odyssey, I've noticed that similar technique was used to render transparent object getting close to the camera. Thought it would be a great example to share a short gif as an example of using it in a game.



Close Menu