Dragons, Musik, Programming and Metal

Main menu

Post navigation

Game Dev Diary 5: About Textures and 2D

After we finished the control schemas last time, it’s time to look a bit deeper into OpenGL.

Since we want to be able to build a basic UI, we will take a look at drawing 2D in OpenGL today. Also we will have a look at texturing our scene and UI elements.

Using 2D Projection

OpenGl is a great tool, to present 3d worlds, as we know. But sometimes we want to simply present 2D content to the user, without worrying, where the camera is or how our scene is lit at a given moment.

We can enable OpenGL to draw 2D content directly to the screen, by using an orthographic projection mode.

Since we want to be able to do that for a specific portion of our drawing cycle, regardless of the specifics of the default projection mode and need to reset it to the default after we’re done, we can use OpenGL’s feature to push and pop matrices from an OpenGL defined stack.

Have a look at the code below, which should go into your OpenGLView’s drawing function, after the drawing of the 3D Objects and right before the call to glFlush():

First we set the matrix mode to GL_PROJECTION since that is the matrix we want to manipulate here.

Then we call glPushMatrix(), which stores the current state of the matrix to a stack and creates a copy as the active matrix, that we can manipulate safely.

The function call to glOrtho() sets OpenGL into an orthographic projection mode. Since we used the actual size of our view, to specify the bounds of the orthographic viewport, the coordinates used by OpenGL will match the pixel height and width of our view perfectly.

Then we switch back to the MODELVIEW matrix, to prepare for drawing, whatever we want to draw. We also disable depth testing, since we don’t need it in 2D (since we don’t have any depth) and there may even be cases, in which depth testing will make for wired results.

After drawing our 2D content, we need to revert all the OpenGL states to the default, which means, pop-ing the MODELVIEW matrix, as well as the PROJECTION matrix back to normal, setting the active matrix to be the MODELVIEW matrix again and re-enableling the depth testing.

Drawing to the 2D Pane

Let’s start using the 2D Context that we just created, to actually do something.

Here’s how you draw a little white square to the lower left of the view, that we can later add a texture to.

The only new thing to note here is that we use glVertex2f() to create the vertices. That function simply omits the Z-coordinate, since we don’t need it in the 2D scope. We could also have created the vertices, using the glVertex3f() function and setting the third parameter to zero, which would have had the same effect.

White is Boring. Let’s add some Texture

When we run the scene now, we notice a little white rectangle. Let’s make things a bit more interesting and add some texture to it.

As with many things, when OpenGL creates a texture, it let’s you keep a GLuint value as an id of the created texture. That’s why we first need to declare a field variable, to hold the texture id, we want to apply to the rectangle.

As you can see, we again bind the texture we want to use. Then, while we are drawing the vertices of our rectangle, we also set texture coordinates, which tell OpenGL, how we want our texture to be projected onto the geometry.

To map a texture to any geometry, OpenGL uses texture coordinates. OpenGL will use relative coordinates around the edges of your texture, where the lower left corner of your image is (0, 0) and the upper right corner is (1, 1).

You can imagine this, as if OpenGL would stick needles through the texture at the points specified as texture coordinates and fixates those at the next vertex you draw.

Enabeling the textures

The code above should work, and we should now be able, to see our texture being rendered onto the rectangle. But unfortunately it remains white.

That is, because we haven’t told OpenGL to use textures in the scene yet. To do that, we need to add the following code to our reshape function:

This tells OpenGL, to use textures and to blend then with the color values, using the alpha values of a texture.

Finally, a textured rectangle.

Textures on 3D objects

Wouldn’t it be cool, to have one of our two cubes textured as well? Let’s do that.

But before we get ready:

Housekeeping

Before we start diving into texturing our cube, we need to make a view simple refactorings, to be able to use the Geometry we already have defined in a textured environment, as well as in there current state.

Let’s have a look at the MyCube class and see how we can improve here.

The first thing you’ll note, is that currently all the drawing state is handled together with the vertex drawing in a single big and ugly drawing function. Let’s separate those two concerns for better readability.

Create a function called drawVertices that get’s called from within the normal draw function, so that the implementation will now look something like this:

The next thing we’d want to fix is the fact, that now we have to give the drawVertices function the scale parameter, so that it can represent size correctly. With OpenGL’s scaling functionality, we can fix that.

First we rewrite the drawVertices function, so that it will draw a default cube, regardless of scale:

Extra Credits: A Category for NSColor

One nice little detail we can also add, to make the coda a bit more readable and also tie it in a bit more with the Cocoa base classes, regards the way we set the OpenGL color attribute at the moment.

Since Cocoa comes with a pretty usable color class, we can create a category for that class, to be able to use it, when drawing to OpenGL.

Categories in Objective C allow you, to augment existing classes with new functionality, without subclassing them. That means, that we can add behavior to classes we do not need to own and use that new behavior on the base classes themselves.

This pattern is called a Mix-In and serves mainly two purposes:

It allows you, to keep your technical concerns separated in different source files, while still keeping the functional concern together on one single class.

It allows for a more elegant way, to write utility functions for basic types, that would only be possible with awkward wrapper objects otherwise.

We create a Category, the same way we create classes, by right-clicking in our project and choosing New File and then using the Objective C/Category type. The wizard will ask us about a name for our category and which class we want to augment.

Let’s choose “OpenGLEnabled” as the name and tell the wizard to augment the NSColor class.

After finishing the wizard, we get presented with a NSColor+OpenGLEnabled header and implementation file, which represents our new category.

Let’s add a function to that, which allows us to set any NSColor as the current OpenGL drawing color.

Now that looks a bit better now. There will be some additional refactoring in the later chapter, but for now, we’re good to go.

MyCube and MyRainbowCube

Since we want only the main cube, to use a texture and still want to have that rainbow color effect on the satellite, we should subclass our cube class, before we continue:

In the end, we want a basic cube, that draws itself in a single color, a rainbow cube that draws itself with the rainbow colors we know and a textured cube, that can take a texture and draw itself, using this texture.

Let’s first create a MyRainbowCube class, that extends the basic MyCube class and overwrites the drawVertices function with the (old) rainbow functionality.

Take note, that we created a named local variable for the texture image as well. That is necessary, because we want to overwrite the setter functionality to also reset the textureIndex for OpenGL. Therefore we can’t rely on the @synthesize functionality, to create our getter and setter.

A bit more on Texture mapping

The observant reader will have noticed, that we took a bit of an easy way out there, since we simply mapped the whole image to every side of the cube.

But what, if we want to map different sides of the cube, to different texture images?

That’s where texture mapping comes in. We still use one image for the whole cube, but now we will define only parts of that image, to be mapped to a single side.

To do this, we first need to create a texture map, that contains all the sides we want to be drawn to the cube. You can have that image’s layout anyway you want, but it is good, to not be wasting any space, to not load more than needed into the GPU’s memory.

As you can see on the right, I choose a layout, where all the sides of the cube are arranged in two rows and three columns, making it easy to tell the different texture coordinates apart, as the column stops will be at 0.0, 0.333, 0.666 and 1.0 as well as the row stops at 0.0, 0.5 and 1.0.

If we simply were to load that image for a texture, we would still get the whole image stretched across each side of the cube, so we need now to change the mapping of our texture coordinates.

To be a bit flexible, regarding the mapping, we give the MyTexturedCube class a variable of texture coordinates as an array of floats and rewrite the drawVertices function to set the texture coordinates from that array.

Since we don’t want to bother the user of our class, to write down 48 float values, to set the coordinates with each texture, we can create some class level functions to help him with that. One, that sets the coordinates for using the whole picture on each side (as it was before), and one, to use the 3 by 2 mapping.

Conclusion

Today, we learned, how to draw in OpenGL a 2D Perspective and added some textures to our scene. We learned, how OpenGL uses textures and how to map a texture to a 3D Geometry, using texture coordinates.

Next time, we will use some of that knowledge, to create a simple game menu, and learn how to draw text to OpenGL.

Also, since I moved my stuff to GitHub, you can download the demo code from there this time: Download the zip

3 thoughts on “Game Dev Diary 5: About Textures and 2D”

For some reason the sound is garbled? Also, if i want to change the raven.png to a different png file the picture becomes garbled. I really wish you’d continued this series, it’s one of the best i’ve seen, i’d really like to know about integrating menu’s and such.