Lazy Foo' Productions

Starting out with OpenGL 2.1

Last Updated 6/21/08
When it comes to OpenGL, the hardest part is getting off the ground. This article was made as a study guide to help you get started.
If you're looking for a step by step tutorial on how to do everything in OpenGL with example programs, you're in the wrong place. I only have about 6 months of coding experience with OpenGL and to create code based tutorials would be irresponsible because I could pass on bad habits. Even if I knew everything about graphics programming it couldn't be covered all in one article. OpenGL is a much more powerful graphics API, so it is much more complex than SDL.

Then what good is this article? Well I may not know everything about OpenGL, but I do know where to start. Trying to start out with OpenGL is quite overwhelming. Most OpenGL resources out there tend not to be newbie friendly and bludgeon you with theory instead of teaching in newbie sized chunks. This article serves as a study guide to give you resources, recommended projects, and an overview on how to get your projects done. This article is designed to teach you how to do in OpenGL what you already know how to do in SDL with some extra stuff you couldn't do in SDL. If you've done my tutorials you've already done almost everything in this article only now you're going to do them in a much more powerful API.

Before I get started, I have to mention that this article assumes you've already read tutorial 36 which covers the basics of setting up an OpenGL window and drawing on the screen. If you haven't already, go read it. There's also three links you should have bookmarked: OpenGL 2.1 API reference, the OpenGL programming manual and of course the OpenGL website. The API reference is also known as the OpenGL Blue Book and the programming manual is known as the OpenGL Red Book both of which you can buy online. Now that you have those bookmarked it's time for your next OpenGL project. I also recommend you download and install the OpenGL Extensions Viewer. It shows you everything about your OpenGL driver.


Project 1: Loading and Displaying a Texture
In tutorial 36, you learned the basics of creating an OpenGL window and displaying a white quad. As your next project, I recommend trying to get this image on the screen:

To display an image in OpenGL you load an image as a texture, and then texture map it onto the quad. The way I recommend doing it is creating a Texture class with a load_image() function and a show() function. To create your load_image() function there's a few routes you can take.

You can use SDL and SDL_image to load the image into a SDL_Surface, and then use the pixels from the SDL_Surface to create the texture. There's an example on how to do this on NeHe's tutorials. Nehe provides a lot of demo programs you can play with. Just keep in mind that the code base was made back in 1998, and could have been maintained a little better.

Before you can use texture mapping make sure to enable 2D texturing using glEnable(). To load a texture with SDL_image, first you load the SDL_Surface, and then call glGenTextures() to create a texture ID. If you're wondering what an image ID is, it's just an integer. After generating an image ID you call glBindTexture() to use the texture ID and glTexImage2D() to send your pixels from the SDL_Surface and create the actual texture. Don't forget to free your surface after creating the texture.

Be advised, if you're using IMG_Load(), the pixel format isn't always going to be the same as the other image file formats you load. So when using IMG_Load() to load the images, use SDL_ConvertSurface() to convert your loaded surfaces to a specific format before using them to create the texture. To learn more about pixel formats, I recommend reading my article on pixels and going into the SDL documentation and reading up on the SDL_PixelFormat data type.

Another route you can take is using the Developer's Image Library or DevIL. If you can actually get it working, I recommend it because it handles a lot of the tedious stuff that you have to do yourself if you're loading textures with SDL_Surfaces. If you're working with the MinGW compiler, you have to do some voodoo on the lib files using reimp.exe from the MinGW utilities. I'll save you the trouble and just link you to the lib files I already made.

You'll have to call ilGenImages() to generate an image ID and then ilBindImage() to use the image ID. You call ilLoadImage() to load your image and ilConvertImage() to convert it to the format you want. Then you call glGenTextures() to create a texture name, call glBindTexture() to use the texture name and glTexImage2D() to send your pixels and create the actual texture. DevIL also has built in OpenGL functions, but I've never really used them. You can look them up in the DevIL documentation. There are other image loaders out there like Corona and SOIL, but I've never used them so I can't really tell you about them.

Now that you got the image loaded, it's time to map it onto the quad. There's an example on how to do this on NeHe's texture mapping tutorial. Pay particular attention again to the use of glBindTexture() to use the texture and glTexCoord() to map the texture. Also notice how texture coordinates work differently from vertex coordinates. 1.0 doesn't mean 1 pixel, it means 100% of the texture width/height. It's also a good idea to look up glGet() and how to get the maximum texture size.

Project 2: Padding and Clipping a Texture
For any of you without newer graphics cards who have tried already know that textures of only certain sizes work. There is a restriction that textures have to have dimensions of a power of two (256 x 256, 512 x 128, etc). As your next project I recommend you learn how to load and display non 2^n textures.

Now what do you do if you want to display a texture of non 2^n dimensions like this one:

What you can do is pad it with pixels so it has 2^n dimensions:

and simply clip off the excess pixels when you map the texture.

When using OpenGL rendering in SDL you can't blit SDL Surfaces to the screen, but you can use software surfaces to blit stuff in memory. This allows you to easily convert and pad your SDL surface. First load your surface using IMG_Load(). Then get the dimensions and find the next power of two. If the surface is 100 x 200, the power of two dimensions are going to be 128 x 256. If the surface is 260 x 128, the dimensions are going to be 512 x 128.

Once you've calculated the power of 2 dimensions create a blank SDL_Surface using SDL_CreateRGBSurface() with the power of 2 dimensions in the pixel format you want the texture pixels to be. Then blit the image with IMG_load() onto the blank buffer surface you created. Now your buffer surface has the image with padding and it's in the format you want. Lastly you use the pixels from the buffer surface in glTexImage2D() to create your padded texture. Don't forget to free your surfaces after you're done with them.

Doing padding in DevIL is easier. First load and convert your image as usual. Find the power of two dimensions, call iluImageParameter() to set the placement to be the upper left and then call iluEnlargeCanvas() to change the image size to the new power of 2 dimensions.

There are also built in OpenGL functions in DevIL which automate texture loading and padding. Look them up in the DevIL documentation.

Now that you got your padded texture ready to go, when you map your texture you want to cut the padding off. Remember, Texture coordinates are given to glTexCoord() as percentages. On a texture that has 75% of the width image and 25% padding, you only want map from 0.0 to 0.75. To calculate your texture offsets, simply divide the offset by the texture width or height. So on a 256 wide texture, 128 is sent as 0.5, and 64 is given as .25.

If you know how to clip off padding, it shouldn't be much harder to create a function to show part of a texture defined by an SDL_Rect. This way you can have multiple images on a spirte sheet and not waste so much memory on padding.

You can also use the GL_ARB_texture_non_power_of_two extension which pretty much gets rid of the power of two restriction on modern graphics cards. However, it's good to know how to do padding to support older hardware. We'll talk about extensions later.

Project 3: Pixel Manipulation
Next project I recommend is replacing the cyan background with a white one on this image:

In order to do that you're going to need to learn how to play with texture pixels. If you haven't already, you should read the article on pixels. Whether they're pixels in SDL or OpenGL, they're still pixels. I also recommend reading the pixels manipulation tutorial if you're more comfortable with SDL and haven't done pixel manipulation before. It serves as a good stepping stone.

First you need a lock() function for your Texture class that gets the texture pixels so you can manipulate the texture. In order to do that, you first need to create an array of GLuints (OpenGL unsigned integers) to store the pixel data in. You'll need texture width * texture height pixels.

To put the texture pixel data in the GLuint array you've made, you'll have to bind the texture and use glGetTexImage(). Now that you have your pixels, go through them so you can replace the cyan pixels with white ones. For those of you wanting to create get_pixel() and put_pixel() functions, remember to do your operations relative to texture width/height not image width/height. For those of you wanting to do cross platform programming with OpenGL, endianness is something you should look up.

Now that you've replaced the cyan pixels, it's time to unlock() your texture by sending your pixel data to the texture using glTexSubImage2D(). Don't forget to delete the pixels once you're done with them.

Project 4: Color keying and Alpha Blending
Next thing you'll want to learn is how to color key and alpha blend your texture. In the last project you did some pseudo color keying, and doing real color keying in your next project isn't much harder.

Remember to first enable blending by using glEnable(). After you've done this, set the blending using glBlendFunc(). glBlendFunc() accepts two arguments to determine how to calculate the source and destination blending. There's loads of different combinations, but only a few are useful. To do blending like it's done in SDL, you use the arguments GL_SRC_ALPHA and GL_ONE_MINUS_SRC_ALPHA. Now that you've got your blending properly set up, it's time to color key your texture.

Considering you've already created the lock() and unlock() functions you need to manipulate the texture pixels, creating your color_key() function for your Texture class is going to be cake. Simply lock() your texture, go through the pixels and when you find a pixel that matches the color key, set its alpha to 0. Once you've color keyed all the pixels, simply unlock() your texture.

Alpha blending your textured quad is also pretty easy. Just call glColor4f() before you draw your texture quad with R, G, and B set to 100%, and have A be the alpha transparency. glColor() is also the function you use to set your polygon's color.

Before I forget to mention it, I recommend looking up depth testing and how it can conflict with alpha blending.

Project 5: Rotation and Scaling a Textured quad.
Now that you can do most of what you can do with a SDL_Surface with a OpenGL texture, it's time to learn rotation and image resizing. You could do these with the SDL_gfx extension in SDL, but OpenGL supports them natively. I recommend that your next project be adding rotation and scaling abilities to your Texture class.

To rotate an image, simply call glRotate() before rendering your textured quad in your Texture class' show() function. glRotate() rotates around the point of translation, so I recommend that you change the way you draw your quad. In tutorial 36, we drew it from the top left corner like this:
I personally prefer to rotate the image around the center, so I draw it like this:
So instead of drawing the top left vertex at 0,0 you draw it at -imgWidth / 2, -imgHeight / 2, you draw the bottom right vertex at imgWidth / 2, imgHeight / 2, etc, etc.

To resize your image, there's two paths you can take. You can either change the image width and image height to the size you want to stretch it to when you draw the vertexes, or you can use glScale(). glScale() is recommended because it works for the other methods of drawing your geometry which we'll talk about next.

Also if you haven't already, you should look up how matricies work in OpenGL. glTranslate(), glRotate(), glScale(), and glLoadIdentity() all effect your matricies and you should understand how they work.

Project 6: Display Lists and Vertex Arrays
Up until now you've been drawing your textured quads by using glBegin()/glEnd(). This is called immediate mode and compared to other methods of drawing your geometry it is slow. Now would be a good time to learn better methods for drawing your geometry.

One method you can use is display lists which store commands in the GPU. First you'll want to generate a list ID by using glGenLists(). Now that you have your display list name, call glNewList() to start your display list and finish compiling it by calling glEndList(). All compatible commands called between glNewList() and glEndList() will be stored in your display list. To find out if your command can be compiled look it up in the OpenGL documentaton.

So if you make a display list that draws a textured quad using glBegin()/glEnd(), all you have to do is bind your texture like usual and use glCallList() to call your compiled display list to draw you textured quad. If you need an example you can check out Nehe's display list tutorial.

Another way to draw your geometry is with vertex arrays. In the simplest sense, vertex arrays are faster because they allow you send vertex data in in large arrays instead of one by one. To draw your textured quad using vertex arrays you'll want to enable texture coordinate arrays and vertex arrays using glEnableClientState(). Now that they're enabled, you'll want to send your texture coordinates using glTexCoordPointer(), and you're vertex data using glVertexPointer().

Now that the GPU has your vertex and texture coordinate data, you can call glDrawElements() or glDrawArrays() so the GPU can draw your quad using the data you gave it. glDrawElements() is the recommended because the benchmarks I've seen show it to be faster. When using glDrawElements() you'll need to send index data to tell which order to render the vertex and texture coordinate data in. Call glDisableClientState() once you're done with working you're texture coordinate/vertex data.

If you need an example of vertex arrays, there's one on Ozone 3D. It's part of a Vertex Buffer Objects article. It's right under the immediate mode example code. I also recommend looking up how structs are stored in memory, because it'll make your vertex arrays easier to manage. Sending a pointer to an array of 3 GLfloats is the same as sending a pointer to a struct having 3 GLfloat member variables.

The thing about display lists and vertex arrays is that they're rumored to be on their way out in OpenGL 3.0. What's generally considered the best method for rendering your geometry is Vertex Buffer Objects. Your knowledge of display lists and vertex arrays isn't entirely useless, since not all hardware supports VBOs and you might end up in a situation where you want to support older hardware. There are also cases where using VBOs is considered wasteful. I recommend researching how they work so you know when to use each method.

Project 7: Vertex Buffer Objects and Extensions
OpenGL extensions allow you to use non-core OpenGL features in your OpenGL application. For example if you want to handle non-power of two textures, you can check if the "GL_ARB_texture_non_power_of_two" extension is available on your GPU. If it is the whole 2^n dimension restriction is gone. However dealing with extensions yourself can be a pain.

Fortunately there are libraries like GLee and GLew that can handle most of the dirty extension stuff for you. They can see which extensions are available, retrieve the extension functions for you and can check which OpenGL version is available. Check the sites for documentation, binaries, tutorials, source codes, and everything else you need to know to use GLee and GLew.

If you're working under windows, GLee and GLew are must haves. If you haven't noticed, your standard OpenGL header is stuck at version 1.1 which was released back in '96 or 1.4 on Vista which was released in 2002. Microsoft isn't a fan of OpenGL, so it doesn't support it. Fortunately any decent GPU manufacturer provides their own OpenGL drivers. GLee and GLew can retrieve the OpenGL 1.1+ functionality for you on Windows so you can use the new OpenGL features.

Using GLee or GLew, you can check if your current GPU supports Vertex Buffer Objects. VBOs were initially introduced as the "ARB_vertex_buffer_object" extension so you can check if that extension is available or you can check if the OpenGL version is 1.5 or greater because that's when VBOs were promoted from extension to core. If you haven't used vertex arrays before I recommend you learn to use them now because my overview of how they work assumes you've worked with vertex arrays. Vertex Buffer Objects are like vertex arrays only instead of sending your data every time you draw, you have your vertex/texture coordinate/normal/etc data sit in the GPU's memory. As your next project, I recommed practicing how to draw with VBOs.

First you'll want to call glGenBuffers() to generate IDs for your vertex and texture coordinate buffers much like you would with a texture. Then you'll want to call glBindBuffer() to use your vertex buffer and use glBufferData() to send your vertex data to the buffer. Next do the same with the texture corrdinate data so you can have your texture coordinate data in its buffer.

Now that your texture and coordinate data is in the GPU, it's time to draw. Make sure you have the texture coordinate array and vertex array client states enabled like you would with vertex arrays. Bind your vertex buffer using glBindBuffer() and call glVertexPointer() only this time put NULL where you normally put a pointer to your vertex data. Now bind your buffer with your texture coordinates then call glTexCoordPointer(). glVertexPointer() and glTexCoordPointer() will use the data in the current bound buffer until you bind a new buffer or unbind the buffer by giving 0 as the buffer ID. Lastly call glDrawElements to draw your geometry.

The last two paragraphs assume you're using VBOs where OpenGL 1.5 or higher is supported. If you're using VBOs via extensions, the function and constant names are going to be different. glGenBuffers() will be glGenBuffersARB() glBindBuffer() will be glBindBufferARB(), glBufferData() is named glBufferDataARB(), and the constant GL_STATIC_DRAW is GL_STATIC_DRAW_ARB. Speaking of which, static VBOs are just one way to handle your VBOs. There's also Dynamic and Stream VBOs. I've only touched on the basics of VBOs and I recommend looking on what else they can do.

If you need some examples, there's a simple one I found at Dev Master. It uses the VBOs as loaded from an extension. There's also an article on Ozone 3d that covers a lot of theoretical stuff you need to know to use VBOs effectively.

When I say VBOs are the best way to render your geometry, I mean they're the best way to render large amounts of polygons. Using VBOs to render a single 4 vertex textured quad is wasteful because the cost of binding a vertex/texture coordinate buffer is greater than just sending your vertex arrays. You want to use VBOs when rendering large ammounts of polygons. For example, you can create a SpriteSheet class that inherits from your Texture class that has a VBO that hold all the vertex/texture coordinate data. You bind your buffers once and draw all the images in the sprite sheet as a batch. Look up how VBOs work so you know when to and not to use them.

Project 8: Bitmap Fonts
Now that you know how to render your 2D images in OpenGL the next thing you'll want to be able to do is render text. The easiest way to do this in OpenGL is via bitmap font.

First I recommend my reading SDL bitmap font tutorial. Bitmap fonts work almost the same no matter what graphics API you're using so if you understand how to make a Bitmap Font in SDL, making one in OpenGL will be no problem. The difference here is that you're working OpenGL textures instead of SDL surfaces. When I made my first BitmapFont class in OpenGL, I took the code from my SDL bitmap font class, replaced the SDL functions with the OpenGL equivalents, and it worked on the first compile.

One thing I did is that I had my Font class inherit from my Texture class. When you think about it, a Font is a texture with the specfic task of showing text. I found it made it easier to make the Font class when it had the Texture class as a base. This is just a suggestion on my part, it's up to you to design your Font class.

The only real significant difference between the SDL and OpenGL implementation will be the build_font() function. You should avoid using the slow immediate mode when possible in OpenGL. So in the the build_font() function when you define the rectangles for each character image you'll also want to build a set of VBOs, VAs, or DLs that holds all the character images. Then in your show_text() function you'll draw your text using your set of VBOs, VAs, or DLs.

Since you're using a more powerful graphics API, you probably want more features out of your bitmap font.

When you did your bitmap fonts in SDL, your text turned out like this:

with the edges being all jagged.

Most people want smoother edges like this:

Most people stick with the flat shaded text because it blits well onto other colors:

Where as the shaded bitmap fonts don't work so well:

Fortunately because of OpenGL's hardware accelerated alpha blending, you can have your smooth text. First when you create your bitmap font image, make it white on a black background. When you load your texture image you'll go through the pixels. From each pixel you'll want to get the brightness of the pixel so get the red component of the pixel. Since all the pixels are black, white or a shade of grey the R, G and B components are all the same so it doesn't really which component you get.

If you don't know how to get the individual color components, I cover it in my pixels article. I know I say to generally never do this, but since OpenGL has no built in mechanism to get a pixel's components it's ok to do it yourself.

Set the pixel alpha to be equal to the color component you have and set the R, G, and B colors of the pixel to be 255. Now since each font pixel blends proportially to how bright it was, it renders smoothly onto what you draw it on.

Now what if you want colored text? This is simple thanks to OpenGL color material. Just enable color material using glEnable() and before you draw your text call glColor4f() with the R, G, and B of the color you want the text to be. This is all assuming you already have the font blending set up like I mentioned in the previous paragraph.

If you want to build a bitmap font from an existing windows font check out the Bitmap Font Builder.

Project 9: True Type Fonts
When you need TTF fonts in SDL, you used SDL_ttf to load and render surfaces for you. You can do this in OpenGL by using Free Type, the library SDL_ttf uses to get pixels for your text images. Pixels are pixels, it doesn't really matter whether you get them from a PNG bitmap or a TTF font. The only thing that really changes is how you load your fonts. You should be able to add a TTF loader to your existing bitmap based fonting engine, and it should pretty much work the same with a few minor tweaks.

The first thing you'll need to do is install the Free Type development libraries. The best place to start is on Free Type website. *nix users should probably try to use their OS's built in package manager. If that doesn't work you'll have to compile the binary from the source. Instructions to do that shouldn't be to hard to find with some quick googling. Windows users can go to the Free Type for Windows page and download the complete package, developer files, and binaries so you can get started.

I got a lot of help for creating my TTF loader from Free Type tutorial on the Free Type website. The thing is the tutorial there is graphics API agnostic so they don't cover any OpenGL stuff. I found an article that's more OpenGL specific on DMedia if you want to check that out.

Now that you've installed the Free Type development libaries, it's time to create your load_ttf() function. Like with most libraries, you'll have to initialize FreeType before using it. You do this by calling FT_Init_FreeType(). FreeType is a little quirky in the sense that you when you initialize it you don't just call a function, you have to create a FT_Library object. Take this into account when you design your program because the FreeType API functions will need access to the FT_Library object.

After you create and initialize the FT_Library object you load a FT_Face object from your ttf font file using FT_New_Face(). Lazy Font is a font where Lazy Font Regular and Lazy Font Bold are face objects. Next you'll want to set your character size using FT_Set_Char_Size(). When you set the size, it's set in 1/64 of a point, not in pixels. You can find out the difference between points and pixels here. They explain it better than I can.

Now that you have your face object loaded you create a blank texture. The way I did is I added a blank_texture() function to my Texture class. It took the dimensions as arguments, created an array of GLuints width * height in size, set all the GLuints to 0, and used the array of GLuints as pixels to create the blank texture. After you created your blank image, lock() it so you can manipulate the pixels. Get a for loop going that goes through all the ASCII values you want in your font image. Now it's time to get the glyphs (individual character images) using FreeType.

In order to get a glyph so you can use it, you'll need the glyph index. Use FT_Get_Char_Index() to get the glyph index from an ASCII value. Next call FT_Load_Glyph() to load the glyph into the FT_GlyphSlot data member of your face object (myFaceObject->glyph). A FT_GlyphSlot is essentially a pointer to a glyph. Next call FT_Render_Glyph() on the glyph slot to render the glyph. FT_Render_Glyph() renders an image in FT_Bitmap data member of your glyph slot object (myGlyph->bitmap). Now you'll have to blit the glyph bitmap onto your texture.

Free Type is graphics API agnostic so it doesn't have a built in function to blit a FT_Bitmap onto an OpenGL texture. Fortunately it's not that hard to create a blitting routine because blitting is just copying pixels onto another set of pixels. Your blit_glyph() function will just copy the pixels one by one, row by row, at the offset you want it to. "myGlyph->bitmap.width" tells you how many pixels there are in a row, "myGlyph->bitmap.rows" will tell you how many rows of pixels there are and "glyph->bitmap.buffer" is the pointer to the actual pixels.

One thing you have to be careful about when copying glyph pixels onto the texture is that glyph pixels aren't 32bit RGBA pixels, they're 8 bit alpha pixels so you can't directly copy the bytes over. Fortunately converting a 8bit glyph pixel into the 32bit texture pixel is also easy. When you want to copy a glyph pixel onto the texture pixels just create a GLuint, set the R, G, and B to be 255 and set A (the last 8 bits of the pixel) to be equal to the glyph pixel. Then simply use your put_pixel() function to copy your GLuint pixel onto the texture.

At the end of your for loop move your next blit offset over to the next space on the texture. As you're blitting the glyphs onto the screen make sure to define the clipping rectangle as you go. Like with bitmap fonts, your font texture is like a sprite sheet, only this time the character images come from a ttf file. Make sure you don't blit off the texture and have it move to the next row when you have to as your for loop blits the character images onto the texture. Once your for loop is done blitting unlock() your texture and build your VBOs from the clip rectangles. Everything else about you fonting engine should work the same, though you may need to tweak the way you handle new lines and spaces.

If you got a good look at the Free Type documentation, you saw the API has tons of features I didn't cover. This method is just one approach you can take. I picked this method because it's pretty fast and allows you to reuse code. If you go even more into Free Type you can have even more powerful fonting engines.

Off you go.
Did you know binding a texture is one of the most expensive state changes in OpenGL? Did you know some pixel formats load into textures faster than others? Did you know some GPUs will claim not to support non power of two textures but can still create them?

This is why I didn't start a set of OpenGL tutorials. With SDL everything was done with software surfaces so there wasn't a lot going on. With OpenGL's hardware accelerated graphics, you need to know a lot of graphics programming theory. You can't learn theory with a step by step tutorial, and you certainly can't learn it by copying and pasting code (you know who you are).

When you go off to learn OpenGL, don't cheat yourself by trying to code as you go. Take time to learn the theory behind graphis programming so you can use it effectively. If I could teach you the theory I would, but now I have to further my study of graphics programming. Hopefully I've given you enough resources to get off the ground with OpenGL.

Good luck on your Game Dev'ing.
I encourage peer review of this article. If you have any suggestions, corrections or criticisms feel free to contact me.