▷▷▷▷▷▷ Follow My Threads Account ◁◁◁◁◁◁
手机版
OpenGL Tutorial (3)
- by Changhai Lu -
Abstract: In this lesson, we will go over the
major OpenGL libraries, and then continue our journey by writing and
analyzing our first OpenGL program.
<< Prev Tutorial
1. OpenGL libraries
In the world of OpenGL programming, in addition to the basic OpenGL library,
you will constantly see two other libraries - the OpenGL Utility library (GLU)
and the OpenGL Utility Toolkit (GLUT) - come into the play. In this section, let's have a quick
review on all these major OpenGL libraries first.
OpenGL is designed as an efficient hardware-independent and cross-platform graphics interface
for 3D rendering and 3D hardware acceleration.
The core OpenGL library has the following two characteristics:
-
It only provides a small (but powerful) set of low-level drawing operations that handles
geometric primitives.
-
Pros: Allows the programmer a great deal of control and flexibility.
-
Cons: All higher-lever drawing needs to be done in terms of the basic operations which
means a great amount of work.
-
It doesn't include functions for interfacing with any particular windowing system or input devices.
-
Pros: It makes the OpenGL specification truely cross-platform.
-
Cons: Application developers must provide platform-specific functions for
the application to work on any given platform which again means a great amount of work.
It is to overcome the disadvantages carried by these two characteristics of the core OpenGL library,
that the GLU and GLUT libraries come to the rescue.
The GLU library was developed to provide both useful functions encapsulate the basic OpenGL commands
and complex components supporting advanced rendering techniques,
similar to the MFC in Windows programming. Apprently GLU is targeted on the first disadvantage
of the basic OpenGL library we mentioned above.
The GLUT library, on the other hand, was developed to provide a
platform-independent interface to the windowing system and
input devices, thus targeted on the second disadvantage of the basic OpenGL library.
The implementation of the GLUT library is of course platform-dependent, but the interface it provides to
the programmers is platform-independent. GLUT is quite good for small programs such as as demos,
but is usually not powerful and flexible enough to support real applications.
This tutorial assumes you use Visual C++ 6.0 on Windows. With Visual C++ 6.0 installed,
you should already have all the necessary files for the basic OpenGL and GLU libriries.
GLUT, on the other hand, is probably not included. The OpenGL website will provide you with
sufficient information for getting GLUT whenever needed.
You might see another library called the AUX library in the literature. The AUX library was
developed early in the OpenGL history and is considered obsolete and replaced by GLUT now.
In addition to these major libraries, most operating systems provide additional supports to OpenGL
(for instance the wiggle functions on Windows API),
we will introduce them when needed.
The following is a list of the locations of all the files needed for using each of the
three major OpenGL libraries:
- The OpenGL library
- Header File: [Compiler Directory]\Include\GL\GL.H
- Lib File File: [Compiler Directory]\Lib\OPENGL32.LIB
- DLL File: [System Directory]\OPENGL32.DLL
- The GLU library
- Header File: [Compiler Directory]\Include\GL\GLU.H
- Lib File File: [Compiler Directory]\Lib\GLU32.LIB
- DLL File: [System Directory]\GLU32.DLL
- The GLUT library
- Header File: [Compiler Directory]\Include\GL\GLUT.H
- Lib File File: [Compiler Directory]\Lib\GLUT32.LIB
- DLL File: [System Directory]\GLUT32.DLL
Where [Compiler Directory] is the Visual C++ compiler directory,
usually at C:\Program Files\Microsoft Visual Studio\VC98.
[System Directory] is the Windows system directory, depending
on the type of your system, usually at C:\WINNT\System32
(for NT series) or C:\Windows\System (for 9x series).
Another thing that is helpful to remember is: functions in those libraries fall
into a nice naming scheme so you can easily tell to which library a function belongs.
The scheme is fairly simple:
names for OpenGL/GLU/GLUT functions have prefix gl/glu/glut.
2. First OpenGL program
| Fig-1: First OpenGL program |
Now we are ready to write our first OpenGL program which will draw
a triangle on the screen (Fig-1).
You might wonder why don't we continue our "Hello world!"
tradition and draw a text string as our first OpenGL program? The reason lies in the fact that
OpenGL is designed primarily for rendering graphics. Viewed as graphical objects,
texts are certain more "advanced" and thus more complicated than triangles.
Triangle is, in some sense, the atom in the OpenGL world. Most video
cards construct complicated objects using trangles.
As we learned in
Lesson Two, the Windows GDI makes use of a device context to
keep track of settings (colors, fonts, etc) of GDI drawing functions. OpenGL, being platform independent,
has its own data structure called rendering context
that does a similar job for the OpenGL drawing functions.
Before any OpenGL drawing can be performed a rendering context must
be specified, which - under Windows - requires a device context
(and particularly its pixel format) be specified first
so the rendering context can attach to.
As we said before, OpenGL doesn't include any function to interface with its host operating system.
In our example, even though everything is simple, a little bridge connecting the OpenGL
rendering context and the Windows device context is still needed. Fortunately, such a
bridge is already available on all the major operating systems. On Windows, it is
provided by a subset of the
so-called wiggle functions (whose names are all prefixed with wgl).
OpenGL allows for multiple rendering
contexts to be created for a single device context, therefore it is necessary that
you specifically make a rendering context current after its creation (and before its using).
In synthesize, the steps you need to go before doing an OpenGL rendering are the following:
- Get a handle to the device context
- Setup pixel format for the device context
- Create a rendering context associated to the device context
- Make the rendering context current
These steps should be done in the WM_CREATE handler when a
program starts.
Now let's take a look at the code:
#include <windows.h>
#include <gl/gl.h>
HDC global_hdc; // A global handle to the device context
// Setup pixel format for the device context
void SetupPixelFormat(HDC hdc)
{
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), 1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0
};
int index = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(hdc, index, &pfd);
}
// Do OpenGL rendering
void MyRendering() {
// Reset the back buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
// Drawing - on the back buffer
glBegin(GL_TRIANGLES);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 0.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 0.0f);
glEnd();
// Swap the back buffer with the front buffer
SwapBuffers(global_hdc);
}
// Window procedure - the message handler
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc = NULL;
HGLRC hrc = NULL;
PAINTSTRUCT ps;
switch (msg) {
case WM_CREATE:
// Get a handle to the device context
hdc = BeginPaint(hwnd, &ps);
global_hdc = hdc;
// Setup pixel format for the device context
SetupPixelFormat(hdc);
// Create a rendering context associated to the device context
hrc = wglCreateContext(hdc);
// Make the rendering context current
wglMakeCurrent(hdc, hrc);
break;
case WM_CLOSE:
// De-select the rendering context
wglMakeCurrent(hdc, NULL);
// Release the rendering context
wglDeleteContext(hrc);
// Release the device context
EndPaint(hwnd, &ps);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
// WinMain function - the entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int iCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
// Specify a window class
wndclass.style = 0;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = "ExampleClass";
// Register the window class
RegisterClass(&wndclass);
// Create a window based on the window class
hwnd = CreateWindow("ExampleClass", "Example", WS_OVERLAPPEDWINDOW,
0, 0, 200, 200, NULL, NULL, hInstance, NULL);
// Display the window on the screen
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
// An alternative message loop
bool quit = false;
while(!quit) {
PeekMessage(&msg, hwnd, NULL, NULL, PM_REMOVE);
if (msg.message == WM_QUIT) {
quit = true;
} else {
MyRendering();
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
The code is developed in the framework set in the previous lessons, and
- as a basic philosophy of all my tutorials - is made as simple as possible.
Everything that stays the same as before is gray out
for us to focus on the new part.
To test the program, simply create an empty Win32 Application, paste the code over and
link the OpenGL library to the program (to do so, go to the
Project menu, open the Settings
window, click on the Link tab and add
opengl32.lib into the beginning of the
Object/library modules list). This example is so simple that
no GLU or GLUT library is ever needed. The Windows wiggle functions are declared in a
header file called wingdi.h that is already included in the master
header file windows.h (so you don't see it explicitly in the program).
You should see Fig-1 as the output of the program.
The first thing we look at is the code in the WM_CREATE handler
that implements the four steps of setting a rendering context
(notice the comments in the code are in one-to-one correspondence with
the steps): A handle to the device context is obtained using the BeginPaint
function similar to what we did in the Win32 "Hello world!" example. We then assign this handle to
a global variable global_hdc so other functions
(to be more specific: the MyRendering function) can access it.
To make the code a bit more modular, we introduced a function SetupPixelFormat to
setup the pixel format for the device context and invoke this function in the message handler to accomplish
the 2nd step. The SetupPixelFormat function takes the device context
as input. The first thing it does is to specify a data structure called PIXELFORMATDESCRIPTOR
which Windows uses to describe the pixel format of a device context. Among the values
assigned to PIXELFORMATDESCRIPTOR,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER
means to support window, OpenGL and something called the double buffering.
(we don't need double buffering for the current static drawing example, but
we leave it here so as we move on there will be no need to touch this part of code again).
32 is color mode (32 bit color). A complete description of
the fields in PIXELFORMATDESCRIPTOR can be found in Reference 1 and 4
listed below. What you specify in the PIXELFORMATDESCRIPTOR
may not be supported by the device context (for instance you may have specified a 32 bit color mode
while the device context only supports 24 bit color), so we use the function
ChoosePixelFormat that examines the device context and returns an index
of the best match to the specified PIXELFORMATDESCRIPTOR.
This index is then used by the SetPixelFormat function to finally
setup the pixel format for the device context. The 3rd and 4th steps are done
straight-forwardly using two wiggle functions:
wglCreateContext and wglMakeCurrent.
As all the four steps are finished, we are ready to do the rendering. As explained in the
Appendix in Lesson One, we place the rendering code in the message loop.
We wrapped the rendering code into a function MyRendering and
invokes that function in the loop, that's the only line of new code in the
WinMain function.
In the function MyRendering,
the glClear function at the beginning clears out both the
color buffer and depth buffer, which makes the background of buffer black.
The glLoadIdentity function brings the coordinate system back
to the default configuration which is located at
the center of the window (or screen if the program is running in full-screen mode) and has its
x-axis pointing to the right, y-axis pointing to the up and z-axis pointing towards the user.
In other words, the glClear function resets the graphic properties
while the glLoadIdentity function resets the geometric
properties of the buffer. Together they reset the buffer.
After reset, we begin to draw the triangle. Most primitive objects in OpenGL are drawn
by enclosing a series of coordinate sets that specify vertices and (optionally) other
informations between Begin/End pairs.
The glBegin function takes a primitive type as input which
basically tells the program what primitive objects to expect. In our case the mode value is
GL_TRIANGLE which means what's in between the
Begin/End pair are vertices for triangle(s). Each vertex in OpenGL is specified by
calling the Vertex3f function which takes the x, y, z
coordinates of the vertex as input. The values of these coordinates are all in relative
scale. By default, a value 1.0 for a coordinate
represents the distance from the center of
the screen to the edge of the screen alone that coordinate
(so 1.0 along
different axis doesn't have to have the same absolute length unless
the screen happens to be square), thus the three vertices in our example are:
the center of the screen (0.0f, 0.0f, 0.0f),
the right edge of the screen (1.0f, 0.0f, 0.0f)
and the up-right corner of the screen (1.0f, 1.0f, 0.0f),
as we can see in Fig-1.
All these three vertices sit on the plane of the screen (because the z-coordinate is zero).
Remember that we are using the double buffering technique, all the drawing are
done on the (invisible) back buffer. So once the drawing is done, we swap the back buffer
with the front buffer for the device context using the SwapBuffers
Win32 API function
(which takes the handle to the device context as input). Now the triangle is displayed on the screen.
The last thing I would like to mention here is that if you looked carefully into the code,
you might have noticed a little difference between the rendering code in Win32 and OpenGL.
In Win32, every GDI function requires a handle to a device context to work, while
OpenGL rendering functions are - as shown in our example -
used without acquiring a handle to a rendering context. This is because the
function wglMakeCurrent makes it unambiguous
for the program to know which rendering context should be used.
That's pretty much all we need to do for our first OpenGL program.
At the end, we release our rendering context
and device context in the WM_CLOSE handler right
before the program exits. The release of the resources should be done in the following order
(in one-to-one correspondence with the comments in the code):
- De-select the rendering context -
by making NULL as the current rendering context
- Release the rendering context -
by calling the wglDeleteContext function
- Release the device context -
by calling the EndPaint function
What's next?
In the next lesson, we will enhance our program by
doing transformations, adding colors and handling window resizing messages. We will also
learn how to setup a full-screen mode for OpenGL programs.
>> Next Tutorial
Double buffering -
Double buffering is a technique used by graphic systems to support smooth
animation through two color buffers: the front color buffer and back color buffer
(both are associated to the device context).
With double buffering, you render all your scenes in the (off-screen) back buffer. After
the scene is finished rendering, you swap the back buffer with the front buffer.
This way, the rendering process is hidden from the user.
Double buffering technique eliminates the flickering effects that usually
arise when scenes are rendered only on the front buffer.
-
Kevin Hawkins, Dave Astle, et al,
OpenGL Game Programming, Prima Publishing, 2001.
-
NeHe Productions, NeHe OpenGL Tutorials, Available on the web.
-
Mark Segal, Kurt Akeley,
The OpenGL Graphics System: A Specification (Version 1.4), 2002.
-
Microsoft Corporation, MSDN Library, comes with Visual Studio 6.0, also available
on the Microsoft website.
posted on December 6, 2002 https://www.changhai.org/
|