OpenGL Game Development by Example - Sample Chapter
OpenGL Game Development by Example - Sample Chapter
OpenGL Game Development by Example - Sample Chapter
$ 59.99 US
38.99 UK
P U B L I S H I N G
Robert Madsen
Stephen Madsen
ee
pl
C o m m u n i t y
E x p e r i e n c e
D i s t i l l e d
Sa
m
Robert Madsen
Stephen Madsen
games to his credit. He started programming in 1979, and he has been a programmer
for all of his professional life. He entered the game industry in 2004, and he founded
SynapticSwitch, LLC in 2010. As studio director, he continues to code while also
managing the broader needs of an independent game development studio.
Stephen Madsen completed his degree in game development from Full Sail Real
World Education in 2007, beginning his first job as a game programmer in 2008.
He then joined SynapticSwitch, LLC as the lead software engineer in 2012. He has
developed and published many titles on the mobile, console, and personal computer
platforms with OpenGL being the foundational rendering technology for most of
these platforms.
Preface
Welcome to OpenGL Game Development Blueprints! We are excited that you chose
this book as your guide to both OpenGL and game development. This section will
provide you with a brief preview of each chapter, followed by the technologies that
are required to complete the work that is presented in the book. Finally, we will
discuss the target audience for this book so that you will know whether this book is
right for you.
Preface
Chapter 6, Polishing the Silver, covers the topics that make a game presentable (but are
often overlooked by novice developers). You will learn how to implement a scoring
system, game over and game won scenarios, and simple level progression. This
chapter will conclude the 2D project of the book.
Chapter 7, Audio Adrenaline, guides you through implementing sound effects and
music in the game. We will provide links to some audio files that you can use in
your game.
Chapter 8, Expanding Your Horizons, will start the second project of the booka 3D
first-person space shooter. At the end of this chapter you will have created a new
project, starting the framework for a 3D game.
Chapter 9, Super Models, introduces you to the concepts of 3D art and modeling,
and then guides you through the process of loading 3D models into the game
environment. Although you will be able try your hand at creating a 3D model, the
resources that are required for the game will be provided online.
Chapter 10, Expanding Space, expands on many of the concepts that were covered in
the 2D segment of the book and applies them to a 3D world. Movement and collision
detection are revamped to take this new dimension into consideration. An input
scheme to move in 3D space is implemented. By the end of this chapter, you will be
able to control a 3D model in 3D space.
Chapter 11, Heads Up, guides you through creating a 2D user interface on top of the
3D world. You will create a menu system to start and end the game, as well as a
heads-up-display (HUD) that shows the score and stats in game. By the end of this
chapter, you will have created a playable 3D shooter game.
Chapter 12, Conquer the Universe, introduces you to some of the more advanced
concepts that were beyond the scope of the book, and it gives you some direction to
advance your skills.
Coordinate systems: The coordinate system is the reference that allows you
to position objects in the game
Primitives: Primitives are the fundamental building blocks of the images that
you see on screen, and OpenGL was designed to work with them
Textures: Textures are image files that are used to give the objects in your
game a realistic appearance
By the time you have read this chapter, you will understand how to use images to
build your game world and display it on the screen.
[ 19 ]
We haven't been completely honest with you. Since OpenGL is a 3D rendering engine, there
is actually one more axis called the Z-axis that we haven't discussed. As this part of the book
focuses on 2D game programming, we will ignore the Z axis for now.
[ 20 ]
Chapter 2
[ 21 ]
Once you have the project created, type following the code into the code window:
#include "stdafx.h"
#include <windows.h>
#include "glut.h"
void initGL() {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
void drawPoints()
{
glBegin(GL_POINTS);
glColor3f(1.0f, 1.0f, 1.0f);
glVertex2f(0.1f, -0.6f);
glVertex2f(0.7f, -0.6f);
glVertex2f(0.4f, -0.1f);
glEnd();
}
void update()
{
glClear(GL_COLOR_BUFFER_BIT);
drawPoints();
glFlush();
}
int _tmain(int argc, _TCHAR* argv[])
{
glutCreateWindow("GL Fun");
glutInitWindowSize(320, 320);
glutInitWindowPosition(50, 50);
glutDisplayFunc(update);
initGL();
glutMainLoop();
return 0;
}
[ 22 ]
Chapter 2
Header files
This code uses three header files:
stdafx.h: This header file loads the precompiled header that was created by
Visual Studio when we created the project
windows.h: This header file allows the window that renders the OpenGL
glut.h: This header file allows us to use the OpenGL Utility Toolkit, which
content to be created
You will need to download the GLUT files and place them in your
project folder. Download the files from http://user.xmission.
com/~nate/glut/glut-3.7.6-bin.zip. Open the zipped file
and copy glut.h, glut32.dll, and glut32.lib into the folder
that contains your source code. You may have to add glut.h to your
project (right-click on Header files | Add | Existing item).
Initializing OpenGL
You will notice a function called initGL. This function currently contains a single
line of code whose sole purpose is to set the background color of the screen at the
start of each frame. This is often referred to as the clear color because it is the default
that OpenGL clears the background to before it begins to render additional items:
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
The four numbers inside the parenthesis define the color, and the opacity of the color.
The first three numbers represent the amount of red, green, and blue (RGB) that will
be used to create the color. The fourth number represents the opacity (or seen another
way, the transparency) of the color. This is also referred to as the alpha channel
(RGBA). The values above create a black background that is 100 percent opaque.
All values in OpenGL have a range from 0 to 1. This means that there will be many
decimal values, known in C++ as floats. Thus, the range in C++ lingo is from 0.0f
to 1.0f.
[ 23 ]
C++ is different from many languages, which use integers or even hexadecimal
numbers to express their ranges. For example, many other languages use a range of
0 to 255 for each color component. In these cases, integer 0 corresponds to 0.0f, and
integer 255 corresponds to 1.0f.
To convert an integer of range 0 to 255 to OpenGL's system,
use the formula (1/255) * value, where value is the integer value
you are trying to convert. Thus, to convert the number 50, you
would calculate (1/255) * 50, which results in 0.1096.
initGL(): This simply calls the initGL function that we described earlier.
glutMainLoop(): This function starts the main game loop, which in turn will
call our update function every frame. This essentially starts our program,
which will run in an infinite loop until we close the program.
window. We have specified 320 pixels by 320 pixels. Feel free to try larger
(or smaller) window sizes.
window's upper-left corner in relation to the device's screen. In this case, the
window will start drawing 50 pixels from the left and 50 pixels from the top
of the screen. Feel free to try other positions.
talked about the game loop? The game loop is the part of the program that
runs over and over again (that is, every frame). We need to tell GLUT the
name of the function that we want to run every frame. In this case, we are
telling GLUT to use a function named update (described in the next section).
[ 24 ]
Chapter 2
return 0: This line is required by the _tmain function. It basically tells our
system that the program has exited and everything is okay. This line of code
won't run until we exit the program.
glFlush(): This function flushes the OpenGL buffer, including the back
buffer that currently holds our render. As a result, the rendering buffer is
flushed, and all of the contents are rendered to the device screen.
OpenGL uses two buffers to draw. One is the screen buffer,
which is what the player currently sees on the computer
display. The other is the back buffer, which is where we create
the objects that we intend to render in the next frame. Once we
are done creating the render in the back buffer, we quickly swap
the contents of the back buffer onto the current screen. This
occurs so quickly that the player cannot detect the swap.
color of the item that is going to be rendered. Remember, OpenGL uses the
RGB color system, so the color will be white (0, 0, 0 specified black).
[ 25 ]
unit from the center. The settings for the camera determine exactly how far
one unit from the center actually is on the screen. There are three glVertex
calls in our example code, one for each of the points that we want to render
to the screen.
The names of OpenGL functions give you a clue as to how to use
the function. For example, glVertex2f means that this function
takes 2 parameters and they will be of type float. In comparison,
the glVertex3f function takes three parameters of type float.
glEnd(): Just like all good things must come to an end, we have to tell
OpenGL when we are done rendering. That is the purpose of the call to glEnd.
You have probably noticed a lot of the use of the lower case letter f;
this stands for float, meaning that a number that may contain a part
after the decimal point (as opposed to an integer, which is always a
whole number). So, a number, such as 0.0f, is telling C++ to treat
the number zero as a floating point number. OpenGL uses a similar
naming convention for its functions. For example, the function
glVertex2f indicates that the function requires two floating point
numbers (in this case, the x and y coordinates of the point to render).
[ 26 ]
Chapter 2
You'll have to look at it closely, but if all went well, you should see three white
points in the lower-right area of the screen. Congratulations! You have rendered
your first OpenGL objects!
Hopefully, you have been able to follow the code. Think of _tmain as a manager that
controls the program by setting everything up and then calling the main loop (just like
we will do in our game). Then GLUT takes over and calls the update function every
frame. The update function initializes the render buffer, draws objects to the render
buffer, and then transfers the contents of the render buffer to the screen. In a game
running at 60 frames per second, this entire operation will happen 60 times a second!
[ 27 ]
Next, go to the update function and replace drawPoints with a call to drawLines.
The new update function will look like this:
void update()
{
glClear(GL_COLOR_BUFFER_BIT);
drawLines();
glFlush();
}
You will notice that there are four glVertex calls. Each pair of vertices sets the
beginning and ending points of a line. As there are four points defined, the result is
that two lines are drawn.
[ 28 ]
Chapter 2
Getting primitive
Basic objects, such as points and lines, are called primitives. It would be pretty
difficult to create everything out of points and lines, so OpenGL defines other
primitive shapes that you can use to create more complicated objects.
In this section, we will dig a little under the hood and find out how OpenGL actually
creates more realistic images on your screen. It may surprise you that a single,
geometric figure is used to create everything from the simplest to the most complex
graphics. So, roll up your sleeves and get ready to get a little greasy.
[ 29 ]
The basic unit that is used to draw all modern graphics is the humble triangle.
Graphic cards have been specifically engineered to be able to draw trianglesreally
small trianglesreally fast. A typical graphics card can draw millions of triangles
every second. Higher end cards reach billions of triangles per second.
Remember when we drew points and lines earlier? Each point had one vertex, and
each line had two vertices. Of course, each triangle has three vertices.
A primitive example
It's time to take a look at some code in action. Add the following code after the
drawLines function in the GLFun project:
void drawSolidTriangle()
{
glBegin(GL_TRIANGLES);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex2f(0.1f, -0.6f);
glVertex2f(0.7f, -0.6f);
glVertex2f(0.4f, -0.1f);
glEnd();
}
Then change the middle line of the update function to call drawSolidTriangle:
void update()
{
glClear(GL_COLOR_BUFFER_BIT);
drawSolidTriangle();
glFlush();
}
[ 30 ]
Chapter 2
Run the program, and you will see the following output:
You may notice a similarity between the code for drawSolidTriangle and
drawPoints. Look closely at the code, and you will see that the three glVertex
functions define the same three points. However, in this case we told OpenGL to
draw triangles, not points. You should also take a look at the code and make sure
you understand why the triangle is rendered blue.
Let's take one more example. Add the following code below the drawSolidTriangle
function:
void drawGradientTriangle()
{
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.3f, -0.4f);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex2f(0.9f, -0.4f);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex2f(0.6f, -0.9f);
glEnd();
}
[ 31 ]
You will immediately notice that this triangle is filled with a gradient instead of
a solid color. If you look closely at the code, you will see that a different color is
being set for each vertex. OpenGL then takes care of interpolating the colors
between each vertex.
[ 32 ]
Chapter 2
Building a model using a single triangle at a time would be very time consuming, so
3D graphics programs, such as Maya and Blender, allow you to create models out
more complex shapes (which are themselves built out of triangles). These models can
then be loaded into your game and rendered by OpenGL. OpenGL literally sends a
the list of points to form these triangles directly to the video card, which then creates
and image out of them on the screen. We will see this process in action when we
begin to deal with 3D game design.
Introducing textures
Images in games are called textures. Textures allow us to use real world images to
paint our world. Think about what it would take to create a dirt road. You could
either color the triangles in exactly the right way to make the overall scene look like
dirt, or you could apply an actual image (that is, a texture) of dirt to the triangles.
Which of these do you think would look more realistic?
When we want to get really tricky, we use textures to fill the inside of our triangles
instead of colors. A marble texture has been applied to the triangle in the preceding
image. You could imagine using this technique to create a marble floor.
[ 33 ]
Remember the car we were working with before? It didn't look much like a triangle,
did it? In fact, many real-world objects look more like rectangles than triangles:
It turns out that that all the textures that we use in games are actually rectangles.
Imagine that the car that we have been dealing with is actually embedded inside
an invisible rectangle, depicted in the following image as light gray:
Most graphic programs use a checkerboard background to indicate the areas of the
image that are transparent.
Using rectangles for all of our shapes solves one big problem that you might not have
thought of earlier. If you recall, it was very important to position the car at exactly
(5, 5). To do so, we decided to place the bottom-left corner of the car at point (5, 5).
[ 34 ]
Chapter 2
Looking at the car, it is actually a little difficult to figure out exactly where the
bottom-left corner would be. Is it the lower left corner of the bumper, the tire, or
somewhere else?
A matter of reference
When working with a texture, it is very important to know what point is being used
as a reference, usually known as the pivot point. In the following images, a black dot
is used to represent the pivot point. The pivot point affects two critical issues. First,
the pivot point determines exactly where the image will be placed on the screen.
Second, the pivot point is the point on which the image will pivot when rotated.
Compare the two scenarios depicted in the following images:
[ 35 ]
The pivot point for the car in the preceding image has been set to the bottom-left
corner of the image. The car has been rotated 90 degrees counter-clockwise.
The pivot point for the car in the preceding image has been set to the center of the
image. The car has been rotated 90 degrees counter-clockwise. Notice how the pivot
point affects not only how the car is rotated but also its final position in relation to its
original position after the rotation is completed.
Of course! You suddenly realize that two triangles can be fit together to form a
rectangle. In fact, this arrangement is so useful that we have given it a name: quad.
When it comes to 2D graphics, the quad is the king.
[ 36 ]
Chapter 2
As usual, change the middle line in update to call drawQuad. Run the program, and
you will get a pretty green square, er quad! It's important to note that the points
are defined in order starting from the upper-left corner and then moving counterclockwise in order.
[ 37 ]
Rendering a texture
Rendering a texture consist of two steps: loading the image and rendering the image
using an OpenGL primitive. Our final achievement in this chapter will be to modify
GLFun so that it will render a texture using a quad.
[ 38 ]
Chapter 2
if (imageSize == 0) imageSize = width*height * 3;
if (dataPos == 0) dataPos = 54;
data = new unsigned char[imageSize];
fread(data, 1, imageSize, file);
fclose(file);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
return true;
}
We are not going to dissect this piece of code line by line. In brief, it opens the image
file, extracts the first 54 bytes of the file (the bmp header data), and stores the rest
of the file as image data. A few OpenGL calls are made to assign this data to an
OpenGL texture and that's it.
You need to have a call that loads the texture in, so add this line of code to _tmain
just after the call to initGL:
loadTexture("car.bmp");
Of course, replace car.bmp with the file that you want to load in. Ensure that you
have placed the appropriate graphic files in the source code folder.
Texture wrapping
In order to display a texture on the screen, OpenGL maps the texture onto another
primitive. This process is known as texture wrapping. As textures are rectangular, it
makes sense to map the texture onto a quad.
[ 39 ]
The following image shows a texture the way that OpenGL sees it: a rectangle with
four texture coordinates:
In order to render a texture, we overlay it (or wrap it) onto a quad. So, let's say we
have the following quad defined:
[ 40 ]
Chapter 2
Maps to
Quad Coordinate
0, 0
0, 0
1, 0
1, 0
1, 0
1, 0
0, 1
0, 1
In its simplest form, texture wrapping is the process of mapping the corners of a
texture to the corners of a quad.
You will see texture wrapping also referred to as uv wrapping.
I always tried to figure out what uv meant! Here's the real story:
x and y were already used to refer to the quad coordinates, and
we had to have something else to call the texture coordinates, so
some bright person said, "Let's use u and v!"
[ 41 ]
0.0);
0.0);
0.5);
0.5);
Each call to glTexCoord2d defines a texture coordinate. The very next line of
code maps the texture coordinate to a quad coordinate. The order is essential:
first define a texture coordinate, then define the corresponding quad coordinate.
By the way, don't forget to replace the middle line of code in update with the
following line of code:
drawTexture();
[ 42 ]
Chapter 2
The triangles
The vertices
The texture
The quad
[ 43 ]
Summary
This chapter has covered the core concepts that are required to display images on
your screen. We started by discussing the OpenGL coordinate system for a 2D game.
The coordinate system allows you to place objects on the screen. This was followed
by a discussion about the camera, OpenGL's way of viewing objects that appear on
your screen.
Next, you learned how triangles and quads are used to create simple graphics, and
how textures can be applied to these primitives to render 2D images to the screen.
You could finally see an image on your screen that has been rendered by OpenGL.
As they say, a picture is worth a thousand lines of code!
In the next chapter, you will learn how to turn your still photography into moving
pictures through the wonder of animation!
[ 44 ]
www.PacktPub.com
Stay Connected: