Lazy Foo' Productions

Matrices and Coloring Polygons

Last Updated 7/06/14
In this tutorial, we're going to set up a 640x480 coordinate system and while we're at it we're going to give some color to our polygon.

We'll also cover a super nutshelled explaination of the OpenGL pipeline and how it turns vertices into pixels.
From LUtil.h
///Screen constants const int SCREEN_WIDTH = 640; const int SCREEN_HEIGHT = 480; const int SCREEN_FPS = 60; //Color modes const int COLOR_MODE_CYAN = 0; const int COLOR_MODE_MULTI = 1; bool initGL(); /* Pre Condition: -A valid OpenGL context Post Condition: -Initializes matrices and clear color -Reports to console if there was an OpenGL error -Returns false if there was an error in initialization Side Effects: -Projection matrix is set to an orthographic matrix -Modelview matrix is set to identity matrix -Matrix mode is set to modelview -Clear color is set to black */ void update(); /* Pre Condition: -None Post Condition: -Does per frame logic Side Effects: -None */ void render(); /* Pre Condition: -A valid OpenGL context -Active modelview matrix Post Condition: -Renders the scene Side Effects: -Clears the color buffer -Swaps the front/back buffer -Sets matrix mode to modelview -Translates modelview matrix to the center of the default screen -Changes the current rendering color */ void handleKeys( unsigned char key, int x, int y ); /* Pre Condition: -None Post Condition: -Toggles the color mode when the user presses q -Cycles through different projection scales when the user presses e Side Effects: -If the user presses e, the matrix mode is set to projection */
Here's our utility function again with some tweaks to the pre/post conditions and side effects. What's new is the color mode constants and handleKeys() function. The color mode constants are just symbols we use to render our quad.

In SDL (I hear there's a good tutorial set here), events are handled with event queues. With GLUT, they are handled with callbacks. In our main() function, we'll set our handleKeys() function as a callback to handle keys.
From LUtil.cpp
#include "LUtil.h" //The current color rendering mode int gColorMode = COLOR_MODE_CYAN; //The projection scale GLfloat gProjectionScale = 1.f;
At the top of LUtil.cpp, we have two global variables. Note: they are only global inside of LUtil.cpp.

"gColorMode" controls whether we render a solid cyan square or a multicolored square.

"gProjectionScale" controls how large of a coordinate area we want to render.

We'll cover how these variables are used in the render() and handleKeys() functions.
From LUtil.cpp
bool initGL() { //Initialize Projection Matrix glMatrixMode( GL_PROJECTION ); glLoadIdentity(); glOrtho( 0.0, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0, 1.0, -1.0 ); //Initialize Modelview Matrix glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); //Initialize clear color glClearColor( 0.f, 0.f, 0.f, 1.f ); //Check for error GLenum error = glGetError(); if( error != GL_NO_ERROR ) { printf( "Error initializing OpenGL! %s\n", gluErrorString( error ) ); return false; } return true; }
Here's our graphics library initialization function. It's largely the same, except now we call glOrtho().

What glOrtho() does is multiply the current matrix against an orthographic (or 2D) perspective matrix with the left, right, bottom, top, near, and far values in the arguments. Here, we're setting up the coordinate system like this (similar to SDL):

Note: +Y points down and -Y points up. For those of you new to graphics programming this may seem weird, but inverted Y axis is actually pretty standard in 2D graphics programming.

Notice how we multiplied the orthographic matrix against the projection matrix. This is what the projection matrix is for, to control how we view our geometry. If we wanted 3D perspective, we'd multiply the projection matrix against the perspective matrix (done with either gluPerspective() or glFrustum()).
From LUtil.cpp
void update() { }
And once again since we have a stationary quad, we don't do much of anything in our update() function. Also, our runMainLoop() function is exactly the same.

From now on, expect to only go over relevant new code. Otherwise, these tutorials would get really repetitive really quickly.
From LUtil.cpp
void render() { //Clear color buffer glClear( GL_COLOR_BUFFER_BIT ); //Reset modelview matrix glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); //Move to center of the screen glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f );
Here's the top of our rendering function. After we clear the screen, we set the current matrix mode to modelview. We do this because in our key handling function we're going to change the projection matrix. If we don't make sure the current matrix is the modelview matrix, the projection and modelview matrix operations are going to be done incorrectly and we'll get funky results.

The reason we need the modelview matrix is because we're going to be applying tranformations to our geometry. Where projection matrix controls how the geometry is viewed, modelview matrix tranformations control how geometry is placed in the rendering world.

Because of the way we set up our projection matrix, the origin of the scene is in the top left corner. We want our square to show up in the middle of the screen, so we translate it (or slide it) to the center of the screen using glTranslate(). glTranslate() multiplies a translation matrix against the current matrix (which in the case is the modelview matrix) so that any geometry that's rendered is translated the x, y, and z amount given in the arguments.

Another thing to note is that in every frame we load the identity modelview matrix before rendering. If we didn't, the translation transformations would accumulate. So if we translated 10 every frame, after 60 frames we would have translated 600. Eventually, we'd translate completely off the screen. Try commenting out glLoadIdentity() in this line of code and see what happens.
From LUtil.cpp
//Render quad if( gColorMode == COLOR_MODE_CYAN ) { //Solid Cyan glBegin( GL_QUADS ); glColor3f( 0.f, 1.f, 1.f ); glVertex2f( -50.f, -50.f ); glVertex2f( 50.f, -50.f ); glVertex2f( 50.f, 50.f ); glVertex2f( -50.f, 50.f ); glEnd(); }
Now it's time to render our square. If the color mode is cyan (which is the initial value), we want to render a solid color quad.

To set the next vertex color, we call glColor() with the red, green, and blue values. Then we set the vertex position with glVertex().

For the second time we send a vertex (as well as the third and fourth), we give no color. So the OpenGL pipeline just uses the last color value that was given. This gives our quad a solid cyan color.

You'll notice that we're making a square with a width of 100. Thanks to our projection matrix, it should be 100 pixels wide.
From LUtil.cpp
else { //RYGB Mix glBegin( GL_QUADS ); glColor3f( 1.f, 0.f, 0.f ); glVertex2f( -50.f, -50.f ); glColor3f( 1.f, 1.f, 0.f ); glVertex2f( 50.f, -50.f ); glColor3f( 0.f, 1.f, 0.f ); glVertex2f( 50.f, 50.f ); glColor3f( 0.f, 0.f, 1.f ); glVertex2f( -50.f, 50.f ); glEnd(); }
If "gColorMode" is not cyan, we assume it must be multicolor.

Notice that this time, we give an individual color for each vertex. Again, it's important to give the color before you give the vertex because OpenGL looks at the most recent color value when rendering that particular vertex.

Ok so we rendered our quad. Now how does OpenGL use matrices to turn polygons into pixels? Let's use a polygon from a 3D cube as an example. Here's a (poorly drawn) cube:

First, the modelview matrix is applied to translate it/rotate it/scale it/skew it/whatever into place:

Remember back to high school art class and those perspective scenes they made you do?

What the projection matrix does is take your vertices from your polygon and multiplies them to transform them into normalized perspective coordinates that OpenGL can use:

Then it connects your polygon vertices

And starts filling in the pixels (this is called rasterization)

Obviously, there's more to the OpenGL pipeline with things to control texturing, coloring, lighting, etc. In terms of how we get from geometry to pixels, all OpenGL does is take vertex coordinates and rasterizes them into pixels with ProjectionMatrix * ModelviewMatrix * Vertex.
From LUtil.cpp
//Update screen glutSwapBuffers(); }
At the end of our render() function after our vertices were rasterized into pixels, we swap the front/back buffer to update the screen.
From LUtil.cpp
void handleKeys( unsigned char key, int x, int y ) { //If the user presses q if( key == 'q' ) { //Toggle color mode if( gColorMode == COLOR_MODE_CYAN ) { gColorMode = COLOR_MODE_MULTI; } else { gColorMode = COLOR_MODE_CYAN; } }
At the top of a key press handling function, we check if the user presses q. If they did, we switch the square back and forth between cyan and multicolor.
From LUtil.cpp
else if( key == 'e' ) { //Cycle through projection scales if( gProjectionScale == 1.f ) { //Zoom out gProjectionScale = 2.f; } else if( gProjectionScale == 2.f ) { //Zoom in gProjectionScale = 0.5f; } else if( gProjectionScale == 0.5f ) { //Regular zoom gProjectionScale = 1.f; } //Update projection matrix glMatrixMode( GL_PROJECTION ); glLoadIdentity(); glOrtho( 0.0, SCREEN_WIDTH * gProjectionScale, SCREEN_HEIGHT * gProjectionScale, 0.0, 1.0, -1.0 ); } }
At the bottom of a key handling function, we check if the user pressed e. If they did, we cycle through the various project scales.
  1. 100% scale: default
  2. 200% scale: we render an area twice as large so everything is smaller
  3. 50% scale: we render an area half as large so everything is close up and bigger
After the scale is selected, we reset the projection matrix identity matrix and multiply with the scaled orthographic perspective matrix.

Those of you that really know your linear algebra are probably thinking "Can't we just multiply a scaling matrix against the projection matrix?". The answer is yes, and we'll be covering making transformation to the projection matrix in the scrolling tutorial.
From main.cpp
int main( int argc, char* args[] ) { //Initialize FreeGLUT glutInit( &argc, args ); //Create OpenGL 2.1 context glutInitContextVersion( 2, 1 ); //Create Double Buffered Window glutInitDisplayMode( GLUT_DOUBLE ); glutInitWindowSize( SCREEN_WIDTH, SCREEN_HEIGHT ); glutCreateWindow( "OpenGL" ); //Do post window/context creation initialization if( !initGL() ) { printf( "Unable to initialize graphics library!\n" ); return 1; } //Set keyboard handler glutKeyboardFunc( handleKeys ); //Set rendering function glutDisplayFunc( render ); //Set main loop glutTimerFunc( 1000 / SCREEN_FPS, runMainLoop, 0 ); //Start GLUT main loop glutMainLoop(); return 0; }
Our main() function is pretty much the same as before, only now we use glutKeyboardFunc() to set our keyboard handler callback.
Download the media and source code for this tutorial here.
Back to OpenGL Tutorials