0
votes

I'm trying to implement light attenuation in a Phong shader. The pixel/fragment shader does the following calculation (per light source):

float3 refl = reflect(e, n);
float dist = length(L.xyz);
float3 l = normalize(L.xyz);
float attn = 1.0 / (1.0 + 1.0 / 30 * dist + 1.0 / 700 * dist * dist);
d += saturate(dot(n,l)) * gLightColor[i].xyz * attn.xxx;
s += pow(saturate(dot(-refl, l)), power) * gLightColor[i].xyz;

where:

  • e is the normalized eye vector
  • n is the normal to the surface
  • L is the (non-normalized) light vector
  • d is the diffuse color
  • s is the specular color

For simplicity, I'm ignoring the effect of attenuation on the specular color and using some built in attenuation coefficients. Generally, I get the correct behavior:

enter image description here

As I increase the distance between the light source and the object, the reduction in intensity becomes not smooth:

enter image description here

enter image description here

I'm getting rings instead of a gradient. Is this a matter of floating point precision? How can I get a smooth intensity change at all distances?

Using D3D 9 and HLSL shader model 3.

1

1 Answers

2
votes

You are encountering color banding. While you're lowering the intensity of your light, you are stretching basically the gradient more and more. As each color is displayed by three 8-bit color channels it has a limited output precision. At some point you can see the single steps between two colors, when the area of one color becomes too broad, because your eye recognizes the border between two flat areas of the colors even they are as close as (0, 64, 64) to (0, 63, 63).

As the normal monitor supports only 24-bit colors, the only viable solution afaik is to apply dithering. This breaks up the flat color areas by adding a noise pattern, so the eye can't catch the borders so easily. I'm sure a google search shows some interesting links, how to implement some kind of dithering (such as this).