In this tutorial we will build an OpenGL program to use as a basis for the rest of our projects. This initially small program will evolve into a complete basis for game development.
So let's start with the basics.
Our project basis is currently Windows and Visual C++ 2008 Express. You can download the whole Visual Studio 2008 Express from Microsoft for free. I prefer it because it assists in the management of the project. You do not have to learn how to build makefiles and so you can focus in the real job and has no problems operating under Windows. However you can use any platform you feel comfortable.
To begin with we create a Win32 project and a Windows application with Visual C++. Click Ok and then compile and run the result application.
Now that we have a running program we start modifying it to make it simpler. After we get rid of the unwanted stuff we add the OpenGL stuff. Actually in order to make an OpenGL program the steps we have to take are a few and simple.
First we have to create the main window which will have a special client area capable of accepting OpenGL rendering. This is done in the 'create_GL_window' function. It replaces the traditional CreateWindow function of the Windows API in our InitInstance function and hides all the details. We have to take a look at it to see what is happening.
The first thing we do is create the window as we would in any windows application
HWND create_GL_window (int wid, int hei, int bitsPerPixel, const char* title, HINSTANCE hInstance, const char* classname, int stencilBuffer) { DWORD windowStyle = WS_OVERLAPPEDWINDOW; // define our window style DWORD windowExtendedStyle = WS_EX_APPWINDOW; // define the window's extended style int width = wid; int height = hei; RECT windowRect = {0, 0, width, height}; // define our window coordinates // adjust window, account for window borders AdjustWindowRectEx (&windowRect, windowStyle, 0, windowExtendedStyle); // create the opengl window hWnd = CreateWindowEx (windowExtendedStyle, // extended style classname, // class name title, // window title windowStyle, // window style 0, 0, // window x, y position windowRect.right - windowRect.left, // window width windowRect.bottom - windowRect.top, // window height HWND_DESKTOP, // desktop is window's parent 0, // no menu hInstance, // pass the window instance 0); if (hWnd == 0) // was window creation a success? { return hWnd; // if not return false }
If everything is OK we proceed with the creation of the drawing surface within the window.
hDC = GetDC(hWnd); // grab a device context for this window if (hDC == 0) // did we get a device context? { // Failed DestroyWindow (hWnd); // destroy the window hWnd = 0; // zero the window handle return hWnd; // return false } PIXELFORMATDESCRIPTOR pfd = // pfd tells windows how we want things to be { sizeof (PIXELFORMATDESCRIPTOR), // size Of this pixel format descriptor 1, // version number PFD_DRAW_TO_WINDOW | // format must support window PFD_SUPPORT_OPENGL | // format must support opengl PFD_DOUBLEBUFFER, // must support double buffering PFD_TYPE_RGBA, // request an rgba format bitsPerPixel, // select our color depth 0, 0, 0, 0, 0, 0, // color bits ignored 0, // no alpha buffer 0, // shift bit ignored 0, // no accumulation buffer 0, 0, 0, 0, // accumulation bits ignored 16, // 16bit z-buffer (depth buffer) stencilBuffer, // stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main drawing layer 0, // reserved 0, 0, 0 // layer masks ignored }; GLuint PixelFormat = ChoosePixelFormat (hDC, &pfd); // find a compatible pixel format if (PixelFormat == 0) // did we find a compatible format? { // Failed ReleaseDC (hWnd, hDC); // release our device context hDC = 0; // zero the device context DestroyWindow (hWnd); // destroy the window hWnd = 0; // zero the window handle return hWnd; // return false } if (SetPixelFormat (hDC, PixelFormat, &pfd) == false)// try to set the pixel format { // Failed ReleaseDC (hWnd, hDC); // release our device context hDC = 0; // zero the device context DestroyWindow (hWnd); // destroy the window hWnd = 0; // zero the window handle return hWnd; // return false } hRC = wglCreateContext (hDC); // try to get a rendering context if (hRC == 0) // did we get a rendering context? { // Failed ReleaseDC (hWnd, hDC); // release our device context hDC = 0; // zero the device context DestroyWindow (hWnd); // destroy the window hWnd = 0; // zero the window handle return hWnd; // return false } // make the rendering context our current rendering context if (wglMakeCurrent (hDC, hRC) == false) // failed { wglDeleteContext (hRC); // delete the rendering context hRC = 0; // zero the rendering context ReleaseDC (hWnd, hDC); // release our device context hDC = 0; // zero the device context DestroyWindow (hWnd); // destroy the Window hWnd = 0; // zero the window handle return hWnd; // return false }
Things are looking good, show the window
ShowWindow (hWnd, SW_NORMAL); // make the window visible isMinimized = false; // set isMinimized to false resize_window (width, height); // reshape our gl window UpdateWindow(hWnd); return hWnd; // window creating was a success }
Now we are ready to enter the main loop of our game. In our case the loop has to cooperate with the OS. When the OS is not sending us any messages to process we simply loop with every frame.
// Main message loop: bool bLooping = true; while (bLooping) { // check for windows message and precess them if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE) != 0) { if (msg.message == WM_QUIT) bLooping = false; TranslateMessage(&msg); DispatchMessage(&msg); } else // no messages, just loop for next frame { if (isMinimized) // if window is minimized { WaitMessage (); // yeld back to system, do not waste processing power } else { frame_render(); // render scene SwapBuffers (hDC); // Swap Buffers (Double Buffering) } } }
The only thing we have to do upon exit is to clean up the OpenGL mess we created
destroy_GL_window();
In the code you will see two functions. First is the 'resize_window'. This is called every time the window size changes to update the scaling in OpenGL system. The other one is 'frame_render' which is called in every loop in order to draw our scene. You should notice that after the call to 'frame_render' in the main loop we call 'SwapBuffers'. This is the key to seamless animation. We actually have two drawing surfaces. One is displayed and we draw on the other. When we are done drawing we swap them and start drawing the next frame.