How lighting works
|Warning: This article describes legacy OpenGL APIs that have been removed from core OpenGL 3.1 and above (they are only deprecated in OpenGL 3.0). It is recommended that you not use this functionality in your programs.|
Many people starting out with OpenGL are confused by the way that OpenGL's built-in lighting works - and consequently how colour functions. This page aims clear up some of the confusion.
What is needed to explain this clearly is a flow chart:
Lighting ENABLED or DISABLED?
The first - and most basic - decision is whether to enable lighting or not.
glEnable ( GL_LIGHTING ) ;
glDisable ( GL_LIGHTING ) ;
If it's disabled then all polygons, lines and points will be coloured according to the setting of the various forms of the glColor command. Those colours will be carried forward without any change other than is imparted by texture or fog if those are also enabled. Hence:
glColor3f ( 1.0f, 0.0f, 0.0f ) ;
...gets you a pure red triangle no matter how it is positioned relative to the light source(s).
With GL_LIGHTING enabled, we need to specify more about the surface than just it's colour - we also need to know how shiney it is, whether it glows in the dark and whether it scatters light uniformly or in a more directional manner.
The idea is that OpenGL switches over to using the current settings of the current 'material' instead of the simplistic idea of a polygon 'colour' that is sufficient when lighting is disabled. We shall soon see that this is an over-simplistic explanation - but keep it firmly in mind.
glMaterial and glLight
The OpenGL light model presumes that the light that reaches your eye from the polygon surface arrives by four different mechanisms:
- AMBIENT - light that comes from all directions equally and is scattered in all directions equally by the polygons in your scene. This isn't quite true of the real world - but it's a good first approximation for light that comes pretty much uniformly from the sky and arrives onto a surface by bouncing off so many other surfaces that it might as well be uniform.
- DIFFUSE - light that comes from a particular point source (like the Sun) and hits surfaces with an intensity that depends on whether they face towards the light or away from it. However, once the light radiates from the surface, it does so equally in all directions. It is diffuse lighting that best defines the shape of 3D objects.
- SPECULAR - as with diffuse lighting, the light comes from a point souce, but with specular lighting, it is reflected more in the manner of a mirror where most of the light bounces off in a particular direction defined by the surface shape. Specular lighting is what produces the shiney highlights and helps us to distinguish between flat, dull surfaces such as plaster and shiney surfaces like polished plastics and metals.
- EMISSION - in this case, the light is actually emitted by the polygon - equally in all directions.
So, there are three light colours for each light - Ambient, Diffuse and Specular (set with glLight) and four for each surface (set with glMaterial). All OpenGL implementations support at least eight light sources - and the glMaterial can be changed at will for each polygon (although there are typically large time penalties for doing that - so we'd like to minimise the number of changes).
The final polygon colour is the sum of all four light components, each of which is formed by multiplying the glMaterial colour by the glLight colour (modified by the directionality in the case of Diffuse and Specular). Since there is no Emission colour for the glLight, that is added to the final colour without modification.
A good set of settings for a light source would be to set the Diffuse and Specular components to the colour of the light source, and the Ambient to the same colour - but at MUCH reduced intensity, 10% to 40% seems reasonable in most cases.
For the glMaterial, it's usual to set the Ambient and Diffuse colours to the natural colour of the object and to put the Specular colour to white. The emission colour is generally black for objects that do not shine by their own light.
Before you can use an OpenGL light source, it must be positioned using the glLight command and enabled using glEnable(GL_LIGHTn) where 'n' is 0 through 7. There are additional commands to make light sources directional (like a spotlight or a flashlight) and to have it attenuate as a function of range from the light source.
This is without doubt the most confusing thing about OpenGL lighting - and the biggest cause of problems for beginners.
The problem with using glMaterial to change polygon colours is three-fold:
- It's slow. Well, the OpenGL manual says it's slow - but it's not certain that all implementations will have trouble with it.
- You frequently need to change glMaterial properties for both Ambient and Diffuse to identical values - this takes two OpenGL function calls which is annoying.
- You cannot change glMaterial settings with many of the more advanced polygon rendering techniques such as Vertex arrays and glDrawElements.
For these reasons, OpenGL has a feature that allows you do drive the glMaterial colours using the more flexible glColor command (which is not otherwise useful when lighting is enabled).
To drive (say) the Emission component of the glMaterial using glColor, you must say:
glColorMaterial ( GL_FRONT_AND_BACK, GL_EMISSION ) ; glEnable ( GL_COLOR_MATERIAL ) ;
From this point performing a glColor command (or setting the glColor via a vertex array or something) has the exact same effect as calling:
glMaterial ( GL_FRONT_AND_BACK, GL_EMISSION, ...colours... ) ;
One especially useful option is:
glColorMaterial ( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE ) ;
This causes glColor commands to change both Ambient and Diffuse colours at the same time. That's a very common thing to want to do for real-world lighting models.
OpenGL's lights are turned on and off with glEnable(GL_LIGHT_n) and glDisable(GL_LIGHT_n) where 'n' is a number in the range zero to the maximum number of lights that this implementation supports (typically eight). The glLight call allows you to specify the colour (ambient, diffuse and specular), position, direction, beam width and attenuation rate for each light. By default, it is assumed that both the light and the viewer are effectively infinitely far from the object being lit. You can change that with the glLightModel call - but doing so is likely to slow down your program - so don't do it unless you have to. glLightModel also allows you to set a global ambient lighting level that's independent of the other OpenGL light sources.
There is also an option to light the front and back faces of your polygons differently. That is also likely to slow your program down - so don't do it.
When lighting is enabled, OpenGL suddenly needs to know the orientation of the surface at each polygon vertex. You need to call glNormal for each vertex to define that - OpenGL does not provide a useful default.
With this huge range of options, it can be hard to pick sensible default values for these things.
My advice for a starting point is to:
- Set GL_LIGHT_0's position to something like 45 degrees to the 'vertical'. Coordinate (1,1,0) should work nicely in most cases.
- Set GL_LIGHT_0's Ambient color to 0,0,0,1
- Set GL_LIGHT_0's Diffuse color to 1,1,1,1
- Set GL_LIGHT_0's Specular color to 1,1,1,1
- Set the glLightModel global ambient to 0.2,0.2,0.2,1 (this is the default).
- Don't set any other glLight or glLightModel options - just let them default.
- Enable GL_LIGHTING and GL_LIGHT_0.
- Enable GL_COLOR_MATERIAL and set glColorMaterial to GL_AMBIENT_AND_DIFFUSE. This means that glMaterial will control the polygon's specular and emission colours and the ambient and diffuse will both be set using glColor.
- Set the glMaterial Specular colour to 1,1,1,1
- Set the glMaterial Emission colour to 0,0,0,1
- Set the glColor to whatever colour you want each polygon to basically appear to be. That sets the Ambient and Diffuse to the same value - which is what you generally want.
Using Alpha with lighting enabled.
One confusing thing is that each of the colour components (Ambient,Diffuse, Specular and Emission) has an associated 'alpha' component for setting transparency. It is important to know that only the DIFFUSE colour's alpha value actually determines the transparency of the polygon. If you have taken my advice and used glColorMaterial to cause glColor to drive the ambient and diffuse components then this seems perfectly natural...but people sometimes want to use the glColor to drive one of the other material components and are then very confused about their inability to set the transparency using glColor. If that happens to you - remember to use glMaterial to set the diffuse colour - and hence the alpha for the polygon overall.
I started out by saying that enabling OpenGL lighting disables the glColor command - but in practice, since we generally want to set the glMaterial specular colour to white and the emission colour to black for almost all of our polygons, it's really convenient to set the glColorMaterial option to GL_AMBIENT_AND_DIFFUSE and simply use glColor to set the 'real' colour of the object just as we did when OpenGL lighting was disabled.