Ian Mallett

Welcome to the wonderful world of graphics!

This tutorial with try to be a gentle introduction to the world of graphics, using OpenGL 2 (with no extensions), SDL, and C++. Installation of these packages is not described, since there are plenty of descriptions already online, most of them work, and most are dependent on what development tools you're using. By contrast, I will try to be mostly compiler- and platform-agnostic, only describing the source file and what's goes into it--though a few platform-specific oddities are mentioned for completeness. This is supposed to be a tutorial, so pro coders may notice that many of the algorithms here are suboptimal and that there's a minimum of error checking. All of the concepts that follow apply when developing OpenGL applications with Python (and probably any other language too). Note that there is Python PyOpenGL basecode on the tutorials page.

You may wish to read this tutorial while keeping in mind the graphics pipeline tutorial (which is admittedly at a very detailed level).

In this tutorial, we'll make a basic OpenGL application that draws a few simple objects. I'll only hit the highlights, but you can find the full source at the bottom of the page.

First, we'll need a source file. This tutorial will have exactly one for simplicity. I called mine "main.cpp". I'm pretty sure C99 will work, if, for some spectacularly bizarre reason, you don't have a C++ compiler. C90 might even work with a bit of modification, too.

Next, you need some "#include"s. Specifically, we need to include SDL, OpenGL, and OpenGL's utility library "GLU":

#include <Windows.h> //Don't worry about this; it's needed to make GL.h work properly on Windows only (and then, only sometimes).

#include <GL/GL.h>
#include <GL/GLU.h>

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>

On Windows, you'll need to link against "opengl32.lib", "glu32.lib", "SDL.lib" and "SDLmain.lib". If you're feeling lazy and you're using MSVC, the following "#pragma"s will do the job. Otherwise add it to your linker's settings. On Linux, or other Unix, you should use CMake to find_package "OpenGL" and "SDL".

#pragma comment(lib,"opengl32.lib")
#pragma comment(lib,"glu32.lib")
#pragma comment(lib,"SDL.lib")
#pragma comment(lib,"SDLmain.lib")

Make your main declaration (the right way), initialize SDL, and make a window. The only tricky thing is the "32", which is the number of bits per pixel. Since OpenGL is doing the work, the value you put there probably doesn't matter much. The rest of the main function goes in the area I commented out in this listing. Once we're done with the program, we deinitialize SDL.

//Initialize everything, but don't catch fatal signals; give them to the OS.
SDL_Init(SDL_INIT_EVERYTHING|SDL_INIT_NOPARACHUTE);

//Creates the window
SDL_SetVideoMode(screen_size[0],screen_size[1], 32, SDL_OPENGL);

/* . . . */

//Clean up
SDL_Quit();

Jumping inside that snipped section in the above code . . .

At this point, we now have an OpenGL context. OpenGL contexts are required for OpenGL calls to work.

The first thing we do is change OpenGL's state to enable depth testing of the depth buffer. If you're not familiar with the depth buffering algorithm, you should see the graphics pipeline tutorial--but in short, the idea is that it allows objects to figure out which is closest to the camera and which therefore should be seen for each pixel.

//Objects need to test each other to see which one is in front.  If you don't do this, you'll "see through" things!
glEnable(GL_DEPTH_TEST);

The function for getting the user's input is pretty straightforward, so we'll now jump to the "draw(void)" function.

The first thing we do is clear the screen. We clear both the color (what you can see) and the depth buffer (an invisible quanitity at each pixel that tells OpenGL how far away everything is; this is necessary for depth testing to work properly).

//Clear the screen's color and depth (default color is black, but can change with glClearColor(...))
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

From here, we set up a viewport and OpenGL's projection matrix, in the manner the comments describe. Again, the pipeline tutorial is relevant, but suffice to say OpenGL's projection matrix (kinda) transforms objects from the world, where they're drawn, to the screen, where you can see them. One tricky thing is the "0.1f" and "100.0f" arguments to "gluPerspective(...)". Put simply, these are the nearest and farthest distances, respectively, that you'll be able to see anything--and, for technical reasons, they should be set as close to each other as is practical. Additionally, the near value should be as far from 0.0f as possible.

//Drawing to an area starting at the bottom left, screen_size[0] wide, and screen_size[1] high.
glViewport(0,0,screen_size[0],screen_size[1]);
//OpenGL is a state machine.  Tell it that future commands changing the matrix are to change OpenGL's projection matrix
glMatrixMode(GL_PROJECTION);
//Reset the projection matrix
glLoadIdentity();
//Multiply a perspective projection matrix into OpenGL's projection matrix
gluPerspective(45.0, (double)(screen_size[0])/(double)(screen_size[1]), 0.1f,100.0f);

Now, we need to set up the camera. This is done by multiplying OpenGL's modelview matrix by a transform that makes it look like you're in a camera at the given position. In reality, you're still at the origin; it's all the objects that get transformed to make it look like you moved!

//Tell it that future commands changing the matrix are to change the modelview matrix
glMatrixMode(GL_MODELVIEW);

//Reset the modelview matrix
glLoadIdentity();
//Multiply OpenGL's modelview matrix with a transform matrix that simulates a camera at (2,3,4) looking towards the location (0,0,0) with up defined to be (0,1,0)
gluLookAt(2.0,3.0,4.0, 0.0,0.0,0.0, 0.0,1.0,0.0);

From here, it draws a few objects and cycles the buffer, as described pretty clearly in the comments. One final note: you will need SDL.dll (for Windows, see your equivalent for other OSes) to run the program. This should have been provided with your SDL's distribution. Look there.

Anyway, the full source listing follows. Enjoy!

#include <Windows.h> //Don't worry about this; it's needed to make GL.h work properly on Windows only (and then, only sometimes).

#include <GL/GL.h>
#include <GL/GLU.h>

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>

#pragma comment(lib,"opengl32.lib")
#pragma comment(lib,"glu32.lib")
#pragma comment(lib,"SDL.lib")
#pragma comment(lib,"SDLmain.lib")

const int screen_size[2] = {800,600};

bool get_input(void) {
	SDL_Event event;
	while (SDL_PollEvent(&event)) {
		switch (event.type) {
			case SDL_QUIT: return false; //The little X in the window got pressed
			case SDL_KEYDOWN:
				if (event.key.keysym.sym==SDLK_ESCAPE) {
					return false;
				}
				break;
		}
	}
	return true;
}
void draw(void) {
	//Clear the screen's color and depth (default color is black, but can change with glClearColor(...))
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

	//Drawing to an area starting at the bottom left, screen_size[0] wide, and screen_size[1] high.
	glViewport(0,0,screen_size[0],screen_size[1]);
	//OpenGL is a state machine.  Tell it that future commands changing the matrix are to change OpenGL's projection matrix
	glMatrixMode(GL_PROJECTION);
	//Reset the projection matrix
	glLoadIdentity();
	//Multiply a perspective projection matrix into OpenGL's projection matrix
	gluPerspective(45.0, (double)(screen_size[0])/(double)(screen_size[1]), 0.1f,100.0f);
	//Tell it that future commands changing the matrix are to change the modelview matrix
	glMatrixMode(GL_MODELVIEW);

	//Reset the modelview matrix
	glLoadIdentity();
	//Multiply OpenGL's modelview matrix with a transform matrix that simulates a camera at (2,3,4) looking towards the location (0,0,0) with up defined to be (0,1,0)
	gluLookAt(2.0,3.0,4.0, 0.0,0.0,0.0, 0.0,1.0,0.0);

	//Begin drawing triangles.  Every subsequent triplet of vertices will be interpreted as a single triangle.
	//OpenGL's default color is white (1.0,1.0,1.0), so that's what color the triangle will be.
	glBegin(GL_TRIANGLES);
	//Three vertices follow, these will form a triangle
	glVertex3f( 0.0f, 0.1f, 0.0f); //Vertex at ( 0.0, 0.1, 0.0)
	glVertex3f(-0.1f,-0.1f, 0.7f); //Vertex at (-0.1,-0.1, 0.7)
	glVertex3f( 1.0f,-0.2f, 0.0f); //Vertex at ( 1.0,-0.2, 0.0)
	//Done drawing triangles
	glEnd();

	//Now we're going to draw some lines to show the cardinal axes.  Every subsequent pair of vertices
	//will be a single line.
	glBegin(GL_LINES);
	//All subsequent vertices will be red.
	glColor3f(1.0f,0.0f,0.0f);
	glVertex3f(0.0f,0.0f,0.0f); glVertex3f(1.0f,0.0,0.0);
	//All subsequent vertices will be green.
	glColor3f(0.0f,1.0f,0.0f);
	glVertex3f(0.0f,0.0f,0.0f); glVertex3f(0.0f,1.0,0.0);
	//All subsequent vertices will be blue.
	glColor3f(0.0f,0.0f,1.0f);
	glVertex3f(0.0f,0.0f,0.0f); glVertex3f(0.0f,0.0,1.0);
	//Since OpenGL thinks the color is blue now, all subsequent vertices will be blue.  But, we want the
	//triangle above to be white the *next* time we call this function!  So, reset the color to white.
	glColor3f(1.0f,1.0f,1.0f);
	glEnd();

	//OpenGL works best double-buffered.  SDL automatically sets that up for us.  This will draw what we have
	//just drawn to the screen so that we can see it.
	SDL_GL_SwapBuffers();
}
int main(int argc, char* argv[]) {
	//Initialize everything, but don't catch fatal signals; give them to the OS.
	SDL_Init(SDL_INIT_EVERYTHING|SDL_INIT_NOPARACHUTE);

	//Creates the window
	SDL_SetVideoMode(screen_size[0],screen_size[1], 32, SDL_OPENGL);

	//We now have an OpenGL context, and can call OpenGL functions.

	//Objects need to test each other to see which one is in front.  If you don't do this, you'll "see through" things!
	glEnable(GL_DEPTH_TEST);

	//Main application loop
	while (true) {
		if (!get_input()) break;
		draw();
	}

	//Normally you'd need to use SDL_FreeSurface, but the SDL_Surface* returned by
	//SDL_SetVideoMode is special, and so it is cleaned up for us.

	//Clean up
	SDL_Quit();

	//Return success; program exits
	return 0;
}


COMMENTS
Ian Mallett - Contact -
Donate
- 2014 - Creative Commons License