Gamasutra is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.

Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
January 18, 2021
arrowPress Releases

If you enjoy reading this site, you might also want to check out these UBM Tech sites:


Banding and Dithering

by Josh Klint on 05/18/16 01:13:00 pm   Featured Blogs

5 comments Share on Twitter    RSS

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.


I came across a very interesting presentation that talks about how to avoid "banding" artifacts in game graphics.  The author uses dithering to add noise to an image and break up the visible lines your eye can detect.  This even works with audio.  When noise is added to the mix, the original tune appears to become more high-fidelity than it actually is:

I was able to use this concept to improve the appearance of banding in shadow acne in a low-resolution spotlight with a large volume.  Here is the original situation, purposely created to maximize the banding effect:

Attached Image

And here is the result when some slight dithering is added:

Attached Image

The trick to dithering is to know how much noise to add.  If we add too much it starts becoming very apparent:

Attached Image

You want to calculate your noise amplitude as the difference between the two discrete levels you are trying to bridge.  In the case of the spotlight shader, random noise is being added to the z coordinate of the shadow lookup, so I want the noise amplitude to be equal to the minimum depth difference the shadowmap can display.  I calculated this as follows, but more experimentation is needed to make sure its right:

float noiselevel = 0.000001 * lightrange.y; shadowcoord.z += rand(lightnormal.xy) * noiselevel - noiselevel * 0.5;

You also want to make sure your random seed is really random.  Using the screen coordinate alone produces bad results because the same random seeds will stay in place as you look around and cause visible patterns.  If you use the "currenttime" shader uniform the noise will constantly change as the camera stays still, resulting in a film grain effect.  I find it is best to multiply the xy components of the fragment coordinate by some other vec2 value.

Here's another example with a directional light exhibiting a banding appearance due to a low angle on the ground:

Attached Image

And here is the improved image with dithering applied:

Attached Image

This code does not eliminate shadow acne, it just breaks it up with some random noise so that your eye can't as easily detect a continuous line.

The same technique can be used to improve the appearance of the new godrays shader I am working on.  With only 16 samples for the rays, banding is very easily visible in this image.

Attached Image

When we add a random offset to the ray starting position, with an amplitude equal to the length of the distance between steps, banding disappears.

Attached Image

Of course more samples are always better, but even at higher sample counts, dithering makes a huge improvement in quality.  Leadwerks Engine 2 actually used 64 samples, with worse results than what I got using just 16 samples above.  The ease with which I can now modify shaders in Leadwerks Editor and see the results instantly really helped develop these techniques. 

Related Jobs

Square Enix, Inc.
Square Enix, Inc. — El Segundo, California, United States

Senior Web Developer
Sucker Punch Productions
Sucker Punch Productions — Bellevue, Washington, United States

Jackbox Games, Inc.
Jackbox Games, Inc. — Chicago, Illinois, United States

Senior Gameplay Engineer
innogames — Hamburg, Germany

Senior Mobile Game Developer (C++) - Forge of Empires

Loading Comments

loader image