OpenGL gotcha: remember GL_LIGHT0

In my experimenting with some simple scene geometry and lighting today, I stumbled into the same pitfall I’ve strumbled into several times before (you’d think I would have learned by now!). While moving around the scene, I found that the lighting on surfaces appeared to change depending on my camera angle — if I looked at a polygon square-on, it seemed much brighter than if I was looking slightly away from it.

There are two common reasons for this problem…

The rookie mistake is to specify your lights before you’ve specified your camera angle. When you specify a light position in OpenGL, it’s modified by the model-view matrix in the same way as geometry. As such, the order for operations should typically be:

  • Reset your matrix (using glLoadIdentity())
  • Specify your camera position/angle (e.g. using gluLookAt())
  • Set your light positions and other parameters (using glLightf*())
  • Render your geometry

In this case, that wasn’t the mistake that I made. Instead, I ran into the easy trap befalling people using light 0 (GL_LIGHT0) as an ambient-only light source. Here’s the code I was using just before rendering my geometry:

glLight4f(GL_LIGHT0, GL_AMBIENT, 0.2f, 0.2f, 0.2f, 1.0f);

The problem is that light 0 is unique in having a default diffuse setting of fully bright white (1,1,1,1), whereas all other lights have a default diffuse setting of totally dark (0,0,0,1). All lights also have a default position of 0,0,1,0, which would effectively place it as a directional light, facing forward, from just behind the camera. Given that the default setting was obviously in the system before my camera was specified, it had the effect of moving around with my camera, and pointing the same direction.

(The “w” parameter in the light position, which is the 4th component, determines whether the light is directional or whether it’s a point source — values 0 and 1 respectively.)

The Solution

The most obvious and effective solution is quite simple:

glLight4f(GL_LIGHT0, GL_DIFFUSE, 0.0f, 0.0f, 0.0f, 1.0f);

Just add that line after the light is enabled (or perhaps even do it once in your initialisation function if you’d prefer). That prevents light 0 from having any diffuse influence as a point/directional source. If you are specifying specular attributes in your polygon materials, then you’ll need to do the same for light 0’s specular colour, since its default for that is 1,1,1,1 as well.

Implementation Note

On looking back over this post, I realise I forgot to mention something. The OpenGL function “glLight4f” doesn’t actually exist. I had defined that as a wrapper function in my code so I didn’t have to keep constructing and passing arrays to “glLightfv”. It’s worth doing, as it saves lots of hassle sometimes! 🙂

2 comments on “OpenGL gotcha: remember GL_LIGHT0

  1. glLight4f(GL_LIGHT0, GL_DIFFUSE, 0.0f, 0.0f, 0.0f, 1.0f);

    This translates to:

    float fTemp[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
    glLightfv(GL_LIGHT0, GL_DIFFUSE, fTemp);

  2. With compilers that allow for anonymous structs and struct packing (doesn’t matter as much for big data types that will probably be aligned next to each other anyway, but still), I find that something like `union {struct {typename f1, f2, f3, …, fn;}; typename fields[n];} data;` works nicely for providing identifier access while also allowing you to pass `data.fields` into functions expecting arrays. You could even do without the union and explicit array by using `struct {typename f1, f2, f3, …, fn;} data; typename *p = &data;` (assuming the struct is properly packed), but that feels slightly more hacky to me even though they’re both kind of hacks.

Leave a Reply

Your email address will not be published. Required fields are marked *