Introduction to OpenGL for Game Programmers

OpenGL

OpenGL

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.

History

OpenGL was introduced by Silicon Graphics Inc in 1992 and was used in various fields that required complex visualization of 2D and 3D images such as CAD, virtual reality, flight simulators, and scientific visualization. It was also very obvious to enthusiasts that OpenGL could be used to render graphics in video games as well.
In an effort from Silicon Graphics Inc (SGI) to get more vendors to produce software that would run on their workstations, SGI together with Digital Equipment Corporation, IBM, Intel, and Microsoft formed the OpenGL Architecture Review Board (ARB).  On July 1, 1992 version 1.0 of the OpenGL specification was released.
In 2006 SGI transfered control of the OpenGL standard from the ARB to a new working group within The Khronos Group.
OpenGL is both free and platform-independent making it the most widely used, supported, and best documented 2D and 3D graphics API.
So enough boring history, lets make something!

Prerequisite

The only prerequisite for developing OpenGL applications using the core OpenGL API is that you have a supported development environment such as Microsoft’s Visual Studio 2008 and that you have supported drivers from your graphics card manufacturer.  Installing Visual Studio 2008 will provide you with the headers and libs that are required to build your OpenGL applications, and your graphics vendor will provide the OpenGL DLL that is used at runtime and implements the OpenGL functionality.

GLUT

Optionally, you can use the OpenGL Utility Toolkit (GLUT) if you want to be able to create code that is portable to other platforms with the least amount of changes to your source.  You can obtain the source code for GLUT from the OpenGL website, or you can download the pre-built windows binaries from Nate Robin’s GLUT for Win32 website.

GLEW or GLEE

If you plan on using extensions in your program, you can use either the OpenGL Extension Wrangler Library (GLEW), or The OpenGL Easy Extension Library (GLEE).

OpenGL Project

In this tutorial, I will show how to create a project in Visual Studio 2008 that can be used as a starting point for future OpenGL projects. I choose to use VS2008 because it is still a very widely used development environment and many samples and tutorials will still use VS2008 for their C++ projects.
We will start by creating a new project in Visual Studio 2008.
In Visual Studio 2008, create a new project by either going to the “File” menu and selecting “New” → “Project…” or use the short-cut “Ctrl-Shift+N”:
VS2008 Create New Project

Create New Project Menu

From the “New Project” dialog box that appears, select “Visual C++\General” from the “Project types:” list and select the “Empty Project” template in the “Templates:” frame on the right.  Choose any name you want for your project (I will use “OpenGL Template”) and choose a location to store your new project.
Create New Project Dialog

Create New Project Dialog

Once you click “OK” on the “New Project” dialog, you will be presented with an empty solution which has only one project.
Visual Studio 2008 New Solution

Visual Studio 2008 New Solution Window

Creating the Source

Create a new source file called “main.cpp” in the OpenGL Template project by right-clicking the project node in the “Solution Explorer” and select “Add”→”New Item…”
Visual Studio 2008 - Add New Item

Visual Studio 2008 - Add New Item

You will be presented with the “Add New Item” dialog box.  Select “Visual C++\Code” from the “Categories:” list and select the “C++ File (.cpp)” from the “Templates:” frame.  Choose “main” for the name of the file to create.  You can choose to have the new file created in the default location which is the same location as that of the project file, but I always like to keep my compiled source files in a folder called “src”.
Visual Studio 2008 - Add New Item Dialog

Visual Studio 2008 - Add New Item Dialog

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

We will first include a few headers into our main source code that we will be using in this example:
#define _USE_MATH_DEFINES
#include <math.h>
#include <iostream>
#include <ctime>
We will also use GLUT in this project, so we will add the GLUT header as well:
#include <GL/glut.h>
If we decided to use GLUT, or any other 3rd party library that isn’t magically part of Visual Studios search paths for headers and libraries, then we must tell Visual Studio explicitly where it can find the headers and libraries that are needed to compile and link our program.  If you would like your project to be portable, this usually means that the 3rd party headers and libraries need to be included together with the source for your project.
In the case of GLUT, we will copy the header to a directory relative to our project folder called “include\GL” and the lib file to a directory in our project called “lib”.  Then we need to tell Visual Studio where to find these headers and libs.
Right-click on the project node in the solution explorer and select “Properties” from the pop-up menu that appears.
Visual Studio 2008 - Project Properties

Visual Studio 2008 - Project Properties

In the “Property Pages” template that appears, select “Configuration Properties\C/C++\General” from the list.
Visual Studio 2008 - Project Property Dialog

Visual Studio 2008 - Project Property Dialog

Project properties are always described relative to the location of the project file itself.  So directories to our additional include folder and additional library directories should also be described relative to our include folder.
In the “Additional Include Directories” text field, add “include” or whatever name you used for the directory that contains your header files.
Next, we need to specify the folder where our compiled libraries are located.
Select the “Linker\General” from the list view and in the “Additional Library Directories” text field, add “lib” or whatever name you used for the directory that contains your compiled library files.
Visual Studio 2008 - Linker Properties (General)

Visual Studio 2008 - Linker Properties (General)

And finally, we need to specify exactly what libs we want to link in our program.
Select the “Linker\Input” option in the list and in the “Additional Dependencies” text field add the name of the 3rd party libs we want to link in our program. In the case of GLUT, we would specify “glut32.lib“.
Visual Studio 2008 - Linker Options (Input)

Visual Studio 2008 - Linker Options (Input)

We could actually argue that the inclusion of this lib as an additional dependency is superfluous because the library will be included automatically by the following code in the glut.h header file:
#   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 */
So as long as your linker can find these libraries in the “Additional Include Directories”, these libs will be automatically linked into your final project without needing to specify them in the “Additional Dependencies” property.
And the final step is to copy the pre-compiled DLL file (glut32.dll) into the same folder where our binary will be generated for our program.  We usually specify a folder relative to our project file called “bin” that will be used to store the compiled result of our program as well as 3rd party compiled DLL’s.  In order for our program to find and load these DLL’s, we must be sure the DLL’s are in the default search paths for executable files.  When we run our program in Visual Studio 2008, by default the working folder is the location of the project file.  This is usually undesirable  because then the runtime will not find the DLL’s in our bin folder because the bin folder is not in the default search paths.  To resolve this we will modify the default working folder that is used when we run our program.
In the configuration properties page, select the “Configuration Properties\Debugging”. In the “Working Directory” text field, we will specify the special macro “$(OutDir)” as the working directory when we debug our program using the Visual Studio debugger.  This will also guarantee that the location of our executable and the 3rd party DLLs we placed there will be found in the default search paths when the runtime is looking for DLL’s to load.
Visual Studio 2008 - Debugging Options

Visual Studio 2008 - Debugging Options

It should be noted that the options specified in the “Debugging” properties are not saved with the project’s settings in the project file, instead these options are stored in the project’s “user settings” file (the file that sits next to the project file that looks like “ProjectName.vcproj.COMPUTERNAME.username.user” (where “COMPUTERNAME” is the name you specified for your computer and “username” is your username on this computer).  This user file is not something you generally package with your project files and source files because it is specific to this user on this computer.  What you can do, is make a copy of this file and remove the “COMPUTERNAME.username” part from the filename which would result in a file with the name “ProjectName.vcproj.user” and this file will be used as the default user file for all new users who will work on your project (and for you the next time you want to build and run your program after a fresh install of your operating system for example).  Visual Studio will automatically generate a new file for the user that is specific to that user on that computer based on the file that is called “ProjectName.vcproj.user”.  Anytime you make changes to the default settings in the debugging options, you will need to copy and rename the file again.  If you want other users to get the changes you specified in the “.user” file, they need to delete their existing “.COMPUTERNAME.username.user” file and Visual Studio will regenerate their user settings file based on the default “.user” file the next time they open the project.

Now that we have our project setup with all our headers, libs and DLL’s we can continue programming!

Globals and Forward-Declarations

The first thing we will do is define a few structs that will be used as vector type objects for 2D and 3D space.
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
  • 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.

  • 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.

An example of using each primitive type is shown in the image below.

OpenGL Primitive Types

OpenGL Primitive Types

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.

The actual meaning of these units is dependent on the type of projection we are using. If you recall from the ReshapeGL method, we setup our projection matrix to be a perspective matrix using the gluPerspective method. This means that the units don’t necessarily refer to screen pixels, but instead to some arbitrary world units. The scale of the world is completely meaningless and we could choose to treat each unit as a meter, a foot, a yard, a centimeter, or a milimeter or any valid measurement of distance. Whatever unit you choose to use you should at least be consistent in your application with your choice of units and the artists should definitely be aware of this choice before they start modeling. Personally, I would recommend you treat one unit as a meter because this makes the most sense when you start working with physics packages that use meters and seconds and the basic units for distance and time.

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.

OpenGL Template - RenderSene1

OpenGL Template - RenderSene1

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.

OpenGL Template - RenderScene2

OpenGL Template - RenderScene2

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.

OpenGL Template - RenderScene3

OpenGL Template - RenderScene3

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 Reference Manual

OpenGL Programming Guide

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)
Beginning OpenGL Grame Programming

Beginning OpenGL Grame Programming

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

[OpenGL Template.zip]

6 thoughts on “Introduction to OpenGL for Game Programmers

  1. 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/

  2. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>