Basic OpenGL Lighting.
by Steve Baker
Introduction.
Many people starting out with OpenGL are confused by the way that
OpenGL's built-in lighting works - and consequently how colour
functions. I hope to be able to 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 ) ;
...or...
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.
glColorMaterial
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.
Light Sources.
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.
glNormal
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.
Good Settings.
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's 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's Specular colour to 1,1,1,1
- Set the glMaterial's 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.
Summary
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.