|Download Files |
Shadows, Reflections, Lighting, Textures. Easy with OpenGL!
Below are several screen snapshots from an OpenGL program demonstrating
shadows, reflections, lighting, and texturing. The point is to show that OpenGL provides all the rendering functionality needed to combine texturing, lighting, reflections, and shadows all in a single scene.
The complete source code for the program is provided so that you can study how easy
it is to generate extremely realistic real-time scenes with OpenGL.
A secondary purpose of these images is to point out limitations in Microsoft's
Direct3D API. Unfortunately, Direct3D represents a significant step backwards for realistic, real-time rendering. Direct3D lacks both the stenciling
and polygon offset capabilities needed to render the scenes as shown below.
On the other hand, OpenGL 1.1 provides these features on all implementations. The program below with all its combined techniques can
run on any OpenGL 1.1 implementation.
While Direct3D claims to be good for games, in fact, Direct3D does not have
adequate rendering support for fast reflections and shadows. Indeed, an
OpenGL game developer can develop a richer experience than can a Direct3D
game developer. Also consider that:
- OpenGL is a better documented API.
- OpenGL is also a cleaner API and much easier to learn and program.
- OpenGL has the best demonstrated 3D performance for any API.
- Microsoft's Direct3D group is already planning a major API change called
DirectPrimitive (to make its API more OpenGL-like) that will leave any existing
investment in learning Direct3D immediate mode largely obsolete.
- SGI is now providing a free software OpenGL implementation called Cosmo OpenGL that is tuned specifically for 3D games to give better performance than
Microsoft's OpenGL 1.1 and Direct3D implementation even with no special
3D hardware.
- OpenGL has a conformance suite to validate that OpenGL implementions correctly
implement OpenGL. Direct3D has no conformance suite; indeed, its feature
set can vary from driver to driver creating a testing nightmare for the
game developers.
- PC 3D hardware vendors are now realizing the tremendous limitations to Direct3D.
The leaders in PC 3D hardware either have OpenGL drivers available already
or are releasing them as soon as possible. 3Dfx, 3Dlabs, Dynamic Pictures,
Intergraph, and others are all supporting OpenGL today.
- Microsoft is the only vendor that controls Direct3D. OpenGL on the other
hand is an open standard jointly maintained by Intel, Microsoft, DEC, IBM,
Evans & Sutherland, HP, SGI, Sun, and Intergraph - all the
leaders in 3D graphics on OpenGL's Architectural Review Board. OpenGL has an open extension mechanism allowing vendors to add new extensions
such as 3D texture mapping, convolutions, better blending modes, multisample
antialiasing. All these features are now available as OpenGL extensions.
On the other hand, Microsoft has no mechanism for hardware vendors to release
new extensions. OpenGL is a far more innovative API.
On with the images. . .
This first image shows a 3D dinosaur (uh, sorry the dinosaur looks so lame;
it could have been any 3D model). The yellow arrow indicates the light source's direction heading
towards the origin (the light is infinitely far away in these snapshots).
Notice that the light also casts a shadow on the texture mapped ground.
Notice the shadow correctly overlays the ground. Also notice the correct reflection on the floor.
I should stress that this is an interactive program. You can change the view and move the
light source with the mouse. The dinosaur is animated and repeatedly jumps
up and down. The shadow and reflection both correctly follow the dinosaur's
jumping. Since the source code for the program can be found below, I encourage
you to compile the program and run it.

The next image shows the light behind the dinosaur and lower to the ground.
Notice that the shadow projects out further. You can also see how both the
reflection, the textured floor, and the shadow all interact correctly at
the dinosaur's foot. The light source is a positional light this time (hence
the yellow ball instead of an arrow) instead of an (infinite) directional
light as in the previous image. Notice that the shadow is a magnification
of the dinosaur.

The image below is actually showing what the scene would look like if the
program didn't use OpenGL's stencil buffer facility. Stencil buffering is
a capability that Direct3D completely lacks so the artifacts below would
afflict a Direct3D program.
Notice how the dinosaur reflection is not correctly terminated at the edge
of the floor. Indeed, you can see how the reflection is really a blended
re-rendering of the dinosaur (geometrically) reflected through the plane
of the floor. However, you don't want the dinosaur to appear in pixels that
do not actually belong to the floor. OpenGL's general stenciling capability
makes it easy to only draw the reflected dinosaur on floor pixels.
You can see a second artifact in the dinosaur's shadow. Notice how some
areas of the shadow appear darker than other areas. This is because the
dinosaur's shoulder gets drawn by multiple pixels when projected onto the
floor. This causes duplicate shadow blends. Notice that the previous two
images have no such artifacts. OpenGL stenciling can be used to ensure that
a shadow pixel is only blended once with the floor. Again, Direct3D would
suffer from the shadowing artifacts below since Direct3D completely lacks
stenciling support.
OpenGL stenciling has a straightforward hardware implementation so stenciling
programs can run very fast on good hardware. Notice that stenciling is used
to eliminate both the reflection and shadow artifacts; actually, stencil
has lots more uses.

The image below has a different artifact due to depth buffer aliasing when
the shadow blends with the floor. The dark shadow area and the floor lie
in almost the same plane so some polygons in the shadow appear, while others
do not. This is a classic depth buffer problem. OpenGL 1.1 provides a robust
solution to this problem with its polygon offset functionality. This feature
of OpenGL lets you bias depth values so that coplanar polygons are correctly
layered. The two images above use polygon offset to slightly lift the shadow's
depth values to eliminate the artifacts shown below. Since Direct3D has
no equivalent capability, Direct3D projected blended shadows would suffer
the artifact demonstrated below.
Actually, the image below is using stenciling. If both stenciling and polygon
offset were unavailable (as in Direct3D), these two scenes would look far
worse.

There are ways to "hack" around Direct3D's lack of stenciling and polygon
offset. In general, these hacks force you to give up something like blended
shadows or reflections or texturing in combination or add constraints on
how the scene can be viewed.
If you are wondering how general these techniques are, I assure you all
the techniques demonstrated can be effectively combined. The images above
are simple so you can see the interactions easily and so I can provide you
the complete source code. With more work, multiple reflecting objects (including
multiple reflections), multiple shadows, and more textures are all possible.
If you want to see an even richer demonstration of these techniques, check
out the OpenGL-rendered (in real-time!) QuickTime movie below. Notice at
the end of the movie that you can see the ceiling fan reflected in the floor
that is reflected in the mirror (multiple reflections!):
The Smoke & Mirrors QuickTime Movie
If you are a game developer and you are exploring your options for 3D APIs,
please seriously consider the issues. Game developers that settle for Direct3D
are very likely to find their games are inferior to the more realistic games
written with fast, portable OpenGL.
If you have not seen John Carmark's treatise on OpenGL vs Direct3D, I recommend that you read it. Id Software has already ported Quake to
OpenGL; based on the problems with Direct3D cited in Carmack's treatise,
Id Software intends no Direct3D port. You will also probably benefit from reading SGI's OpenGL Perspective on Direct3D. If you aren't thinking about these issues, I assure you that your competitors
are.
To compile the code below on Windows 95, you will need
Microsoft's
OpenGL 1.1 DLL. You will also need the Win32 version of the OpenGL Utility Toolkit (GLUT). You can also get the full GLUT source code distribution for more OpenGL sample code. (By the way, this code is portable and works
on Unix workstations.)
Click here to download dinoshade.c or see the code listed below.
- OPENGL Web site
/* Copyright (c) Mark J. Kilgard, 1994, 1997. */
/* This program is freely distributable without licensing fees
and is provided without guarantee or warrantee expressed or
implied. This program is -not- in the public domain. */
/* Example for PC game developers to show how to *combine* texturing,
reflections, and projected shadows all in real-time with OpenGL.
Robust reflections use stenciling. Robust projected shadows
use both stenciling and polygon offset. PC game programmers
should realize that neither stenciling nor polygon offset are
supported by Direct3D, so these real-time rendering algorithms
are only really viable with OpenGL.
The program has modes for disabling the stenciling and polygon
offset uses. It is worth running this example with these features
toggled off so you can see the sort of artifacts that result.
Notice that the floor texturing, reflections, and shadowing
all co-exist properly. */
/* When you run this program: Left mouse button controls the
view. Middle mouse button controls light position (left &
right rotates light around dino; up & down moves light
position up and down). Right mouse button pops up menu. */
/* Check out the comments in the "redraw" routine to see how the
reflection blending and surface stenciling is done. You can
also see in "redraw" how the projected shadows are rendered,
including the use of stenciling and polygon offset. */
/* This program is derived from glutdino.c */
/* Compile: cc -o dinoshade dinoshade.c -lglut -lGLU -lGL -lXmu -lXext -lX11 -lm */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h> /* for cos(), sin(), and sqrt() */
#include <GL/glut.h> /* OpenGL Utility Toolkit header */
/* Some <math.h> files do not define M_PI... */
#ifndef M_PI
#define M_PI 3.14159265
#endif
/* Variable controlling various rendering modes. */
static int stencilReflection = 1, stencilShadow = 1, offsetShadow = 1;
static int renderShadow = 1, renderDinosaur = 1, renderReflection = 1;
static int linearFiltering = 0, useMipmaps = 0, useTexture = 1;
static int reportSpeed = 0;
static int animation = 1;
static GLboolean lightSwitch = GL_TRUE;
static int directionalLight = 1;
static int forceExtension = 0;
/* Time varying or user-controled variables. */
static float jump = 0.0;
static float lightAngle = 0.0, lightHeight = 20;
GLfloat angle = -150; /* in degrees */
GLfloat angle2 = 30; /* in degrees */
int moving, startx, starty;
int lightMoving = 0, lightStartX, lightStartY;
enum {
MISSING, EXTENSION, ONE_DOT_ONE
};
int polygonOffsetVersion;
static GLdouble bodyWidth = 3.0;
/* *INDENT-OFF* */
static GLfloat body[][2] = { {0, 3}, {1, 1}, {5, 1}, {8, 4}, {10, 4}, {11, 5},
{11, 11.5}, {13, 12}, {13, 13}, {10, 13.5}, {13, 14}, {13, 15}, {11, 16},
{8, 16}, {7, 15}, {7, 13}, {8, 12}, {7, 11}, {6, 6}, {4, 3}, {3, 2},
{1, 2} };
static GLfloat arm[][2] = { {8, 10}, {9, 9}, {10, 9}, {13, 8}, {14, 9}, {16, 9},
{15, 9.5}, {16, 10}, {15, 10}, {15.5, 11}, {14.5, 10}, {14, 11}, {14, 10},
{13, 9}, {11, 11}, {9, 11} };
static GLfloat leg[][2] = { {8, 6}, {8, 4}, {9, 3}, {9, 2}, {8, 1}, {8, 0.5}, {9, 0},
{12, 0}, {10, 1}, {10, 2}, {12, 4}, {11, 6}, {10, 7}, {9, 7} };
static GLfloat eye[][2] = { {8.75, 15}, {9, 14.7}, {9.6, 14.7}, {10.1, 15},
{9.6, 15.25}, {9, 15.25} };
static GLfloat lightPosition[4];
static GLfloat lightColor[] = {0.8, 1.0, 0.8, 1.0}; /* green-tinted */
static GLfloat skinColor[] = {0.1, 1.0, 0.1, 1.0}, eyeColor[] = {1.0, 0.2, 0.2, 1.0};
/* *INDENT-ON* */
/* Nice floor texture tiling pattern. */
static char *circles[] = {
"....xxxx........",
"..xxxxxxxx......",
".xxxxxxxxxx.....",
".xxx....xxx.....",
"xxx......xxx....",
"xxx......xxx....",
"xxx......xxx....",
"xxx......xxx....",
".xxx....xxx.....",
".xxxxxxxxxx.....",
"..xxxxxxxx......",
"....xxxx........",
"................",
"................",
"................",
"................",
};
static void
makeFloorTexture(void)
{
GLubyte floorTexture[16][16][3];
GLubyte *loc;
int s, t;
/* Setup RGB image for the texture. */
loc = (GLubyte*) floorTexture;
for (t = 0; t < 16; t++) {
for (s = 0; s < 16; s++) {
if (circles[t][s] == 'x') {
/* Nice green. */
loc[0] = 0x1f;
loc[1] = 0x8f;
loc[2] = 0x1f;
} else {
/* Light gray. */
loc[0] = 0xaa;
loc[1] = 0xaa;
loc[2] = 0xaa;
}
loc += 3;
}
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (useMipmaps) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_LINEAR);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, 16, 16,
GL_RGB, GL_UNSIGNED_BYTE, floorTexture);
} else {
if (linearFiltering) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
glTexImage2D(GL_TEXTURE_2D, 0, 3, 16, 16, 0,
GL_RGB, GL_UNSIGNED_BYTE, floorTexture);
}
}
enum {
X, Y, Z, W
};
enum {
A, B, C, D
};
/* Create a matrix that will project the desired shadow. */
void
shadowMatrix(GLfloat shadowMat[4][4],
GLfloat groundplane[4],
GLfloat lightpos[4])
{
GLfloat dot;
/* Find dot product between light position vector and ground plane normal. */
dot = groundplane[X] * lightpos[X] +
groundplane[Y] * lightpos[Y] +
groundplane[Z] * lightpos[Z] +
groundplane[W] * lightpos[W];
shadowMat[0][0] = dot - lightpos[X] * groundplane[X];
shadowMat[1][0] = 0.f - lightpos[X] * groundplane[Y];
shadowMat[2][0] = 0.f - lightpos[X] * groundplane[Z];
shadowMat[3][0] = 0.f - lightpos[X] * groundplane[W];
shadowMat[X][1] = 0.f - lightpos[Y] * groundplane[X];
shadowMat[1][1] = dot - lightpos[Y] * groundplane[Y];
shadowMat[2][1] = 0.f - lightpos[Y] * groundplane[Z];
shadowMat[3][1] = 0.f - lightpos[Y] * groundplane[W];
shadowMat[X][2] = 0.f - lightpos[Z] * groundplane[X];
shadowMat[1][2] = 0.f - lightpos[Z] * groundplane[Y];
shadowMat[2][2] = dot - lightpos[Z] * groundplane[Z];
shadowMat[3][2] = 0.f - lightpos[Z] * groundplane[W];
shadowMat[X][3] = 0.f - lightpos[W] * groundplane[X];
shadowMat[1][3] = 0.f - lightpos[W] * groundplane[Y];
shadowMat[2][3] = 0.f - lightpos[W] * groundplane[Z];
shadowMat[3][3] = dot - lightpos[W] * groundplane[W];
}
/* Find the plane equation given 3 points. */
void
findPlane(GLfloat plane[4],
GLfloat v0[3], GLfloat v1[3], GLfloat v2[3])
{
GLfloat vec0[3], vec1[3];
/* Need 2 vectors to find cross product. */
vec0[X] = v1[X] - v0[X];
vec0[Y] = v1[Y] - v0[Y];
vec0[Z] = v1[Z] - v0[Z];
vec1[X] = v2[X] - v0[X];
vec1[Y] = v2[Y] - v0[Y];
vec1[Z] = v2[Z] - v0[Z];
/* find cross product to get A, B, and C of plane equation */
plane[A] = vec0[Y] * vec1[Z] - vec0[Z] * vec1[Y];
plane[B] = -(vec0[X] * vec1[Z] - vec0[Z] * vec1[X]);
plane[C] = vec0[X] * vec1[Y] - vec0[Y] * vec1[X];
plane[D] = -(plane[A] * v0[X] + plane[B] * v0[Y] + plane[C] * v0[Z]);
}
void
extrudeSolidFromPolygon(GLfloat data[][2], unsigned int dataSize,
GLdouble thickness, GLuint side, GLuint edge, GLuint whole)
{
static GLUtriangulatorObj *tobj = NULL;
GLdouble vertex[3], dx, dy, len;
int i;
int count = dataSize / (2 * sizeof(GLfloat));
if (tobj == NULL) {
tobj = gluNewTess(); /* create and initialize a GLU
polygon * * tesselation object */
gluTessCallback(tobj, GLU_BEGIN, glBegin);
gluTessCallback(tobj, GLU_VERTEX, glVertex2fv); /* semi-tricky */
gluTessCallback(tobj, GLU_END, glEnd);
}
glNewList(side, GL_COMPILE);
glShadeModel(GL_SMOOTH); /* smooth minimizes seeing
tessellation */
gluBeginPolygon(tobj);
for (i = 0; i < count; i++) {
vertex[0] = data[i][0];
vertex[1] = data[i][1];
vertex[2] = 0;
gluTessVertex(tobj, vertex, data[i]);
}
gluEndPolygon(tobj);
glEndList();
glNewList(edge, GL_COMPILE);
glShadeModel(GL_FLAT); /* flat shade keeps angular hands
from being "smoothed" */
glBegin(GL_QUAD_STRIP);
for (i = 0; i <= count; i++) {
/* mod function handles closing the edge */
glVertex3f(data[i % count][0], data[i % count][1], 0.0);
glVertex3f(data[i % count][0], data[i % count][1], thickness);
/* Calculate a unit normal by dividing by Euclidean
distance. We * could be lazy and use
glEnable(GL_NORMALIZE) so we could pass in * arbitrary
normals for a very slight performance hit. */
dx = data[(i + 1) % count][1] - data[i % count][1];
dy = data[i % count][0] - data[(i + 1) % count][0];
len = sqrt(dx * dx + dy * dy);
glNormal3f(dx / len, dy / len, 0.0);
}
glEnd();
glEndList();
glNewList(whole, GL_COMPILE);
glFrontFace(GL_CW);
glCallList(edge);
glNormal3f(0.0, 0.0, -1.0); /* constant normal for side */
glCallList(side);
glPushMatrix();
glTranslatef(0.0, 0.0, thickness);
glFrontFace(GL_CCW);
glNormal3f(0.0, 0.0, 1.0); /* opposite normal for other side */
glCallList(side);
glPopMatrix();
glEndList();
}
/* Enumerants for refering to display lists. */
typedef enum {
RESERVED, BODY_SIDE, BODY_EDGE, BODY_WHOLE, ARM_SIDE, ARM_EDGE, ARM_WHOLE,
LEG_SIDE, LEG_EDGE, LEG_WHOLE, EYE_SIDE, EYE_EDGE, EYE_WHOLE
} displayLists;
static void
makeDinosaur(void)
{
extrudeSolidFromPolygon(body, sizeof(body), bodyWidth,
BODY_SIDE, BODY_EDGE, BODY_WHOLE);
extrudeSolidFromPolygon(arm, sizeof(arm), bodyWidth / 4,
ARM_SIDE, ARM_EDGE, ARM_WHOLE);
extrudeSolidFromPolygon(leg, sizeof(leg), bodyWidth / 2,
LEG_SIDE, LEG_EDGE, LEG_WHOLE);
extrudeSolidFromPolygon(eye, sizeof(eye), bodyWidth + 0.2,
EYE_SIDE, EYE_EDGE, EYE_WHOLE);
}
static void
drawDinosaur(void)
{
glPushMatrix();
/* Translate the dinosaur to be at (0,8,0). */
glTranslatef(-8, 0, -bodyWidth / 2);
glTranslatef(0.0, jump, 0.0);
glMaterialfv(GL_FRONT, GL_DIFFUSE, skinColor);
glCallList(BODY_WHOLE);
glTranslatef(0.0, 0.0, bodyWidth);
glCallList(ARM_WHOLE);
glCallList(LEG_WHOLE);
glTranslatef(0.0, 0.0, -bodyWidth - bodyWidth / 4);
glCallList(ARM_WHOLE);
glTranslatef(0.0, 0.0, -bodyWidth / 4);
glCallList(LEG_WHOLE);
glTranslatef(0.0, 0.0, bodyWidth / 2 - 0.1);
glMaterialfv(GL_FRONT, GL_DIFFUSE, eyeColor);
glCallList(EYE_WHOLE);
glPopMatrix();
}
static GLfloat floorVertices[4][3] = {
{ -20.0, 0.0, 20.0 },
{ 20.0, 0.0, 20.0 },
{ 20.0, 0.0, -20.0 },
{ -20.0, 0.0, -20.0 },
};
/* Draw a floor (possibly textured). */
static void
drawFloor(void)
{
glDisable(GL_LIGHTING);
if (useTexture) {
glEnable(GL_TEXTURE_2D);
}
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex3fv(floorVertices[0]);
glTexCoord2f(0.0, 16.0);
glVertex3fv(floorVertices[1]);
glTexCoord2f(16.0, 16.0);
glVertex3fv(floorVertices[2]);
glTexCoord2f(16.0, 0.0);
glVertex3fv(floorVertices[3]);
glEnd();
if (useTexture) {
glDisable(GL_TEXTURE_2D);
}
glEnable(GL_LIGHTING);
}
static GLfloat floorPlane[4];
static GLfloat floorShadow[4][4];
static void
redraw(void)
{
int start, end;
if (reportSpeed) {
start = glutGet(GLUT_ELAPSED_TIME);
}
/* Clear; default stencil clears to zero. */
if ((stencilReflection && renderReflection) || (stencilShadow && renderShadow)) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
} else {
/* Avoid clearing stencil when not using it. */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
/* Reposition the light source. */
lightPosition[0] = 12*cos(lightAngle);
lightPosition[1] = lightHeight;
lightPosition[2] = 12*sin(lightAngle);
if (directionalLight) {
lightPosition[3] = 0.0;
} else {
lightPosition[3] = 1.0;
}
shadowMatrix(floorShadow, floorPlane, lightPosition);
glPushMatrix();
/* Perform scene rotations based on user mouse input. */
glRotatef(angle2, 1.0, 0.0, 0.0);
glRotatef(angle, 0.0, 1.0, 0.0);
/* Tell GL new light source position. */
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
if (renderReflection) {
if (stencilReflection) {
/* We can eliminate the visual "artifact" of seeing the "flipped"
dinosaur underneath the floor by using stencil. The idea is
draw the floor without color or depth update but so that
a stencil value of one is where the floor will be. Later when
rendering the dinosaur reflection, we will only update pixels
with a stencil value of 1 to make sure the reflection only
lives on the floor, not below the floor. */
/* Don't update color or depth. */
glDisable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
/* Draw 1 into the stencil buffer. */
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
/* Now render floor; floor pixels just get their stencil set to 1. */
drawFloor();
/* Re-enable update of color and depth. */
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);
/* Now, only render where stencil is set to 1. */
glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
}
glPushMatrix();
/* The critical reflection step: Reflect dinosaur through the floor
(the Y=0 plane) to make a relection. */
glScalef(1.0, -1.0, 1.0);
/* Reflect the light position. */
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
/* To avoid our normals getting reversed and hence botched lighting
on the reflection, turn on normalize. */
glEnable(GL_NORMALIZE);
glCullFace(GL_FRONT);
/* Draw the reflected dinosaur. */
drawDinosaur();
/* Disable noramlize again and re-enable back face culling. */
glDisable(GL_NORMALIZE);
glCullFace(GL_BACK);
glPopMatrix();
/* Switch back to the unreflected light position. */
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
if (stencilReflection) {
glDisable(GL_STENCIL_TEST);
}
}
/* Back face culling will get used to only draw either the top or the
bottom floor. This let's us get a floor with two distinct
appearances. The top floor surface is reflective and kind of red.
The bottom floor surface is not reflective and blue. */
/* Draw "bottom" of floor in blue. */
glFrontFace(GL_CW); /* Switch face orientation. */
glColor4f(0.1, 0.1, 0.7, 1.0);
drawFloor();
glFrontFace(GL_CCW);
if (renderShadow) {
if (stencilShadow) {
/* Draw the floor with stencil value 3. This helps us only
draw the shadow once per floor pixel (and only on the
floor pixels). */
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 3, 0xffffffff);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
}
}
/* Draw "top" of floor. Use blending to blend in reflection. */
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.7, 0.0, 0.0, 0.3);
glColor4f(1.0, 1.0, 1.0, 0.3);
drawFloor();
glDisable(GL_BLEND);
if (renderDinosaur) {
/* Draw "actual" dinosaur, not its reflection. */
drawDinosaur();
}
if (renderShadow) {
/* Render the projected shadow. */
if (stencilShadow) {
/* Now, only render where stencil is set above 2 (ie, 3 where
the top floor is). Update stencil with 2 where the shadow
gets drawn so we don't redraw (and accidently reblend) the
shadow). */
glStencilFunc(GL_LESS, 2, 0xffffffff); /* draw if ==1 */
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
}
/* To eliminate depth buffer artifacts, we use polygon offset
to raise the depth of the projected shadow slightly so
that it does not depth buffer alias with the floor. */
if (offsetShadow) {
switch (polygonOffsetVersion) {
case EXTENSION:
#ifdef GL_EXT_polygon_offset
glEnable(GL_POLYGON_OFFSET_EXT);
break;
#endif
#ifdef GL_VERSION_1_1
case ONE_DOT_ONE:
glEnable(GL_POLYGON_OFFSET_FILL);
break;
#endif
case MISSING:
/* Oh well. */
break;
}
}
/* Render 50% black shadow color on top of whatever the
floor appareance is. */
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_LIGHTING); /* Force the 50% black. */
glColor4f(0.0, 0.0, 0.0, 0.5);
glPushMatrix();
/* Project the shadow. */
glMultMatrixf((GLfloat *) floorShadow);
drawDinosaur();
glPopMatrix();
glDisable(GL_BLEND);
glEnable(GL_LIGHTING);
if (offsetShadow) {
switch (polygonOffsetVersion) {
#ifdef GL_EXT_polygon_offset
case EXTENSION:
glDisable(GL_POLYGON_OFFSET_EXT);
break;
#endif
#ifdef GL_VERSION_1_1
case ONE_DOT_ONE:
glDisable(GL_POLYGON_OFFSET_FILL);
break;
#endif
case MISSING:
/* Oh well. */
break;
}
}
if (stencilShadow) {
glDisable(GL_STENCIL_TEST);
}
}
glPushMatrix();
glDisable(GL_LIGHTING);
glColor3f(1.0, 1.0, 0.0);
if (directionalLight) {
/* Draw an arrowhead. */
glDisable(GL_CULL_FACE);
glTranslatef(lightPosition[0], lightPosition[1], lightPosition[2]);
glRotatef(lightAngle * -180.0 / M_PI, 0, 1, 0);
glRotatef(atan(lightHeight/12) * 180.0 / M_PI, 0, 0, 1);
glBegin(GL_TRIANGLE_FAN);
glVertex3f(0, 0, 0);
glVertex3f(2, 1, 1);
glVertex3f(2, -1, 1);
glVertex3f(2, -1, -1);
glVertex3f(2, 1, -1);
glVertex3f(2, 1, 1);
glEnd();
/* Draw a white line from light direction. */
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_LINES);
glVertex3f(0, 0, 0);
glVertex3f(5, 0, 0);
glEnd();
glEnable(GL_CULL_FACE);
} else {
/* Draw a yellow ball at the light source. */
glTranslatef(lightPosition[0], lightPosition[1], lightPosition[2]);
glutSolidSphere(1.0, 5, 5);
}
glEnable(GL_LIGHTING);
glPopMatrix();
glPopMatrix();
if (reportSpeed) {
glFinish();
end = glutGet(GLUT_ELAPSED_TIME);
printf("Speed %.3g frames/sec (%d ms)\n", 1000.0/(end-start), end-start);
}
glutSwapBuffers();
}
/* ARGSUSED2 */
static void
mouse(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON) {
if (state == GLUT_DOWN) {
moving = 1;
startx = x;
starty = y;
}
if (state == GLUT_UP) {
moving = 0;
}
}
if (button == GLUT_MIDDLE_BUTTON) {
if (state == GLUT_DOWN) {
lightMoving = 1;
lightStartX = x;
lightStartY = y;
}
if (state == GLUT_UP) {
lightMoving = 0;
}
}
}
/* ARGSUSED1 */
static void
motion(int x, int y)
{
if (moving) {
angle = angle + (x - startx);
angle2 = angle2 + (y - starty);
startx = x;
starty = y;
glutPostRedisplay();
}
if (lightMoving) {
lightAngle += (x - lightStartX)/40.0;
lightHeight += (lightStartY - y)/20.0;
lightStartX = x;
lightStartY = y;
glutPostRedisplay();
}
}
/* Advance time varying state when idle callback registered. */
static void
idle(void)
{
static float time = 0.0;
time = glutGet(GLUT_ELAPSED_TIME) / 500.0;
jump = 4.0 * fabs(sin(time)*0.5);
if (!lightMoving) {
lightAngle += 0.03;
}
glutPostRedisplay();
}
enum {
M_NONE, M_MOTION, M_LIGHT, M_TEXTURE, M_SHADOWS, M_REFLECTION, M_DINOSAUR,
M_STENCIL_REFLECTION, M_STENCIL_SHADOW, M_OFFSET_SHADOW,
M_POSITIONAL, M_DIRECTIONAL, M_PERFORMANCE
};
static void
controlLights(int value)
{
switch (value) {
case M_NONE:
return;
case M_MOTION:
animation = 1 - animation;
if (animation) {
glutIdleFunc(idle);
} else {
glutIdleFunc(NULL);
}
break;
case M_LIGHT:
lightSwitch = !lightSwitch;
if (lightSwitch) {
glEnable(GL_LIGHT0);
} else {
glDisable(GL_LIGHT0);
}
break;
case M_TEXTURE:
useTexture = !useTexture;
break;
case M_SHADOWS:
renderShadow = 1 - renderShadow;
break;
case M_REFLECTION:
renderReflection = 1 - renderReflection;
break;
case M_DINOSAUR:
renderDinosaur = 1 - renderDinosaur;
break;
case M_STENCIL_REFLECTION:
stencilReflection = 1 - stencilReflection;
break;
case M_STENCIL_SHADOW:
stencilShadow = 1 - stencilShadow;
break;
case M_OFFSET_SHADOW:
offsetShadow = 1 - offsetShadow;
break;
case M_POSITIONAL:
directionalLight = 0;
break;
case M_DIRECTIONAL:
directionalLight = 1;
break;
case M_PERFORMANCE:
reportSpeed = 1 - reportSpeed;
break;
}
glutPostRedisplay();
}
/* When not visible, stop animating. Restart when visible again. */
static void
visible(int vis)
{
if (vis == GLUT_VISIBLE) {
if (animation)
glutIdleFunc(idle);
} else {
if (!animation)
glutIdleFunc(NULL);
}
}
/* Press any key to redraw; good when motion stopped and
performance reporting on. */
/* ARGSUSED */
static void
key(unsigned char c, int x, int y)
{
if (c == 27) {
exit(0); /* IRIS GLism, Escape quits. */
}
glutPostRedisplay();
}
/* Press any key to redraw; good when motion stopped and
performance reporting on. */
/* ARGSUSED */
static void
special(int k, int x, int y)
{
glutPostRedisplay();
}
static int
supportsOneDotOne(void)
{
const char *version;
int major, minor;
version = (char *) glGetString(GL_VERSION);
if (sscanf(version, "%d.%d", &major, &minor) == 2)
return major >= 1 && minor >= 1;
return 0; /* OpenGL version string malformed! */
}
int
main(int argc, char **argv)
{
int i;
glutInit(&argc, argv);
for (i=1; i<argc; i++) {
if (!strcmp("-linear", argv[i])) {
linearFiltering = 1;
} else if (!strcmp("-mipmap", argv[i])) {
useMipmaps = 1;
} else if (!strcmp("-ext", argv[i])) {
forceExtension = 1;
}
}
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL | GLUT_MULTISAMPLE);
#if 0
/* In GLUT 4.0, you'll be able to do this an be sure to
get 2 bits of stencil if the machine has it for you. */
glutInitDisplayString("samples stencil>=2 rgb double depth");
#endif
glutCreateWindow("Shadowy Leapin' Lizards");
if (glutGet(GLUT_WINDOW_STENCIL_SIZE) <= 1) {
printf("dinoshade: Sorry, I need at least 2 bits of stencil.\n");
exit(1);
}
/* Register GLUT callbacks. */
glutDisplayFunc(redraw);
glutMouseFunc(mouse);
glutMotionFunc(motion);
glutVisibilityFunc(visible);
glutKeyboardFunc(key);
glutSpecialFunc(special);
glutCreateMenu(controlLights);
glutAddMenuEntry("Toggle motion", M_MOTION);
glutAddMenuEntry("-----------------------", M_NONE);
glutAddMenuEntry("Toggle light", M_LIGHT);
glutAddMenuEntry("Toggle texture", M_TEXTURE);
glutAddMenuEntry("Toggle shadows", M_SHADOWS);
glutAddMenuEntry("Toggle reflection", M_REFLECTION);
glutAddMenuEntry("Toggle dinosaur", M_DINOSAUR);
glutAddMenuEntry("-----------------------", M_NONE);
glutAddMenuEntry("Toggle reflection stenciling", M_STENCIL_REFLECTION);
glutAddMenuEntry("Toggle shadow stenciling", M_STENCIL_SHADOW);
glutAddMenuEntry("Toggle shadow offset", M_OFFSET_SHADOW);
glutAddMenuEntry("----------------------", M_NONE);
glutAddMenuEntry("Positional light", M_POSITIONAL);
glutAddMenuEntry("Directional light", M_DIRECTIONAL);
glutAddMenuEntry("-----------------------", M_NONE);
glutAddMenuEntry("Toggle performance", M_PERFORMANCE);
glutAttachMenu(GLUT_RIGHT_BUTTON);
makeDinosaur();
#ifdef GL_VERSION_1_1
if (supportsOneDotOne() && !forceExtension) {
polygonOffsetVersion = ONE_DOT_ONE;
glPolygonOffset(-2.0, -1.0);
} else
#endif
{
#ifdef GL_EXT_polygon_offset
/* check for the polygon offset extension */
if (glutExtensionSupported("GL_EXT_polygon_offset")) {
polygonOffsetVersion = EXTENSION;
glPolygonOffsetEXT(-0.1, -0.002);
} else
#endif
{
polygonOffsetVersion = MISSING;
printf("\ndinoshine: Missing polygon offset.\n");
printf(" Expect shadow depth aliasing artifacts.\n\n");
}
}
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glLineWidth(3.0);
glMatrixMode(GL_PROJECTION);
gluPerspective( /* field of view in degree */ 40.0,
/* aspect ratio */ 1.0,
/* Z near */ 20.0, /* Z far */ 100.0);
glMatrixMode(GL_MODELVIEW);
gluLookAt(0.0, 8.0, 60.0, /* eye is at (0,0,30) */
0.0, 8.0, 0.0, /* center is at (0,0,0) */
0.0, 1.0, 0.); /* up is in postivie Y direction */
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor);
glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.1);
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
makeFloorTexture();
/* Setup floor plane for projected shadow calculations. */
findPlane(floorPlane, floorVertices[1], floorVertices[2], floorVertices[3]);
glutMainLoop();
return 0; /* ANSI C requires main to return int. */
}
|