return (msg.wParam); // Exit The Program
}
I really hope you've enjoyed this tutorial. I know it could use a little more work. It was one of the more difficult tutorials that I have written. It's easy for me to understand what everything is doing, and what commands I need to use to create cool effects, but when you sit down and actually try to explain things keeping in mind that most people have never even heard of the stencil buffer, it's tough! If you notice anything that could be made clearer or if you find any mistakes in the tutorial please let me know. As always, I want this tutorial to be the best it can possibly be, your feedback is greatly appreciated.
* DOWNLOAD Visual C++ Code For This Lesson.
* DOWNLOAD Linux Code For This Lesson. (Conversion by Gray Fox)
* DOWNLOAD Mac OS X/Cocoa Code For This Lesson. (Conversion by Bryan Blackburn)
Lesson 27
Welcome to a fairly complex tutorial on shadow casting. The effect this demo creates is literally incredible. Shadows that stretch, bend and wrap around other objects and across walls. Everything in the scene can be moved around in 3D space using keys on the keyboard.
This tutorial takes a fairly different approach — It assumes you have a lot of OpenGL knowledge. You should already understand the stencil buffer, and basic OpenGL setup. If you need to brush up, go back and read the earlier tutorials. Functions such as CreateGLWindow and WinMain will NOT be explained in this tutorial. Additionally, some fundamental 3D math is assumed, so keep a good textbook handy! (I used my 1st year maths lecture notes from University — I knew they'd come in handy later on! :)
First we have the definition of INFINITY, which represents how far to extend the shadow volume polygons (this will be explained more later on). If you are using a larger or smaller coordinate system, adjust this value accordingly.
// Definition Of "INFINITY" For Calculating The Extension Vector For The Shadow Volume
#define INFINITY 100
Next is the definition of the object structures.
The Point3f structure holds a coordinate in 3D space. This can be used for vertices or vectors.
// Structure Describing A Vertex In An Object
struct Point3f {
GLfloat x, y, z;
};
The Plane structure holds the 4 values that form the equation of a plane. These planes will represent the faces of the object.
// Structure Describing A Plane, In The Format: ax + by + cz + d = 0
struct Plane {
GLfloat a, b, c, d;
};
The Face structure contains all the information necessary about a triangle to cast a shadow.
• The indices specified are from the object's array of vertices.
• The vertex normals are used to calculate the orientation of the face in 3D space, so you can determine which are facing the light source when casting the shadows.
• The plane equation describes the plane that this triangle lies in, in 3D space.
• The neighbour indices are indices into the array of faces in the object. This allows you to specify which face joins this face at each edge of the triangle.
• The visible parameter is used to specify whether the face is "visible" to the light source which is casting the shadows.
// Structure Describing An Object's Face
struct Face {
int vertexIndices[3]; // Index Of Each Vertex Within An Object That Makes Up The Triangle Of This Face
Point3f normals[3]; // Normals To Each Vertex
Plane planeEquation; // Equation Of A Plane That Contains This Triangle
int neighbourIndices[3]; // Index Of Each Face That Neighbours This One Within The Object
bool visible; // Is The Face Visible By The Light?
};
Finally, the ShadowedObject structure contains all the vertices and faces in the object. The memory for each of the arrays is dynamically created when it is loaded.
struct ShadowedObject {
int nVertices;
Point3f *pVertices; // Will Be Dynamically Allocated
int nFaces;
Face *pFaces; // Will Be Dynamically Allocated
};
The readObject function is fairly self explanatory. It will fill in the given object structure with the values read from the file, allocating memory for the vertices and faces. It also initializes the neighbours to –1, which means there isn't one (yet). They will be calculated later.
bool readObject(const char *filename, ShadowedObject& object) {
FILE *pInputFile;
int i;
pInputFile = fopen(filename, "r");
if (pInputFile == NULL) {
cerr << "Unable to open the object file: " << filename << endl;
return false;
}
// Read Vertices
fscanf( pInputFile, "%d", &object.nVertices );
object.pVertices = new Point3f[object.nVertices];
for ( i = 0; i < object.nVertices; i++ ) {
fscanf( pInputFile, "%f", &object.pVertices[i].x );
fscanf( pInputFile, "%f", &object.pVertices[i].y );
fscanf( pInputFile, "%f", &object.pVertices[i].z );
}
// Read Faces
fscanf( pInputFile, "%d", &object.nFaces );
object.pFaces = new Face[object.nFaces];
for ( i = 0; i < object.nFaces; i++ ) {
int j;
Face *pFace = &object.pFaces[i];
for ( j = 0; j < 3; j++ ) pFace->neighbourIndices[j] = –1; // No Neigbours Set Up Yet
for ( j = 0; j < 3; j++ ) {
fscanf( pInputFile, "%d", &pFace->vertexIndices[j] );
pFace->vertexIndices[j]-; // Files Specify Them With A 1 Array Base, But We Use A 0 Array Base
}
for ( j = 0; j < 3; j++ ) {
fscanf( pInputFile, "%f", &pFace->normals[j].x );
fscanf( pInputFile, "%f", &pFace->normals[j].y );
fscanf( pInputFile, "%f", &pFace->normals[j].z );
}
}
return true;
}
Likewise, killObject is self-explanatory — just delete all those dynamically allocated arrays inside the object when you are done with them. Note that a line was added to KillGLWindow to call this function for the object in question.
void killObject( ShadowedObject& object ) {
delete[] object.pFaces;
object.pFaces = NULL;
object.nFaces = 0;
delete[] object.pVertices;
object.pVertices = NULL;
object.nVertices = 0;
}
Now, with setConnectivity it starts to get interesting. This function is used to find out what neighbours there are to each face of the object given. Here's some pseudo code: