modelAngle += (float) (milliseconds) / 10.0f; // Update Angle Based On The Clock
}
The function you've all been waiting for. The Draw function does everything — calculates the shade values, renders the mesh, renders the outline, and, well that's it really.
void Draw (void) {
TmpShade is used to store the shader value for the current vertex. All vertex data is calculate at the same time, meaning that we only need to use a single variable that we can just keep reusing.
The TmpMatrix, TmpVector and TmpNormal structures are also used to calculate the vertex data. TmpMatrix is set once at the start of the function and never changed until Draw is called again. TmpVector and TmpNormal on the other hand, change when another vertex is processed.
float TmpShade; // Temporary Shader Value
MATRIX TmpMatrix; // Temporary MATRIX Structure
VECTOR TmpVector, TmpNormal; // Temporary VECTOR Structures
Lets clear the buffers and matrix data.
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Buffers
glLoadIdentity (); // Reset The Matrix
The first check is to see if we want to have smooth outlines. If so, then we turn on anti-alaising. If not, we turn it off. Simple!
if (outlineSmooth) // Check To See If We Want Anti-Aliased Lines
{
glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); // Use The Good Calculations
glEnable (GL_LINE_SMOOTH); // Enable Anti-Aliasing
} else // We Don't Want Smooth Lines
glDisable (GL_LINE_SMOOTH); // Disable Anti-Aliasing
We then setup the viewport. We move the camera back 2 units, and then rotate the model by the angle. Note: because we moved the camera first, the model will rotate on the spot. If we did it the other way around, the model would rotate around the camera.
We then grab the newly created matrix from OpenGL and store it in TmpMatrix.
glTranslatef (0.0f, 0.0f, –2.0f); // Move 2 Units Away From The Screen
glRotatef (modelAngle, 0.0f, 1.0f, 0.0f); // Rotate The Model On It's Y-Axis
glGetFloatv (GL_MODELVIEW_MATRIX, TmpMatrix.Data); // Get The Generated Matrix
The magic begins. We first enable 1D texturing, and then enable the shader texture. This is to be used as a look-up table by OpenGL. We then set the color of the model (white). I chose white because it shows up the highlights and shading much better then other colors. I suggest that you don't use black :)
// Cel-Shading Code
glEnable (GL_TEXTURE_1D); // Enable 1D Texturing
glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // Bind Our Texture
glColor3f (1.0f, 1.0f, 1.0f); // Set The Color Of The Model
Now we start drawing the triangles. We look though each polygon in the array, and then in turn each of it's vertexes. The first step is to copy the normal information into a temporary structure. This is so we can rotate the normals, but still keep the original values preserved (no precision degradation).
glBegin (GL_TRIANGLES); // Tell OpenGL That We're Drawing Triangles
for (i = 0; i < polyNum; i++) // Loop Through Each Polygon
{
for (j = 0; j < 3; j++) // Loop Through Each Vertex
{
TmpNormal.X = polyData[i].Verts[j].Nor.X; // Fill Up The TmpNormal Structure With The
TmpNormal.Y = polyData[i].Verts[j].Nor.Y; // Current Vertices' Normal Values
TmpNormal.Z = polyData[i].Verts[j].Nor.Z;
Second, we rotate the normal by the matrix grabbed from OpenGL earlier. We then normalize this so it doesn't go all screwy.
// Rotate This By The Matrix
RotateVector (TmpMatrix, TmpNormal, TmpVector);
Normalize (TmpVector); // Normalize The New Normal
Third, we get the dot product of the rotated normal and light direction (called lightAngle, because I forgot to change it from my old light class). We then clamp the value to the range 0-1 (from –1 to +1).
// Calculate The Shade Value
TmpShade = DotProduct (TmpVector, lightAngle);
if (TmpShade < 0.0f) TmpShade = 0.0f; // Clamp The Value to 0 If Negative
Forth, we pass this value to OpenGL as the texture co-ordinate. The shader texture acts as a lookup table (the shader value being the index), which is (i think) the main reason why 1D textures were invented. We then pass the vertex's position to OpenGL, and repeat. And Repeat. And Repeat. And I think you get the idea.
glTexCoord1f (TmpShade); // Set The Texture Co-ordinate As The Shade Value
// Send The Vertex Position
glVertex3fv (&polyData[i].Verts[j].Pos.X);
}
}
glEnd (); // Tell OpenGL To Finish Drawing
glDisable (GL_TEXTURE_1D); // Disable 1D Textures
Now we move onto the outlines. An outline can be defined as “an edge where one polygon is front facing, and the other is backfacing”. In OpenGL, it's where the depth test is set to less than or equal to (GL_LEQUAL) the current value, and when all front faces are being culled. We also blend the lines in, to make it look nice :)
So, we enable blending and set the blend mode. We tell OpenGL to render backfacing polygons as lines, and set the width of those lines. We cull all front facing polygons, and set the depth test to less than or equal to the current Z value. After this the color of the line is set, and we loop though each polygon, drawing its vertices. We only need to pass the vertex position, and not the normal or shade value because all we want is an outline.
// Outline Code
if (outlineDraw) // Check To See If We Want To Draw The Outline
{
glEnable (GL_BLEND); // Enable Blending
// Set The Blend Mode
glBlendFunc (GL_SRC_ALPHA ,GL_ONE_MINUS_SRC_ALPHA);
glPolygonMode (GL_BACK, GL_LINE); // Draw Backfacing Polygons As Wireframes
glLineWidth (outlineWidth); // Set The Line Width
glCullFace (GL_FRONT); // Don't Draw Any Front-Facing Polygons
glDepthFunc (GL_LEQUAL); // Change The Depth Mode
glColor3fv (&outlineColor[0]); // Set The Outline Color
glBegin (GL_TRIANGLES); // Tell OpenGL What We Want To Draw
for (i = 0; i < polyNum; i++) // Loop Through Each Polygon
{
for (j = 0; j < 3; j++) // Loop Through Each Vertex
{
// Send The Vertex Position
glVertex3fv (&polyData[i].Verts[j].Pos.X);
}
}
glEnd (); // Tell OpenGL We've Finished
After this, we just set everything back to how it was before, and exit.
glDepthFunc (GL_LESS); // Reset The Depth-Testing Mode
glCullFace (GL_BACK); // Reset The Face To Be Culled
glPolygonMode (GL_BACK, GL_FILL); // Reset Back-Facing Polygon Drawing Mode
glDisable (GL_BLEND); // Disable Blending
}
}