Image

Lesson 01 - Creating an OpenGL Window

Modified: 2008/07/08 13:53 by AnTeevY - Categorized as: New Lessons
Welcome to the first in a new series of OpenGL tutorials. The old NeHe lessons have been around for a very long time. They have helped many people learn OpenGL (myself included) and are an excellent resource. However, time has passed and now the original lessons are outdated. In this new series we hope to fix some of the faults of the old lessons and bring them back up to date.

When writing the new tutorials we have several goals:
  • Write using a clean, simple and consistent coding standard
  • Write using modern C++
  • Remain totally cross-platform (that is OpenGL's strength after all)
  • Inspire people to submit their own lessons based on our basecode

To keep things focused on OpenGL and make the code portable, we have chosen to use SDL to manage the Windowing system, input and events. The lesson is meant to be read together with the code, so download the code for this lesson at the bottom of the page. If you need to setup a great compiler, take a look here: CodeBlocks. Now without further ado, let's get on with it ;)

Edit

The Basecode



The new basecode structure creates a clean divide between the code that creates the window, and the code the describes the lesson. This allows us to easily reuse the basecode in every lesson, and keep all lessons up-to-date with the latest code. It also allows you to port the lessons to a different basecode, perhaps a native Windows implementation that doesn't require SDL for example. Here is a diagram of how the basecode classes are structured:

Edit

The Window



Before we go any further, let's take a look at the Window class definition:


	class Window{
		private:
			int m_width;
			int m_height;
			int m_bpp;
			bool m_fullscreen;
			string m_title;
			
		public:
			Window();
			~Window();
			bool createWindow(int width, int height, int bpp, bool fullscreen, const string& title);
			void setSize(int width, int height);
			int getHeight();
			int getWidth();
	};

You'll notice our Window class has 4 public methods (excluding the constructor and destructor) which are our interface. What they do is fairly self explanatory but I will go through the createWindow method in depth, this is the interesting part of the tutorial after all ;)

The first thing we need to do is initialize SDL, specifically the video subsystem.


	bool Window::createWindow(int width, int height, int bpp, bool fullscreen, const string& title)
	{
		if( SDL_Init( SDL_INIT_VIDEO ) != 0 ) 
		{		
			return false;
		}

SDL_Init will return zero on success, if initialization failed for some reason, then we return false from the method to indicate that something went wrong.

Next, we store the required width and height of the window, and set some attributes for SDL:


		m_height=height;
		m_width=width;
		
		//all values are "at least"!	
		SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
		SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
		SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
		SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 5 );
		SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
		SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

// Set the title. SDL_WM_SetCaption(title.c_str(), title.c_str());

This basically tells SDL that we want our GL context to be double-buffered, and that we want at least a 16 bit depth buffer (for sorting objects by depth when rendering) and at least 5 bits per colour component per pixel. We then set the title caption of the window. We are using C++'s built in string class to store our title, but SDL wants an old C-style string (const char*) fortunately the string class can pretend to be a C-style string by using the c_str() method, so SDL is happy and we can more easily manipulate the title :)


		// Flags tell SDL about the type of window we are creating.
		int flags = SDL_OPENGL;
		if(fullscreen == true)
		{
			flags |= SDL_FULLSCREEN;
		}

These few lines set some options for the OpenGL window. We tell SDL that we want our window to support OpenGL (obviously), then we tell SDL we want a fullscreen window only if the fullscreen parameter is true.

Now we are ready for SDL to create the window, here is the magical function call that does that:


		// Create the window
		SDL_Surface * screen = SDL_SetVideoMode( width, height, bpp, flags );
		
		if(screen == 0)
		{
			return false;
		}

SDL_SetVideoMode will return a pointer to an SDL_Surface if the video mode was set successfully. If it failed then it will return a NULL pointer (which is zero). If screen is equal to zero we know window failed to be created so we return false to indicate that.

Now, so far we have covered enough to create an SDL/OpenGL window, however we noticed that when the window is created SDL does not send a message to the operating system indicating that the window was resized. We need SDL to send this message so we can set the correct aspect ratio in OpenGL. So what we do to fix this is create and send our own resize event:


		//SDL doesn't trigger off a ResizeEvent at startup, but as we need this for OpenGL, we do this ourself
		SDL_Event resizeEvent;
		resizeEvent.type=SDL_VIDEORESIZE;
		resizeEvent.resize.w=width;
		resizeEvent.resize.h=height;
		SDL_PushEvent(&resizeEvent);

SDL_PushEvent will add our resize event to the queue, which will then be processed with the other events by our program.

Finally we return true to indicate that creating a window was successful.


		return true;
	}

Edit

The Lesson



Everything that may need to be customized on a per-lesson basis has been put into the Lesson class. This includes the following:

  • The resize method - Some lessons will use different projection set ups than others
  • The processEvents method - Each lesson will process different events
  • The draw method - Obviously every lesson will draw different things
  • The init method - This is where resource loading happens, this will vary per lesson
  • The run method - It's possible that we might need to add other methods that hook into the main loop (timer updates etc.) putting this in the lesson class makes that easy

Here is the class declaration:


	class Lesson{
		protected:
			Window m_window;
			uint m_keys[SDLK_LAST];
		
			virtual void draw();
			virtual void resize(int x, int y);
			virtual bool processEvents();

public: Lesson(); virtual ~Lesson(); virtual bool init(); virtual void run(); };

All of the methods in the class are declared "virtual" this allows us to use the default behaviour, but also to override anything that we might need to in a lesson. Now, back to the tutorial...

OK, so far we have covered creating an OpenGL window using SDL, but that's not much good if you can't do anything with it. The next thing we need to look at is event processing. Whenever you click the mouse, press a key, resize the window etc. the operating system will notify SDL, which will then add an event to an event queue. Each event in the queue stores specific information depending on the type of event. For example, a resize event will store the new width and height of the window, a keydown event will store the key that was pressed.

In our application we need to process these events, and react accordingly. Our program by default will do the following:
  • Listen for resize events and notify OpenGL when this happens
  • Listen for key press events and update an internal array of keys that keep track of which are keys are pressed down, and which aren't.
  • Listen for the quit event so that we can exit if someone attempts to close the window
  • Handle the specific case of the escape key being pressed, if it is, we will exit

Each frame we need to process the whole queue of events, we do this in the processEvents method which is inside the lesson class:


	bool Lesson::processEvents()
	{

SDL_Event event; while (SDL_PollEvent(&event))//get all events {

As you can see, we create a temporary variable for holding an event, and then in a loop we grab the next event in the queue until there are none left (SDL_PollEvent returns zero if there are no events left)


			switch (event.type)
			{
				// Quit event
				case SDL_QUIT:
				{
					// Return false because we are quitting.
					return false;
				}

Inside the loop we check what type of event the current event is. In the above code we return "false" if the event is a quit event. The program will end the main loop if processEvents returns false for any reason.


				case SDL_KEYDOWN:
				{
					SDLKey sym = event.key.keysym.sym;

if(sym == SDLK_ESCAPE) //Quit if escape was pressed { return false; } m_keys[sym] = 1; break; }

Next we check for a "keydown" event. We receive events every time a key is pressed, and then again when it is released. In the above code we first get which key was pressed and store it in the "sym" variable. Then if sym is the escape key we return false, which then ends the program. If it was any other key that was pressed, we update the internal key array and set it's value to 1 to indicate that the key is currently being pressed down.


				case SDL_KEYUP:
				{
					SDLKey sym = event.key.keysym.sym;
					m_keys[sym] = 0;
					break;
				}


Here we update the key array to indicate that a key has been released, the code is pretty much the same as above, but we set to zero instead of one.


				case SDL_VIDEORESIZE:
				{
					//the window has been resized so we need to set up our viewport and projection according to the new size
					resize(event.resize.w, event.resize.h);
					break;
				}

// Default case default: { break; } }

Finally we handle window resize events. When a resize event occurs we call the lesson's resize method with the new window size, this will then notify OpenGL.

Edit

Lesson 1



Now we have described the basecode in detail, onto the actual code for this lesson. It's dead simple, all we do is create a window and then each frame clear the screen. First of all here's our lesson 1 baseclass:


class Lesson01 : public NeHe::Lesson{
	private:
		virtual void draw();
	public:
		Lesson01();
		virtual bool init();
};


We override the draw and init methods, but will rely on the majority of code from the base class.

Now, init is called just before we enter the main loop, and the first thing we need to do is create a window to our own specification, here is the code to do that:


bool Lesson01::init()
{
	if (!m_window.createWindow(800, 600, 32, false, "Ne(w)He Lesson 1 - The first OpenGL window")) {
		return false;
	}
	
	glClearColor(0.0, 0.0, 0.0, 0.0);
	
	return true;
}

See, piece of cake. We call the create window function setting a resolution of 800x600, a 32 bit colour depth. We don't want fullscreen so we pass in false there, and then we set our funky window caption.

The rendering code is even simpler:


void Lesson01::draw()
{
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
}

Each frame we clear the colour buffer and the depth buffer. The colour buffer is a buffer storing the colour of every pixel on the screen, calling glClear on this buffer will clear the screen to the colour specified by glClearColor which as you can see in the init method is black. The depth buffer stores the depth of each pixel on the screen, this is used to determine whether pixels being rendered are in front, or behind a previously rendered pixel. Clearing this resets the buffer ready for rendering.

Finally glLoadIdentity() resets the modelview matrix (basically resetting the camera back to the centre of the OpenGL world (0, 0, 0) ).

That's it for this lesson, the code is linked below. If you are having trouble following the C++ code, try taking a look at the Python port. The syntax is more easily readable, but the code logic is the same.

Luke @ Nehe Team

Currently rated 2301821 by 275 people

  • Currently 2301821/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Edit

Downloads

Windows
Linux
Cross-platform

ScrewTurn Wiki version 2.0.27. Some of the icons created by FamFamFam.