GLSL multiple lights

Hi, does anyone know how to implement multiple lights in GLSL?

I have implemented the following code:
vertex shader:

varying vec3 normal, lightDir;
varying vec4 diffuse;
void main()
{
  gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
 
  vec3 transformedNormal = gl_NormalMatrix * gl_Normal;

  normal = gl_NormalMatrix * gl_Normal;

  if(vec3(gl_LightSource[0].position) != vec3(0.0, 0.0, 0.0)) {
    lightDir = vec3(gl_LightSource[0].position);
    diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
  }

}

fragment shader:

varying vec3 normal, lightDir;
varying vec4 diffuse;
void main()
{ 
  vec4 color;
  float dotProduct;  

  dotProduct = dot(normal, lightDir);

  color = dotProduct * diffuse;
		  
  gl_FragColor = color;
}

So now I want to add 3 other lights, gl_LightSource[1], gl_LightSource[2] and gl_LightSource[3] which comes from different directions and have different colors and are defined in the .c file. How do I do this? You turn the lights on and off with the F1, F2, F3 and F4 keys and my if statement is supposed to check if the light is activated or not. I managed to get all the lights to work with a for statement before the if statement but the lights couldn’t be activated at the same time with that solution, just one at a time. I want to be able to have blue light on one side of an object and red light on the other side, for an example.

You have several solution for your problem and that where it starts to become interesting. The easy way is to use a uniform variable to specific the light count… However, this fast to code solution wrong work on other hardware than GeForce 8 I guest.

General a for loop can be use to iterate over the lights. But there is a limitation with shader model 3.0 (and below) so that it isn’t possible to index uniform variables with a dynamic variable in fragment shaders.


for (int light= 0; light < MAXLIGHTS; light++){
    gl_FragColor += gl_LightSource[light].diffuse;
    }

This code will run on a shader model 4.0 card without limitations. MAXLIGHTS can be a uniform or a constant. On a shader model 3.0 card. It is not possible. In that case it’s recommend to compile a own shader for each number of lights, by replacing MAXLIGHTS by a constant (can be done with with #define)

If many lights should be handled by a fragment shader it is recommend to calculate the lighting in model view space, to keep the number of varyings low.

What about the light direction and the dot product? Maybe I don’t need them, I don’t know.

I’ve tried this code before:

for(int i = 0; i <= 3; i++) {
  if(vec3(gl_LightSource[i].position) != vec3(0.0, 0.0, 0.0)) {
    lightDir += vec3(gl_LightSource[i].position);
    diffuse += gl_FrontMaterial.diffuse * gl_LightSource[i].diffuse;
  }
}

But the result I got from that was that when I activated the blue and red lights for an example, I got a purple light with a position between the positions the two lights was supposed to have. So I still only got one light at a time.

You must sum final lighting contributions, direction and diffuse properties of the lights must remain independent.

For directional lights which you are using, you can simply access the gl_LightSource[0] uniforms from the fragment shader to get entire direction so there is no need to send it trough varying.

For point lights you can use vertex shader to send fragment position in any suitable space to the fragment shader and calculate the directions there.

Solved it! Made a function for each light.

vertex shader

varying vec3 normal;
void main()
{
  gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;

  normal = gl_NormalMatrix * gl_Normal;

}

fragment shader

varying vec3 normal;
 
vec4 light0 ()
{
  vec4 color;
 
  vec3 lightDir = vec3(gl_LightSource[0].position); 

  vec4 ambient = gl_LightSource[0].ambient * gl_FrontMaterial.ambient;
 
  vec4 diffuse = gl_LightSource[0].diffuse * max(dot(normal,lightDir),0.0) * gl_FrontMaterial.diffuse;
 
  color = ambient + diffuse;

  return color;
}

vec4 light1 ()
{
  vec4 color;
 
  vec3 lightDir = vec3(gl_LightSource[1].position); 

  vec4 ambient = gl_LightSource[1].ambient * gl_FrontMaterial.ambient;
 
  vec4 diffuse = gl_LightSource[1].diffuse * max(dot(normal,lightDir),0.0) * gl_FrontMaterial.diffuse;
 
  color = ambient + diffuse;

  return color;
}

vec4 light2 ()
{
  vec4 color;
 
  vec3 lightDir = vec3(gl_LightSource[2].position); 

  vec4 ambient = gl_LightSource[2].ambient * gl_FrontMaterial.ambient;
 
  vec4 diffuse = gl_LightSource[2].diffuse * max(dot(normal,lightDir),0.0) * gl_FrontMaterial.diffuse;
 
  color = ambient + diffuse;

  return color;
}

vec4 light3 ()
{
  vec4 color;
 
  vec3 lightDir = vec3(gl_LightSource[3].position); 

  vec4 ambient = gl_LightSource[3].ambient * gl_FrontMaterial.ambient;
 
  vec4 diffuse = gl_LightSource[3].diffuse * max(dot(normal,lightDir),0.0) * gl_FrontMaterial.diffuse;
 
  color = ambient + diffuse;

  return color;
} 

void main()
{ 
  vec4 light;

  if(vec3(gl_LightSource[0].position) != vec3(0.0, 0.0, 0.0))
    light += light0();
  if(vec3(gl_LightSource[1].position) != vec3(0.0, 0.0, 0.0))
    light += light1();
  if(vec3(gl_LightSource[2].position) != vec3(0.0, 0.0, 0.0))
    light += light2();
  if(vec3(gl_LightSource[3].position) != vec3(0.0, 0.0, 0.0))
    light += light3();

  gl_FragColor = light;
}

Thanks for your help though.

That isn’t the best solution, use a integer as argument to set the lightnumber for the light function.
The if statement isn’t a good solution too, it would be faster to use a own shader for each number of lights.