
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