glStencilFunc( GL_NOTEQUAL, 0, 0xFFFFFFFFL );
glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
glPushMatrix();
glLoadIdentity();
glBegin( GL_TRIANGLE_STRIP );
glVertex3f(-0.1f, 0.1f,-0.10f);
glVertex3f(-0.1f,-0.1f,-0.10f);
glVertex3f( 0.1f, 0.1f,-0.10f);
glVertex3f( 0.1f,-0.1f,-0.10f);
glEnd();
glPopMatrix();
glPopAttrib();
}
Ok, the next part draws the shadowed quads. How does that work? What happens is that you go through every face, and if it is visible, then you check all of its edges. If at the edge, there is no neighbouring face, or the neighbouring face is not visible, the edge casts a shadow. If you think about the two cases clearly, then you'll see this is true. By drawing a quadrilateral (as two triangles) comprising of the points of the edge, and the edge projected backwards through the scene you get the shadow cast by it.
The brute force approach used here just draws to "infinity", and the shadow polygon is clipped against all the polygons it encounters. This causes piercing, which will stress the video hardware. For a high-performance modification to this algorithm, you should clip the polygon to the objects behind it. This is much trickier and has problems of its own, but if that's what you want to do, you should refer to this Gamasutra article.
The code to do all of that is not as tricky as it sounds. To start with, here is a snippet that loops through the objects. By the end of it, we have an edge, j , and its neighbouring face, specified by neighbourIndex .
void doShadowPass( ShadowedObject& object, GLfloat *lightPosition ) {
for ( int i = 0; i < object.nFaces; i++ ) {
const Face& face = object.pFaces[i];
if ( face.visible ) {
// Go Through Each Edge
for ( int j = 0; j < 3; j++ ) {
int neighbourIndex = face.neighbourIndices[j];
Next, check if there is a visible neighbouring face to this object. If not, then this edge casts a shadow.
// If There Is No Neighbour, Or Its Neighbouring Face Is Not Visible, Then This Edge Casts A Shadow
if ( neighbourIndex == –1 || object.pFaces[neighbourIndex].visible == false ) {
The next segment of code will retrieve the two vertices from the current edge, v1 and v2. Then, it calculates v3 and v4, which are projected along the vector between the light source and the first edge. They are scaled to INFINITY, which was set to a very large value.
// Get The Points On The Edge
const Point3f& v1 = object.pVertices[face.vertexIndices[j]];
const Point3f& v2 = object.pVertices[face.vertexIndices[( j+1 )%3]];
// Calculate The Two Vertices In Distance
Point3f v3, v4;
v3.x = ( v1.x-lightPosition[0] )*INFINITY;
v3.y = ( v1.y-lightPosition[1] )*INFINITY;
v3.z = ( v1.z-lightPosition[2] )*INFINITY;
v4.x = ( v2.x-lightPosition[0] )*INFINITY;
v4.y = ( v2.y-lightPosition[1] )*INFINITY;
v4.z = ( v2.z-lightPosition[2] )*INFINITY;
I think you'll understand the next section, it justs draws the quadrilateral defined by those four points:
// Draw The Quadrilateral (As A Triangle Strip)
glBegin( GL_TRIANGLE_STRIP );
glVertex3f( v1.x, v1.y, v1.z );
glVertex3f( v1.x+v3.x, v1.y+v3.y, v1.z+v3.z );
glVertex3f( v2.x, v2.y, v2.z );
glVertex3f( v2.x+v4.x, v2.y+v4.y, v2.z+v4.z );
glEnd();
}
}
}
}
}
With that, the shadow casting section is completed. But we are not finished yet! What about drawGLScene? Lets start with the simple bits: clearing the buffers, positioning the light source, and drawing a sphere:
bool drawGLScene() {
GLmatrix16f Minv;
GLvector4f wlp, lp;
// Clear Color Buffer, Depth Buffer, Stencil Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glLoadIdentity(); // Reset Modelview Matrix
glTranslatef(0.0f, 0.0f, –20.0f); // Zoom Into Screen 20 Units
glLightfv(GL_LIGHT1, GL_POSITION, LightPos); // Position Light1
glTranslatef(SpherePos[0], SpherePos[1], SpherePos[2]); // Position The Sphere
gluSphere(q, 1.5f, 32, 16); // Draw A Sphere
Next, we have to calculate the light's position relative to the local coordinate system of the object. The comments explain each step in detail. Minv stores the object's transformation matrix, however it is done in reverse, and with negative arguments, so it is actually the inverse of the transformation matrix. Then lp is created as a copy of the light's position, and multiplied by the matrix. Thus, lp is the light's position in the object's coordinate system.
glLoadIdentity(); // Reset Matrix
glRotatef(-yrot, 0.0f, 1.0f, 0.0f); // Rotate By –yrot On Y Axis
glRotatef(-xrot, 1.0f, 0.0f, 0.0f); // Rotate By –xrot On X Axis
glTranslatef(-ObjPos[0], –ObjPos[1], –ObjPos[2]); // Move Negative On All Axis Based On ObjPos[] Values (X, Y, Z)
glGetFloatv(GL_MODELVIEW_MATRIX,Minv); // Retrieve ModelView Matrix (Stores In Minv)
lp[0] = LightPos[0]; // Store Light Position X In lp[0]
lp[1] = LightPos[1]; // Store Light Position Y In lp[1]
lp[2] = LightPos[2]; // Store Light Position Z In lp[2]
lp[3] = LightPos[3]; // Store Light Direction In lp[3]
VMatMult(Minv, lp); // We Store Rotated Light Vector In 'lp' Array
Now, palm off some of the work to draw the room, and the object. Calling castShadow draws the shadow of the object.
glLoadIdentity(); // Reset Modelview Matrix
glTranslatef(0.0f, 0.0f, –20.0f); // Zoom Into The Screen 20 Units
DrawGLRoom(); // Draw The Room
glTranslatef(ObjPos[0], ObjPos[1], ObjPos[2]); // Position The Object
glRotatef(xrot, 1.0f, 0.0f, 0.0f); // Spin It On The X Axis By xrot
glRotatef(yrot, 0.0f, 1.0f, 0.0f); // Spin It On The Y Axis By yrot
drawObject(obj); // Procedure For Drawing The Loaded Object
castShadow(obj, lp); // Procedure For Casting The Shadow Based On The Silhouette
The following few lines draw a little orange circle where the light is:
glColor4f(0.7f, 0.4f, 0.0f, 1.0f); // Set Color To An Orange
glDisable(GL_LIGHTING); // Disable Lighting
glDepthMask(GL_FALSE); // Disable Depth Mask
glTranslatef(lp[0], lp[1], lp[2]); // Translate To Light's Position
// Notice We're Still In Local Coordinate System