In this article I will demonstrate a basic introduction in OpenGL. It will be in tutorial format that the reader can follow along on their own. The final result should be a working template that can be used to create your own projects using OpenGL.
Table of Contents
- History
- Prerequisite
- OpenGL Project
-
Creating the Source
- Headers
- Globals and Forward-Declarations
- The Main Method
- The Cleanup Method
- The InitGL Function
- The DisplayGL Method
- The IdleGL Method
- The KeyboardGL Method
- The MouseGL and MotionGL Methods
- The ReshapeGL Method
- Rendering 2D Primitives
- Drawing 2D Polygons
- Using Colors
- Adding some Spin
- Enter the 3rd Dimension
- Video Lecture
- References
- Download the Source
History
Prerequisite
GLUT
- GLUT Source: http://www.opengl.org/resources/libraries/glut/
- GLUT pre-built binaries for Win32: http://www.xmission.com/~nate/glut.html
GLEW or GLEE
OpenGL Project
Creating the Source
After you click the “Add” button, the “main.cpp” source file should be added to your project’s “Source Files” folder and opened in the editor.
Headers
#define _USE_MATH_DEFINES #include <math.h> #include <iostream> #include <ctime>
#include <GL/glut.h>
# pragma comment (lib, "opengl32.lib") /* link with Microsoft OpenGL lib */ # pragma comment (lib, "glu32.lib") /* link with Microsoft OpenGL Utility lib */ # pragma comment (lib, "glut32.lib") /* link with Win32 GLUT lib */
Now that we have our project setup with all our headers, libs and DLL’s we can continue programming!
Globals and Forward-Declarations
struct float2
{
float2( float _x = 0.0f, float _y = 0.0f ) : x(_x), y(_y) {}
float x;
float y;
};
struct float3
{
float3( float _x = 0.0f, float _y = 0.0f, float _z = 0.0f ) : x(_x), y(_y), z(_z) {}
float x;
float y;
float z;
};
And a few global variables that will be used by GLUT to initialize the render window:
int g_iWindowWidth = 512; int g_iWindowHeight = 512; int g_iGLUTWindowHandle = 0;
If an error occurs, we will use a global error code to report the error when the application exists.
int g_iErrorCode = 0;
And we will also forward-declare the OpenGL callback functions.
void InitGL( int argc, char* argv[] ); void DisplayGL(); void IdleGL(); void KeyboardGL( unsigned char c, int x, int y ); void MouseGL( int button, int state, int x, int y ); void MotionGL( int x, int y ); void ReshapeGL( int w, int h );
Since these are pretty important functions, I will take some time to explain their use.
DiplayGL(void): This function will be registered as the render function that is invoked by GLUT when the current window needs to be redisplayed.
IdleGL(void): This function is registered with GLUT and it is invoked when windows system events are not being received. This function is ideal for background processing tasks such as the game’s Update methods.
KeyboardGL( unsigned char c, int x, int y ): Whenever the user presses a key on the keyboard, this method will be used as the callback method.
- unsigned char c contains the ASCII keyboard key code that was pressed.
- int x, int y are the mouse’s location in window-relative coordinates at the moment the key was pressed.
MouseGL( int button, int state, int x, int y ): This method is registered with GLUT and it will be invoked when the user presses a mouse button on the current window. Each press and release action will generate an event.
- int button will be one of GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, or GLUT_RIGHT_BUTTON.
- int state will be either GLUT_DOWN, or GLUT_UP indicating whether the event was a pressed, or released event.
- int x, int y store the position of the mouse relative to the current window.
MotionGL( int x, int y ): This method is invoked when the mouse moves within the window while one or more mouse buttons are pressed.
- int x, int y store the position of the mouse relative to the current window.
ReshapGL( int width, int height ): The reshape callback function is invoked when the render window is resized. This method will also be called the first time the window is displayed so it can be used as the only method that is used to setup the projection matrix.
- int width, int height parameters specify the new window size in pixels.
We will also declare a few functions that will be used to draw some primitives.
void DrawRectangle( float width, float height ); void DrawCircle( float radius, int numSides = 8 ); void DrawTriangle( float2 p1, float2 p2, float2 p3 );
void DrawCube( float width, float height, float depth ); void DrawSphere( float radius ); void DrawPyramid( float scale = 1.0f );
These functions will be defined later.
There will be several scenes in this demo, so I will also declare an enumeration to store the different scene types and a variable to store the current value.
enum ESceneType
{
ST_Scene1 = 0,
ST_Scene2,
ST_Scene3,
ST_Scene4,
ST_NumScenes
};
ESceneType g_eCurrentScene = ST_Scene1;
Since we also want to rotate some of our primitives, we will store some global parameters that will keep track of the current rotation.
float g_fRotate1 = 0.0f; float g_fRotate2 = 0.0f; float g_fRotate3 = 0.0f;
And we will use the clock_t type to store the number of “ticks” between frames.
std::clock_t g_PreviousTicks; std::clock_t g_CurrentTicks;
Since our demo will render several different scenes, we will also declare some functions to render those scenes.
// Render a simple scene with 2D primitives void RenderScene1(); // Render a slightly more complex scene using different colors void RenderScene2(); // Render a scene with animated transformations void RenderScene3(); // Render a scene with 3D objects that perform rotations on all 3 axis. void RenderScene4();
And the last function that will be forward-declared is the function that is responsible for cleaning up our resources.
void Cleanup( int exitCode, bool bExit = true );
The Main Method
The first function we will define is the “main” method. Here is where it all begins.
int main( int argc, char* argv[] )
{
// Capture the previous time to calculate the delta time on the next frame
g_PreviousTicks = std::clock();
InitGL( argc, argv );
glutMainLoop();
}
The first thing we do in this function is to capture the current ticks from the system clock. Later, we will query the clock again to find out how much time has passed since the last time we updated our game state.
The next method “InitGL( int argc, char* argv[] )” will initialize the OpenGL context and the render window. This function will be described in more detail later.
Then we invoke the “glutMainLoop()” method. Invoking this function will start the GLUT event processing loop. Once it is called, it will never end. Everything your program does will be done using the function callbacks which will be registered in the “InitGL” method.
The Cleanup Method
The “Cleanup( int errorCode, bool bExit )” method is used to cleanup resources used by your program. In this case, we only have to destroy the render window we have created.
void Cleanup( int errorCode, bool bExit )
{
if ( g_iGLUTWindowHandle != 0 )
{
glutDestroyWindow( g_iGLUTWindowHandle );
g_iGLUTWindowHandle = 0;
}
if ( bExit )
{
exit( errorCode );
}
}
The InitGL Function
Setting up an OpenGL context and render window is very easy using GLUT as we will see in the next function.
void InitGL( int argc, char* argv[] )
{
std::cout << "Initialise OpenGL..." << std::endl;
glutInit(&argc, argv);
int iScreenWidth = glutGet(GLUT_SCREEN_WIDTH);
int iScreenHeight = glutGet(GLUT_SCREEN_HEIGHT);
glutInitDisplayMode( GLUT_RGBA | GLUT_ALPHA | GLUT_DOUBLE | GLUT_DEPTH );
glutInitWindowPosition( ( iScreenWidth - g_iWindowWidth ) / 2,
( iScreenHeight - g_iWindowHeight ) / 2 );
glutInitWindowSize( g_iWindowWidth, g_iWindowHeight );
g_iGLUTWindowHandle = glutCreateWindow( "OpenGL" );
// Register GLUT callbacks
glutDisplayFunc(DisplayGL);
glutIdleFunc(IdleGL);
glutMouseFunc(MouseGL);
glutMotionFunc(MotionGL);
glutKeyboardFunc(KeyboardGL);
glutReshapeFunc(ReshapeGL);
// Setup initial GL State
glClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
glClearDepth( 1.0f );
glShadeModel( GL_SMOOTH );
std::cout << "Initialise OpenGL: Success!" << std::endl;
}
The glutInit method is used to initialize the GLUT library and initiate communication with the windowing system. In this demo, we simply pass along the program’s command-line arguments without change.
The glutGet method can query the current GLUT state variables. In this case, we will simply query the current screen width and height using the GLUT_SCREEN_WIDTH, and GLUT_SCREEN_HEIGHT . This is the resolution of the current screen that is associated with the current windowing system. We will use this value to position our window in the middle of the screen.
The glutInitDisplayMode method is used to initialize the display mode that will be used to create new top-level windows. The bitmask mode parameter specifies the display mode.
- GLUT_RGBA: Set’s the color mode to RGBA. The render window will be created with a color buffer that stores components for the color channels (as apposed to GLUT_INDEX which stores the color buffer in index color mode).
- GLUT_ALPHA: This will ensure that the color buffer has a channel to store the alpha of the pixel.
- GLUT_DOUBLE: The window will be double buffered. Two buffers will be used to present the final image, one is used to render to, while the other is the one currently being presented on the screen. At the moment the application is finished rendering, the buffers are swapped. This prevents an effect called “screen tearing“.
- GLUT_DEPTH: This will ensure a depth buffer is created for the render window. The depth buffer is used to make sure that pixels that appear closer to the viewer are not drawn behind pixels that appear further away from the viewer, regardless of the order in which the pixels are rasterized to the color buffer.
The glutInitWindowPosition and glutInitWindowSize methods will initialize the position and size of the newly created window. The “x”, and “y” parameters are the number of pixels relative to screen space position where the top-left is (0,0). The “width” and “height” parameters are specified in screen pixels.
The glutCreateWindow will actually create the render window using the parameters we have specified before we call this function. The single parameter to this function specifies the name that will be used to identify the window. This method returns a single int that is used to refer to this window. This will also implicitly set the current window state for GLUT to the newly created window.
The next set of functions glutDiplayFunc to glutReshapeFunc will register the callbacks for the GULT event system. These callbacks will be associated with the current window.
The glClearColor method specifies the color that will be used to clear the color buffer when the glClear method is invoked with the GLUT_COLOR_BUFFER_BIT bitfield parameter.
The glClearDepth method is used to specify the value to clear the depth buffer to when the glClear method is invoked with the GLUT_DEPTH_BUFFER_BIT bitfield parameter. In this case, we specify “1.0″ as the only parameter to this method. A valid depth range is from 0.0 (absolutely close to the viewer), to 1.0 (absolutely far from the viewer). Setting the depth buffer to 1.0 will ensure that all new pixels (within the view frustum) will be drawn.
The glShadeModel method specifies the shading model to use for rendering. The GL_SMOOTH parameter will ensure that the color of pixels are interpolated between vertices for a smoother looking surfaces. The other option GL_FLAT will shade the face of a triangle with a flat color. The actual color used is dependent on the type of primitive being drawn.
The DisplayGL Method
The display method is the callback function that was registered with the GLUT event processing loop and is invoked whenever the current window contents need to be redrawn.
void DisplayGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
switch ( g_eCurrentScene )
{
case ST_Scene1:
{
RenderScene1();
}
break;
case ST_Scene2:
{
RenderScene2();
}
break;
case ST_Scene3:
{
RenderScene3();
}
break;
case ST_Scene4:
{
RenderScene4();
}
break;
}
glutSwapBuffers();
glutPostRedisplay();
}
The first thing we will do at the beginning of almost every render call (except in the case where you might want to perform some special effect that might require the color buffer not to be cleared) is to clear our buffers. This is done with the glClear method. This method will reset the contents of the current render target (this is usually the frame buffer for the current window). The parameters that are passed to this method will determine which buffers are cleared. In our case, we want to clear the color buffer and the depth buffer using the parameters GL_COLOR_BUFFER_BIT and GL_DEPTH_BUFFER_BIT. We don’t need to specify the GL_STENCIL_BUFFER_BIT since we didn’t specify a stencil buffer in our glutInitDisplayMode method.
The switch statement will be used to determine the scene that will be rendered. Each scene will be described in more detail later.
The method glutSwapBuffers will flip the back buffer (the off-screen buffer that is currently being rendered to) with the front buffer (the frame buffer that is currently being displayed). If we didn’t specify GLUT_DOUBLE in the glutInitDisplayMode, then this method would have no effect because we would only have one buffer (the front buffer that is always displayed).
And the final function call glutPostRedisplay is used to tell GLUT that we are ready to render another frame. Invoking this method will not cause another frame to be immediately rendered with the DisplayGL method (otherwise calling this method here would cause an infinite recursive function loop), but instead it simply marks, or flags the current window to be redisplayed.
The IdleGL Method
The IdleGL method will be invoked whenever there are no events to be processed in the GLUT even loop. We will use this method to update the logic of our demo.
void IdleGL()
{
// Update our simulation
g_CurrentTicks = std::clock();
float deltaTicks = ( g_CurrentTicks - g_PreviousTicks );
g_PreviousTicks = g_CurrentTicks;
float fDeltaTime = deltaTicks / (float)CLOCKS_PER_SEC;
// Rate of rotation in (degrees) per second
const float fRotationRate = 50.0f;
// Update our rotation parameters
g_fRotate1 += ( fRotationRate * fDeltaTime );
g_fRotate1 = fmodf( g_fRotate1, 360.0f );
g_fRotate2 += ( fRotationRate * fDeltaTime );
g_fRotate2 = fmodf( g_fRotate2, 360.0f );
g_fRotate3 += ( fRotationRate * fDeltaTime );
g_fRotate3 = fmodf( g_fRotate3, 360.0f );
glutPostRedisplay();
}
The first thing we do is calculate the amount of time that has passed since the last time this method has been called. The “std::clock()” function returns the number of “clock ticks” that have elapsed since the program has started execution. We will use the calculated deltaTime value to ensure that our program runs smoothly independently of the frame rate and we can express our program’s values in terms of elapsed time in seconds.
The next few lines (181-188) will update our rotation parameters that will be used later to rotate our primitives. You may have noticed that all three parameters are updated at the same rate and I probably could have just used a single rotate variable, but if I wanted to, I could rotate each axis of the primitive with at a different rate.
The final line has the same effect here as it did in the DisplayGL method. It marks the current window to be redisplayed. This makes sense since we have updated some of the state of our demo, we want to see the results but to do that, we need to redisplay the scene by telling GLUT that we want to invoke the DisplayGL method again.
The KeyboardGL Method
This method is invoked by the GLUT even loop whenever the user presses a key on the keyboard. The method will be passed an ASCII character code that represents the keyboard key that was pressed, as well as the x and y window relative position of the mouse in pixels.
void KeyboardGL( unsigned char c, int x, int y )
{
// Store the current scene so we can test if it has changed later.
ESceneType currentScene = g_eCurrentScene;
switch ( c )
{
case '1':
{
glClearColor( 1.0f, 1.0f, 1.0f, 1.0f ); // White background
g_eCurrentScene = ST_Scene1;
}
break;
case '2':
{
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); // Black background
g_eCurrentScene = ST_Scene2;
}
break;
case '3':
{
glClearColor( 0.27f, 0.27f, 0.27f, 1.0f ); // Dark-Gray background
g_eCurrentScene = ST_Scene3;
}
break;
case '4':
{
glClearColor( 0.73f, 0.73f, 0.73f, 1.0f ); // Light-Gray background
g_eCurrentScene = ST_Scene4;
}
break;
case 's':
case 'S':
{
std::cout << "Shade Model: GL_SMOOTH" << std::endl;
// Switch to smooth shading model
glShadeModel( GL_SMOOTH );
}
break;
case 'f':
case 'F':
{
std::cout << "Shade Model: GL_FLAT" << std::endl;
// Switch to flat shading model
glShadeModel( GL_FLAT );
}
break;
case 'r':
case 'R':
{
std::cout << "Reset Parameters..." << std::endl;
g_fRotate1 = g_fRotate2 = g_fRotate3 = 0.0f;
}
break;
case '\033': // escape quits
case '\015': // Enter quits
case 'Q': // Q quits
case 'q': // q (or escape) quits
{
// Cleanup up and quit
Cleanup(0);
}
break;
}
if ( currentScene != g_eCurrentScene )
{
std::cout << "Changed Render Scene: " << ( g_eCurrentScene + 1 ) << std::endl;
}
glutPostRedisplay();
}
In this method, I arbitrarily choose the keys that I wanted to be handled for this demo. I should note that by default, the program will do nothing when the user presses keys on the keyboard while the render window has focus. So you will have to program all the events yourself.
Keyboard keys ’1′-’4′ will be used to change the current scene that will be rendered when DisplayGL is invoked. We also change the clear color of the color buffer when we change the scene. This will make it more obvious when we change between the different scenes.
The ‘s’ key is used to enable smooth shading model and the ‘f’ key is used to enable flat shading model.
Pressing the ‘r’ key will reset our rotation parameters that are used to rotate our primitives.
Pressing the ‘esc’, ‘enter’, or ‘q’ key will quite the demo and close the render window by calling the “Cleanup” method. In general, I like to use the ‘esc’ key at a minimum to close demo programs.
And finally we call the “glutPostRedisplay” method to ensure our window gets marked for redisplay.
The MouseGL and MotionGL Methods
The MouseGL and MotionGL methods are used to handle mouse button clicks and mouse movement events that are sent from the GLUT event loop.
void MouseGL( int button, int state, int x, int y )
{
}
void MotionGL( int x, int y )
{
}
In this particular demo, we don’t do anything with the mouse, so these methods are left blank. I included them here because the idea is we can use this source to create additional demos in OpenGL in which case we might actually want to do something with the mouse. I should also not that there is additional mouse motion function that will receive events when the mouse is moved over the render window when no buttons are pressed. This method is called “PassiveMotionGL” and is registered with the “glutPassiveMotionFunc” method.
The ReshapeGL Method
The ReshapeGL method is invoked by the GLUT event loop whenever the render window is resized. This method will also be called whenever the render window is shown for the first time so we can use it to setup our viewport and projection parameters.
void ReshapeGL( int w, int h )
{
std::cout << "ReshapGL( " << w << ", " << h << " );" << std::endl;
if ( h == 0) // Prevent a divide-by-zero error
{
h = 1; // Making Height Equal One
}
g_iWindowWidth = w;
g_iWindowHeight = h;
glViewport( 0, 0, g_iWindowWidth, g_iWindowHeight );
// Setup the projection matrix
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( 60.0, (GLdouble)g_iWindowWidth/(GLdouble)g_iWindowHeight, 0.1, 100.0 );
glutPostRedisplay();
}
To prevent a divide-by-zero error, we first check to make sure the height is not zero.
The glViewport method is used to setup the viewport rectangle for the current OpenGL context. The first two parameters specify the bottom-left corner of the viewport rectangle in pixels. The second two parameters specify the width and height of the viewport in pixels. For this demo, we will only specify one view that fills the entire screen.
We will also setup the projection matrix in this method. To do that we first set our matrix mode to GL_PROJECTION and use the gluPerspective method to initialize the projection matrix to a perspective projection matrix. The parameters to the gluPerspective proejction are:
- fovy: The field of view angle measured in degrees, in the y-direction.
- aspect: The aspect ratio of the viewport. This is almost always going to be set to (viewportWidth / viewportHeight) which in this case is the same as our window width and window height respectively.
- zNear: This is the distance from the viewer to the “near” clipping plane. Objects that are in front of this plane will be clipped by the rasterizer.
- zFar: This is the distance from the viewer to the “far” clipping plane. Object that are farther away than this will be clipped from view by the rasterizer.
And the final thing we will do is notify GLUT that the current window can be redisplayed.
Rendering 2D Primitives
The next methods that we will define are helper functions that will draw some basic primitives using OpenGL immediate mode. We will combine calls to these methods in our scene rendering functions to create a complex scenes.
When we use immediate mode to draw primitives, we always begin our primitive list with a call to glBegin and our primitive list is always terminated with a call to glEnd. The parameter that is passed to glBegin determines the type of primitive we want to draw and it can be one of the following values:
- GL_POINTS treats each vertex as a point that will be rendered to the screen. Points will always be drawn the same size irrelevant of their distance to the camera determined by the value specified by the glPointSize(GLfloat size) method. The standard size for points is 1.
- GL_LINES treats each pair of vertices as separate line segments. You must specify at least 2 vertices for a line to be drawn.
- GL_LINE_STRIP will draw an open connected series of lines where each vertex is connected by a line segment. The last vertex specified will not be automatically connected to the first vertex.
- GL_LINE_LOOP will draw a closed connected series of lines where each vertex is connected by a line segment. The last vertex will be automatically connected to the first vertex in the list.
- GL_TRIANGLES treats each triple set of vertices as a single triangle. Individual triangle primitives will not be connected.
- GL_TRIANGLE_STRIP
- GL_TRIANGLE_FAN is useful for drawing circles with any number of vertices where the first vertex is the central vertex that is used to connect all additional vertices in the list.
- GL_QUADS is used to draw a set of separated quadrilaterals (4-vertex polygons).
- GL_QUAD_STRIP is used to draw a set of connected quadrilaterals.
- GL_POLYGON is used to draw a closed (convex) polygon from any number of vertices.
will draw a connected group of triangles. For each additional vertex after the 3rd, the triangle is closed by adding an edge from the to the
vertex.
An example of using each primitive type is shown in the image below.
In our methods, we will only use a few of the primitive types to draw our shapes.
void DrawTriangle( float2 p1, float2 p2, float2 p3 )
{
glBegin( GL_TRIANGLES );
glVertex2f( p1.x, p1.y );
glVertex2f( p2.x, p2.y );
glVertex2f( p3.x, p3.y );
glEnd();
}
void DrawRectangle( float width, float height )
{
const float w = width / 2.0f;
const float h = height / 2.0f;
glBegin( GL_QUADS );
glVertex2f( -w, h ); // Top-Left
glVertex2f( w, h ); // Top-Right
glVertex2f( w, -h ); // Bottom-Right
glVertex2f( -w, -h ); // Bottom-Left
glEnd();
}
void DrawCircle( float radius, int numSides /* = 8 */ )
{
const float step = M_PI / numSides;
glBegin( GL_TRIANGLE_FAN );
glVertex2f(0.0f, 0.0f);
for ( float angle = 0.0f; angle < ( 2.0f * M_PI ); angle += step )
{
glVertex2f( radius * sinf(angle), radius * cosf(angle) );
}
glVertex2f( 0.0f, radius ); // One more vertex to close the circle
glEnd();
}
Starting with the first method DrawTriangle, we use a the GL_TRIANGLES primitive type to draw a single triangle from three vertices.
The next method show is DrawRectangle in which case the GL_QUADS primitive type is best suited for this purpose.
The best primitive to draw a circle is GL_TRIANGLE_FAN so we’ll use that in the DrawCircle method. Notice the first vertex at position is the common vertex that will join all the other vertices. We also add another vertex on line 331 that has the same position as the second vertex. This extra vertex is needed to close the circle, otherwise our circle would have a half-eaten pizza shape!
Drawing 2D Polygons
The first render method I will show you is a simple method that simply draws three 2D shapes, a red triangle, a blue square (quad), and a yellow circle.
void RenderScene1()
{
glMatrixMode( GL_MODELVIEW ); // Switch to modelview matrix mode
glLoadIdentity(); // Load the identity matrix
glTranslatef( -1.5f, 1.0f, -6.0f ); // Translate our view matrix back and a bit to the left.
glColor3f( 1.0f, 0.0f, 0.0f ); // Set Color to red
DrawTriangle( float2(0.0f, 1.0f), float2(-1.0f, -1.0f), float2(1.0f, -1.0f ) );
glTranslatef( 3.0f, 0.0f, 0.0f ); // Shift view 3 units to the right
glColor3f( 0.0f, 0.0f, 1.0f ); // Set Color to blue
DrawRectangle( 2.0f, 2.0f );
glTranslatef( -1.5f, -3.0f, 0.0f ); // Back to center and lower screen
glColor3f( 1.0f, 1.0f, 0.0f ); // Set color to yellow
DrawCircle( 1.0f, 16 );
}
The first thing we do in this method is to switch to modelview matrix mode. OpenGL supports three matrix stacks (four if the ARB_imaging extension is supported).
- GL_MODELVIEW matrix stack is a combination of view and model (or world) matrix transformation. Manipulating the modelview matrix will determine how objects are placed in the world relative to the viewer. We can imagine that the viewer is always at the origin of the global coordinate system and objects are positioned in the world by manipulating this matrix relative to that origin.
- GL_PROJECTION matrix stack is used to manipulate the lens of our camera. Changes made to the projection matrix will determine how the objects will appear on the screen. There are generally two types of projections used in games:
- Perspective Projection: Using a perspective projection, objects that are farther away from the viewer will appear smaller while objects closer to the viewer will appear larger. This is called perspective.
- Orthographic Projection: Using an orthographic projection (also known as orthogonal projection or parallel projection) results in all objects, regardless of their distance from the viewer, appear to be the same size. This is useful to screen elements like a HUD or for displaying text. This type of projection is also useful for rendering schematic views such as a maps or building schematics.
- GL_TEXTURE matrix stack is applied to the texture coordinates of a model before any texture mapping is applied. Using the texture matrix stack, you can scale, rotate, or translate a texture on a model to achieve interesting effects.
Then we reset the modelview matrix to the identity matrix using the glLoadIdentity() method. This method has the effect of reseting any translations, scales, and rotations back to the default state thus setting the world back the origin of the global coordinate space.
The next line causes the modelview matrix to be translated 6 units away from the viewer (along the z-axis), one unit up (along the y-axis), and 1.5 units to the left (along the x-axis) of the current viewer. All primitives rendered will now be rendered relative to this position.
On line 341 we specify a color that is to be used for all additional vertices that we send to the OpenGL display list. In this case the color we want to use is red. OpenGL can be thought of as a state-management system. OpenGL stores many state variables that remain constant until you explicitly change the value of that state. Using the glColor3f we can manipulate the value of the color state value. Every vertex we render after this call will get this color when rendered to the screen until we explicitly change the color state.
Then we draw the triangle using the DrawTriangle method described above.
Since we don’t want the next primitive to overlap the triangle we just drew, we will move the relative center three units to the right using the method glTranslatef. You should be aware that this translation occurs relative to the previous translation we performed, so this means that the next primitive is not drawn at but it is actually drawn at
because that is the combination of our previous translation and this one.
Then we set the color to blue and draw a rectangle using the method described above.
Then we do another translation, and draw another primitive, this time a yellow circle.
The result of rendering this scene is shown below.
Not very exciting is it… Let’s see if we can do something about that.
Using Colors
The next type of scene we will render will be similar to the first, but this time we will add some different colors to each vertex.
void RenderScene2()
{
glMatrixMode( GL_MODELVIEW ); // Switch to modelview matrix mode
glLoadIdentity(); // Load the identity matrix
glTranslatef( -1.5f, 1.0f, -6.0f ); // Translate back and to the left
// Draw a triangle with different colors on each vertex
glBegin( GL_TRIANGLES );
glColor3f( 1.0f, 0.0f, 0.0f ); // Red
glVertex2f( 0.0f, 1.0f ); // Top-Center
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex2f( -1.0f, -1.0f ); // Bottom-Left
glColor3f( 0.0f, 0.0f, 1.0f ); // Blue
glVertex2f( 1.0f, -1.0f ); // Bottom-Right
glEnd();
glTranslatef( 3.0f, 0.0f, 0.0f ); // Translate right
// Draw a rectangle with different colors on each vertex
glBegin( GL_QUADS );
glColor3f( 1.0f, 0.0f, 0.0f ); // Red
glVertex2f( -1.0f, 1.0f ); // Top-Left
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex2f( 1.0f, 1.0f ); // Top-Right
glColor3f( 0.0f, 0.0f, 1.0f ); // Blue
glVertex2f( 1.0f, -1.0f ); // Bottom-Right
glColor3f( 1.0f, 1.0f, 1.0f ); // White
glVertex2f( -1.0f, -1.0f ); // Bottom-Left
glEnd();
glTranslatef( -1.5f, -3.0f, 0.0f ); // Back to center and lower screen
// Draw a circle with blended red/blue vertices.
const float step = M_PI / 16;
const float radius = 1.0f;
glBegin( GL_TRIANGLE_FAN );
glColor3f( 1.0f, 1.0f, 1.0f );
glVertex2f(0.0f, 0.0f);
for ( float angle = 0.0f; angle < ( 2.0f * M_PI ); angle += step )
{
float fSin = sinf(angle);
float fCos = cosf(angle);
glColor3f( ( fCos + 1.0f ) * 0.5f, ( fSin + 1.0f ) * 0.5f , 0.0f);
glVertex2f( radius * fSin, radius * fCos );
}
glColor3f( 1.0f, 0.5f, 0.0f );
glVertex2f( 0.0f, radius ); // One more vertex to close the circle
glEnd();
}
This function is quite a bit longer than the previous, that’s because we are unrolling the work we did in the helper functions. I had to do this because the helper functions can only draw primitives with a single color, but I wanted to set the color of each vertex for this scene so I had to write-out all of the operations by hand.
The only difference between the two scenes is in this scene I change the color state before I send a vertex to the display list. If GL_SMOOTH is the current shader model, then the colors will be interpolated between the different vertices. If GL_FLAT is the current shader model, then each primitive (triangle or quad in this case) will be draw in a single color. The color of the last vertex in the primitive is used to fill the entire primitive. You can see this for yourself by pressing the ‘f’ key to switch to flat shading model and the ‘s’ key will switch to smooth shading model.
The following image shows how this scene should look.
Adding some Spin
Well, even looking at colored primitives is not that exciting. How about adding some animation? In the next scene we will draw the same three primitives, but this time we will add a bit of spin.
void RenderScene3()
{
glMatrixMode( GL_MODELVIEW ); // Switch to modelview matrix mode
glLoadIdentity(); // Load the identity matrix
glTranslatef( -1.5f, 1.0f, -6.0f ); // Translate back and to the left
glPushMatrix(); // Push the current transformation onto the matrix stack
glRotatef( g_fRotate1, 0.0f, 0.0f, 1.0f );
// Draw a triangle with different colors on each vertex
glBegin( GL_TRIANGLES );
glColor3f( 1.0f, 0.0f, 0.0f ); // Red
glVertex2f( 0.0f, 1.0f ); // Top-Center
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex2f( -1.0f, -1.0f ); // Bottom-Left
glColor3f( 0.0f, 0.0f, 1.0f ); // Blue
glVertex2f( 1.0f, -1.0f ); // Bottom-Right
glEnd();
glPopMatrix();
glTranslatef( 3.0f, 0.0f, 0.0f ); // Translate right
glPushMatrix();
glRotatef( g_fRotate2, 0.0f, 0.0f, 1.0f );
// Draw a rectangle with different colors on each vertex
glBegin( GL_QUADS );
glColor3f( 1.0f, 0.0f, 0.0f ); // Red
glVertex2f( -1.0f, 1.0f ); // Top-Left
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex2f( 1.0f, 1.0f ); // Top-Right
glColor3f( 0.0f, 0.0f, 1.0f ); // Blue
glVertex2f( 1.0f, -1.0f ); // Bottom-Right
glColor3f( 1.0f, 1.0f, 1.0f ); // White
glVertex2f( -1.0f, -1.0f ); // Bottom-Left
glEnd();
glPopMatrix();
glTranslatef( -1.5f, -3.0f, 0.0f ); // Back to center and lower screen
glPushMatrix();
glRotatef( g_fRotate3, 0.0f, 0.0f, 1.0f );
// Draw a circle with blended red/blue vertecies
const float step = M_PI / 16;
const float radius = 1.0f;
glBegin( GL_TRIANGLE_FAN );
glColor3f( 1.0f, 1.0f, 1.0f );
glVertex2f(0.0f, 0.0f);
for ( float angle = 0.0f; angle < ( 2.0f * M_PI ); angle += step )
{
float fSin = sinf(angle);
float fCos = cosf(angle);
glColor3f( ( fCos + 1.0f ) * 0.5f, ( fSin + 1.0f ) * 0.5f , 0.0f);
glVertex2f( radius * fSin, radius * fCos );
}
glColor3f( 1.0f, 0.5f, 0.0f );
glVertex2f( 0.0f, radius ); // One more vertex to close the circle
glEnd();
glPopMatrix();
}
The only difference between this scene and the 2nd is the addition of the glPushMatrix() followed by the glRotatef(angle, x, y, z) shown in the highlighted lines above.
The glPushMatrix function pushes the state of the current matrix onto the matrix stack. Since we don’t want to bother calculated the inverse for the rotation matrix (to undo the rotation), we simply “push-on” the current matrix state and after we finished rendering our primitive, we simply “pop-off” the rotated matrix resulting in the unrotated matrix. Be careful to pair each occurrence of a glPushMatrix with a matching glPopMatrix.
The glRotatef will add a rotation to the current matrix. This method takes three parameters:
- angle: the angle of rotation in degrees.
- x, y, z: the x, y, and z coordinates of a vector about which the rotation will occur.
In this example, we simply rotate around the z-axis since we are looking directly down the z-axis, this will be the most obvious rotation to apply.
The image below shows the result of rotating the primitives.
Enter the 3rd Dimension
Up until this point we have only been using 2D vertices but this isn’t really taking advantage of the amazing power of the rendering hardware. In the next scene we will add 3D primitives. Instead of a triangle, we’ll render a pyramid. Instead of a a rectangle, we’ll render a cube. And instead of a circle, we’ll render a sphere.
void RenderScene4()
{
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glEnable( GL_DEPTH_TEST );
glTranslatef( -1.5f, 1.0f, -6.0f ); // Translate back and to the left
glPushMatrix(); // Push the current modelview matrix on the matrix stack
glRotatef(g_fRotate1, 1.0f, 1.0f, 1.0f ); // Rotate on all 3 axis
glBegin( GL_TRIANGLES ); // Draw a pyramid
glColor3f( 1.0f, 0.0f, 0.0f ); // Red
glVertex3f( 0.0f, 1.0f, 0.0f ); // Top of front face
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex3f( -1.0f, -1.0f, 1.0f ); // Left of front face
glColor3f( 0.0f, 0.0f, 1.0f ); // Blue
glVertex3f( 1.0f, -1.0f, 1.0f ); // Right of front face
glColor3f( 1.0f, 0.0f, 0.0f ); // Red
glVertex3f( 0.0f, 1.0f, 0.0f ); // Top of right face
glColor3f( 0.0f, 0.0f, 1.0f ); // Blue
glVertex3f( 1.0f, -1.0f, 1.0f ); // Left of right face
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex3f( 1.0f, -1.0f, -1.0f ); // Right of right face
glColor3f( 1.0f, 0.0f, 0.0f ); // Red
glVertex3f( 0.0f, 1.0f, 0.0f ); // Top of back face
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex3f( 1.0f, -1.0f, -1.0f ); // Left of back face
glColor3f( 0.0f, 0.0f, 1.0f ); // Blue
glVertex3f( -1.0f, -1.0f, -1.0f ); // Right of back face
glColor3f( 1.0f, 0.0f, 0.0f ); // Red
glVertex3f( 0.0f, 1.0f, 0.0f ); // Top of left face
glColor3f( 0.0f, 0.0f, 1.0f ); // Blue
glVertex3f( -1.0f, -1.0f, -1.0f ); // Left of left face
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex3f( -1.0f, -1.0f, 1.0f ); // Right of left face
glEnd();
// Render a quad for the bottom of our pyramid
glBegin( GL_QUADS );
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex3f( -1.0f, -1.0f, 1.0f ); // Left/right of front/left face
glColor3f( 0.0f, 0.0f, 1.0f ); // Blue
glVertex3f( 1.0f, -1.0f, 1.0f ); // Right/left of front/right face
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex3f( 1.0f, -1.0f, -1.0f ); // Right/left of right/back face
glColor3f( 0.0f, 0.0f, 1.0f ); // Blue
glVertex3f( -1.0f, -1.0f, -1.0f ); // Left/right of right/back face
glEnd();
glPopMatrix();
glTranslatef( 3.0f, 0.0f, 0.0f ); // Translate right
glPushMatrix(); // Push the current modelview matrix on the matrix stack
glRotatef( g_fRotate2, 1.0f, 1.0f, 1.0f ); // Rotate the primitive on all 3 axis
glBegin( GL_QUADS );
// Top face
glColor3f( 0.0f, 1.0f, 0.0f ); // Green
glVertex3f( 1.0f, 1.0f, -1.0f ); // Top-right of top face
glVertex3f( -1.0f, 1.0f, -1.0f ); // Top-left of top face
glVertex3f( -1.0f, 1.0f, 1.0f ); // Bottom-left of top face
glVertex3f( 1.0f, 1.0f, 1.0f ); // Bottom-right of top face
// Bottom face
glColor3f( 1.0f, 0.5f, 0.0f ); // Orange
glVertex3f( 1.0f, -1.0f, -1.0f ); // Top-right of bottom face
glVertex3f( -1.0f, -1.0f, -1.0f ); // Top-left of bottom face
glVertex3f( -1.0f, -1.0f, 1.0f ); // Bottom-left of bottom face
glVertex3f( 1.0f, -1.0f, 1.0f ); // Bottom-right of bottom face
// Front face
glColor3f( 1.0f, 0.0f, 0.0f ); // Red
glVertex3f( 1.0f, 1.0f, 1.0f ); // Top-Right of front face
glVertex3f( -1.0f, 1.0f, 1.0f ); // Top-left of front face
glVertex3f( -1.0f, -1.0f, 1.0f ); // Bottom-left of front face
glVertex3f( 1.0f, -1.0f, 1.0f ); // Bottom-right of front face
// Back face
glColor3f( 1.0f, 1.0f, 0.0f ); // Yellow
glVertex3f( 1.0f, -1.0f, -1.0f ); // Bottom-Left of back face
glVertex3f( -1.0f, -1.0f, -1.0f ); // Bottom-Right of back face
glVertex3f( -1.0f, 1.0f, -1.0f ); // Top-Right of back face
glVertex3f( 1.0f, 1.0f, -1.0f ); // Top-Left of back face
// Left face
glColor3f( 0.0f, 0.0f, 1.0f); // Blue
glVertex3f( -1.0f, 1.0f, 1.0f); // Top-Right of left face
glVertex3f( -1.0f, 1.0f, -1.0f); // Top-Left of left face
glVertex3f( -1.0f, -1.0f, -1.0f); // Bottom-Left of left face
glVertex3f( -1.0f, -1.0f, 1.0f); // Bottom-Right of left face
// Right face
glColor3f( 1.0f, 0.0f, 1.0f); // Violet
glVertex3f( 1.0f, 1.0f, 1.0f); // Top-Right of left face
glVertex3f( 1.0f, 1.0f, -1.0f); // Top-Left of left face
glVertex3f( 1.0f, -1.0f, -1.0f); // Bottom-Left of left face
glVertex3f( 1.0f, -1.0f, 1.0f); // Bottom-Right of left face
glEnd();
glPopMatrix();
glTranslatef( -1.5f, -3.0f, 0.0f ); // Back to center and lower screen
glPushMatrix();
glRotatef( g_fRotate3, 1.0f, 1.0f, 1.0f );
glColor3f( 1.0f, 1.0f, 0.0f ); // Yellow
glutSolidSphere( 1.0f, 16, 16 ); // Use GLUT to draw a solid sphere
glScalef( 1.01f, 1.01f, 1.01f );
glColor3f( 1.0f, 0.0f, 0.0f ); // Red
glutWireSphere( 1.0f, 16, 16 ); // Use GLUT to draw a wireframe sphere
glPopMatrix();
}
The first thing you’ll probably notice is this is the longest render method thus far. Drawing 3D primitives requires plenty of vertices (less if we use vertex buffers because then we can reuse vertices that appear at the same location).
There really isn’t anything new being show here except for the fact that we are plotting vertices with glVertex3f instead of glVertex2f. You may also notice that I’m using a built-in method provided by GLUT to draw the sphere (on line 579). I’m just too lazy to plot all the points for the sphere. However using this function means I can only render the sphere with a single solid color. Because it is difficult to see the rotation of a solid sphere, I added the wireframe sphere that is drawn just above the solid sphere using the glutWireSphere method on line 581.
If everything is correct, you should see something similar to what is shown below.
Keep in mind this is only a very brief introduction to OpenGL. There are still many more things to learn such as, lighting, textures, meshes, animation, and the programmable pipeline.
Video Lecture
I’ve created a video lecture for this article. This video is best viewed at 1080p but it should also be perfectly viewable at 720p. It may be difficult to read the text at lower resolutions.
In this video I am using Visual Studio 2012 to setup an OpenGL template that can be used to create other OpenGL demos later.
There is a known issue with Camtasia Studio that prevents pop-up windows from being captured. I didn’t realize this was happening until after I recorded the entire tutorial. There are a few times when I right-click on the project in Visual Studio to bring up the pop-up menu and select “Properties” to display the project’s properties. This however wasn’t captured by Camtasia Studio!
References
![]() OpenGL Reference Manual OpenGL Architecture Review Board, Dave Shreiner. OpenGL(R) Reference Manual: The Official Reference Document to OpenGL, Version 1.4 (4th Edition)
|
![]() OpenGL Programming Guide OpenGL Architecture Review Board, Dave Shreiner, Mason Woo, Jackie Neider, Tom Davis. OpenGL(R) Programming Guide: The Official Guide to Learning OpenGL(R), Version 2 (5th Edition)
|
|
Luke Benstead, Dave Astle, Kevin Hawkins (2009). Beginning OpenGL: Game Programming Second Edition. Course Technology.
|
Download the Source
You can download the source code (including the Visual Studio 2008 project files):


















Alright, so that thing with the matrices I talked about in class. The thing is, the glPushMatrix, glPopMatrix, glTranslate and glRotate functions are considered deprecated in OpenGL 3.0 and above because they were never hardware accelerated to begin with: http://www.opengl.org/wiki/FAQ#glTranslate.2C_glRotate.2C_glScale
They have always been really slow. If you want speed (and 3.0 compliance) you will have to make your own matrix stack and upload these matrices to your shaders.
Fortunately, the GLM library is here to save the day (and your ass): http://glm.g-truc.net/
Thanks Quinten for the information. I havn’t used GLM before but it looks like a pretty good math library if you are using the OpenGL API. I think I will incorporate it into future lectures.
The link appears to be broken, google docs says the document can’t be found.
I double-checked the link.. It should be working. Anyone else having trouble with the google doc link?
I tried to send an e-mail to your account but apparently I can’t send e-mail to you. Perhaps you have to verify your gmail account before you can use it?
The links should be fixed now.