Выбрать главу

 glLoadIdentity(); // Reset The Projection Matrix

 // Calculate The Aspect Ratio Of The Window

 gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);

 glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix

 glLoadIdentity(); // Reset The Modelview Matrix

}

In the next section of code we do all of the setup for OpenGL. We set what color to clear the screen to, we turn on the depth buffer, enable smooth shading, etc. This routine will not be called until the OpenGL Window has been created. This procedure returns a value but because our initialization isn't that complex we wont worry about the value for now.

int InitGL(GLvoid) // All Setup For OpenGL Goes Here

{

The next line enables smooth shading. Smooth shading blends colors nicely across a polygon, and smoothes out lighting. I will explain smooth shading in more detail in another tutorial.

 glShadeModel(GL_SMOOTH); // Enables Smooth Shading

The following line sets the color of the screen when it clears. If you don't know how colors work, I'll quickly explain. The color values range from 0.0f to 1.0f. 0.0f being the darkest and 1.0f being the brightest. The first parameter after glClearColor is the Red Intensity, the second parameter is for Green and the third is for Blue. The higher the number is to 1.0f, the brighter that specific color will be. The last number is an Alpha value. When it comes to clearing the screen, we wont worry about the 4th number. For now leave it at 0.0f. I will explain its use in another tutorial.

You create different colors by mixing the three primary colors for light (red, green, blue). Hope you learned primaries in school. So, if you had glClearColor(0.0f,0.0f,1.0f,0.0f) you would be clearing the screen to a bright blue. If you had glClearColor(0.5f,0.0f,0.0f,0.0f) you would be clearing the screen to a medium red. Not bright (1.0f) and not dark (0.0f). To make a white background, you would set all the colors as high as possible (1.0f). To make a black background you would set all the colors to as low as possible (0.0f).

 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Black Background

The next three lines have to do with the Depth Buffer. Think of the depth buffer as layers into the screen. The depth buffer keeps track of how deep objects are into the screen. We won't really be using the depth buffer in this program, but just about every OpenGL program that draws on the screen in 3D will use the depth buffer. It sorts out which object to draw first so that a square you drew behind a circle doesn't end up on top of the circle. The depth buffer is a very important part of OpenGL.

 glClearDepth(1.0f); // Depth Buffer Setup

 glEnable(GL_DEPTH_TEST); // Enables Depth Testing

 glDepthFunc(GL_LEQUAL); // The Type Of Depth Test To Do

Next we tell OpenGL we want the best perspective correction to be done. This causes a very tiny performance hit, but makes the perspective view look a bit better.

 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations

Finally we return TRUE. If we wanted to see if initialization went ok, we could check to see if TRUE or FALSE was returned. You can add code of your own to return FALSE if an error happens. For now we won't worry about it.

 return TRUE; // Initialization Went OK

}

This section is where all of your drawing code will go. Anything you plan to display on the screen will go in this section of code. Each tutorial after this one will add code to this section of the program. If you already have an understanding of OpenGL, you can try creating basic shapes by adding OpenGL code below glLoadIdentity() and before return TRUE. If you're new to OpenGL, wait for my next tutorial. For now all we will do is clear the screen to the color we previously decided on, clear the depth buffer and reset the scene. We wont draw anything yet.

The return TRUE tells our program that there were no problems. If you wanted the program to stop for some reason, adding a return FALSE line somewhere before return TRUE will tell our program that the drawing code failed. The program will then quit.

int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing

{

 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer

 glLoadIdentity(); // Reset The Current Modelview Matrix

 return TRUE; // Everything Went OK

}

The next section of code is called just before the program quits. The job of KillGLWindow() is to release the Rendering Context, the Device Context and finally the Window Handle. I've added a lot of error checking. If the program is unable to destroy any part of the Window, a message box with an error message will pop up, telling you what failed. Making it a lot easier to find problems in your code.

GLvoid KillGLWindow(GLvoid) // Properly Kill The Window

{

The first thing we do in KillGLWindow() is check to see if we are in fullscreen mode. If we are, we'll switch back to the desktop. We should destroy the Window before disabling fullscreen mode, but on some video cards if we destroy the Window BEFORE we disable fullscreen mode, the desktop will become corrupt. So we'll disable fullscreen mode first. This will prevent the desktop from becoming corrupt, and works well on both Nvidia and 3dfx video cards!

 if (fullscreen) // Are We In Fullscreen Mode?

 {

We use ChangeDisplaySettings(NULL,0) to return us to our original desktop. Passing NULL as the first parameter and 0 as the second parameter forces Windows to use the values currently stored in the Windows registry (the default resolution, bit depth, frequency, etc) effectively restoring our original desktop. After we've switched back to the desktop we make the cursor visible again.

  ChangeDisplaySettings(NULL,0); // If So Switch Back To The Desktop

  ShowCursor(TRUE); // Show Mouse Pointer

 }

The code below checks to see if we have a Rendering Context (hRC). If we don't, the program will jump to the section of code below that checks to see if we have a Device Context.

 if (hRC) // Do We Have A Rendering Context?

 {

If we have a Rendering Context, the code below will check to see if we are able to release it (detach the hRC from the hDC). Notice the way I'm checking for errors. I'm basically telling our program to try freeing it (with wglMakeCurrent(NULL,NULL), then I check to see if freeing it was successful or not. Nicely combining a few lines of code into one line.

  if (!wglMakeCurrent(NULL,NULL)) // Are We Able To Release The DC And RC Contexts?

  {

If we were unable to release the DC and RC contexts, MessageBox() will pop up an error message letting us know the DC and RC could not be released. NULL means the message box has no parent Window. The text right after NULL is the text that appears in the message box. "SHUTDOWN ERROR" is the text that appears at the top of the message box (title). Next we have MB_OK, this means we want a message box with one button labelled "OK". MB_ICONINFORMATION makes a lower case i in a circle appear inside the message box (makes it stand out a bit more).

   MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

  }

Next we try to delete the Rendering Context. If we were unsuccessful an error message will pop up.

  if (!wglDeleteContext(hRC)) // Are We Able To Delete The RC?

  {

If we were unable to delete the Rendering Context the code below will pop up a message box letting us know that deleting the RC was unsuccessful. hRC will be set to NULL.

   MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

  }

  hRC=NULL; // Set RC To NULL

 }

Now we check to see if our program has a Device Context and if it does, we try to release it. If we're unable to release the Device Context an error message will pop up and hDC will be set to NULL.

 if (hDC && !ReleaseDC(hWnd,hDC)) // Are We Able To Release The DC

 {

  MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

  hDC=NULL; // Set DC To NULL

 }

Now we check to see if there is a Window Handle and if there is, we try to destroy the Window using DestroyWindow(hWnd). If we are unable to destroy the Window, an error message will pop up and hWnd will be set to NULL.

 if (hWnd && !DestroyWindow(hWnd)) // Are We Able To Destroy The Window?

 {

  MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

  hWnd=NULL; // Set hWnd To NULL

 }

Last thing to do is unregister our Windows Class. This allows us to properly kill the window, and then reopen another window without receiving the error message "Windows Class already registered".

 if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class

 {

  MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

  hInstance=NULL; // Set hInstance To NULL

 }

}

The next section of code creates our OpenGL Window. I spent a lot of time trying to decide if I should create a fixed fullscreen Window that doesn't require a lot of extra code, or an easy to customize user friendly Window that requires a lot more code. I decided the user friendly Window with a lot more code would be the best choice. I get asked the following questions all the time in emaiclass="underline" How can I create a Window instead of using fullscreen? How do I change the Window's title? How do I change the resolution or pixel format of the Window? The following code does all of that! Therefore it's better learning material and will make writing OpenGL programs of your own a lot easier!

As you can see the procedure returns BOOL (TRUE or FALSE), it also takes 5 parameters: title of the Window, width of the Window, height of the Window, bits (16/24/32), and finally fullscreenflag TRUE for fullscreen or FALSE for windowed. We return a boolean value that will tell us if the Window was created successfully.

BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) {

When we ask Windows to find us a pixel format that matches the one we want, the number of the mode that Windows ends up finding for us will be stored in the variable PixelFormat.

 GLuint PixelFormat; // Holds The Results After Searching For A Match

wc will be used to hold our Window Class structure. The Window Class structure holds information about our window. By changing different fields in the Class we can change how the window looks and behaves. Every window belongs to a Window Class. Before you create a window, you MUST register a Class for the window.

 WNDCLASS wc; // Windows Class Structure

dwExStyle and dwStyle will store the Extended and normal Window Style Information. I use variables to store the styles so that I can change the styles depending on what type of window I need to create (A popup window for fullscreen or a window with a border for windowed mode)

 DWORD dwExStyle; // Window Extended Style

 DWORD dwStyle; // Window Style

The following 5 lines of code grab the upper left, and lower right values of a rectangle. We'll use these values to adjust our window so that the area we draw on is the exact resolution we want. Normally if we create a 640×480 window, the borders of the window take up some of our resolution.

 RECT WindowRect; // Grabs Rectangle Upper Left / Lower Right Values

 WindowRect.left=(long)0; // Set Left Value To 0

 WindowRect.right=(long)width; // Set Right Value To Requested Width

 WindowRect.top=(long)0; // Set Top Value To 0

 WindowRect.bottom=(long)height; // Set Bottom Value To Requested Height

In the next line of code we make the global variable fullscreen equal fullscreenflag. So if we made our Window fullscreen, the variable fullscreenflag would be TRUE. If we didn't make the variable fullscreen equal fullscreenflag, the variable fullscreen would stay FALSE. If we were killing the window, and the computer was in fullscreen mode, but the variable fullscreen was FALSE instead of TRUE like it should be, the computer wouldn't switch back to the desktop, because it would think it was already showing the desktop. God I hope that makes sense. Basically to sum it up, fullscreen has to equal whatever fullscreenflag equals, otherwise there will be problems.

 fullscreen=fullscreenflag; // Set The Global Fullscreen Flag

In the next section of code, we grab an instance for our Window, then we define the Window Class.

The style CS_HREDRAW and CS_VREDRAW force the Window to redraw whenever it is resized. CS_OWNDC creates a private DC for the Window. Meaning the DC is not shared across applications. WndProc is the procedure that watches for messages in our program. No extra Window data is used so we zero the two fields. Then we set the instance. Next we set hIcon to NULL meaning we don't want an ICON in the Window, and for a mouse pointer we use the standard arrow. The background color doesn't matter (we set that in GL). We don't want a menu in this Window so we set it to NULL, and the class name can be any name you want. I'll use "OpenGL" for simplicity.

 hInstance = GetModuleHandle(NULL); // Grab An Instance For Our Window

 wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Redraw On Move, And Own DC For Window

 wc.lpfnWndProc = (WNDPROC) WndProc; // WndProc Handles Messages

 wc.cbClsExtra = 0; // No Extra Window Data

 wc.cbWndExtra = 0; // No Extra Window Data

 wc.hInstance = hInstance; // Set The Instance

 wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Load The Default Icon

 wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Load The Arrow Pointer

 wc.hbrBackground = NULL; // No Background Required For GL

 wc.lpszMenuName = NULL; // We Don't Want A Menu

 wc.lpszClassName = "OpenGL"; // Set The Class Name

Now we register the Class. If anything goes wrong, an error message will pop up. Clicking on OK in the error box will exit the program.

 if (!RegisterClass(&wc)) // Attempt To Register The Window Class

 {

  MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);

  return FALSE; // Exit And Return FALSE

 }