18 Lights and Shadows
18.010
What should I know about lighting
in general?
You must specify normals along with your
geometry, or you must generate them automatically with
evaluators, in order for lighting to work as expected. This
is covered in question 18.020.
Lighting does not work with the current
color as set by glColor*(). It works with material colors.
Set the material colors with glMaterial*(). Material colors
can be made to track the current color with the color
material feature. To use color material, call glEnable(GL_COLOR_MATERIAL).
By default, this causes ambient and diffuse material colors
to track the current color. You can specify which material
color tracks the current color with a call to glColorMaterial().
Changing the material colors with color
material and glColor*() calls may be more efficient than
using glMaterial*(). See question 18.080 for more
information.
Lighting is computed at each vertex (and
interpolated across the primitive, when glShadeModel() is set
to GL_SMOOTH). This may cause primitives to appear too dark,
even though a light is centered over the primitive. You can
obtain more correct lighting with a higher surface
approximation, or by using light maps.
A light's position is transformed by the
current ModelView matrix at the time the position is
specified with a call to glLight*(). This is analogous to how
geometric vertices are transformed by the current ModelView
matrix when they are specified with a call to glVertex*().
For more information on positioning your light source, see question 18.050.
18.020
Why are my objects all one flat
color and not shaded and illuminated?
This effect occurs when you fail to supply
a normal at each vertex.
OpenGL needs normals to calculate lighting
equations, and it won't calculate normals for you (with the
exception of evaluators). If your application doesn't call
glNormal*(), then it uses the default normal of (0.0, 0.0, 1.0)
at every vertex. OpenGL will then compute the same, or nearly
the same, lighting result at each vertex. This will cause
your model to look flat and lack shading.
The solution is to simply calculate the
normals that need to be specified at any given vertex. Then
send them to OpenGL with a call to glNormal3f() just prior to
specifying the vertex, which the normal is associated with.
If you don't know how to calculate a normal,
in most cases you can do it simply with a vector cross
product. The OpenGL Programming Guide contains a small section explaining how to
calculate normals. Also most basic 3D computer graphics books
cover it, because it's not OpenGL-specific.
18.030
How can I make OpenGL
automatically calculate surface normals?
OpenGL won't do this unless you're using
evaluators.
18.040
Why do I get only flat shading
when I light my model?
First, check the obvious. glShadeModel()
should be set to GL_SMOOTH, which is the default value, so if
you haven't called glShadeModel() at all, it's probably
already set to GL_SMOOTH, and something else is wrong.
If glShadeModel() is set correctly, the
problem is probably with your surface normals. To achieve a
smooth shading effect, generally you need to specify a
different normal at each vertex. If you have set the same
normal at each vertex, the result, in most cases, will be a
flatly shaded primitive.
Keep in mind that a typical surface normal
is perpendicular to the surface that you're attempting to
approximate.
This scenario can be tough to debug,
especially for large models. The best debugging approach is
to write a small test program that draws only one primitive,
and try to reproduce the problem. It's usually easy to use a
debugger to isolate and fix a small program, which reproduces
the problem.
18.050
How can I make my light move or
not move and control the light position?
First, you must understand how the light
position is transformed by OpenGL.
The light position is transformed by the
contents of the current top of the ModelView matrix stack
when you specify the light position with a call to glLightfv(GL_LIGHT_POSITION,…).
If you later change the ModelView matrix, such as when the
view changes for the next frame, the light position isn't
automatically retransformed by the new contents of the
ModelView matrix. If you want to update the lights
position, you must again specify the light position with a
call to glLightfv(GL_LIGHT_POSITION,…).
Asking the question “how do I make my
light move” or “how do I make my light stay still”
usually doesn't provide enough information to answer the
question. For a better answer, you need to be more specific.
Here are some more specific questions, and their answers:
- How can I make my light position stay
fixed relative to my eye position? How do I make a
headlight?
You need to specify your light in eye
coordinate space. To do so, set the ModelView matrix to
the identity, then specify your light position. To make a
headlight (a light that appears to be positioned at or
near the eye and shining along the line of sight), set
the ModelView to the identity, set the light position at
(or near) the origin, and set the direction to the
negative Z axis.
When a lights position is fixed
relative to the eye, you don't need to respecify the
light position for every frame. Typically, you specify it
once when your program initializes.
- How can I make my light stay fixed
relative to my scene? How can I put a light in the
corner and make it stay there while I change my view?
As your view changes, your ModelView
matrix also changes. This means you'll need to respecify
the light position, usually at the start of every frame.
A typical application will display a frame with the
following pseudocode:
Set the view transform.
Set the light position //glLightfv(GL_LIGHT_POSITION,…)
Send down the scene or model geometry.
Swap buffers.
If your light source is part of a light
fixture, you also may need to specify a modeling
transform, so the light position is in the same location
as the surrounding fixture geometry.
- How can I make a light that moves
around in a scene?
Again, you'll need to respecify this
light position every time the view changes. Additionally,
this light has a dynamic modeling transform that also
needs to be in the ModelView matrix before you specify
the light position. In pseudocode, you need to do
something like:
Set the view transform
Push the matrix stack
Set the model transform to update the lights position
Set the light position //glLightfv(GL_LIGHT_POSITION,…)
Pop the matrix stack
Send down the scene or model geometry
Swap buffers.
18.060
How can I make a spotlight work?
A spotlight is simply a point light source
with a small light cone radius. Alternatively, a point light
is just a spot light with a 180 degree radius light cone. Set
the radius of the light cone by changing the cutoff parameter
of the light:
glLightf (GL_LIGHT1, GL_SPOT_CUTOFF, 15.f);
The above call sets the light cone radius
to 15 degrees for light 1. The light cone's total spread will
be 30 degrees.
A spotlight's position and direction are
set as for any normal light.
18.070
How can I create more lights than
GL_MAX_LIGHTS?
First, make sure you really need more than
OpenGL provides. For example, when rendering a street scene
at night with many buildings and streetlights, you need to
ask yourself: Does every building need to be illuminated by
every single streetlight? When light attenuation and
direction are accounted for, you may find that any given
piece of geometry in your scene is only illuminated by a
small handful of lights.
If this is the case, you need to reuse or
cycle the available OpenGL lights as you render your scene.
The GLUT distribution comes with a small
example that might be informative to you. Its called
multilight.c.
If you really need to have a single piece
of geometry lit by more lights than OpenGL provides, you'll
need to simulate the effect somehow. One way is to calculate
the lighting for some or all the lights. Another method is to
use texture maps to simulate lighting effects.
18.080
Which is faster: making
glMaterial*() calls or using glColorMaterial()?
Within a glBegin()/glEnd() pair, on most
OpenGL implementations, a call to glColor3f() generally is
faster than a call to glMaterialfv(). This is simply because
most implementations tune glColor3f(), and processing a
material change can be complex and difficult to optimize. For
this reason, glColorMaterial() generally is recognized as the
most efficient way to change an objects material color.
18.090
Why is the lighting incorrect
after I scale my scene to change its size?
The OpenGL specification needs normals to
be unit length to achieve typical lighting results. The
current ModelView matrix transforms normals. If that matrix
contains a scale transformation, transformed normals might
not be unit length, resulting in undesirable lighting
problems.
OpenGL 1.1 lets you call glEnable(GL_NORMALIZE),
which will make all normals unit length after they're
transformed. This is often implemented with a square root and
can be expensive for geometry limited applications.
Another solution, available in OpenGL 1.2 (and
as an extension to many 1.1 implementations), is glEnable(GL_RESCALE_NORMAL).
Rather than making normals unit length by computing a square
root, GL_RESCALE_NORMAL multiplies the transformed normal by
a scale factor. If the original normals are unit length, and
the ModelView matrix contains uniform scaling, this
multiplication will restore the normals to unit length.
If the ModelView matrix contains nonuniform
scaling, GL_NORMALIZE is the preferred solution.
18.100
After I
turn on lighting, everything is lit. How can I light only some of
the objects?
Remember that OpenGL is a state machine.
You'll need to do something like this:
glEnable(GL_LIGHTING);
// Render lit geometry.
glDisable(GL_LIGHTING);
// Render non-lit geometry.
18.110
How can I use light maps (e.g.,
Quake-style) in OpenGL?
See this
question in the Texture Mapping section.
18.120
How can I achieve a refraction
lighting effect?
First, consider whether OpenGL is the right
API for you. You might need to use a ray tracer to achieve
complex light affects such as refraction.
If you're certain that you want to use
OpenGL, you need to keep in mind that OpenGL doesnt
provide functionality to produce a refraction effect. You'll
need to fake it. The most likely solution is to calculate an
image corresponding to the refracted rendering, and texture
map it onto the surface of the primitive that's refracting
the light.
18.130
How can I render caustics?
OpenGL can't help you render caustics,
except for texture mapping. GLUT 3.7 comes with some demos
that show you how to achieve caustic lighting effects.
18.140
How can I add shadows to my scene?
The GLUT 3.7 distribution comes with
examples that demonstrate how to do this using projection
shadows and the stencil buffer.
Projection shadows are ideal if your shadow
is only to lie on a planar object. You can generate geometry
of the shadow using glFrustum() to transform the object onto
the projection plane.
Stencil buffer shadowing is more flexible,
allowing shadows to lie on any object, planar or otherwise.
The basic algorithm is to calculate a "shadow volume".
Cull the back faces of the shadow volume and render the front
faces into the stencil buffer, inverting the stencil values.
Then render the shadow volume a second time, culling front
faces and rendering the back faces into the stencil buffer,
again inverting the stencil value. The result is that the
stencil planes will now contain non-zero values where the
shadow should be rendered. Render the scene a second time
with only ambient light enabled and glDepthFunc() set to GL_EQUAL.
The result is a rendered shadow.
Another mechanism for rendering shadows is
outlined in the SIGGRAPH '92 paper Fast Shadows and
Lighting Effects Using Texture Mapping, Mark Segal et
al. This paper describes a relatively simple extension to
OpenGL for using the depth buffer as a shadow texture map.
Both the GL_EXT_depth_texture and the GL_EXT_texture3D
(or OpenGL 1.2) extensions are required to use this method.
|