The Basics
 
Last Update - 14/07/2005

Why OpenGL?

To quote the Official Website, "OpenGL is the premier environment for developing portable, interactive 2D and 3D graphics applications."
It is also surprisingly easy to get to grips with and produces good results without major headaches.
The cross-platform capabilities of OpenGL are probably it's greatest strength, there are other 3D API's around, but none that you can take between languages and platforms like OpenGL. DirectX is a great API for Windows (and the XBox), but you won't find it anywhere else.

Setting Up an OpenGL Window

There are many GL utility libraries which will set up your OpenGL environment and do a heap of work for you. GLFW is a good one that I may use in future examples. However, for this first venture into OpenGL, I am going to use the Windows API and Visual C++ 6.0, sticking to the 'Old Way' so that we can see what goes on behind the scenes.

First, start a new Win32 project, just a simple Windows app with no 'Hello World' junk will be fine.
We need to include the OpenGL headers, so add the following lines:


 #include <GL\gl.h>
 #include <GL\glu.h>

We also need to link in the libraries:


 #pragma comment(lib,"opengl32.lib");
 #pragma comment(lib,"glu32.lib");

The pragma line is an MSVC shortcut for linking a library, if you don't want to use it, (for portability or if you're not using MSVC) just make sure you've added opengl32.lib and glu32.lib to your linker settings.

Gl.h/opengl32.lib is the main opengl library, glu.h/glu32.lib is a utility library with some nice helper functions. We don't need it for this example, but it doesn't hurt to include it for future use.
So, now we have linked GL to our project, we'll get on with the coding.

Our main() function looks like this:


HINSTANCE gInst;
DEVMODE OldScrRes;
HDC   DC;  // The device context of our Window 
HGLRC RC;  // The rendering context for OpenGL 


int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
 {
  HWND hWnd;
  MSG msg;

  gInst=hInstance;

  hWnd=InitGL(800,600,32); // Start OpenGL at 800x600, 32bit colour

   if(hWnd==NULL)
    {
     MessageBox(NULL,"OpenGL Init Failed!","Error",MB_OK | MB_ICONERROR);    
     return 0;
    }

  // Non-blocking Message Pump
  do
   {
     if(PeekMessage(&msg,NULL,0,0,PM_REMOVE ))
      {
       TranslateMessage(&msg);
       DispatchMessage(&msg);
      }
     Render(); // Draw our stuff
   }while(msg.message!=WM_QUIT);

  StopGL(hWnd); // Shut down OpenGL

  return (msg.wParam);
 }

We've declared a couple of globals that we'll see later. The three functions that do the work here are: InitGL(), Render() and StopGL().
InitGL() Sets up the OpenGL environment.
Render() Draws our scene, in this case a single polygon.
StopGL() Closes down the program cleanly.

So let's look at InitGL().
First, we register a Window in the usual way:


HWND InitGL(int ScrX, int ScrY, int BPP)
 {
  WNDCLASSEX glwc;                  // Our Window class
  DEVMODE NewScrRes;                // Screen resolution structure
  HWND hWnd;                        // Handle for our Window
  PIXELFORMATDESCRIPTOR PixFmtDesc; // Describes a Window's graphical properties    

  // Define our window
  glwc.cbSize=sizeof(WNDCLASSEX); 
  glwc.style=CS_OWNDC; 
  glwc.lpfnWndProc=GLWinProc; 
  glwc.cbClsExtra=0; 
  glwc.cbWndExtra=0; 
  glwc.hInstance=gInst; 
  glwc.hIcon=LoadIcon(NULL, IDI_WINLOGO); 
  glwc.hCursor=LoadCursor(NULL, IDC_ARROW); 
  glwc.hbrBackground=NULL; 
  glwc.lpszMenuName=NULL; 
  glwc.lpszClassName="GLWindow"; 
  glwc.hIconSm=NULL; 

  // Register the Window class
   if(!RegisterClassEx(&glwc))
    return NULL;

Then change the screen mode to our target resolution and create a fullscreen window:


  // Store the current screenmode
  EnumDisplaySettings(NULL,ENUM_CURRENT_SETTINGS,&OldScrRes); // OldScrRes is a global. Lazy, I know.    

  // Change the screen mode
  memset(&NewScrRes,0,sizeof(NewScrRes));
  NewScrRes.dmSize=sizeof(NewScrRes);
  NewScrRes.dmPelsWidth =ScrX; // Width
  NewScrRes.dmPelsHeight=ScrY; // Height
  NewScrRes.dmBitsPerPel=BPP;  // Bits per pixel
  NewScrRes.dmFields=DM_PELSWIDTH  | DM_PELSHEIGHT | DM_BITSPERPEL;

   if(ChangeDisplaySettingsEx(NULL,&NewScrRes,NULL,CDS_FULLSCREEN,NULL)!=DISP_CHANGE_SUCCESSFUL)
    return NULL;

  // Create a Window
  hWnd=CreateWindowEx(WS_EX_APPWINDOW,"GLWindow",
                      "KGLWindow",WS_POPUP | WS_VISIBLE,
                      0,0,800,600,
                      NULL,NULL,gInst,0);

   if(!hWnd)
    return NULL;

Then we need to specify the pixel format that we want to use on this Window.


  // Define the pixel format for our Window
  PixFmtDesc.nSize=sizeof (PIXELFORMATDESCRIPTOR);
  PixFmtDesc.nVersion=1;
  PixFmtDesc.dwFlags= PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;    
  PixFmtDesc.iPixelType=PFD_TYPE_RGBA;  // 32bit colour display
  PixFmtDesc.cColorBits=32;             // 32bit colour depth
  PixFmtDesc.cRedBits=0;
  PixFmtDesc.cRedShift=0;
  PixFmtDesc.cGreenBits=0;
  PixFmtDesc.cGreenShift=0;
  PixFmtDesc.cBlueBits=0;
  PixFmtDesc.cBlueShift=0;
  PixFmtDesc.cAlphaBits=0;
  PixFmtDesc.cAlphaShift=0;
  PixFmtDesc.cAccumBits=0;     // No Accumulation buffer
  PixFmtDesc.cAccumRedBits=0;  
  PixFmtDesc.cAccumGreenBits=0;
  PixFmtDesc.cAccumBlueBits=0;
  PixFmtDesc.cAccumAlphaBits=0;
  PixFmtDesc.cDepthBits=32;    // 32bit depth buffer
  PixFmtDesc.cStencilBits=0;   // No stencil buffer
  PixFmtDesc.cAuxBuffers=0;
  PixFmtDesc.iLayerType=PFD_MAIN_PLANE;
  PixFmtDesc.bReserved=0;
  PixFmtDesc.dwLayerMask=0;
  PixFmtDesc.dwVisibleMask=0;
  PixFmtDesc.dwDamageMask=0;

Then we grab the context of our window and apply the specified pixel format to it.


  // Get the Window's device context
  DC=GetDC(hWnd);

   if(!DC)
    return NULL;
		
  // Find a pixel format that matches our requirements    
  int PixFmt=ChoosePixelFormat(DC,&PixFmtDesc);

   if(!PixFmt)
    return NULL;

  // Set pixel format on our Window
  if(!SetPixelFormat(DC,PixFmt,&PixFmtDesc))
   return NULL;

Now we can create and select our GL context, this directs all our GL calls within the current thread to that surface.


  // Create GL context
  RC=wglCreateContext(DC);

   if(!RC)
    return NULL;

   // Set GL Context as current
   if(!wglMakeCurrent(DC,RC))
    return NULL;

Finally we set some parameters to control our OpenGL output.
You can spot the OpenGL calls easily as they all start with 'gl'. Here we reset the view and specify a colour for clear operations. Don't worry too much about this just now as it will be covered later when we look at specifying projection matrices.


  glViewport(0,0,ScrX,ScrY);         // Make sure our view covers the whole window    

  glMatrixMode(GL_PROJECTION);       // Select the projection matrix
  glLoadIdentity();                  // Reset the projection matrix

  glMatrixMode(GL_MODELVIEW);        // Select the modelview matrix
  glLoadIdentity();                  // Reset the modelview matrix

  glClearColor(0.0f,0.0f,0.0f,1.0f); // Clear colour for viewport

  return hWnd;
 } // End of InitGL()

You may notice that the colour values in glClearColor() are floating point values rather than the normal 0-255 integer range. This is common in OpenGL, values for many functions tend to be in the range 0.0f to 1.0f. It's not that difficult to adjust to working in this way.

Now that we have a GL surface to play with, we can look at Render() and some OpenGL drawing functions.
Let's clear the screen, the start of most graphics loops. Note that the clear colour was specified back in the init function.


void Render(void)
 {
  static float Rotate=0.0f;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the display     

OpenGL maintains the state of the many parameters that affect it's operation until told otherwise. The clear color has a state, and here it has been set to 0.0f,0.0f,0.0f,1.0f (black with full opacity). It will remain like this until we say otherwise, there is no need to set it every frame.

Next we'll reset the current matrix, this matrix is maintained by OpenGL and determines how points in 3D space are translated to our 2D screen.


  glLoadIdentity(); // Reset current GL matrix

Next we'll apply a rotation to our modelview to give us something to look at. glRotate() takes 4 parameters: the first is the angle of rotation, the rest are a vector from 0,0,0 that specify the axis for the rotation. Play with the last 3 parameters and see how the rotation is affected.


  glRotatef(Rotate,0.0f,0.0f,1.0f); // Rotate the modelview around the Z axis    

And at last we draw our polygon into the rotated modelview!


   glBegin(GL_TRIANGLES); // Draw our Triangle
    glVertex3f( 0.0f, 0.8f,0.0f);
    glVertex3f( 0.7f,-0.6f,0.0f);
    glVertex3f(-0.7f,-0.6f,0.0f);
   glEnd();

glBegin() tells OpenGL that we are going to throw some vertex data at it. The GL_TRIANGLES parameter means that every set of three points will form a triangle. Other parameters include:

So, in this example, every set of three vertices between glBegin() and glEnd will be drawn as triangles.
The glVertex3f() function is straight forward, there are a few versions, some take integer or double values, some omit the Z value, but they all pass a point in space to OpenGL.

Finally, we increase the rotation for the next frame and swap the buffers to reveal our polygon. Note that the buffer swap is a GDI operation, we can still use GDI functions to draw on a GL-enabled window if we wish.


  Rotate+=1.0f; // Increase the rotation angle    

  SwapBuffers(DC); // Show the result

As this code is called each frame, the angle of rotation is increased and our polygon spins around nicely.

StopGL() is called when the program exits. it simply restores the old desktop resolution and clears away our window.


 bool StopGL(HWND hWin)
 {
  // Shut our Window
   if(hWin)
    DestroyWindow(hWin);

  // Restore the old screenmode
  ChangeDisplaySettings(&OldScrRes,CDS_RESET);

  // Clear our Window class
  UnregisterClass("GLWindow",gInst);
  return TRUE;
 }

There is plenty wrong with this code. The polygon distorts as it rotates because the projection parameters have not been set up and it relys on a bunch of default settings within OpenGL. However, It does show the basics of setting up OpenGL and hopefully demonstrates how easy it is to start out in 3D coding.

Get the source and Win32 binary Here

Codehead - 31/12/2004

Problems, ideas, better routines ?
Mail me or drop into the forums


Home EMail Forum