# flat shading with multiple light sources

## Recommended Posts

staticVoid2    381
How would you normally apply two (or more) light sources to a surface color. I've been testing a lot of ways but none of them seem to work.

struct Light
{
vector pos;
};

void flatShade(const polygon &p, const Light *lights, unsigned nLights)
{
vector average = p.getAveragedPoint();

vector lightvect;
color surfacecolor = p.getColor();

float cosTheta;

for(int i = 0; i < nLights; ++i)
{
lightvect = lights[i].pos - averaged;
lightVect.normalize();
cosTheta = lightVect.dotProduct(p.normal);

if(surfacecolor * cosTheta > surfacecolor) // if the new surface color is brighter
surfacecolor *= cosTheta;
}
}


I've also tried interpolating between each surface color at each light pass. what's the proper way to do this? [Edited by - staticVoid2 on May 13, 2008 4:44:09 AM]

##### Share on other sites
tweduk    144
The general principle is that you sum together the illumination contributions from each light, which gives you a total illumination factor T. Then you multiply the polygon's color by T to get the final color.

This assumes a couple of things though:

1. The lightsources are white lights. If they are not white, you may find that the results look strange. It's possible to use coloured lights, but doing so in a physically accurate way can be nontrivial. Your code already implies that they are white lights, so that's OK.

2. If you're not using high dynamic range lighting, T should sum to no more than 1. If T > 1, then you can have a final color with RGB components outside the range [0, 1], which will probably be clamped to [0, 1]. That will look unnatural.

Therefore, you should probably have an intensity for each light, so that you have a way to prevent T exceeding 1 (unless you're using HDR, of course).

If T does manage to exceed 1 and you're not using HDR, you can clamp it to the range [0, 1]. It's not a perfect solution, but it will look better than allowing the final colour to be clamped.

So what you probably want is something like:

void flatShade(const polygon &p, const Light *lights, unsigned nLights){   vector average = p.getAveragedPoint();   vector lightvect;   color surfacecolor = p.getColor();   float contribution;   // init. total illumination factor to ambient light level   float T = ambient;   for(int i = 0; i < nLights; ++i)   {      lightvect = lights[i].pos - averaged;      lightVect.normalize();      contribution = lightVect.dotProduct(p.normal) * lights[i].intensity;      if (contribution < 0.0f) {          // dotProduct can return < 0 - we don't want "dark lights"!          contribution = 0.0f;      }      T += contribution; // sum the contributions from lights   }   if (T > 1.0f) {      // Clamp T to [0, 1] if not using HDR      T = 1.0f;   }   surfaceColor *= T; // calculate final color}

If you don't want any ambient light, just make 'ambient' equal to 0.0f.

Note that your lighting model isn't very physically accurate, because you have what are effectively point lights but with no attentuation over distance. Normally you would make the light's intensity fall off over distance from the point it is illuminating, e.g.

attenuation = (some function of magnitude of lightVect before normalization);

So the contribution per light could be modified to incorporate the attenuation term:

contribution = lightVect.dotProduct(p.normal) * lights[i].intensity * attenuation;

On the other hand, you might decide that you don't want to attenuate a light in a particular situation because it looks better. That's fine - I'm just pointing this out in case you're not aware that your lighting model isn't very physically accurate.

The only time you can really have no attentuation is when the light source is extremely bright and at a very large distance away from the point being illuminated. The sun would be such a light, because it is so bright and far away from Earth that its intensity is more or less constant anywhere in the neighbourhood of the Earth (at a given point in time). Note that such a light is best characterised not by { position, intensity } as in a point light, but rather by { direction, intensity }.

##### Share on other sites
staticVoid2    381
thanx a lot, after all this I've discovered the real problem - when I was averaging the polygons points, I took the first light source position added (point[1] to point[nPoints-1]) from the polygon and divided by polygon.nPoints, don't ask why.

Quote:
 Note that your lighting model isn't very physically accurate, because you have what are effectively point lights but with no attentuation over distance. Normally you would make the light's intensity fall off over distance from the point it is illuminating, e.g.

I had this working before but cut a lot of code out just to debug. I had a radius value for each light source and then caluculated:

if(intensity > 1.0f) continue;
contribution *= (1-intensity)

(before normalizing the light vector)