|
Welcome back to a new installment of OpenGL and OS/2. I hope everyone
liked last months' column (well you waited long enough for it). If anyone
has managed to come up with interesting bump mappings, send them in and
I'll publish them here.
This month, because many of you do not have VisualAge to compile the
samples, I'll show you how to create OpenGL programs using the other
windowing toolkits.
Visit the Bookstore
Now available in the EDM/2
bookstore are two great titles that are a must have for every serious
OpenGL programmer. They are "OpenGL Programming Guide : The Official
Guide to Learning OpenGL", and "OpenGL Reference Manual : The Official
Reference Manual for OpenGL". The first describes OpenGL and the
different effects that you can do with it, while the second book is a
reference for all of the OpenGL commands. If you are short on cash, you
can get a great deal by getting the 1.0 version of these books. The newer
1.1 versions, however, describe the new OpenGL v1.1 commands, expand into
more detail on some of the descriptions and examples, and are a little
better organized.
A Quick Review
First, I will go back and show the basic rendering method using VisualAge.
This method has been used in all of the previous examples so it should be
very familiar. Here is the skeleton of what we were using. I have left
out the class definitions and have just shown the relevant functions.
void GLPaintHandler::initOpenGL()
{
int attribList[] = { PGL_RGBA, PGL_RED_SIZE, 2, PGL_GREEN_SIZE, 2,
PGL_BLUE_SIZE, 2, PGL_ALPHA_SIZE, 1, PGL_DOUBLEBUFFER, 0 };
// get a handle to anchor block
HAB hab = IThread::current().anchorBlock();
// create a visual config
PVISUALCONFIG pVisualConfig = pglChooseConfig( hab, attribList );
// create a direct OpenGL context
hgc = pglCreateContext( hab, pVisualConfig, NULL, true );
// and make the context we got the current one
Boolean val = pglMakeCurrent( hab, hgc, window.handle() );
// enable depth buffer
glEnable( GL_DEPTH_TEST );
glClearColor( 0.0, 0.0, 0.0, 1.0 );
glDrawBuffer( GL_FRONT_AND_BACK );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glDrawBuffer( GL_BACK );
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glShadeModel( GL_FLAT );
// free the visual config created above
delete [] pVisualConfig;
}
// openGL settings
void GLPaintHandler::setWindowSize( ISize size )
{
// setup the 3D perspective window
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( FOVY, (float)window.size().width() /
(float)window.size().height(),
MINVIEWDISTANCE, MAXVIEWDISTANCE );
glMatrixMode( GL_MODELVIEW );
// make a viewport the acual size of the window
glViewport( 0, 0, size.width(), size.height() );
}
Boolean GLPaintHandler::paintWindow( IPaintEvent &event )
{
// clear the buffer
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// no rotations
glLoadIdentity();
// draw something here ...
// swap the back draw buffer to the front
pglSwapBuffers( IThread::current().anchorBlock(), window.handle() );
// return true to indicate no additional processing needed
return( true );
}
// constructor
GLWindow::GLWindow()
: IFrameWindow( "OpenGL Demonstration 11 - visualAge", 0x1000,
IFrameWindow::defaultStyle()
| IFrameWindow::noMoveWithOwner | IWindow::synchPaint ),
canvas( 0x8008, this, this ),
paintHandler( canvas ),
resizeHandler( this )
{
// setup
setClient( &canvas );
setDestroyOnClose( true );
// draw on screen
sizeTo( ISize( 400, 400 ));
setFocus();
show();
// initialize openGL
paintHandler.initOpenGL();
// set the size of the 3D window
paintHandler.setWindowSize( canvas.size() );
// attach the handlers
paintHandler.handleEventsFor( &canvas );
resizeHandler.handleEventsFor( &canvas );
}
For those not familiar with C++, functions are grouped into structures
called classes. Classes are simply a way of organizing functions of
similar nature. You can tell what class a function is in by looking at
the name preceeding the :: before the function name. All of our OpenGL
calls have been grouped together in the GLPaintHandler class. Since those
functions consist of 'C' based OpenGL calls, they can be converted from
C++ to C by simply removing the class identifier 'GLPaintHandler::'.
Functions and data inside classes are grouped into three areas. 'public:'
means any other class can access the functions or data freely,
'protected:' means that only inherited classes can access those functions
and/or data (I won't get into that), and 'private:' means that only other
functions within the same class can access the functions or data. Just
think of a class as a structure that can contain functions, and is
divisible into different areas for organization and protection.
The GLWindow::GLWindow() function is a special function called a
constructor that gets called when the GLWindow class is created. In our
constructor we create a window (the IFrameWindow call), do some sizing,
call initOpenGL, and attach the handlers to the window. The handlers are
asynchronous classes called when something happens to the application: the
paint handler is called when the window requires painting, and the resize
hander is called when the window gets resized. Kind of logical huh? The
resize handler calls a function inside the paint handler called
setWindowSize(), and the repaint action on the paint handler calls the
function paintWindow(). Inside of paintWindow is where we place the
OpenGL draw commands.
void main()
{
// create the main window
GLWindow *glWindow = new GLWindow;
// main messaging processing loop
IThread::current().processMsgs();
}
The above code is for the main() function that gets called when the
application is stated. All it does in our case is create the GLWindow
class (which in turn calls the GLWindow constructor called above), and
then enters an infinite loop that processes window messages.
As you remember from previous columns, and as you can see from the code
above, we use the pgl*() calls to draw onto the window itself. Remember
that OpenGL itself cannot actually render to the screen, but requires some
sort of windowing interface. In OS/2 it is pgl, and in Windows it is wgl.
Three optional toolkits that work with OpenGL have the interface compiled
into them so you don't have to worry about it. They are called GLUT (GL
UTility), AUX (AUXiliary), and TK (Nano) toolkits. Using these toolkits
pretty well guarantees that you can simply recompile your code on any
platform you desire without changing the source. The only caveat is that
these toolkits are slower than if you coded using pgl or an equivalent.
The GLUT Toolkit
The GLUT toolkit is an optional component that you can download. If you
have the sample GLUT applications, you will see that one of the best
OpenGL samples, Atlantis, is a GLUT application.
The sequence for starting up a GLUT application is as follows:
void main()
{
glutInitWindowSize( 400, 400 );
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );
glutCreateWindow( "OpenGL Demonstration 11 - glut" );
// setup stuff in here
glutMainLoop();
}
The sequence shown here is very similar to the VisualAge opening sequence.
The following shows the equivalent functions in both GLUT and pgl.
glutInitWindowSize() -> sizeTo()
glutInit(), glutInitDisplayMode() -> functions inside initOpenGL()
glutCreateWindow() -> IFrameWindow() constructor, show()
glutMainLoop() -> IThread::current().processMsgs();
One thing that you may notice above is that we initialize GL (through
glutInit() and glutInitDisplayMode() ) before we actually create the
window glutCreateWindow(). This seemingly violates our rule stated in
previous columns that we have to show the window before we create the GL
context (the equivalent of plgCreateContext() is embedded in glutInit() ).
The rule actually is that we cannot create the context with a zero size
window. Normally a window size request does not take effect until after
the window is created. If you try to create the GL context before the
window is shown the first time, the context will fail because the window
has zero size before the initial showing. In GLUT however, you'll notice
that we have an extra call before glutInit() called glutInitWindowSize().
As you might suspect, this simply sets the size that the window is going
to be so that the context can be created successfully. The window is
actually created later with a glutCreateWindow() call.
The code so far simply sets everything up and displays the window. We
still have to create the functions that draw onto the screen and handle
window resizing, equivalent to the paintHandler() and resizeHandler()
functions in the VisualAge code above. There are a number of functions
that accomplish this, and they follow the pattern glut*Func(). The two
that match what we are looking for are glutDisplayFunc() and
glutReshapeFunc(). The parameter that you pass to these functions is
the address of the function that you wish called. For example, if I
wanted the function paintWindow() called when glut decided that the window
needs repainting, I would use the call glutDisplayFunc( paintWindow );
Similarly the reshape function would be:
glutReshapeFunc( setWindowSize );
Our paintWindow() function becomes:
void APIENTRY paintWindow( void )
{
// clear the buffer
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// no rotations
glLoadIdentity();
// draw something here ...
// swap the back draw buffer to the front
glutSwapBuffers();
}
And the reshape function is:
void APIENTRY setWindowSize(int width, int height)
{
// setup the 3D perspective window
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( FOVY, (float)width / (float)height,
MINVIEWDISTANCE, MAXVIEWDISTANCE );
glMatrixMode( GL_MODELVIEW );
// make a viewport the acual size of the window
glViewport( 0, 0, width, height );
}
There are all kinds of callback functions for most anything you want to
do. For those unfamiliar with the way callback functions work, the
callback function takes as an argument a name of a function. This
function will be called when the action corresponding to the callback
happens. For most of these callbacks, you can figure out what they do by
their name:
glutDisplayFunc()
glutReshapeFunc()
glutKeyboardFunc()
glutMouseFunc()
glutMotionFunc()
glutPassiveMotionFunc()
glutEntryFunc()
glutVisibilityFunc()
glutIdleFunc()
glutTimerFunc()
glutMenuStateFunc()
glutSpecialFunc()
glutSpaceballMotionFunc()
glutSpaceballRotateFunc()
glutSpaceballButtonFunc()
glutButtonBoxFunc()
glutDialsFunc()
glutTabletMotionFunc()
glutTabletButtonFunc()
As you can see, there is an extensive list of functions that do a variety
of effects and connect to a number of devices such as mice and spaceballs.
There are also a number of pre-built models that you can use:
glutWireSphere()
glutSolidSphere()
glutWireCone()
glutSolidCone()
glutWireCube()
glutSolidCube()
glutWireTorus()
glutSolidTorus()
glutWireDodecahedron()
glutSolidDodecahedron()
glutWireTeapot()
glutSolidTeapot()
glutWireOctahedron()
glutSolidOctahedron()
glutWireTetrahedron()
glutSolidTetrahedron()
glutWireIcosahedron()
glutSolidIcosahedron()
More information on the GLUT toolkit can be obtained from SGI's online
reference
The Auxilliary Toolkit
The Auxilliary Toolkit ( or AUX ) is similar to GLUT in that it allows you
to compile OpenGL apps with a portable toolkit instead of with a windowing
extension. The AUX library is smaller than the GLUT library and as such
has less functions. AUX is really only meant for quick prototyping
because of this lack of more advanced functions.
An AUX application would start up as follows:
void main( )
{
auxInitDisplayMode( AUX_DOUBLE | AUX_RGB | AUX_DEPTH );
auxInitPosition( 10, 10, 400, 400 );
auxInitWindow( "OpenGL Demonstration 11 - aux" );
// setup stuff in here
auxMainLoop( paintWindow );
}
One difference in the AUX toolkit from the GLUT toolkit is that the
display routine is integrated into the main loop routine. The only other
function we need to add for our example is the one for the reshape
function. The reshape function is (as you can probably guess by now):
auxReshapeFunc( setWindowSize );
The setWindowSize() function itself can remain exactly as it was. The
paintWindow() function needs to only change the line that swaps buffers to
the window; glutSwapBuffers() gets changed to auxSwapBuffers().
The AUX toolkit has callback functions similar to the GLUT toolkit
although there are fewer of them. The AUX callbacks are as follows:
auxMainLoop()
auxExposeFunc()
auxReshapeFunc()
auxIdleFunc()
auxKeyFunc()
auxKeyDownFunc()
auxMouseFunc()
auxMouseDownFunc()
auxMouseUpFunc()
auxMouseMoveFunc()
In addition to the callback functions, there are also a number of prebuilt
functions. You may remember that we used some of these models in previous
examples.
auxWireSphere()
auxSolidSphere()
auxWireCube()
auxSolidCube()
auxWireBox()
auxSolidBox()
auxWireTorus()
auxSolidTorus()
auxWireCylinder()
auxSolidCylinder()
auxWireIcosahedron()
auxSolidIcosahedron()
auxWireOctahedron()
auxSolidOctahedron()
auxWireTetrahedron()
auxSolidTetrahedron()
auxWireDodecahedron()
auxSolidDodecahedron()
auxWireCone()
auxSolidCone()
auxWireTeapot()
auxSolidTeapot()
The Nano Window Toolkit
Just when you thought you had the naming conventions down, ( GLUT
functions start with glut*() and AUXilliary functions start with aux*() ),
along comes the Nano toolkit that starts with tk*(). Although not as well
known as the other toolkits, it has all of the capability and functions of
the previous two.
Similar to before, the main routine becomes:
void main( )
{
tkInitDisplayMode( TK_DOUBLE | TK_RGB | TK_DEPTH );
tkInitPosition( 10, 10, 400, 400 );
tkInitWindow( "OpenGL Demonstration 11 - tk" );
// setup stuff in here
tkExec();
}
For this toolkit, the paint routine is setup with the callback:
tkDisplayFunc( paintWindow );
The actual bufferswap function inside the paint function now becomes
tkSwapBuffers();
The resize function is setup with
tkReshapeFunc( setWindowSize );
The callback functions allowable in the Nano toolkit are
tkExposeFunc()
tkReshapeFunc()
tkDisplayFunc()
tkKeyDownFunc()
tkMouseDownFunc()
tkMouseUpFunc()
tkMouseMoveFunc()
tkIdleFunc()
Like the other toolkits, this one also has a number of pre-built objects
that you can use.
tkWireSphere()
tkSolidSphere()
tkWireCube()
tkSolidCube()
tkWireBox()
tkSolidBox()
tkWireTorus()
tkSolidTorus()
tkWireCylinder()
tkSolidCylinder()
tkWireCone()
tkSolidCone()
PGL Toolkit
The toolkits mentioned above are great for prototyping or for simple
applications, but when it comes to a full application you need to use
OS/2's native windowing functions. This means using the pgl functions
directly, and using either the VisualAge libraries or the OS/2 toolkit.
Since all of the previous examples in the previous months have been with
VisualAge, and we have reviewed the VisualAge calls at the beginning of
the article, I will now give you a sample OpenGL application skeleton
using only the OS/2 toolkit.
In PM, the main window is setup as follows:
void main( void )
{
HAB hab = NULLHANDLE;
HMQ hmq = NULLHANDLE;
HWND hwndFrame = NULLHANDLE;
HWND hwndClient;
ULONG flCreate = 0UL;
QMSG qmsg;
int retVal = 1;
do
{
/* Initialize PM and create a message queue of default size */
if( ( hab = WinInitialize( 0UL ) ) == NULLHANDLE )
break;
if( ( hmq = WinCreateMsgQueue( hab, 0UL ) ) == NULLHANDLE )
break;
/* Register client window class */
if( !WinRegisterClass( hab, "PGL_Demo", ClientWindowProc,
CS_SIZEREDRAW, 0L ) )
break;
/* Create the windows */
flCreate = FCF_STANDARD & ~(FCF_ICON | FCF_MENU | FCF_ACCELTABLE);
hwndFrame = WinCreateStdWindow( HWND_DESKTOP, WS_VISIBLE, &flCreate,
"PGL_Demo", "OpenGL Demonstration 11 - pm" ,
CS_SIZEREDRAW, 0L, 0x1000, &hwndClient );
if( hwndFrame == NULLHANDLE )
break;
/* window created, initialize opengl */
initOpenGL();
/* Message loop */
while( WinGetMsg( hab, &qmsg, 0L, 0L, 0L ) )
WinDispatchMsg( hab, &qmsg );
retVal = 0;
} while( FALSE );
/* cleanup */
if ( hwndFrame != NULLHANDLE )
WinDestroyWindow( hwndFrame );
/* Destroy the message queue and release the anchor block */
if ( hmq != NULLHANDLE )
WinDestroyMsgQueue( hmq );
if ( hab != NULLHANDLE )
WinTerminate( hab );
return( retVal );
}
/* message handling function */
MRESULT EXPENTRY ClientWindowProc( HWND hwnd, ULONG msg,
MPARAM mp1, MPARAM mp2 )
{
HPS hps = NULLHANDLE;
switch( msg )
{
case WM_PAINT:
/* paint routine */
hps = WinBeginPaint( hwnd, NULLHANDLE, NULL );
/* call our opengl paint function */
paintWindow();
WinEndPaint( hps );
break;
case WM_SIZE:
/* tell new window resize to opengl */
setWindowSize( SHORT1FROMMP( mp2 ), SHORT2FROMMP( mp2 ) );
break;
}
return( NULL );
}
The paintWindow() and setWindowSize() are identical to functions stated
previously, except we are using pglSwapBuffers() to copy the buffer from
the back buffer to the screen. The initOpenGL() call that we are calling
in the main() routine, is identical to the initOpenGL() function listed in
the VisualAge C++ listing.
As you can see, the longest program by far is the PM version with pgl.
Most of this complexity is because of the PM initialization code and the
messaging function. The toolkits are the smallest which make them great
for prototyping; their only drawback being a lack of flexibility, and a
decrease in performance. As you can probably tell by now, I prefer using
the VisualAge C++ user interface libraries. You get all of the speed of
the PM version, with an object-oriented environment. The only real
drawback with VisualAge is the large size of the executable.
For completeness, you will find attached some code that displays a
sphere in a window. This application is not complex, the purpose being to
show the differences and similarities between each of the toolkits. I
tried to keep the structure between the five pieces of code as similar as
possible to help you see where the equivalent pieces of code are.
Visual Age C++, PGL
PM, PGL
GLUT
AUX
TK
Conclusions
That's it for this month. I hope this addresses some of the concerns that
I've received on how to use each of the toolkits. There is a lot more to
to each of the toolkits that I didn't go into, such as how to run timers,
create menus and such, but there is only so much I can describe here in
this column. GLUT is well described in the reference given above, and pgl
is used extensively in this column.
Next month, I'll be describing one of the great mathematical mysteries
of OpenGL - Non-Uniform Rational B-Splines, or NURBS for short. After
that, we'll start developing a real application together, one that I think
you'll have a lot of fun with.
|
|