Выбрать главу

for each face (A) in the object

 for each edge in A

  if we don't know this edges neighbour yet

   for each face (B) in the object (except A)

    for each edge in B

     if A's edge is the same as B's edge, then they are neighbouring each other on that edge

      set the neighbour property for each face A and B, then move onto next edge in A

The last two lines are accomplished with the following code. By finding the two vertices that mark the ends of an edge and comparing them, you can discover if it is the same edge. The part (edgeA+1)%3 gets a vertex next to the one you are considering. Then you check if the vertices match (the order may be different, hence the second case of the if statement).

int vertA1 = pFaceA->vertexIndices[edgeA];

int vertA2 = pFaceA->vertexIndices[( edgeA+1 )%3];

int vertB1 = pFaceB->vertexIndices[edgeB];

int vertB2 = pFaceB->vertexIndices[( edgeB+1 )%3];

// Check If They Are Neighbours – IE, The Edges Are The Same

if (( vertA1 == vertB1 && vertA2 == vertB2 ) || ( vertA1 == vertB2 && vertA2 == vertB1 )) {

 pFaceA->neighbourIndices[edgeA] = faceB;

 pFaceB->neighbourIndices[edgeB] = faceA;

 edgeFound = true;

 break;

}

Luckily, another easy function while you take a breath. drawObject renders each face one by one.

// Draw An Object – Simply Draw Each Triangular Face.

void drawObject( const ShadowedObject& object ) {

 glBegin( GL_TRIANGLES );

 for ( int i = 0; i < object.nFaces; i++ ) {

  const Face& face = object.pFaces[i];

  for ( int j = 0; j < 3; j++ ) {

   const Point3f& vertex = object.pVertices[face.vertexIndices[j]];

   glNormal3f( face.normals[j].x, face.normals[j].y, face.normals[j].z );

   glVertex3f( vertex.x, vertex.y, vertex.z );

  }

 }

 glEnd();

}

Calculating the equation of a plane looks ugly, but it is just a simple mathematical formula that you grab from a textbook when you need it.

void calculatePlane( const ShadowedObject& object, Face& face ) {

 // Get Shortened Names For The Vertices Of The Face

 const Point3f& v1 = object.pVertices[face.vertexIndices[0]];

 const Point3f& v2 = object.pVertices[face.vertexIndices[1]];

 const Point3f& v3 = object.pVertices[face.vertexIndices[2]];

 face.planeEquation.a = v1.y*(v2.z-v3.z) + v2.y*(v3.z-v1.z) + v3.y*(v1.z-v2.z);

 face.planeEquation.b = v1.z*(v2.x-v3.x) + v2.z*(v3.x-v1.x) + v3.z*(v1.x-v2.x);

 face.planeEquation.c = v1.x*(v2.y-v3.y) + v2.x*(v3.y-v1.y) + v3.x*(v1.y-v2.y);

 face.planeEquation.d = –( v1.x*( v2.y*v3.z – v3.y*v2.z ) + v2.x*(v3.y*v1.z – v1.y*v3.z) + v3.x*(v1.y*v2.z – v2.y*v1.z) );

}

Have you caught your breath yet? Good, because you are about to learn how to cast a shadow! The castShadow function does all of the GL specifics, and passes it on to doShadowPass to render the shadow in two passes.

First up, we determine which surfaces are facing the light. We do this by seeing which side of the plane the light is on. This is done by substituting the light's position into the equation for the plane. If this is larger than 0, then it is in the same direction as the normal to the plane and visible by the light. If not, then it is not visible by the light. (Again, refer to a good Math textbook for a better explanation of geometry in 3D).

void castShadow( ShadowedObject& object, GLfloat *lightPosition ) {

 // Determine Which Faces Are Visible By The Light.

 for ( int i = 0; i < object.nFaces; i++ ) {

  const Plane& plane = object.pFaces[i].planeEquation;

  GLfloat side = plane.a*lightPosition[0]+ plane.b*lightPosition[1]+ plane.c*lightPosition[2]+ plane.d;

  if (side > 0) object.pFaces[i].visible = true;

  else object.pFaces[i].visible = false;

 }

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. We'll come back to that in 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 the 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 );