Introduction to C++ OpenGL Programming
Welcome to another fine lesson in C++! Today you'll be introduced to the wonderful world of OpenGL. OpenGL is a fairly straight forward -- although at many times confusing -- concept. OpenGL gives the programmer an interface with the graphics hardware. OpenGL is a low-level, widely supported modeling and rendering software package, available on all platforms. It can be used in a range of graphics applications, such as games, CAD design, or modeling (to name a few).
OpenGL is the core graphics rendering option for many 3D games, such as Quake 3. The providing of only low-level rendering routines is fully intentional because this gives the programmer a great control and flexibility in his applications. These routines can easily be used to build high-level rendering and modeling libraries. The OpenGL Utility Library (GLU) does exactly this, and is included in most OpenGL distributions!
Unlike DirectX, OpenGL is only a graphics API; it doesn't include support for functionality such as sound, input, or networking (or anything not related to graphics). That's ok, however, because in future tutorials I will teach you how you how to use DirectX for these things.
OpenGL was originally developed in 1992 by Silicon Graphics, Inc, (SGI) as a multi-purpose, platform independent graphics API. Since 1992 all of the development of OpenGL has been headed by the OpenGL Architecture Review Board (ARB). This exclusive board is composed of the major graphics vendors and industry leaders. Some of these are Intel, IBM, NVIDIA, Microsoft, and Silicon Graphics.
OpenGL is a collection of several hundred functions that provide access to all of the features that your graphics hardware has to offer. Internally it acts like a state machine-a collection of states that tell OpenGL what to do. Using the API you can set various aspects of this state machine, including current color, blending, lighting effect, etc.
This is a very general introduction to OpenGL, and you may find other in-depth introductions elsewhere. There is a reason, however, to my generalized introduction. I don't want to slam you with specifics, but give you an idea as to what OpenGL is so that you may decide for yourself if this set of lessons is for you. As always, happy coding, and see you next lesson (I hope)!
OpenGL vs. DirectX: A Comparison
The competition between OpenGL and DirectX is possibly as well known as the wars waged between AMD and Intel enthusiasts. This topic has sparked the fires of many flame wars throughout the years, and I don't anticipate that changing anytime soon. I won't preach why I prefer OpenGL over DirectX, but rather lay out the facts and let you make that decision. So let's dive in!
Perhaps the most obvious difference is that DirectX, as opposed to OpenGL, is more than just a graphics API. DirectX contains tools to deal with such components of a game as sound, music, input, networking, and multimedia. On the other hand, OpenGL is strictly a graphics API. So what aspect of OpenGL sets it apart from the DirectX graphics component?
Well, first things first: both APIs rely on the use of the traditional graphics pipeline. This is the same pipeline that has been used in computer games since the early days of computer graphics. Although it has been modified in slight ways to adapt with advancements in hardware, the basic idea remains intact.
Both OpenGL and DirectX describe vertices as a set of data consisting of coordinates in space that define the vertex location and any other vertex related data. Graphics primitives, such as points, lines, and triangles, are defined as an ordered set of vertices. There is a difference in how each API handles how vertices are combined to form primitives.
There are a bunch of differences in the DirectX and OpenGL APIs, so I will list a few of those for you. This chart is based off the book, OpenGL Game Programming and a few of these may now be incorrect as new DirectX versions are released. If you wish to correct me, please do via one of the ways listed at the end of this tutorial.
Table 1.1:
Well, first things first: both APIs rely on the use of the traditional graphics pipeline. This is the same pipeline that has been used in computer games since the early days of computer graphics. Although it has been modified in slight ways to adapt with advancements in hardware, the basic idea remains intact.
Both OpenGL and DirectX describe vertices as a set of data consisting of coordinates in space that define the vertex location and any other vertex related data. Graphics primitives, such as points, lines, and triangles, are defined as an ordered set of vertices. There is a difference in how each API handles how vertices are combined to form primitives.
There are a bunch of differences in the DirectX and OpenGL APIs, so I will list a few of those for you. This chart is based off the book, OpenGL Game Programming and a few of these may now be incorrect as new DirectX versions are released. If you wish to correct me, please do via one of the ways listed at the end of this tutorial.
Table 1.1:
Feature: | OpenGL | DirectX |
Vertex Blending | N/A | Yes |
Multiple Operating Systems | Yes | No |
Extension Mechanism | Yes | Yes |
Development | Multiple member Board | Microsoft |
Thorough Specification | Yes | No |
Two-sided lighting | Yes | No |
Volume Textures | Yes | No |
Hardware independent Z-buffers | Yes | No |
Accumulation buffers | Yes | No |
Full-screen Antialiasing | Yes | Yes |
Motion Blur | Yes | Yes |
Depth of field | Yes | Yes |
Stereo Rendering | Yes | No |
Point-size/line-width attributes | Yes | No |
Picking | Yes | No |
Parametric curves and surfaces | Yes | No |
Cache geometry | Display Lists | Vertex Buffers |
System emulation | Hardware not present | Let app determine |
Interface | Procedure calls | COM |
Updates | Yearly | Yearly |
Source Code | Sample | SDK Implementation |
So now you know what separates DirectX and OpenGL, and hopefully you have chosen based on facts which you would prefer, not on myths or opinions. I hope you chose OpenGL, as that's what I will be teaching. If you chose DirectX my tutorials won't help you very much. In the next lesson we will begin with our first OpenGL program.
Introduction to Windows Programming and OpenGL
At this time we will take a look into the vast world of Windows programming. Since this tutorial is based entirely on programming done within the Windows environment, it will be required of you to have access to a Windows machine and compiler such as Code Blocks.
For the programs we will be creating you will need a base understanding of the mechanics and structuring of the Windows operating system. Not to worry, however, because I am going to teach this to you!
Microsoft Windows is a multi-tasking operating system that allows multiple applications, referred to here on out as processes. Every process in Windows is given a certain amount of time, called a time slice, where the application is given the right to control the system without being interrupted by the other processes. The runtime priority and the amount of time allocated to a process are determined by the scheduler.
The scheduler is, simply put, the manager of this multi-tasking operating system, ensuring that each process is given the time and the priority it needs depending on the current state of the system.
When it comes to game developers, you will find a large common interest in multithreading. Processes can be broken down into threads, where each thread can execute its own code to allow for multitasking within a single application. The scheduling for threads is the same as that for processes, except that threads are what make up the process.
This means that within your games you can have multiple threads running that can perform multiple calculations at once and thus provide your game with multitasking within itself. To go a step farther we can do a quick explanation of fibers. In newer versions of Windows (Windows 98+) there is an even lower level execution object exists, called a fiber. Each thread in your process has the ability to house multiple fibers that can perform multiple operations at once, all in a single thread.
Windows is what is known as an event-driven operating system. What this means is that each process is executed based on the events they receive from the operating system. For instance, an application may sit at idle and wait for the user to press a key. When that key is pressed Windows will register an event to the application that the key is down.
Windows programming is not difficult at all in most cases, and to start we'll use the most basic program ever created. Hello World is used in almost every class when you begin programming in any language, and that won't change here. So, shall we have a look at Hello World in Windows style? Sure!
#include <windows.h> int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MessageBox(NULL, "\tHello World!", "My first windows app", NULL); return 0; }
As you can see, it's pretty straight forward. Don't pay attention to the things that don't make sense, as in the next lesson we will go in-depth to explain WinMain () and other functions, as well as show how this and OpenGL work together!
WinMain() and the Windows Procedu
Every Windows programming needs a main entry point. That being said, you may be wondering what this entry point is. The main entry point for any Windows program is called WinMain. The function prototype for WinMain is a little confusing at first, but as we continue to work with it you'll notice it becomes much easier to understand. Well. we've told you what it is; now were going to show you! Here's the prototype for WinMain:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd);
As you may have already noticed, the return type for WinMain is, and always will be, int. All 32-bit Windows operating system applications use the calling convention WINAPI. This calling convention MUST be used to distinguish the function as the entry point. Now for the parameters. As you can see, WinMain uses four parameters when the program starts. Let's have a look at them, shall we?
hInstance - is a handle to your applications instance, where an instance can be considered to be a single run of your application. The instance is used by windows as a reference to your application for event handling, message processing, and various other duties. hPrevInstance - is always NULL. lpCmdLine - is a pointer string that is used to hold any command-line arguments that may have been specified when the application began. For example, if the user opened the Run application and typed myapp.exe myparameter 1, then lpCmdLine would be myparameter 1. nShowCMD - is the parameter that determines how your application's window will be displayed once it begins executing.
Pretty simple, right? Don't worry if it's confusing, it will make sense soon enough! The Windows program you create is going to need some way to handle the messages that Windows will send to it. A few examples of these messages are: WM_CREATE, WM_SIZE, and WM_MOVE. There are TONS of these messages, and we'll show you how to handle a large number of them. To allow Windows to communicate with your application, we'll create a dandy little function called a Windows procedure. The most common name for this function is WndProc. This function MUST be created and used to determine how your application will respond to various events. The Windows procedure may also be called the event handler because, well, it responds to Windows events! So let's have a quick look at the prototype:
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
This function is declared with a return type of LRESULT CALLBACK. The LRESULT type is used by Windows to declare a long integer, and CALLBACK is a calling convention used with functions that are called by Windows. The Windows Procedure is a function pointer, which allows you to call it whatever you want because the function's address will be assigned as a function pointer upon creation of the window class.
hwnd - Only important if you have several windows of the same class open at one time. This is used to determine which window hwnd pointed to before deciding on an action. message - The actual message identifier that WndProc will be handling. wParam and lParam - Extensions of the message parameter. Used to give more information and point to specifics that message cannot convey on its own.
Well now you should have a better understanding of these two topics. You may be wondering when you get to see some openGL usage, and the answer is soon. We first need to cover the basics, let's remember, we're here to learn!
First Windows Application
The following code is a build up on the basic "hello world" program I showed you earlier. Take a minute to review it, maybe even key it into your compiler and run it, and then we will break it down so that it is easier to understand what is actually going on.
/* Trim fat from windows*/ #define WIN32_LEAN_AND_MEAN #pragma comment(linker, "/subsystem:windows") /* Pre-processor directives*/ #include "stdafx.h" #include <windows.h> /* Windows Procedure Event Handler*/ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT paintStruct; /* Device Context*/ HDC hDC; /* Text for display*/ char string[] = "Hello, World!"; /* Switch message, condition that is met will execute*/ switch(message) { /* Window is being created*/ case WM_CREATE: return 0; break; /* Window is closing*/ case WM_CLOSE: PostQuitMessage(0); return 0; break; /* Window needs update*/ case WM_PAINT: hDC = BeginPaint(hwnd,&paintStruct); /* Set txt color to blue*/ SetTextColor(hDC, COLORREF(0x00FF0000)); /* Display text in middle of window*/ TextOut(hDC,150,150,string,sizeof(string)-1); EndPaint(hwnd, &paintStruct); return 0; break; default: break; } return (DefWindowProc(hwnd,message,wParam,lParam)); } /* Main function*/ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX windowClass; //window class HWND hwnd; //window handle MSG msg; //message bool done; //flag saying when app is complete /* Fill out the window class structure*/ windowClass.cbSize = sizeof(WNDCLASSEX); windowClass.style = CS_HREDRAW | CS_VREDRAW; windowClass.lpfnWndProc = WndProc; windowClass.cbClsExtra = 0; windowClass.cbWndExtra = 0; windowClass.hInstance = hInstance; windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); windowClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); windowClass.lpszMenuName = NULL; windowClass.lpszClassName = "MyClass"; windowClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); /* Register window class*/ if (!RegisterClassEx(&windowClass)) { return 0; } /* Class registerd, so now create window*/ hwnd = CreateWindowEx(NULL, //extended style "MyClass", //class name "A Real Win App", //app name WS_OVERLAPPEDWINDOW | //window style WS_VISIBLE | WS_SYSMENU, 100,100, //x/y coords 400,400, //width,height NULL, //handle to parent NULL, //handle to menu hInstance, //application instance NULL); //no extra parameter's /* Check if window creation failed*/ if (!hwnd) return 0; done = false; //initialize loop condition variable /* main message loop*/ while(!done) { PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE); if (msg.message == WM_QUIT) //check for a quit message { done = true; //if found, quit app } else { /* Translate and dispatch to event queue*/ TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; }
Well isn't this a whole mess of code! The first thing that you may have noticed is #define WIN32_LEAN_AND_MEAN. This syntax prevents Visual C++ from linking modules that you aren't going to need in your application.
Moving on we come to the include line #include <windows.h>. This includes all the headers you need in this application. Sometimes you may want to include <windowsx.h>, which will give you a few useful macros to use in Windows development.
The first function we arrive at is the WndProc function. We've discussed this before, so I am just going to highlight two lines I have added here.
Moving on we come to the include line #include <windows.h>. This includes all the headers you need in this application. Sometimes you may want to include <windowsx.h>, which will give you a few useful macros to use in Windows development.
The first function we arrive at is the WndProc function. We've discussed this before, so I am just going to highlight two lines I have added here.
HDC hDC; //device context Char string[] = "Hello World!"; //display text
These two lines are essential as they declare the device context that we are going to use to output to the window we create, and the string that we will display. As we continue on in the code we arrive at the switch statement. This switch is used to determine the message being passed to the windows procedure. In this particular instance we want to take a closer look at the WM_PAINT block.
case WM_PAINT: hDC = BeginPaint(hwnd,&paintStruct); /* Set txt color to blue*/ SetTextColor(hDC, COLORREF(0x00FF0000)); /* Display text in middle of window*/ TextOut(hDC,150,150,string,sizeof(string)-1); EndPaint(hwnd, &paintStruct); return 0; break;
When the window is moved, resized, or is otherwise changed the window needs to be updated. The first noticeable change here is the use of the hDC device context. The win32 function BeginPaint() returns the graphics device context for the hwnd passed to it. You can then use this hDC to set the text color with SetTextColor() and follow up with the TextOut() function to display the text. If you want to know more about these functions, check out MSDN.
Next function up is the WinMain() function. Most of the WinMain() content is pretty straight forward, but were going to review a few parts of it for good measure. The msg variable holds the message received by PeekMessage() from the queue, and will be sent to TranslateMessage() and DispatchMessage(). The variable done is a Boolean value used by your message loop and will not equal true until a WM_QUIT message has been received from the queue to indicate that the application is about to be closed.
For easier understanding we will break the order of each setup task into a list.
Next function up is the WinMain() function. Most of the WinMain() content is pretty straight forward, but were going to review a few parts of it for good measure. The msg variable holds the message received by PeekMessage() from the queue, and will be sent to TranslateMessage() and DispatchMessage(). The variable done is a Boolean value used by your message loop and will not equal true until a WM_QUIT message has been received from the queue to indicate that the application is about to be closed.
For easier understanding we will break the order of each setup task into a list.
1. Window-class setup 2. Window-class registration 3. Window Creation 4. Message loop with event handler
We now have a fully working Windows application! I encourage you to toy around with the code and alter it to your liking. Don't be discouraged by errors or problems, for it is these things that make us better.
WGL and the Wiggle Functions in C++
The simple fact that OpenGL is only a graphics API means that any user interaction, or things like screen and window issues needs to be handled by the operating system. Most operating systems come with a set of extensions that tie OpenGL together with user interaction and window management. This tends to make our lives a little bit easier, but only if we know how to take advantage of this option. In Windows this set of functions are called the wiggle functions. Each of these functions is prefixed with wgl.
To maintain the portability of OpenGL, each operating system must supply functionality for specifying the rendering window before OpenGL can use it. In Windows the Graphics Device Interface uses a device context to remember settings about drawing modes and commands, but in OpenGL the rendering context is used to remember these things. You need to remember, however, that the device context is not a rendering context. The device context MUST be specified in a GDI call, unlike a rendering context, which is specified in an OpenGL call.
If you wish to render more than one window at once, multiple rendering contexts are allowed. You must ensure, however, that the proper rendering context is being used on the proper window. Another thing to remember is that OpenGL is thread-safe. You can have multiple threads rendering to the same device context at one time.
As I mentioned to you earlier, wiggle functions bring Windows API support into OpenGL. Well let's take this time to have a look at a few common wiggle functions:
If you wish to render more than one window at once, multiple rendering contexts are allowed. You must ensure, however, that the proper rendering context is being used on the proper window. Another thing to remember is that OpenGL is thread-safe. You can have multiple threads rendering to the same device context at one time.
As I mentioned to you earlier, wiggle functions bring Windows API support into OpenGL. Well let's take this time to have a look at a few common wiggle functions:
- wglCreateContext();
- wglDeleteContext();
- wglMakeCurrent();
The wglCreateContext() function creates a handle to the OpenGL rendering context while being passed like a handle to a GDI device context. The correct prototype for this function is:
HGLRC wglCreateContext(HDC hDC);
This function should only be called after the pixel format for the device context has been set. Don't worry, pixel format is coming into teaching shortly.
Keep in mind that as with a device context, a rendering context must be deleted after you are finished with it. This is where the wglDeleteContext() function comes into play. Let's have a look at the prototype for good measure:
Keep in mind that as with a device context, a rendering context must be deleted after you are finished with it. This is where the wglDeleteContext() function comes into play. Let's have a look at the prototype for good measure:
BOOL wglDeleteContext(HGLRC hRC);
The name wglMakeCurrent() is highly accurate to what the function does. The function makes the rendering context passed to it the current rendering context. Makes a lot of sense, doesn't it? The device context used must have the same pixel format characteristics as the device context that was used to create the rendering context. This means that the device context used to create the rendering context does not need to be the same as the device context assigned to the wglMakeCurrent() function. Without further delay, let's see the prototype!
BOOL wglMakeCurrent(HDC hDC, HGLRC hRC);
You need to ensure that the device context and the rendering context passed to the function have the same pixel format, or the function will not work. To deselect the rendering context you can simply pass NULL for the hRC parameter, or simply pass another rendering context.
Both the wglCreateContext() and the wglMakeCurrent() functions should be called upon window creation. Let's look at a code snippet for an example of these in use:
Both the wglCreateContext() and the wglMakeCurrent() functions should be called upon window creation. Let's look at a code snippet for an example of these in use:
LRESULT CALLBACK WndProc (HWND hwnd, UNIT message, WPARAM wParam, LPARAM lParam) { static HGLRC hRC; //rendering context static HDC hDC; //device context switch (message) { case WM_CREATE: hDC = GetDC(hwnd); //get the device context for window hRC = wglCreateContext(hDC); //create rendering context wglMakeCurrent(hDC,hRC); //make rendering context current break; case WM_DESTROY: wglMakeCurrent(hDC,NULL); //deselect rendering context wglDeleteContext(hRC); //delete rendering context PostQuitMessage(0); //send wm_quit break; } }
This code creates and destroys your OpenGL window. Before you actually do this, however, we must first discuss the PIXELFORMATDESCRIPTOR, which we will do in lesson 5.
Sample OpenGL Program in C or C++
In this lesson I shall introduce several functions and show you actual OpenGL rendering in a program. Prior to showing you the code, however, I want to go over a few things with you. This will give you a better understanding of what is going on when you do see the code, so you don't stare at the screen wondering what you're looking at. So, on with the show.
Transformations in OpenGL rely on the matrix for all mathematical computations. No, not the movie. Concentrate grasshopper. OpenGL has what is known as a matrix stack, which comes in handy for constructing models composed of many simple objects.
The modelview matrix defines the coordinate system that is being used to place and orient objects. It is a 4x4 matrix that is multiplied by vertices and transformations to create a new matrix that reflects the result of any transformations that have been applied to the vertices. When we want to modify the modelview matrix we use the command glMatrixMode(). We define this as
void glMatrixMode(GLenum mode);
Before you call any transformation commands you MUST specify whether you want to modify the modelview matrix or the projection matrix. The argument for modifying the modelview matrix is GL_MODELVIEW. So the complete line would appear as:
void glMatrixMode(GL_MODELVIEW);
Now we will look at translation. Translation allows you to move an object from one location to another within a 3D environment. The functions for this in OpenGL are glTranslatef() and glTranslated(). Here are their descriptions:
void glTranslatef(GLfloat x, GLfloat y, GLfloat z); void glTranslated(GLdouble x, GLdouble y, GLdouble z);
Note that you must pass float types to glTranslatef() and double types to glTranslated(). X, Y, and Z represent the amount of translation on that axis.
Rotation in OpenGL is accomplished through the glRotate*() function, which is defined as
Rotation in OpenGL is accomplished through the glRotate*() function, which is defined as
void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); void glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z);
Now let's take a look at these, and a few others, functions mentioned in a program. The following code is taken from OpenGL Game Programming and is commented by myself. If you have any problems building and using this code, feel free to contact me.
/* Steven Billington January 13, 2003 May 26, 2003 - UPDATE RobotOGL.cpp rod@cprogramming.com The following program creates a window and then uses multiple OpenGL functions to display a animated robot constructed of different size cubes. To compile this code you must make the proper library links in project--->settings. I apologize for any amount of scattered code or mis-formatting that printing this may occur. Please feel free to email me at the address above for the .cpp file. */ /* These are what we refer to as Pre-processor Directives. In order for certain functions in C++ to operate you must include certain header files. Each header file below contains different functions needed throughout this program. */ #pragma comment(linker, "/subsystem:windows") #include <windows.h> #include <gl/gl.h> #include <gl/glu.h> #include <gl/glaux.h> /* Here we find a few global variables. While i don't really like to use global variables, i found them very handy for this particular program. These variables will control angles, fullscreen, and the global device context. */ HDC g_HDC; float angle = 0.0f; float legAngle[2] = {0.0f, 0.0f}; float armAngle[2] = {0.0f, 0.0f}; bool fullScreen = false; /* Function: DrawCube Purpose: As the name would suggest, this is the function for drawing the cubes. */ void DrawCube(float xPos, float yPos, float zPos) { glPushMatrix(); glBegin(GL_POLYGON); /* This is the top face*/ glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, -1.0f); glVertex3f(-1.0f, 0.0f, -1.0f); glVertex3f(-1.0f, 0.0f, 0.0f); /* This is the front face*/ glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(-1.0f, 0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glVertex3f(0.0f, -1.0f, 0.0f); /* This is the right face*/ glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, -1.0f, 0.0f); glVertex3f(0.0f, -1.0f, -1.0f); glVertex3f(0.0f, 0.0f, -1.0f); /* This is the left face*/ glVertex3f(-1.0f, 0.0f, 0.0f); glVertex3f(-1.0f, 0.0f, -1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, 0.0f); /* This is the bottom face*/ glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, -1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, 0.0f); /* This is the back face*/ glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(-1.0f, 0.0f, -1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(0.0f, -1.0f, -1.0f); glEnd(); glPopMatrix(); } /* Function: DrawArm Purpose: This function draws the arm for the robot. */ void DrawArm(float xPos, float yPos, float zPos) { glPushMatrix(); /* Sets color to red*/ glColor3f(1.0f, 0.0f, 0.0f); glTranslatef(xPos, yPos, zPos); /* Creates 1 x 4 x 1 cube*/ glScalef(1.0f, 4.0f, 1.0f); DrawCube(0.0f, 0.0f, 0.0f); glPopMatrix(); } /* Function: DrawHead Purpose: This function will create the head for the robot. */ void DrawHead(float xPos, float yPos, float zPos) { glPushMatrix(); /* Sets color to white*/ glColor3f(1.0f, 1.0f, 1.0f); glTranslatef(xPos, yPos, zPos); /* Creates 2 x 2 x 2 cube*/ glScalef(2.0f, 2.0f, 2.0f); DrawCube(0.0f, 0.0f, 0.0f); glPopMatrix(); } /* Function: DrawTorso Purpose: Function will do as suggested and draw a torso for our robot. */ void DrawTorso(float xPos, float yPos, float zPos) { glPushMatrix(); /* Sets color to blue*/ glColor3f(0.0f, 0.0f, 1.0f); glTranslatef(xPos, yPos, zPos); /* Creates 3 x 5 x 1 cube*/ glScalef(3.0f, 5.0f, 1.0f); DrawCube(0.0f, 0.0f, 0.0f); glPopMatrix(); } /* Function: DrawLeg Purpose: Not to sound repetitve, but as suggested this function will draw our robots legs. */ void DrawLeg(float xPos, float yPos, float zPos) { glPushMatrix(); /* Sets color to yellow*/ glColor3f(1.0f, 1.0f, 0.0f); glTranslatef(xPos, yPos, zPos); /* Creates 1 x 5 x 1 cube*/ glScalef(1.0f, 5.0f, 1.0f); DrawCube(0.0f, 0.0f, 0.0f); glPopMatrix(); } /* Function: DrawRobot Purpose: Function to draw our entire robot */ void DrawRobot(float xPos, float yPos, float zPos) { /* Variables for state of robots legs. True means the leg is forward, and False means the leg is back. The same applies to the robots arm states. */ static bool leg1 = true; static bool leg2 = false; static bool arm1 = true; static bool arm2 = false; glPushMatrix(); /* This will draw our robot at the desired coordinates. */ glTranslatef(xPos, yPos, zPos); /* These three lines will draw the various components of our robot. */ DrawHead(1.0f, 2.0f, 0.0f); DrawTorso(1.5f, 0.0f, 0.0f); glPushMatrix(); /* If the arm is moving forward we will increase the angle; otherwise, we will decrease the angle. */ if (arm1) { armAngle[0] = armAngle[0] + 1.0f; } else { armAngle[0] = armAngle[0] - 1.0f; } /* Once the arm has reached its max angle in one direction, we want it to reverse and change direction. */ if (armAngle[0] >= 15.0f) { arm1 = false; } if (armAngle[0] <= 15.0f) { arm1 = true; } /* Here we are going to move the arm away from the torso and rotate. This will create a walking effect. */ glTranslatef(0.0f, -0.5f, 0.0f); glRotatef(armAngle[0], 1.0f, 0.0f, 0.0f); DrawArm(2.5f, 0.0f, -0.5f); glPopMatrix(); glPushMatrix(); /* If the arm is moving forward we will increase the angle, otherwise we will decrease the angle */ if (arm2) { armAngle[1] = armAngle[1] + 1.0f; } else { armAngle[1] = armAngle[1] - 1.0f; } /* Here we are going to move the arm away from the torso and rotate. This will create a walking effect. */ glTranslatef(0.0f, -0.5f, 0.0f); glRotatef(armAngle[1], 1.0f, 0.0f, 0.0f); DrawArm(-1.5f, 0.0f, -0.5f); glPopMatrix(); /* Now its time to rotate the legs relative to the robots position in the world, this is the first leg, ie the right one. */ glPushMatrix(); /* If the leg is moving forward we will increase the angle; otherwise, we will decrease the angle. */ if (leg1) { legAngle[0] = legAngle[0] + 1.0f; } else { legAngle[0] = legAngle[0] - 1.0f; } /* Once the leg has reached its max angle in one direction, we want it to reverse and change direction. */ if (legAngle[0] >= 15.0f) { leg1 = false; } if (legAngle[0] <= -15.0f) { leg1 = true; } /* Here we are going to move the leg away from the torso and rotate. This will create a walking effect. */ glTranslatef(0.0f, -0.5f, 0.0f); glRotatef(legAngle[0], 1.0f, 0.0f, 0.0f); /* Time to draw the leg. */ DrawLeg(-0.5f, -5.0f, -0.5f); glPopMatrix(); /* Same as above, for the left leg. */ glPushMatrix(); /* If the leg is moving forward we will increase the angle, otherwise we will decrease the angle */ if (leg2) { legAngle[1] = legAngle[1] + 1.0f; } else { legAngle[1] = legAngle[1] - 1.0f; } /* Once the leg has reached its max angle in one direction, we want it to reverse and change direction. */ if (legAngle[1] >= 15.0f) { leg2 = false; } if (legAngle[1] <= -15.0f) { leg2 = true; } /* Here we are going to move the leg away from the torso and rotate. This will create a walking effect. */ glTranslatef(0.0f, -0.5f, 0.0f); glRotatef(legAngle[1], 1.0f, 0.0f, 0.0f); DrawLeg(1.5f, -5.0f, -0.5f); glPopMatrix(); glPopMatrix(); } /* Function: Render Purpose: This function will be responsible for the rendering, got to love my descriptive function names : ) */ void Render() { /* Enable depth testing */ glEnable(GL_DEPTH_TEST); /* Heres our rendering. Clears the screen to black, clear the color and depth buffers, and reset our modelview matrix. */ glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); /* Increase rotation angle counter */ angle = angle + 1.0f; /* Reset after we have completed a circle */ if (angle >= 360.0f) { angle = 0.0f; } glPushMatrix(); glLoadIdentity(); /* Move to 0,0,-30 , rotate the robot on its y axis, draw the robot, and dispose of the current matrix. */ glTranslatef(0.0f, 0.0f, -30.0f); glRotatef(angle, 0.0f, 1.0f, 0.0f); DrawRobot(0.0f, 0.0f, 0.0f); glPopMatrix(); glFlush(); /* Bring back buffer to foreground */ SwapBuffers(g_HDC); } //function to set the pixel format for the device context /* Function: SetupPixelFormat Purpose: This function will be responsible for setting the pixel format for the device context. */ void SetupPixelFormat(HDC hDC) { /* Pixel format index */ int nPixelFormat; static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), //size of structure 1, //default version PFD_DRAW_TO_WINDOW | //window drawing support PFD_SUPPORT_OPENGL | //opengl support PFD_DOUBLEBUFFER, //double buffering support PFD_TYPE_RGBA, //RGBA color mode 32, //32 bit color mode 0, 0, 0, 0, 0, 0, //ignore color bits 0, //no alpha buffer 0, //ignore shift bit 0, //no accumulation buffer 0, 0, 0, 0, //ignore accumulation bits 16, //16 bit z-buffer size 0, //no stencil buffer 0, //no aux buffer PFD_MAIN_PLANE, //main drawing plane 0, //reserved 0, 0, 0 }; //layer masks ignored /* Choose best matching format*/ nPixelFormat = ChoosePixelFormat(hDC, &pfd); /* Set the pixel format to the device context*/ SetPixelFormat(hDC, nPixelFormat, &pfd); } /* Windows Event Procedure Handler */ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { /* Rendering and Device Context variables are declared here. */ static HGLRC hRC; static HDC hDC; /* Width and Height for the window our robot is to be displayed in. */ int width, height; switch(message) { case WM_CREATE: //window being created hDC = GetDC(hwnd); //get current windows device context g_HDC = hDC; SetupPixelFormat(hDC); //call our pixel format setup function /* Create rendering context and make it current */ hRC = wglCreateContext(hDC); wglMakeCurrent(hDC, hRC); return 0; break; case WM_CLOSE: //window is closing /* Deselect rendering context and delete it*/ wglMakeCurrent(hDC, NULL); wglDeleteContext(hRC); /* Send quit message to queue*/ PostQuitMessage(0); return 0; break; case WM_SIZE: /* Retrieve width and height*/ height = HIWORD(lParam); width = LOWORD(lParam); /* Don't want a divide by 0*/ if (height == 0) { height = 1; } /* Reset the viewport to new dimensions*/ glViewport(0, 0, width, height); /* Set current Matrix to projection*/ glMatrixMode(GL_PROJECTION); glLoadIdentity(); //reset projection matrix /* Time to calculate aspect ratio of our window. */ gluPerspective(54.0f, (GLfloat)width/(GLfloat)height, 1.0f, 1000.0f); glMatrixMode(GL_MODELVIEW); //set modelview matrix glLoadIdentity(); //reset modelview matrix return 0; break; default: break; } return (DefWindowProc(hwnd, message, wParam, lParam)); } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX windowClass; //window class HWND hwnd; //window handle MSG msg; //message bool done; //flag for completion of app DWORD dwExStyle; //window extended style DWORD dwStyle; //window style RECT windowRect; /* Screen/display attributes*/ int width = 800; int height = 600; int bits = 32; 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 /* Fill out the window class structure*/ windowClass.cbSize = sizeof(WNDCLASSEX); windowClass.style = CS_HREDRAW | CS_VREDRAW; windowClass.lpfnWndProc = WndProc; windowClass.cbClsExtra = 0; windowClass.cbWndExtra = 0; windowClass.hInstance = hInstance; windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); windowClass.hbrBackground = NULL; windowClass.lpszMenuName = NULL; windowClass.lpszClassName = "MyClass"; windowClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); /* Register window class*/ if (!RegisterClassEx(&windowClass)) { return 0; } /* Check if fullscreen is on*/ if (fullScreen) { DEVMODE dmScreenSettings; memset(&dmScreenSettings, 0, sizeof(dmScreenSettings)); dmScreenSettings.dmSize = sizeof(dmScreenSettings); dmScreenSettings.dmPelsWidth = width; //screen width dmScreenSettings.dmPelsHeight = height; //screen height dmScreenSettings.dmBitsPerPel = bits; //bits per pixel dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN != DISP_CHANGE_SUCCESSFUL)) { /* Setting display mode failed, switch to windowed*/ MessageBox(NULL, "Display mode failed", NULL, MB_OK); fullScreen = false; } } /* Check if fullscreen is still on*/ if (fullScreen) { dwExStyle = WS_EX_APPWINDOW; //window extended style dwStyle = WS_POPUP; //windows style ShowCursor(FALSE); //hide mouse pointer } else { dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; //window extended style dwStyle = WS_OVERLAPPEDWINDOW; //windows style } AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle); /* Class registerd, so now create our window*/ hwnd = CreateWindowEx(NULL, "MyClass", //class name "OpenGL Robot", //app name dwStyle | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, //x and y coords windowRect.right - windowRect.left, windowRect.bottom - windowRect.top,//width, height NULL, //handle to parent NULL, //handle to menu hInstance, //application instance NULL); //no xtra params /* Check if window creation failed (hwnd = null ?)*/ if (!hwnd) { return 0; } ShowWindow(hwnd, SW_SHOW); //display window UpdateWindow(hwnd); //update window done = false; //initialize loop condition variable /* Main message loop*/ while (!done) { PeekMessage(&msg, hwnd, NULL, NULL, PM_REMOVE); if (msg.message == WM_QUIT) //did we receive a quit message? { done = true; } else { Render(); TranslateMessage(&msg); DispatchMessage(&msg); } } if (fullScreen) { ChangeDisplaySettings(NULL, 0); ShowCursor(TRUE); } return msg.wParam; }
That's a lot of code! Spend some time studying the example, practice a bit, and then we will proceed to lesson 7, where more of this code will be explained. Also, in lesson 7 we will cover Projections.
Orthographic and Perspective Projections in OpenGL
Welcome to the seventh lesson in my OpenGL series! In this lesson we will learn about Projections and even put them to some simple use to see them in action. Some of you may have realized that we've used projection transformations in code before, and you're right, we have. The difference is now we will discuss how they work, and then demonstrate these concepts.
OpenGL consists of two general classes of projection transformations: orthographic (parallel) and perspective. So let's get into detail on both of these!
Orthographic Projections
Orthographic, or parallel, projections consist of those that involve no perspective correction. There is no adjustment for distance from the camera made in these projections, meaning objects on the screen will appear the same size no matter how close or far away they are.
Traditionally this type of projection was included in OpenGL for uses in CAD, or Computer Aided Design. Some uses of orthographic projections are making 2D games, or for creating isometric games. To setup this type of projection we use the OpenGL provided glOrtho() function.
Traditionally this type of projection was included in OpenGL for uses in CAD, or Computer Aided Design. Some uses of orthographic projections are making 2D games, or for creating isometric games. To setup this type of projection we use the OpenGL provided glOrtho() function.
glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
left and right specify the x-coordinate clipping planes, bottom and top specify the y-coordinate clipping planes, and near and far specify the distance to the z-coordinate clipping planes. Together these coordinates provide a box shaped viewing volume.
Since orthographic projections are commonly used in 2D scenes the Utility Library provides an additional routine to set them up for scenes that won't be using the z-coordinate.
Since orthographic projections are commonly used in 2D scenes the Utility Library provides an additional routine to set them up for scenes that won't be using the z-coordinate.
gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top);
Perspective Projections
Although orthographic projections can be interesting, perspective projections create more realistic looking scenes, so that's what you will most likely be using most often. In perspective projections, as an object gets farther from the viewer it will appear smaller on the screen- an effect often referred to as foreshortening. The viewing volume for a perspective projection is a frustum, which looks like a pyramid with the top cut off, with the narrow end toward the user.
There is a few different ways you can setup the view frustum, and thus the perspective projection. The first we will look at is as follows:
There is a few different ways you can setup the view frustum, and thus the perspective projection. The first we will look at is as follows:
void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
Using glFrustum enables you to specify an asymmetrical frustum, which can be very useful in some instances, but isn't what you typically want to do. For a different solution we again turn to the Utility Library:
void gluPerspective(GLdouble fov, GLdouble aspect, GLdouble near, GLdouble far);
fov specifies, in degrees, the angle in the y direction that is visible to the user; aspect is the aspect ratio of the scene, which is width divided by the height. This will determine the field of view in the x direction.
Ok, so let's look at some code. This code is taken right from OpenGL Game Programming. Take the time to study and manipulate the code, then you will be ready for lesson 8!
Ok, so let's look at some code. This code is taken right from OpenGL Game Programming. Take the time to study and manipulate the code, then you will be ready for lesson 8!
// OpenGLProjectionExample.cpp : Defines the entry point for the application. // #define WIN32_LEAN_AND_MEAN #pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glu32.lib") #pragma comment(lib, "glaux.lib") #pragma comment(linker, "/subsystem:windows") #include "stdafx.h" #include <windows.h> // standard Windows app include #include <winuser.h> // Windows constants #include <gl/gl.h> // standard OpenGL include #include <gl/glu.h> // OpenGL utilties #include <glut.h> // OpenGL utilties #define WND_CLASS_NAME "OpenGL Window Class" /*************************** Constants and Macros ***************************/ const int SCREEN_WIDTH = 500; const int SCREEN_HEIGHT = 500; const int SCREEN_BPP = 32; const bool USE_FULLSCREEN = false; const char *APP_TITLE = "Projections"; /********************************* Globals **********************************/ HDC g_hdc; // global device context HGLRC g_hrc; // global rendering context BOOL g_isFullscreen = TRUE; // toggles fullscreen and windowed display BOOL g_isActive = TRUE; // false if window is minimized HWND g_hwnd = NULL; // handle of our window HINSTANCE g_hInstance; // application instance /******************************** Prototypes ********************************/ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); BOOL SetupWindow(const char *title, int width, int height, int bits, bool isFullscreen); BOOL KillWindow(); GLvoid ResizeScene(GLsizei width, GLsizei height); BOOL InitializeScene(); BOOL DisplayScene(); BOOL Cleanup(); void UpdateProjection(GLboolean toggle = GL_FALSE); /***************************************************************************** WinMain() Windows entry point *****************************************************************************/ int WINAPI WinMain(HINSTANCE g_hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { MSG msg; // message BOOL isDone; // flag indicating when the app is done // if the window is set up correctly, we can proceed with the message loop if (SetupWindow(APP_TITLE, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, USE_FULLSCREEN)) isDone = FALSE; // otherwise, we need to never enter the loop and proceed to exit else isDone = TRUE; // main message loop while (!isDone) { if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) { if (msg.message == WM_QUIT) // do we receive a WM_QUIT message? { isDone = TRUE; // if so, time to quit the application } else { TranslateMessage(&msg); // translate and dispatch to event queue DispatchMessage(&msg); } } // don't update the scene if the app is minimized if (g_isActive) { // update the scene every time through the loop DisplayScene(); // switch the front and back buffers to display the updated scene SwapBuffers(g_hdc); } } Cleanup(); KillWindow(); return msg.wParam; } // end WinMain() /***************************************************************************** WndProc() Windows message handler *****************************************************************************/ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_ACTIVATE: // watch for the window being minimized and restored { if (!HIWORD(wParam)) { // program was restored or maximized g_isActive = TRUE; } else { // program was minimized g_isActive=FALSE; } return 0; } case WM_SYSCOMMAND: // look for screensavers and powersave mode { switch (wParam) { case SC_SCREENSAVE: // screensaver trying to start case SC_MONITORPOWER: // monitor going to powersave mode // returning 0 prevents either from happening return 0; default: break; } } break; case WM_CLOSE: // window is being closed { // send WM_QUIT to message queue PostQuitMessage(0); return 0; } case WM_SIZE: { // update perspective with new width and height ResizeScene(LOWORD(lParam), HIWORD(lParam)); return 0; } case WM_CHAR: { switch (toupper(wParam)) { case VK_SPACE: { UpdateProjection(GL_TRUE); return 0; } case VK_ESCAPE: { // send WM_QUIT to message queue PostQuitMessage(0); return 0; } default: break; }; } break; default: break; } return (DefWindowProc(hwnd, message, wParam, lParam)); } // end WndProc() /***************************************************************************** SetupWindow() Create the window and everything else we need, including the device and rendering context. If a fullscreen window has been requested but can't be created, the user will be prompted to attempt windowed mode. Finally, InitializeScene is called for application-specific setup. Returns TRUE if everything goes well, or FALSE if an unrecoverable error occurs. Note that if this is called twice within a program, KillWindow needs to be called before subsequent calls to SetupWindow. *****************************************************************************/ BOOL SetupWindow(const char *title, int width, int height, int bits, bool isFullscreen) { // set the global flag g_isFullscreen = isFullscreen; // get our instance handle g_hInstance = GetModuleHandle(NULL); WNDCLASSEX wc; // window class // fill out the window class structure wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = g_hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // default icon wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // windows logo small icon wc.hCursor = LoadCursor(NULL, IDC_ARROW); // default arrow wc.hbrBackground = NULL; // no background needed wc.lpszMenuName = NULL; // no menu wc.lpszClassName = WND_CLASS_NAME; // register the windows class if (!RegisterClassEx(&wc)) { MessageBox(NULL,"Unable to register the window class", "Error", MB_OK | MB_ICONEXCLAMATION); // exit and return FALSE return FALSE; } // if we're in fullscreen mode, set the display up for it if (g_isFullscreen) { // set up the device mode structure DEVMODE screenSettings; memset(&screenSettings,0,sizeof(screenSettings)); screenSettings.dmSize = sizeof(screenSettings); screenSettings.dmPelsWidth = width; // screen width screenSettings.dmPelsHeight = height; // screen height screenSettings.dmBitsPerPel = bits; // bits per pixel screenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; // attempt to switch to the resolution and bit depth we've selected if (ChangeDisplaySettings(&screenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { // if we can't get fullscreen, let them choose to quit or try windowed mode if (MessageBox(NULL, "Cannot run in the fullscreen mode at the selected resolution\n" "on your video card. Try windowed mode instead?", "OpenGL Game Programming", MB_YESNO | MB_ICONEXCLAMATION) == IDYES) { g_isFullscreen = FALSE; } else { return FALSE; } } } DWORD dwExStyle; DWORD dwStyle; // set the window style appropriately, depending on whether we're in fullscreen mode if (g_isFullscreen) { dwExStyle = WS_EX_APPWINDOW; dwStyle = WS_POPUP; // simple window with no borders or title bar ShowCursor(FALSE); // hide the cursor for now } else { dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; dwStyle = WS_OVERLAPPEDWINDOW; } // set up the window we're rendering to so that the top left corner is at (0,0) // and the bottom right corner is (height,width) RECT windowRect; windowRect.left = 0; windowRect.right = (LONG) width; windowRect.top = 0; windowRect.bottom = (LONG) height; // change the size of the rect to account for borders, etc. set by the style AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle); // class registered, so now create our window g_hwnd = CreateWindowEx(dwExStyle, // extended style WND_CLASS_NAME, // class name title, // app name dwStyle | // window style WS_CLIPCHILDREN | // required for WS_CLIPSIBLINGS, // using OpenGL 0, 0, // x,y coordinate windowRect.right - windowRect.left, // width windowRect.bottom - windowRect.top, // height NULL, // handle to parent NULL, // handle to menu g_hInstance, // application instance NULL); // no extra params // see if our window handle is valid if (!g_hwnd) { MessageBox(NULL, "Unable to create window", "Error", MB_OK | MB_ICONEXCLAMATION); return FALSE; } // get a device context if (!(g_hdc = GetDC(g_hwnd))) { MessageBox(NULL,"Unable to create device context", "Error", MB_OK | MB_ICONEXCLAMATION); return FALSE; } // set the pixel format we want PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // size of structure 1, // default version PFD_DRAW_TO_WINDOW | // window drawing support PFD_SUPPORT_OPENGL | // OpenGL support PFD_DOUBLEBUFFER, // double buffering support PFD_TYPE_RGBA, // RGBA color mode bits, // 32 bit color mode 0, 0, 0, 0, 0, 0, // ignore color bits, non-palettized mode 0, // no alpha buffer 0, // ignore shift bit 0, // no accumulation buffer 0, 0, 0, 0, // ignore accumulation bits 16, // 16 bit z-buffer size 8, // no stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main drawing plane 0, // reserved 0, 0, 0 }; // layer masks ignored GLuint pixelFormat; // choose best matching pixel format if (!(pixelFormat = ChoosePixelFormat(g_hdc, &pfd))) { MessageBox(NULL, "Can't find an appropriate pixel format", "Error", MB_OK | MB_ICONEXCLAMATION); return FALSE; } // set pixel format to device context if(!SetPixelFormat(g_hdc, pixelFormat,&pfd)) { MessageBox(NULL, "Unable to set pixel format", "Error", MB_OK | MB_ICONEXCLAMATION); return FALSE; } // create the OpenGL rendering context if (!(g_hrc = wglCreateContext(g_hdc))) { MessageBox(NULL, "Unable to create OpenGL rendering context", "Error",MB_OK | MB_ICONEXCLAMATION); return FALSE; } // now make the rendering context the active one if(!wglMakeCurrent(g_hdc, g_hrc)) { MessageBox(NULL,"Unable to activate OpenGL rendering context", "ERROR", MB_OK | MB_ICONEXCLAMATION); return FALSE; } // show the window in the forground, and set the keyboard focus to it ShowWindow(g_hwnd, SW_SHOW); SetForegroundWindow(g_hwnd); SetFocus(g_hwnd); // set up the perspective for the current screen size ResizeScene(width, height); // do one-time initialization if (!InitializeScene()) { MessageBox(NULL, "Initialization failed", "Error", MB_OK | MB_ICONEXCLAMATION); return FALSE; } return TRUE; } // end SetupWindow() /***************************************************************************** KillWindow() Deletes the DC, RC, and Window, and restores the original display. *****************************************************************************/ BOOL KillWindow() { // restore the original display if we're in fullscreen mode if (g_isFullscreen) { ChangeDisplaySettings(NULL, 0); ShowCursor(TRUE); } // if we have an RC, release it if (g_hrc) { // release the RC if (!wglMakeCurrent(NULL,NULL)) { MessageBox(NULL, "Unable to release rendering context", "Error", MB_OK | MB_ICONINFORMATION); } // delete the RC if (!wglDeleteContext(g_hrc)) { MessageBox(NULL, "Unable to delete rendering context", "Error", MB_OK | MB_ICONINFORMATION); } g_hrc = NULL; } // release the DC if we have one if (g_hdc && !ReleaseDC(g_hwnd, g_hdc)) { MessageBox(NULL, "Unable to release device context", "Error", MB_OK | MB_ICONINFORMATION); g_hdc = NULL; } // destroy the window if we have a valid handle if (g_hwnd && !DestroyWindow(g_hwnd)) { MessageBox(NULL, "Unable to destroy window", "Error", MB_OK | MB_ICONINFORMATION); g_hwnd = NULL; } // unregister our class so we can create a new one if we need to if (!UnregisterClass(WND_CLASS_NAME, g_hInstance)) { MessageBox(NULL, "Unable to unregister window class", "Error", MB_OK | MB_ICONINFORMATION); g_hInstance = NULL; } return TRUE; } // end KillWindow() /***************************************************************************** ResizeScene() Called once when the application starts and again every time the window is resized by the user. *****************************************************************************/ GLvoid ResizeScene(GLsizei width, GLsizei height) { // avoid divide by zero if (height==0) { height=1; } // reset the viewport to the new dimensions glViewport(0, 0, width, height); // set up the projection, without toggling the projection mode UpdateProjection(); } // end ResizeScene() /***************************************************************************** InitializeScene() Performs one-time application-specific setup. Returns FALSE on any failure. *****************************************************************************/ BOOL InitializeScene() { glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); return TRUE; } // end InitializeScene() /***************************************************************************** DisplayScene() The work of the application is done here. This is called every frame, and handles the actual rendering of the scene. *****************************************************************************/ BOOL DisplayScene() { GLfloat yellow[4] = { 1.0f, 1.0f, 0.2f, 1.0f }; GLfloat blue[4] = { 0.2f, 0.2f, 1.0f, 1.0f }; GLfloat green[4] = { 0.2f, 1.0f, 0.2f, 1.0f }; glLoadIdentity(); gluLookAt(-0.5, 1.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, yellow); glPushMatrix(); glTranslatef(0.3, 0.0, 1.0); glutSolidCube(0.5); glPopMatrix(); glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, blue); glPushMatrix(); glutSolidCube(0.5); glPopMatrix(); glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, green); glPushMatrix(); glTranslatef(-0.3, 0.0, -1.0); glutSolidCube(0.5); glPopMatrix(); return TRUE; } // end DisplayScene() /***************************************************************************** Cleanup() Called at the end of successful program execution. *****************************************************************************/ BOOL Cleanup() { return TRUE; } // end Cleanup() /**************************************************************************** UpdateProjection() Sets the current projection mode. If toggle is set to GL_TRUE, then the projection will be toggled between perspective and orthograpic. Otherwise, the previous selection will be used again. *****************************************************************************/ void UpdateProjection(GLboolean toggle) { static GLboolean s_usePerspective = GL_TRUE; // toggle the control variable if appropriate if (toggle) s_usePerspective = !s_usePerspective; // select the projection matrix and clear it out glMatrixMode(GL_PROJECTION); glLoadIdentity(); // choose the appropriate projection based on the currently toggled mode if (s_usePerspective) { // set the perspective with the appropriate aspect ratio glFrustum(-1.0, 1.0, -1.0, 1.0, 5, 100); } else { // set up an orthographic projection with the same near clip plane glOrtho(-1.0, 1.0, -1.0, 1.0, 5, 100); } // select modelview matrix and clear it out glMatrixMode(GL_MODELVIEW); } // end UpdateProjection
So now you have learned about Projections, and use them in code. In lesson 9 we will move on to Matrices.
Aucun commentaire:
Enregistrer un commentaire