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

We will also use GLUT in this project, so we will add the GLUT header as well:

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

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, the default working folder is the location of the project file. This is usually undesirable because 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 will be found in the search paths.

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 of your computer and username is your username).  

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.

And a few global variables that will be used by GLUT to initialize the render window:

If an error occurs, we will use a global error code to report the error when the application exists.

And we will also forward-declare the OpenGL callback functions.

Since these are pretty important functions, I will take some time to explain their use.

DisplayGL(): This function will be registered as the render function that is invoked by GLUT when the current window needs to be redisplayed.

IdleGL(): 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.

These functions are 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.

Since we also want to rotate some of our primitives, we will store some global parameters that will keep track of the current rotation.

And we will use the clock_t type to store the number of ticks between frames.

Since our demo will render several different scenes, we will also declare some functions to render those scenes.

And the last function that will be forward-declared is the function that is responsible for cleaning up our resources.

The Main Method

The first function we will define is the “main” method. Here is where it all begins.

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

The InitGL Function

Setting up an OpenGL context and render window is very easy using GLUT as we will see in the next function.

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.

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

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.

In this method, I arbitrarily choose the keys that I wanted to be handled for this demo.

Keyboard keys 14 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 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.

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.

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 \(n^{th}\) to the \((n-2)^{th}\) 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.

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 (0.0, 0.0) 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.

The first thing we do in this method is to switch to modelview matrix mode. OpenGL supports three matrix stacks.

  • 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 resetting 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 millimeter 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 (3.0, 0.0, 0.0) but it is actually drawn at (1.5, 1.0, -6.0) 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.

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.

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 (0.0, 0.0, 1.0) 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.

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 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 Game 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]

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

  3. You are great people !!!
    Thank you for your explanation from ground up not like OpenGL Super Bible
    where is included they own framework and in order to draw simple triangles,
    beginner have to understand complicated framework first.
    In another tutorials claimed for beginners to draw triangles you have to include
    100 classes(because one class call another and so on).
    Great job thank you.
    Have nice time.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.