• No results found

Figure 1: First Pass Figure 2: Second Pass

In document Nehe Opengl Tutorial (Page 176-181)

The next section sets up the necessary OpenGL states for rendering the shadows.

First, we push all the attributes onto the stack that will be modified. This makes changing them back a lot easier.

Lighting is disabled because we will not be rendering to the color (output) buffer, just the stencil buffer. For the same reason, the color mask turns off all color components (so drawing a polygon won't get through to the output buffer).

Although depth testing is still used, we don't want the shadows to appear as solid objects in the depth buffer , so the depth mask prevents this from happening.

The stencil buffer is turned on as that is what is going to be used to draw the shadows into.

glPushAttrib( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT | GL_POLYGON_BIT | GL_STENCIL_BUFFER_BIT );

glDisable( GL_LIGHTING );// Turn Off Lighting

glDepthMask( GL_FALSE );// Turn Off Writing To The Depth-Buffer glDepthFunc( GL_LEQUAL );

glEnable( GL_STENCIL_TEST );// Turn On Stencil Buffer Testing

glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );// Don't Draw Into The Colour Buffer glStencilFunc( GL_ALWAYS, 1, 0xFFFFFFFFL );

Ok, now the shadows are actually rendered. W e'll come back to that i n a moment when we look at the doShadowPass function.

They are rendered in two passes as you can see, one incrementing the stencil buffer with the front faces (casting t he shadow), the second decrementing the stencil buffer with the backfaces ("turning off" the shadow between the object and any other surfaces).

// First Pass. Increase Stencil Value In The Shadow glFrontFace( GL_CCW );

glStencilOp( GL_KEEP, GL_KEEP, GL_INCR );

doShadowPass( object, lightPosition );

// Second Pass. Decrease Stencil Value In The Shadow glFrontFace( GL_CW );

glStencilOp( GL_KEEP, GL_KEEP, GL_DECR );

doShadowPass( object, lightPosition );

To understand how the second pass works, my best advise is to comment it out and run the tutorial again. To save you the trouble, I have done it here:

Figure 1: First Pass Figure 2: Second Pass

The final section of this function draws one blended rectangle over the whole screen, to cast a shadow. The darker you make this rectangle, the darker the shadows will be. So to change the properties of the shadow, change the glColor4f statement. Higher alpha will make it more black. Or you can make it red, green, purple, ...!

glFrontFace( GL_CCW );

glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );// Enable Rendering To Colour Buffer For All Components

// Draw A Shadowing Rectangle Covering The Entire Screen glColor4f( 0.0f, 0.0f, 0.0f, 0.4f );

glEnable( GL_BLEND );

glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

Lesson 27 – Shadows 173

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 tothis 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 ) {

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 ar e 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;

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 );

With that, the shadow casting section is completed. But we are not finished yet! What about drawGLScene? Lets start with the

Lesson 27 – Shadows 174

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 l ight'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

gluSphere(q, 0.2f, 16, 8);// Draw A Little Yellow Sphere (Represents Light) glEnable(GL_LIGHTING);// Enable Lighting

glDepthMask(GL_TRUE);// Enable Depth Mask

The last part updates the object's position and returns.

xrot += xspeed; // Increase xrot By xspeed yrot += yspeed; // Increase yrot By yspeed

glFlush();// Flush The OpenGL Pipeline return TRUE;// Everything Went OK }

We did specify a DrawGLRoom function, and here it is - a bunch of rectangles to cast shadows against:

void DrawGLRoom()// Draw The Room (Box) {

glBegin(GL_QUADS);// Begin Drawing Quads // Floor

glNormal3f(0.0f, 1.0f, 0.0f);// Normal Pointing Up glVertex3f(-10.0f,-10.0f,-20.0f);// Back Left glVertex3f(-10.0f,-10.0f, 20.0f);// Front Left glVertex3f( 10.0f,-10.0f, 20.0f);// Front Right glVertex3f( 10.0f,-10.0f,-20.0f);// Back Right // Ceiling

glNormal3f(0.0f,-1.0f, 0.0f);// Normal Point Down glVertex3f(-10.0f, 10.0f, 20.0f);// Front Left glVertex3f(-10.0f, 10.0f,-20.0f);// Back Left glVertex3f( 10.0f, 10.0f,-20.0f);// Back Right glVertex3f( 10.0f, 10.0f, 20.0f);// Front Right // Front Wall

glNormal3f(0.0f, 0.0f, 1.0f);// Normal Pointing Away From Viewer glVertex3f(-10.0f, 10.0f,-20.0f);// Top Left

glVertex3f(-10.0f,-10.0f,-20.0f);// Bottom Left

Lesson 27 – Shadows 175

glVertex3f( 10.0f,-10.0f,-20.0f);// Bottom Right glVertex3f( 10.0f, 10.0f,-20.0f);// Top Right // Back Wall

glNormal3f(0.0f, 0.0f,-1.0f);// Normal Pointing Towards Viewer glVertex3f( 10.0f, 10.0f, 20.0f);// Top Right

glVertex3f( 10.0f,-10.0f, 20.0f);// Bottom Right glVertex3f(-10.0f,-10.0f, 20.0f);// Bottom Left glVertex3f(-10.0f, 10.0f, 20.0f);// Top Left // Left Wall

glNormal3f(1.0f, 0.0f, 0.0f);// Normal Pointing Right glVertex3f(-10.0f, 10.0f, 20.0f);// Top Front glVertex3f(-10.0f,-10.0f, 20.0f);// Bottom Front glVertex3f(-10.0f,-10.0f,-20.0f);// Bottom Back glVertex3f(-10.0f, 10.0f,-20.0f);// Top Back // Right Wall

glNormal3f(-1.0f, 0.0f, 0.0f);// Normal Pointing Left glVertex3f( 10.0f, 10.0f,-20.0f);// Top Back

glVertex3f( 10.0f,-10.0f,-20.0f);// Bottom Back glVertex3f( 10.0f,-10.0f, 20.0f);// Bottom Front glVertex3f( 10.0f, 10.0f, 20.0f);// Top Front glEnd();// Done Drawing Quads

}

And before I forget, here is the VMatMult function which multiplies a vector by a matrix (get that Math textbook out again!):

void VMatMult(GLmatrix16f M, GLvector4f v) {

GLfloat res[4];// Hold Calculated Results

res[0]=M[ 0]*v[0]+M[ 4]*v[1]+M[ 8]*v[2]+M[12]*v[3];

res[1]=M[ 1]*v[0]+M[ 5]*v[1]+M[ 9]*v[2]+M[13]*v[3];

res[2]=M[ 2]*v[0]+M[ 6]*v[1]+M[10]*v[2]+M[14]*v[3];

res[3]=M[ 3]*v[0]+M[ 7]*v[1]+M[11]*v[2]+M[15]*v[3];

v[0]=res[0]; // Results Are Stored Back In v[]

v[1]=res[1];

v[2]=res[2];

v[3]=res[3]; // Homogenous Coordinate }

The function to load the object is simple, just calling readObject, and then setting up the connectivity and the plane equations for each face.

int InitGLObjects()// Initialize Objects {

if (!readObject("Data/Object2.txt", obj))// Read Object2 Into obj {

return FALSE;// If Failed Return False }

setConnectivity(obj);// Set Face To Face Connectivity

for ( int i=0;i < obj.nFaces;i++)// Loop Through All Object Faces

calculatePlane(obj, obj.pFaces[i // Compute Plane Equations For All Faces return TRUE;// Return True

}

Finally, KillGLObjects is a convenience function so that if you add more objects, you can add them in a central place.

void KillGLObjects() {

killObject( obj );

}

All of the other functions don't require any further explanantion. I have left out the standard NeHe tutorial code, as well as all of the variable definitions and the keyboard processing function. The commenting alone explains these sufficiently.

Some things to note about the tutorial:

• The sphere doesn't stop shadows being projected on the wall. In reality, the sphere should also be casting a shadow, so seeing the one on the wall won't matter, it's hidden. It 's just there to see what happens on curved surfaces :)

• If you are noticing extremely slow frame rates, try switching to fullscreen mode, or setting your desktop colour depth to 32bpp.

• Arseny L. writes: If you are having problems with a TNT2 in Windowed mode, make sure your desktop color depth is not set to 16bit. In 16bit color mode, the stencil buffer is emulated, resulting in sluggish performance. There are no problems in 32bit mode (I have a TNT2 Ultra and I checked it).

I've got to admit this was a lengthy task to write out this tutorial. It gives you full appreciation for the work that Jeff puts in! I hope you enjoy it, and give a huge thanks to Banu who wrote the srcinal code! IF there is anything that needs further explaining in here, you are welcome to contact me (Brett), [email protected].

Lesson 27 – Shadows 176

Banu Cosmin

Banu Cosmin(ChokoChoko) & Brett Porter Brett Porter Jeff Molofee

Jeff Molofee(NeHeNeHe)

Lesson 28 – Bezier Patches / Fullscreen Fix 177

Lesson 28

Lesson 28

In document Nehe Opengl Tutorial (Page 176-181)