▷▷▷▷▷▷ Follow My Threads Account ◁◁◁◁◁◁
手机版
OpenGL Tutorial (4)
- by Changhai Lu -
Abstract: In this lesson, we will enhance our program by
performing simple transformations, adding colors and handling window resizing messages. We will also
learn how to run an OpenGL program in full-screen mode.
<< Prev Tutorial
1. Let it rotate!
So far all the excercises we had are static. The key point of using OpenGL, however, is
to create dynamic graphics. So in this section, we will throw some dynamics into
the program. To be more specific, what we are going to do here in this section is to make the
triangle created in the previous lesson rotating. The code segment is very simple (as usual we will focus on
the part that is different from the previous lesson), but the structure and the logic of the program can
be used in much more general situations.
Notice that even though the triangle we had in the previous lesson looks static, it is drawn repetitively
by the (busy) message loop. Now if instead of drawing the same identical triangle again and again in the loop, we give it a
slightly different orientation each time, then the overall visual effect will be the same as that of
a rotating triangle. That is exactly what we will do. The following code demonstrates how we do it:
float angle = 0.0f; // A global variable for the anlge of rotation
// Do OpenGL rendering
void MyRendering() {
// Reset the back buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
angle = angle + 0.1f;
if (angle >= 360.0f) {
angle = 0.0f;
}
glRotatef(angle, 0.0f, 0.0f, 1.0f);
// 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);
}
| Fig-1: A Snapshot of the rotating triangle |
A snapshot of the running triangle is shown in Fig-1 (with the color effect
from the next section also applied).
Only the rendering function is shown here since that's where the modifications go.
A global variable angle is introduced
to keep track of the orientation of the triangle. It is initialized to zero.
In the rendering function MyRendering, we
increase the angle by 0.1, this way each time the triangle is
rendered (namely the MyRendering function is called),
it will be rotated 0.1 degree relative to its previous configuration.
When the angle reaches a full circle - 360 degrees,
we set it back to zero (if we don't do this, the program will still run, but the
float variable angle will grow unbounded and eventually run out
of range).
The actual rotation of the triangle is performed using the
glRotatef function. If you have experience in analytic geometry,
you probably have learned that there are always two ways to perform a transformation to
a geometric object: you either rotate the object directly, or rotate the coordinate system
while keeping the object fixed in the system. What glRotatef does is the latter,
namely it rotates the whole coordinate system and leaves the geometric object fixed in the coordinate system.
The beauty of this approach is you don't need to change the code - those enclosed in between the
glBegin/glEnd pair -
that draws the geometric object (because the drawing is relative to the coordinate system).
glRotatef takes the angle of rotation (in degrees) as its first argument. The other
three arguments specifies the direction of the axis of rotation (the z-axis in our case).
There are several other transformation functions available in the core OpenGL library, for instance the
glTranslatef function for translating objects and the glScalef
function for scaling objects. I will leave it for you to try out those transformations.
One thing you might ask about the program is: how fast will the triangle rotate?
If you look at the code, you will find nothing about time control. In the message loop (the while loop)
that repetitively invoke the MyRendering function, we could have setup a
timer that controls the frequency of calling the rendering function. We omitted this
for simplicity, so it is totally up to the computer system (especially its video card) that runs the
program to determine how fast MyRendering will be repeated.
If you find the triangle rotates too fast or too slow on your system, feel free to set the
increment of angle to a different value.
2. Adding color
Now that we all probably have stared too long on our little black-and-white triangle, it is the time to dress
it with some color. This can be done by a single line of code that invokes a function called
glColor3f, which sets the color for the rendering context
(therefore affects all the renderings that follow).
glColor3f takes three obvious arguments that represent the RGB (Red, Green, Blue) values
(scaled into the range of 0.0f - 1.0f) of the color.
In our example, the color is set to be red
(1.0f, 0.0f, 0.0f).
glColor3f is only one of the many OpenGL functions that manipulate colors,
check out the reference materials at the end of the lesson if you are interested in other similar functions.
The updated MyRendering
function is shown below:
// Do OpenGL rendering
void MyRendering() {
// Reset the back buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
angle = angle + 0.1f;
if (angle >= 360.0f) {
angle = 0.0f;
}
glRotatef(angle, 0.0f, 0.0f, 1.0f);
// Drawing - on the back buffer
glColor3f(1.0f, 0.0f, 0.0f);
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);
}
Before we have a something similar to the holodeck in Star Trek,
we will probably live with 2D computer screens for a while.
On a screen that is physically 2D, color effects are one of the major group of effects
that give us the illusion of 3D images. We will learn more about those effects as we move on.
3. Window resizing
If you played the program a little, you may have noticed that when you resize the application
window, the triangle doesn't adjust itself to fit the new window size. This is
because we haven't wrote the code to handle the window resizing message
- the WM_SIZE message - that is sent to the window procedure when the size of
the application window is changed. We will do it in this section.
To catch the WM_SIZE message, we simply add a
new branch - the WM_SIZE handler -
to the switch statement in the window procedure:
// 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;
case WM_SIZE:
height = HIWORD(lParam);
width = LOWORD(lParam);
glViewport(0, 0, width, height);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
The WM_SIZE handler is fairly simple in our case.
as we mentioned in Lesson One,
each Windows message carries some message-dependent extra information in the data fields
wParam and lParam. In the case of
a WM_SIZE message, the (32-bit double word) lParam
contains the width and the height (both are 16-bit values) of the new window in its lower and higher halves and
can be retrieved using two macro functions: LOWORD and HIWORD.
Once these informations are retrieved, we can set the rendering area
(also called the viewport) to fit the new window size.
This is done by the glViewport function that takes
the coordinate of the lower-left corner (defaults to 0, 0), the
width and the height of the viewport as arguments. All the renderings after this
will fit into the new window size.
Due to the 2-D characteristics of our example, we have ignored the complexity of setting up
a perspective view and updating the projection matrix (we haven't even introduced those
concepts so far). We will come back to these pieces when discussing truely 3-D examples.
4. Full-screen OpenGL
Many OpenGL programs, especially 3-D games, are running in the so-called
full-screen mode in which the application window takes the whole
screen. In this section, we
will learn how to setup a full-screen mode for our program. At a fairly low level,
Windows uses a data structure called DEVMODE to keep information
about the device initialization and environment of an output device (screen in our case).
We never mentioned this data structure before because the default values it carries work just fine
for all our previous examples that run in a standard window. To setup a full-screen mode, however, we have
to make changes to several data fields in DEVMODE.
These changes need to be done in the WinMain function before the creation of
the application window (which in this case should take up the whole screen).
The updated WinMain function is shown below:
// 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);
DEVMODE screenSettings;
// Specify values for relevant data fields
screenSettings.dmPelsWidth = 640;
screenSettings.dmPelsHeight = 480;
screenSettings.dmBitsPerPel = 16;
// Indicate which fields are manually initialized
screenSettings.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
// Apply changes
ChangeDisplaySettings(&screenSettings, CDS_FULLSCREEN);
// Create a window based on the window class
hwnd = CreateWindow("ExampleClass", "Example", WS_POPUP,
0, 0, 640, 480, 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;
}
Let's go through the code line by line as what we have been doing all the time.
We declare our own copy of the DEVMODE structure and
assign values to three data fields that are relevant to us:
dmPelsWidth, dmPelsHeight and
dmBitsPerPel, which contain the (horizontal and vertical)
screen resolution and the color depth for the
full-screen mode (you can use your favorite values instead of the values used in the example).
Since DEVMODE contains many other fields, and we don't want to
incidentally overwrite those other values by the un-initialized data fields from our copy of the
DEVMODE structure. Therefore it is important that
we indicate which fields should pick up the manually defined values. This is done by setting up
the relevant bit flags in a variable called dmFields (which itself is a data field in
DEVMODE). Each bit flags in dmFields specifies
whether certain members of the DEVMODE
structure have been initialized. If a field is initialized, its corresponding bit flag is set,
otherwise the bit flag is clear. Since we only need to set the the resolution and color depth, so
dmFields contains only those bits.
Finally, we change the setting of the display with the new DEVMODE by using the
ChangeDisplaySettings function. This function takes
the handle to the DEVMODE structure whose data fields have
been changed by us as its first argument.
The second argument it takes is a flag that indicates how the graphics mode should be changed.
The value we use - CDS_FULLSCREEN - removes the taskbar from the screen,
and is the least obtrusive one (what this means is all the changes we make will be temporary,
the display setting will go back to whatever mode it was when the program exits).
The ChangeDisplaySettings will check the dmFields
field and extract only those fields from our DEVMODE structure
that are indicated by dmFields.
Once again, I would like to say that in a real program, it is always helpful to write some
error checking code to make sure everything goes fine as expected. The
ChangeDisplaySettings function, for instance, will return
DISP_CHANGE_SUCCESSFUL on success, you can check this value and
do something if it is not equal to DISP_CHANGE_SUCCESSFUL.
Once the setting is successfully changed, we then continue the standard procedure of using the
CreateWindow function to create the application window. Of course, the size of the
application window should match the full-screen resolution we setup before, which is 640 × 480,
and we also change the style of the window to WS_POPUP which means there
will be no border for this window.
Another thing I would like to mention is that our program won't exit gracefully,
so if you run the program, you won't be able to stop it by,
say, pressing the ESC key. To exit the program, you will need to kill the process
(to do so, press Ctrl-Alt-Del, select it from the task manager and end it).
You are strongly encouraged to write a handler for the program so when you press the ESC key,
it exits.
That's all for this lesson. The concept and code are both pretty simple, but the technique and
code structure introduced in the lesson are standard and can be used in other programs.
What's next?
To be announced ...
-
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 March 12, 2003 https://www.changhai.org/
|