<p>This is a common problem with very subtle gradients. The problem is that the image is displayed with 8 bits per component and this is not quite enough precision to make a gradient that is smooth to the eye under these conditions.</p>

<p>However, the underlying calculations being done in the shader are at higher precision (e.g. float = 24 effective bits of mantissa), so you can apply dithering to improve the appearance of the image.</p>

<p>You can do this by adding a slight amount of random noise to the value of each pixel in the shader. The noise can come from a texture, filled with random grayscale values in [0, 1] and mapped 1:1 to screen pixels. In the shader you remap this to the range [−0.5/255, 0.5/255] and add it to the output color.</p>

<p><a href="https://www.shadertoy.com/view/MlV3R1" rel="noreferrer">Here's a Shadertoy demonstrating this.</a> The left side has no dithering; the right side uses the procedure just described. As you can see, the banding vanishes on the right.</p>

<p><a href="https://i.sstatic.net/X61tI.png" rel="noreferrer"><img src="https://i.sstatic.net/X61tI.png" alt="banding on left; dithered on right"></a></p>

<p>Dithering effectively allows you to represent intermediate values between 8-bit integer levels. For instance, an area that the shader computes as 82.3 (out of 255) would normally be uniformly rounded down to 82, but with dithering it will come out as a random mix with 70% of the pixels set to 82, and 30% set to 83. The eye averages over this very slight, high-frequency noise and it becomes practically invisible.</p>

CC BY-SA 3.0
2016-09-08T06:23:01.810Z
Nice trick. I noticed you're adding .5 to pixel coordinates in the Shadertoy, is this to hit pixel centres in the texture rather than lerped samples?
russ
CC BY-SA 3.0
2016-09-08T06:28:14.263Z
@russ Hmm, I actually copy-pasted that bit from another Shadertoy. :D On a second look, I think that .5 is actually not needed; `fragCoord` should be on pixel centers already.
Nathan Reed
CC BY-SA 3.0
2016-09-09T14:38:59.200Z
For anyone interested, [here](http://www.anisopteragames.com/how-to-fix-color-banding-with-dithering/) is also a well described article how to implement dithering to avoid color banding. Happy coding!
enne87
CC BY-SA 3.0
2018-04-25T07:38:58.433Z
you can try to perform actual statistics error distribution instead of just add noise uniformly, for an even better result. http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/
v.oddou
CC BY-SA 3.0
2018-04-25T15:55:28.370Z
@v.oddou Maybe. Error diffusion would require an extra fullscreen pass, I think, versus adding noise which can be done inline in a shader.
Nathan Reed
CC BY-SA 3.0
2016-09-08T05:45:00.897Z
2016-09-08T05:45:00.897Z
Nathan Reed
<p>I use Phong shading as my lighting model. Nevertheless, the specular shading produces gradients in the light cone:</p>

<p><a href="https://i.sstatic.net/RknYH.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/RknYH.png" alt="enter image description here"></a></p>

<p>Hopefully you can see the gradient starting from the lower left corner expanding along the cuboid.</p>

<p>Here is the shader code that produces this specular light:</p>

<pre><code>vec3 viewDir = normalize(-FragPos);
vec3 lightDir = normalize(-light.direction);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
</code></pre>

<p>By the way, I'm doing my lighting calculations in view-space. </p>

<p>Is there any chance to make the lighting smoother?</p>

CC BY-SA 3.0
2016-09-07T20:53:47.633Z
When you say "gradients", do you mean visible discontinuities in the colour? I'm assuming you don't want flat colour, but a smooth gradient with no bands visible?
trichoplax is on Codidact now
CC BY-SA 3.0
2016-09-07T20:59:10.497Z
That is exactly what I want.
enne87
CC BY-SA 3.0
2016-09-10T00:49:21.630Z
Are you passing lightDir is in viewspace in this case?
Daniel Tran
CC BY-SA 3.0
2016-09-10T10:03:47.850Z
Yes, I do lighting in view-space.
enne87
<opengl><glsl><lighting>
OpenGL specular shading - Gradient banding issues
enne87
# OpenGL specular shading - Gradient banding issues

(Asked by: username_0 on 2016-09-07)

I use Phong shading as my lighting model. Nevertheless, the specular shading produces gradients in the light cone:

[![enter image description here](https://i.sstatic.net/RknYH.png)](https://i.sstatic.net/RknYH.png)

Hopefully you can see the gradient starting from the lower left corner expanding along the cuboid.

Here is the shader code that produces this specular light:

```
vec3 viewDir = normalize(-FragPos);
vec3 lightDir = normalize(-light.direction);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);

```

By the way, I'm doing my lighting calculations in view-space.

Is there any chance to make the lighting smoother?

**Tags:** opengl, glsl, lighting

> **Comments:**
> **username_1**: When you say "gradients", do you mean visible discontinuities in the colour? I'm assuming you don't want flat colour, but a smooth gradient with no bands visible?  
> **username_0**: That is exactly what I want.  
> **username_2**: Are you passing lightDir is in viewspace in this case?  
> **username_0**: Yes, I do lighting in view-space.  

---

## 1 answers

### Answer by username_3 on 2016-09-08. Score: 10 | Accepted answer

This is a common problem with very subtle gradients. The problem is that the image is displayed with 8 bits per component and this is not quite enough precision to make a gradient that is smooth to the eye under these conditions.

However, the underlying calculations being done in the shader are at higher precision (e.g. float = 24 effective bits of mantissa), so you can apply dithering to improve the appearance of the image.

You can do this by adding a slight amount of random noise to the value of each pixel in the shader. The noise can come from a texture, filled with random grayscale values in [0, 1] and mapped 1:1 to screen pixels. In the shader you remap this to the range [−0.5/255, 0.5/255] and add it to the output color.

[Here's a Shadertoy demonstrating this.](https://www.shadertoy.com/view/MlV3R1) The left side has no dithering; the right side uses the procedure just described. As you can see, the banding vanishes on the right.

[![banding on left; dithered on right](https://i.sstatic.net/X61tI.png)](https://i.sstatic.net/X61tI.png)

Dithering effectively allows you to represent intermediate values between 8-bit integer levels. For instance, an area that the shader computes as 82.3 (out of 255) would normally be uniformly rounded down to 82, but with dithering it will come out as a random mix with 70% of the pixels set to 82, and 30% set to 83. The eye averages over this very slight, high-frequency noise and it becomes practically invisible.

> **Comments:**
> **username_4**: Nice trick. I noticed you're adding .5 to pixel coordinates in the Shadertoy, is this to hit pixel centres in the texture rather than lerped samples?  
> **username_3**: @username_4 Hmm, I actually copy-pasted that bit from another Shadertoy. :D On a second look, I think that .5 is actually not needed; `fragCoord` should be on pixel centers already.  
> **username_0**: For anyone interested, [here](http://www.anisopteragames.com/how-to-fix-color-banding-with-dithering/) is also a well described article how to implement dithering to avoid color banding. Happy coding!  
> **username_5**: you can try to perform actual statistics error distribution instead of just add noise uniformly, for an even better result. http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/  
> **username_3**: @username_5 Maybe. Error diffusion would require an extra fullscreen pass, I think, versus adding noise which can be done inline in a shader.  

---

