Getting Started
Introduction
Sample Programs
IDEs
History
Advice
Mini-Tutorial
Tutorials
Code Snippets

Resources
Web Sites
More Tutorials
Forums
Vendors/Tools
Books
Magazines
Newsletters
NewsGroups
User Groups
Talk Shows
Blogs

Controls
Overview
Button
Check3State
Checkbox
ComboBox
Frame
Graphic
Image
ImageX
ImgButton
ImgButtonX
Label
Line
ListBox
ListView
Option
Progress Bar
Scrollbar
StatusBar
TAB
TextBox
Toolbar
TreeView

GBIC >> PowerBASIC >> Magazines

OpenGL for PowerBASIC - Pathway To 3D Graphics
The built in Windows graphics API (GDI and the newer GDI+) are essentially 2D graphics technologies. They can be used to create 3D graphics but can require a significant amount of coding and are subject to noticeable performance limitations.

Fortunately, several alternative technologies exist which can make it easier for a programmer to create 3D graphics while maintaining the highest performance levels. DirectX and OpenGL are the two most popular technologies, and this tutorial focuses on OpenGL.

My own observation is that using OpenGL and using API (which PowerBASIC programmers already use extensively) are so similar that it is conceptually easier to learn and to utilize OpenGL in PowerBASIC applications. However, both technologies have their champions and both have their application successes. So this tutorial spends no time justifying one technology over the other. It simply tries to introduce PowerBASIC programmers to OpenGL and to provide extensive, informative source code examples on how to utilize OpenGL in PowerBASIC applications.

Here are the topics covered in this tutorial.


Here's an early word of encouragement to programmers new to OpenGL. Even though OpenGL consists of almost 300 commands, all of my tutorials were written using less than 50 of those commands. The point is that just like with API, programmers rarely have to learn all of the OpenGL commands in order to create their applications.

This tutorial is a bit long, but represents information I've found to be essential for understanding how to use OpenGL in my PowerBASIC apps. But if you're looking for a quick "bottom line" to the tutorial, give this source code a test drive. It's a handy template for starting an OpenGl application and comes with several user-interface routines that are not available from OpenGL.


Return to top of document

Tutorial Source Code

In case you want to jump straight to the various source code examples, here are links to the code found within this tutorial. The tutorial is designed to be read top to bottom, but feel free to jump around as you wish.

In addition to the source code embedded in this tutorial, the next section provides links to source code that I've placed online at the gbSnippets source code page.


Return to top of document

gbSnippets Source Code

I've also created an expanded set of OpenGL examples and placed them in my gbSnippets PowerBASIC Source Code library. The code is available as part of the gbSnippets distribution and is also available for viewing online.

These snippets, like those in the tutorials, are considered as beginner-intermediate examples of using OpenGL in PowerBASIC applications.

I'll be adding several more snippets over the next few days.



Return to top of document

OpenGL Introduction

The new Graphic commands of PowerBASIC are essentially wrappers for the GDI graphic functions, making it much easier to add 2D graphics to your PowerBASIC applications.

The newer GDI+ API improve on GDI by adding new features and by optimizing existing features. However, there are no PowerBASIC commands which wrap the newer GDI+ API, which provide class-based 2D vector graphics.

The take away from both statements is that the GDI/GDI+ API focus on 2D graphics. And while these API can be used to generate 3D graphics, there are more powerful solutions available to PowerBASIC programmers.

The two best known solutions are DirectX and OpenGL, both of which are included in the Windows distribution. DirectX is Microsoft's offering for enhanced graphics, whereas OpenGL is an open source solution. Both technologies have their proponents and both have significant success stories in terms of commercial applications (particularly games).

Neither approach would ever be described as "easy to learn", as each requires a significant commitment of time by programmers wanting to use the technologies in their applications. However, despite their potentially steep learning curves both technologies are used successfully by programmers all over the world.

For PowerBASIC programmers, the difficulty in getting started with OpenGL is that there are no books or how-to documents that comprehensively walk through the use of OpenGL in PowerBASIC applications. This tutorial looks to satisfy that need - the information needed to get started with OpenGL in PowerBASIC applications.

While OpenGL introduces several 3D-related concepts that are not a part of PowerBASIC, the use of OpenGL functions is nonetheless very similar to using PowerBASIC commands. There are perhaps 25-50 OpenGL commands needed to get a good start in OpenGL - well within the skills of most PowerBASIC programmers.

The more critical aspect of learning to use OpenGL is to understand the 3D concepts and how the various OpenGL commands play together to create an image on the screen.

Finally here are three key facts about OpenGL that will be repeated often throughout this tutorial.

  • 1. OpenGL cannot create windows
    As you will see, you will use PowerBASIC and API functions to create the window in which OpenGL will work

  • 2. OpenGL cannot receive user inputs
    Mouse and keyboard events are not detected by OpenGL. You will use PowerBASIC and API functions to monitor mouse and keyboard activity.

  • 3. OpenGL is Part of the Windows Distribution
    If you include OpenGL in your PowerBASIC application, you do not have to distribute the OpenGL DLLs (opengl32.dll and glu32.dll). They are already part of every Windows distribution.

The significance of items #1 and #2 is that 90% of your OpenGL application will be written using the PowerBASIC functions you're already familiar with!

Creating an OpenGL application, then, basically consists of 3 parts:

  • 1. Use Window's API to create an OpenGL-compatible drawing canvas (dialog or control)
  • 2. Use PowerBASIC code to detect user actions (mouse/keyboard)
  • 3. Use OpenGL to draw your 3D scenes

You'll see that using OpenGL is just as straight-forward as the above list suggests. But as they say, "The Devil is in the details!", and that is exactly what this tutorial is all about - showing you the details of how to use OpenGL in your PowerBASIC application with both discussion and source code examples.


Return to top of document

OpenGL Library Files

Before going too far, here is some information about the two OpenGL files (DLLs) which provide all of the OpenGL functions.

    OpenGL is part of the Windows distribution.

A great thing about OpenGL is that is already part of Windows. The OpenGL files are included as a part of every Windows installation. So when you distribute an application that uses OpenGL functions, you do not have to include the OpenGL library files in your distribution file(s)!

The two OpenGL DLLs are:

  • opengl32.dll - primary file containing most drawing commands

  • glu32.dll - commonly used graphics routines, quadric surfaces (spheres, cones, open cylinders, tessellated disks), polygonal surfaces (concave polygons, polygons with holes, self-intersecting polygons, ...), NURBS, matrix and mipmap utilities.

When we get to using OpenGL commands, you'll see that any commands coming from the opengl32.dll use the prefix "gl", whereas functions that come from glu32.dll all use the prefix "glu".

The opengl32.dll library contains the majority of OpenGL features. The glu32.dll library provides additional capabilities such as commonly used graphic routines, surface routines (spheres, cones, various polygons, NURBS and other utilities).

I suggest that you visit, and become familiar with the content of the OpenGL Home Page and the MSDN pages on OpenGL.

OpenGL Include Files
Using OpenGL in PowerBASIC applications is much simpler thanks to the efforts of Jose Roca. The declarations for using OpenGL functions can be found at his web site. The OpenGL includes are part of the download that contains his free Windows API includes.

He also has a section of his forums dedicated to OpenGL.

Place this two OpenGL includes (gl.inc and glu.inc) in your PowerBASIC include folder (c:\pbwin90\winapi on my PC). Then put these two lines in your PowerBASIC application.

   #Include "gl.inc"
   #Include "glu.inc"

That's it. Now, you can use all of the OpenGL functions just as though you had written them yourself.

Thank you Jose!

GLUT
A third available library, called GLUT (OpenGL Utility Kit, glut32.dll), is also freely available but is not distributed with Windows. GLUT is not considered part of OpenGL, but has been very popular with OpenGL users because it provides functions to create windows as well as to detect keyboard/mouse events.

GLUT was last updated in 2001 but is still used by many programmers. There is also a replacement called freeGLUT, which continues to be developed. The web site are each are here:

You can find the PowerBASIC includes for both of these "GLUT" libraries at Jose Roca's web site.

Even though you can perform many of GLUT's capabilities with Windows API, GLUT is popular because many programmers do not like to work at the API level. Also, GLUT has been compiled for several operating systems, so programmers who need to port their applications between different OS's may prefer to use GLUT.

Because PowerBASIC and API statements can replicate many of GLUT's features, I don't recommend the use of GLUT. However, GLUT does provide some other useful utilities, such as functions to create objects (such as wireframe/solid cubes) that are more complex than the primitives supported by OpenGL. So many PowerBASIC programmers may still find it useful.

This tutorial does not make use of GLUT. However, be aware that GLUT seems to be quite popular in college courses on OpenGL and GLUT is prevalent in many of the tutorials that are found on the web.


Return to top of document

Elements of OpenGL

The following is a list of the basic features supported by OpenGL. Those items marked with an * covered in this tutorial.

  • Drawing Geometry and Clearing the Screen *
  • Points, Lines, Triangles, Polygons *
  • Images and Bitmaps
  • Transformations *
  • Colors and Shading *
  • Blending and Antialiasing
  • Lighting and Texturing *
  • Hidden Surface Removal *
  • Display Lists *
  • Fog and Depth Cueing
  • Accumulation Buffer
  • Stencil Planes
  • Feedback and Selection
  • Evaluators (NURBS)

This might seem to be a fairly long list. But since OpenGL has about 300 functions, that figures out to only about 15-20 OpenGL commands per topic. That's not a trivial exercise in learning, but each category is small enough that taking on one at a time is not so formidable.

Plus, remember as I mentioned earlier, that less than 50 commands were needed to create this tutorial and source code examples. Thus, getting familiar with each of the above topics require learning only a relatively small number of commands. Getting started in OpenGL does not require that you immediately learn all of the available OpenGL functions. This is exactly like the learning curve with API - you start off with a few functions that you need and build your repertoire over time.

The items marked with an * are not covered in this tutorial, but I expect to add coverage with the next few weeks.


Return to top of document

Getting Started: Windows Code Only

Here are two key facts to know about OpenGL:

  • OpenGL works within a window provided by the Windows operating system - there are no OpenGL API for creating a window.
  • OpenGL has no commands for obtaining user input - it does not recognize mouse or keyboard events

So the PowerBASIC programmer must create the window using either DDT or SDK options, and must provide the code for detecting mouse or keyboards inputs from the user.

Rendering Context
However, whereas Windows API work with a device context, OpenGL functions work with what is called a rendering context. Regardless of the name, drawing takes place in a window, which can be a dialog or a child window (control) of a dialog.

Rendering contexts are always associated with a device context, which is in turn associated with a window. So while you can think of a rendering context as the canvas on which the OpenGL graphics will be drawn, it is the underlying window where the graphics will be displayed.

So the sequence of coding events to get a PowerBASIC application ready to use OpenGL commands is:

  • PowerBASIC creates a window (SDK or DDT)
  • A window (dialog or control) device context is retrieved
  • The device context properties are set to be compatible with OpenGL
  • A rendering context is created and associated with the device context
  • Programmer uses OpenGL functions to draw on the rendering context
  • The drawings are visible on the window

With the rendering context in hand, the entire range of OpenGL functions are then available.

OpenGL Compatible Device Context
Before a device context can be used to create a rendering context, the pixel format properties of the device context must be made compatible with a rendering context. In particular, two Windows API are used.

  • ChoosePixelFormat - returns nearest pixel format matching requested properties
  • SetPixelFormat - sets device context to the value returned by ChoosePixelFormat

Using ChoosePixelFormat requires that a programmer provide format information using a variable of type PIXELFORMATDESCRIPTON. That type has quite a few members, but as you'll see in the example coming up that only a few of the members require setting. Default values of most members are used.

Creating a Rendering Context
With an OpenGL-compatible device context in hand, the programmer can then use one Windows API to create, select or delete a rendering context. Note again - these are Windows API - not OpenGL functions.

Their names are pretty self-explanatory, and are very similar to the API PowerBASIC programmers already use to create, select, and delete window device contexts. Because of the "wgl" prefix used in the API names, these rendering context management API are often referred to as "wiggle" functions. There three API are all that is usually needed to work with rendering contexts in a PowerBASIC application.

  • wglCreateContext - create a rendering context (requires a windows DC)
  • wglMakeCurrent - selects a rendering context (multiple contexts are possible)
  • wglDeleteCurrent - delete the context when you're done with it.

The wglMakeCurrent function suggests that an application may contain several rendering contexts. While true, only one rendering context may be selected (made current) at any time.

With a rendering context in hand, a PowerBASIC programmer can use OpenGL functions to draw graphics in the dialog or control which was selected as the canvas (whose device context was used with the wiggle functions to create the rendering context).

Using the Windows functions just described, the following example demonstrates the code needed to create a rendering context and make the application ready to use with OpenGL statements. As you can see, it first sets the desired pixel format properties, creates a device context for the canvas, and finally creates a rendering context that is used with OpenGL commands.

     Sub GetRenderContext
        Local pfd As PIXELFORMATDESCRIPTOR, fmt As Long

        'fill variable with desired pixel format properties
        pfd.nSize       =  SizeOf(PIXELFORMATDESCRIPTOR)
        pfd.nVersion    =  1
        pfd.dwFlags     = %PFD_DRAW_TO_WINDOW Or _ 
                          %PFD_SUPPORT_OPENGL Or %PFD_DOUBLEBUFFER
        pfd.dwlayermask = %pfd_main_plane
        pfd.iPixelType  = %PFD_TYPE_RGBA
        pfd.ccolorbits  = 24
        pfd.cdepthbits  = 24

        'create render context and make it current
        hDC = GetDC(hDlg)                 'DC for dialog
        fmt = ChoosePixelFormat(hDC, pfd)
        SetPixelFormat(hDC, fmt, pfd)     'set device context properties
        hRC = wglCreateContext (hDC)      'get rendering context
        wglMakeCurrent hDC, hRC           'make the RC current
     End Sub

In this example, the rendering is to be done directly on a dialog. A child control (label, graphic control) could just as well have been used.

And just for completeness, here is the code to use to delete the rendering context when the PowerBASIC program shuts down. This is similar to the action taken to delete a device context when an application closes.

    Case %WM_Close
        wglmakecurrent %null, %null
        wgldeletecontext hRC

Note again that no OpenGL statements were used in the above code. The Win32API.inc file has the declarations for all of the functions and equates that were used.

As was noted before, the variable pfd (type PIXELFORMATDESCRIPTOR) has several members other than those shown in the example, but their default values are compatible with OpenGL and do not generally need to be changed.

Sample OpenGL Applicaton
Before we get into the details of using individual OpenGL functions we need to go over several OpenGL 3D technology concepts. But rather than make you wait, here's a quick peek of what is to come.

This next listing takes the code above, which creates a rendering context, and adds just the minimum OpenGL code needed to add some OpenGL graphics (a triangle). It doesn't do much, but it is a complete PowerBASIC application that uses OpenGL to create graphics.

    #Compile Exe
    #Dim All
    #Include "win32api.inc"
    #Include "gl.inc"
    #Include "glu.inc"
    Global hDlg, hDC, hRC As DWord

    Function PBMain() As Long
      Dialog New Pixels, 0, "OpenGL Example",,, 320, 240, _
                               %WS_OverlappedWindow To hDlg
      Dialog Show Modal hdlg Call dlgproc
    End Function

    CallBack Function dlgproc()
       Select Case CB.Msg
          Case %WM_InitDialog
              GetRenderContext
              InitializeScene
          Case %WM_Size
              ResizeScene Lo(Word, CB.lParam), Hi(Word, CB.lParam)
              DrawScene
          Case %WM_Paint
              DrawScene
          Case %WM_Close
              wglmakecurrent %null, %null 'unselect rendering context
              wgldeletecontext hRC        'delete the rendering context
       End Select
    End Function

    Sub GetRenderContext
       Local pfd As PIXELFORMATDESCRIPTOR, fmt As Long
       pfd.nSize       =  SizeOf(PIXELFORMATDESCRIPTOR)
       pfd.nVersion    =  1
       pfd.dwFlags     = %pfd_draw_to_window Or _
                         %pfd_support_opengl Or %pfd_doublebuffer
       pfd.dwlayermask = %pfd_main_plane
       pfd.iPixelType  = %pfd_type_rgba
       pfd.ccolorbits  = 24
       pfd.cdepthbits  = 24

       hDC = GetDC(hDlg)                 'DC for dialog
       fmt = ChoosePixelFormat(hDC, pfd) 'set device context properties
       SetPixelFormat(hDC, fmt, pfd)     'set properties of device context
       hRC = wglCreateContext (hDC)      'get rendering context
       wglMakeCurrent hDC, hRC           'make the RC current
    End Sub

    Sub InitializeScene
       glClearColor 0,0,0,0   'color to be used with glClear
       glClearDepth 1         'zvalue to be used with glClear
    End Sub

    Sub ResizeScene (w As Long, h As Long)
       glViewport 0, 0, w, h             'resize viewport
       glMatrixMode %gl_projection       'select projection matrix
       glLoadIdentity                    'reset projection matrix
       gluPerspective 45, w/h, 0.1, 100  'set perspective aspect ratio
       glMatrixMode %gl_modelview        'select modelview matrix
    End Sub

    Sub DrawScene
       glClear %gl_color_buffer_bit Or %gl_depth_buffer_bit
       glLoadIdentity               'clear the modelview matrix
       glBegin %gl_triangles        'select triangles as primitive
          glcolor3ub 255,0,0        'set default vertex color
          glvertex3f  0, 1,  -4     'vertex1
          glvertex3f  -1, 0, -4     'vertex2
          glvertex3f  1, -1, -4     'vertex3
       glEnd
       SwapBuffers hDC              'display the buffer (image)
    End Sub


Return to top of document

Selected OpenGL Concepts

Before we can get into more detailed code examples of displaying 3D images using OpenGL commands, there are several concepts that we need to cover first. With these in mind, it will be much easier to understand the OpenGL source code in the examples to come.

Several of these sections will introduce OpenGL commands, but do not provide complete applications using those commands.

Immediately after this section, we'll go over the complete code to display OpenGL images in a PowerBASIC application, including the use of OpenGL commands introduced in this section.

OpenGL Command Formats
The OpenGL DLLs (opengl32.dll and glu32.dll) expose over 300 functions. All of these use a common format, which helps a programmer select the correct command as well as to debug code he has written.

For example, the sections of the glVertex3fv command are interpreted as follows:

   glVertex3f(0.0f, 0.0f, 0.0f);
    |   |  ||
    |   |  ||
    |   |  |+- f means parameters are floats
    |   |  |
    |   |  +- 3 is the number of parameters
    |   |
    |   +- Vertex is function name (renders a 3D point)
    |
    +- gl   specifies the opengl library
            (glu specifies the GLU library)

Data Types
The OpenGL specification defines it's own data types. In the literature you'll see reference to OpenGL data types such as GLfloat.

However, the include files provided by Jose Roca convert all of the declarations to the appropriate PowerBASIC data types, so you won't have to make any conversions yourself.

If there's any doubt as to which data type to use with a function, you an look up the declarations in Jose's include files.

State Machine If you read much about OpenGL you'll hear that it is a "State Machine". In general, that simply means that many properties of a scene continue to apply until specifically changed. For example, you can set a default vertex color (a "state") and that color will remain the default until you set a new value.

Primitives: Points, Lines, Triangles, Polygons
OpenGl works at the "primitive" level. It provides commands to draw points, lines, triangles, and polygons. If you want to make complex scenes, you have to create the scene using one or more of the primitive drawing commands. OpenGL also provides means of creating multiple points/lines/triangles. The full set of ten primitives supported by OpenGL are shown in this next image:

Source code is provided later in this tutorial showing how each of the ten primitives are created. You will see that the combination primitives require fewer lines of code than would be required if they were made from individual point/line/triangle primitives.

In addition to requiring less code, the combination primitives also render more quickly. So using them carries several advantage to a programmer.

Vertex / Vertices
All primitives are made out of vertices. There are several OpenGL commands to create vertices, but a commonly used function is this:

   glVertex3f(x,y,z)       'creates single vertex at coordinate xyz

The basic point/line/triangle/quad primitives require an exact number of vertices - 1 for a point, 2 for a line, 3 for a triangle, and 4 for a quad.

The combination type primitives can have a variable number of vertices, as will be discussed in the later section that provides drawing command examples for each primitive.

Colors
When a vertex is created it can be given a color, either an RGB value or a color-table index value. This tutorial uses only RGB color values. In particular, the following OpenGL function is used to set the color of a vertex:

   glColor3ub R, G, B     'RGB are values 0-255

OpenGL also provides the function glColor3f, which uses a value of 0-1 for each RGB component. This tutorial uses only the glColor3ub function.

When glColor3ub is use to set a default color, all subsequently created vertices use that color. The color must be explicitly changed to give the next vertex a different color.

If all vertices of a surface (line/triangle/quad) use the same color, then the entire object uses that color.

But if line/triangle/quad vertices have different colors, then the OpenGL function glShadeModel is used to determine how the entire primitive surface is colored. glShadeModel takes two arguments - %gl_smooth or %gl_flat.

If %gl_flat is used, then the entire surface is given a single color. For a line/triangle, the color is that of the last vertex. For a quad, it is the color of the first vertex. This is called flat-shading.

If %gl_smooth is used, then the surface of the line/triangle/quad is colored by interpolating between the colors of each vertex. This is the default setting for OpenGL. This is called smooth shading.

Coordinates
When a rendering context is created, the 0,0,0 origin is located at the center of the window. This is unlike PowerBASIC, where the upper-left corner is the 0,0 graphics origin.

The horizontal and vertical directions are x and y, respectively - just as you would expect.

The +z axis points out of the screen, towards the viewer. The -z axis points into the screen, away from the viewer.

Positive rotations are counterclockwise around the axis of rotation. Negative rotations are clockwise.

When building 3D objects, you usually want to build them so that their local center is at 0,0,0. OpenGL commands that cause an object to rotate ensure that the object rotates around its local center, so following this rule ensures that your objects will rotate evenly.

Viewport
All 3D content of an OpenGL scene must be displayed on the screen - a 2D surface. Once a rendering context is selected (representing the surface of a dialog or child control) OpenGL can set the position and size of the display area anywhere within the rendering context. The display area is called the viewport.

The viewport is rectangular and can cover the entire rendering surface(dialog/control) or just a portion of the surface. By default, the viewport is the same size as the rendering context (size of the dialog/control used for the display).

The OpenGL function glViewport is used to set the viewport properties (size/location). Dimensions are in pixels. You would typically call glViewPort on startup and every time the window is resized, adjusting the size of the viewport to match the new window size. The function looks like this:

   glViewport x,y,w,h

x/y are client area coordinates of the lower-left corner of the viewport (0,0 is the lower left hand corner of a rendering context). w/h are the dimensions of the viewport. So, for example, to get a 10 pixel border around the viewport you would use this:

   glViewport 10,10,w-20,h-20

Transformations
OpenGL provides a variety of ways to modify how the rendered scene looks. All of these fall under the category of "transformation". Transformation can mean modifying the content of the scene or simply changing the point from which the scene is viewed. The three types transformations OpenGL supports are:

  • Projection  
  • Converts 3D images to a 2D image for display on the screen. Perspective and orthographic projection is supported. With orthographic projection, the distance of an object in the scene has no effect on how the object looks in the screen image. With perspective projection the farther away and object is, the smaller it seems in the screen image.
  • Viewing
  • Determines the point of observation and the direction in which the observer is looking.
  • Modeling
  • Manipulates objects in the scene by translation (moving), rotating, and scaling.

We'll go into all 3, but here's a quick summary of the OpenGL transformation commands:

      Projection
      	 glFrustum (left,right,bottom,top,near,far)
      	 glOrtho (left,right,bottom,top,near,far)
      	 glPerspective 
      	 glOrtho2D
      Viewing
      	 gluLookAt
      	 glViewPort
      Modeling
      	 glTranslate (x,y,z)
      	 glRotate   (theta,x,y,z)       
      	 glScale  (sx,sy,sz)

Projection transformations will be discussed first since they are an tied to the viewport topic we just covered.

Projection Transformations
OpenGL uses the concept of a viewing volume to determine what will be displayed. Anything within the viewing volume is displayed. Anything outside the viewing volume is not displayed. The next image shows the two types of viewing volumes supported by OpenGL - orthographic (parallel) and perspective.

With parallel projection the viewing volume is a box, so distance from the camera doesn't affect how large an object appears. This type of projection is used for applications such as creating architectural blueprints and computer-aided design, where it's crucial to maintain the actual sizes of objects and angles between them as they're projected.

With perspective projection the viewing volume is a frustum, so the farther an object is from the camera, the smaller it appears in the final image. This method of projection is commonly used for animation, visual simulation, and any other applications that strive for some degree of realism because it's similar to how our eye (or a camera) works.

A frustum is the portion of a solid (normally a cone or pyramid) which lies between two parallel planes cutting it.

The planes that cut the frustum perpendicular to the viewing direction are called the near plane and far plane. Objects closer to the camera than the near plane or beyond the far plane are not drawn.

You can place the far plane infinitely far away so all objects within the frustum are drawn regardless of their distance from the camera.

There are 3 OpenGL commands used to control the type of viewing volume.

  • glOrthographic - creates a parallel viewing volume
  • glFrustum - creates a frustum viewing volume
  • gluPerspective - creates a frustum viewing volume

The gluPerspective OpenGL command is generally more popular because it is easier to use (glFrustum allows creation of a non-symmetrical viewing volume). All of my tutorials use glPerspective for creating a perspective viewing model (frustum).

Arguments for glPerspective are:

  • glPerspective fovy, aspect_ratio, near, far

The location of the arguments within a viewing volume is shown in this next image.

It is important to know that the aspect ratio of the viewport and of the viewing volume near plane must be the same in order to avoid distortion. These next two lines of code show the use of glViewPort and gluPerspective, both using the same aspect ratio.

    gluPerspective(fovy, 2.0, near, far);
    glViewport(0, 0, 400, 200);

Note that fovy is an angle measurement, in degrees.

Viewing Transformations
A 3D scene uses an "eye" or "camera" position - a point from which the scene is viewed. By default, the OpenGL camera position is at coordinates 0,0,0 and looks down the -z axis.

This is important to know because if you build a primitive with all positive z values, the primitive will not be visible!

Fortunately, OpenGL provides a function which change change the camera position and the direction the camera is looking:

   gluLookAt 

A common use of gluLookAt is perform a "walk-through" of a 3D scene - changing the position/direction of the camera rather than moving/translating the objects in the scene.

The section on modeling transformations will discuss how to implement a walk-through using OpenGL move/translate commands.

Modeling Transformations
The three basic transformations possible with objects in a 3D scene are translation (movement), rotation, and scaling. This are provided by the following OpenGL commands.

  • glTranlatef dx, dy, dz
  • glRotatef angle, x, y, z
  • glScalef sx, sy, sz

With glTranslatef a separate translation value for the x-axis, y-axis, and z-axis is provided.

With glRotatef the first argument is the amount of rotation (degrees). The next 3 arguments tells whether the rotation is ao occur for the x-axis, y-axis, or z-axis. Use zero for no rotation on the axis or one for rotation on the axis.

With glScalef separate scaling factors are provided for each axis.

An application can use modeling transformations to simulate the use of the previously mentioned gluLookAt. To orient the viewer in a "pilot view" (roll, pitch, and heading) at position (x, y, z), use this transofrmation seqeunce:

   glRotatef roll,0,0,1
   glRotatef pitch, 0,1,0
   glRotatef heading, 1,0,0
   glTranslatef -x, -y, -z

Matrices
Here's some good news about matrices. Even though you'll see/use the word "matrix" a lot in writing OpenGL programs, you don't have to understand matrix mathematics. Matrix manipulation is handled entirely by OpenGL functions.

OpenGL keeps state information (scene properties) in three matrices - the ModelView, Projection, and Texture matrices. At any one time, one of these is "selected", meaning that the state information (OpenGL scene properties) of that matrix can be changed. You'll use OpenGL commands to modify matrix information, but how the matrices work will be transparent to you as a programmer.

Here's what each matrix is used for:

  • ModelView - draw/manipulate primitives
  • Projection - define clipping volume and viewing perspective
  • Texture - define material properties of primitives

Here's a common sequence of events concerning the use of a matrix:

  • 1. Select a matrix
  • 2. Clear the matrix
  • 3. Modify a state
  • 4. Display the result

And here's a very typical DrawScene code example. We have not discussed most of the OpenGL commands in this code, so don't worry too much about each line. The point is that most OpenGL drawing routines follow a very simlilar code sequence - pick the matrix, clear the matrix, clear the image, set states (rotation, ..., etc.), draw the primitive, and display the results.

Sub DrawScene
   glMatrixMode %gl_modelview     'select the modelview matrix
   glLoadIdentity                 'clear the matrix (current mode)
   glClear %gl_color_buffer_bit   'set background color)
   glRotatef 25,1,1,1             'rotate 25 degress on all 3 axes
   ... draw a primitive    
   SwapBuffers hDC                'display rotated primitive
End Sub

Matrix Stack
There are two OpenGL functions which greatly facilitate independently setting state values for each primitive (or groups of primitives) in a scene - glPushMatrix and glPopMatrix.

OpenGL creates what is called a matrix stack, into which a current matrix state can be placed and from which it can be restored.

  • glPushMatrix - Save the current matrix by pushing it onto the stack
  • glPopMatrix - Restore the last transformation matrix

Here's a typical sequence that uses these functions:

Sub DrawScene
   glClear %gl_color_buffer_bit  'clear buffers
   glLoadIdentity                'clear the modelview matrix

   glPushMatrix            'save current state (no rotation)

   glRotatef 25,1,1,1       'rotate
   glBegin %gl_points
      glVertex3f(0,0,0)    'creates a vertex (rotated 25 degress)
   glEnd
   
   glPopMatrix             'return to no-rotation state
  
   glRotatef 40,1,1,1
   glBegin %gl_points
      glVertex3f(1,1,0)    'create  a vertex (rotated 40 degrees)
   glEnd

   SwapBuffers hDC         'display the buffer (image)
End Sub

By using glPushMatrix and glPopMatrix, the rotation functions are applied only to a single vertex. In particular, the 2nd vertex is not rotated twice. Without the push/pop functions, it would have rotated by 40 additional degrees in the second glRotate statement, giving an accumulated 65 degrees of rotation.

If you have wanted the 2nd vertex to be rotated by 40 degrees, then withou glPushMatrx/glPopMatrix support you would have to manage the incremental rotations yourself. In this simple example, that would be not problem, but when you have many transformations in a scene with many objects, you can see that keeping tracking of cumulative changes would get to be very complicated and prone to error. So the use of glPushMatrix and glPopMatrix can greatly simplify the handling of complex scenes.

Optimization: Depth Testing, Double-Buffering, and Culling
There are 3 particular means of improving the look of an OpeGL scene or the rendering speed needed to draw the scene. These are disabled by default and must be enabled in order take advantage of the improvements they offer.

  • Double-Buffering - draw to a hidden surface, then displaying all results at once
  • Hidden Surface Removal - not drawing obscured objects (those the eye cannot see)
  • Culling - selective drawing of surfaces (typically to elminate back-facing surfaces)
Each of these are discussed next.

Double-Buffering
Double-buffering works much like the concept of redraw for a Graphic control. When double-buffering is set, all drawing actions are accumulated in a "back" buffer. Then, when all drawing actions are completed, swapbuffers is executed to swap the front and back window buffers. From the users viewpoint, the new image appears instantly.

Note that swapbuffers is a Windows API and that its argument is the device context of the display window, not the rendering context.

Without double-buffering the user might can see the scene building as OpenGL drawing commands are exectued.

We've already seen how to set double-buffering when we used the following statement (see the earlier section on getting your PowerBASIC application read for OpenGL).

   pfd.dwFlags     = %PFD_DRAW_TO_WINDOW Or _ 
                     %PFD_SUPPORT_OPENGL Or %PFD_DOUBLEBUFFER
If single-buffering is used (%pfd_doublebuffer is not used), then it is not necessary to use the SwapBuffers command.

Hidden-Surface Removal
OpenGL can associate a depth with each pixel, maintained in a so-called depth buffer (also called a z-buffer). When enabled, before each pixel of an object is drawn a comparison is made with the depth buffer value for that pixel. If the new pixel is closer (in front of) that value, the new pixel's color and depth values replace those that are currently in the depth buffer and the pixel is drawn. If the new pixel is obscured (farther away), the new pixel is not drawn.

To enable hidden-surface removal, use this OpenGL command:

    glEnable %gl_depth_test        - enable hidden-surface removal
    glClear %gl_depth_buffer_but   - clear z-buffer

The glEnable command is used at startup, whereas the glClear is used each time the scene is cleared before redrawing a new scene.

Culling
Culling refers to excluding selected surfaces from a drawing.

Primitives such as triangles, polygons, and quads are said to have a front surface and a back surface. A surface is front-facing if its vertices are drawn in CCW order on the screen. A surface is back-facing if its vertices order is CW.

By default OpenGL draws both the front and back sides of primitive surfaces, but these defaults can be changes.

The simplest way to speed up rrawing (rendering) of an object is to reduce the number of surfaces to be drawn. So, a strategy of culling (not drawing) any surface which the user cannot see is often used. This could include back-facing surfaces of an enclosed object. It could also include front-facing surfaces which face away from the viewer.

The OpenGL commands to control culling are glEnable and glCullFace.

   glEnable %gl_cull_face
   glCullFace %gl_back

glEnable is used to turn culling on. glCullFace is used to determine which surfaces are culled (%gl_back or %gl_front).


Return to top of document

Minimal OpenGL Code

Now that we're through with the general discussion on OpenGL concepts, let's demonstrate those concepts with some source code! In particular, let's look at the OpenGL code we need to add to the above examples to generate an actual 3D image.

In particular, the required OpenGL code can be broken into initialization code (performed on startup and/or when the window is resized) and drawing code (performed every time the image changes).

The discussion in this section will expand on the OpenGL code example (triangle display) that I showed earlier.

Initialization
Before drawing primitives can be accomplished, OpenGL must be initialized. There are no default initialization values for this process. If the following minimum initialization steps are not completed the OpenGL scene will be empty.

In particular, the viewport dimension and perspective properties for a scene must be defined. Once these are defined, OpenGL must be placed in ModelView mode to be ready to receive commands to draw primitives.

Here's a function containing the typical OpenGL initialization commands. It's just 6 lines of code, all of which are OpenGL functions.

    Sub InitializeScene (w As Long, h as Long)
       glClearColor 0,0,0,0               'background color
       glClearDepth 1                     'z-buffer default values
       glviewport 0,0,w,h                 'viewport dimensions
       glmatrixmode %gl_projection        'set mode to GL_PROJECTION
       glLoadIdentity                     'reset projection matrix
       gluperspective 45.0,w/h,0.1,150.0  'set viewport aspect ratio
       glmatrixmode %gl_modelview         'set mode to GL_MODELVIEW
    End Sub
    

Initialization (Split)
In an application, we would normally re-initialize the rendering context when the user resizes the window. And since the WM_Size event is called when a PowerBASIC program is started, we can break the initialization code into two procedures (see below) and be assured that all of the initialization code is executed.

Also, by splitting the code in this way, when the dialog is resized only the necessary parts of the OpenGL initialization code will be repeated (glClearColor and glClearDepth need not be called each time the dialog is sized).

This first procedure would be called on startup - such as in the WM_InitDialog event.

    Sub InitializeScene
       glClearColor 0,0,0,0     'background color
       glClearDepth 1           'default z-buffer values
    End Sub
    

Both of these statements define what values are set when the OpenGL glCear function is called.

This second function would be called during the WM_Size event:

    Sub ResizeScene (w As Long, h As Long)
       glViewport 0, 0, w, h             'resize viewport to window size
       glMatrixMode %gl_projection       'select projection matrix
       glLoadIdentity                    'reset projection matrix
       gluPerspective 45, w/h, 0.1, 100  'calculate window aspect ratio
       glMatrixMode %gl_modelview        'select modelview matrix
    End Sub
    

Rendering an Image (Drawing)
Rendering a scene involves clearing the screen, setting properties (states) and then drawing the primitives. Also, where double-buffering is used, the hidden buffer must be swapped with the visible buffer to make the changes visible on the screen.

Here's example OpenGL code that draws a simple primitive - a triangle. The screen and modelview matrix are cleared (glClear and glLoadIdentify), the primitive is drawn (glBegin, glEnd, glColor, glVertex), and the image is made visible (SwapBuffers).

    Sub DrawScene
       glClear %gl_color_buffer_bit Or %gl_depth_buffer_bit  'clear buffers
       glLoadIdentity               'clear modelview matrix
       glBegin %gl_triangles        'use triangle primitives
          glcolor3ub 255,0,0        'default vertex color
          glvertex3f  0, 1,  -4     'vertex1
          glvertex3f  -1, 0, -4     'vertex2
          glvertex3f  1, -1, -4     'vertex3
       glEnd
       SwapBuffers hDC              'display the buffer (image)
    End Sub 
    

This code will display the triangle in the center of the OpenGL rendering context. Unlike PowerBASIC, which uses the upper-left corner of the screen as the origin of the coordinate system, OpenGL puts the origin in the center of the window. Thus you see no code in this example to position the triangle to the center of the window.

One thing to note is that primitives, or object made from them, are usually designed so that the origin (0,0,0) is the center of the object. This assures that the graphics are centered in the window and that the objects move uniformly when rotated (more on this later).

In the next section we'll discuss the use of glBegin/glEnd - and what goes in between them - much more extensively. For now, just understand that those two commands are a requirement - and that all drawing commands go between those two commands.

At this point, we have all we need to create a PowerBASIC application that uses OpenGL to create graphic images. Put these three procedures into a PowerBASIC application and you have a ready to go OpenGL application!

  • Sub GetRenderContext
  • Sub Initialization
  • Sub DrawScene

Complete OpenGL Application
And here's the source code that does just that. It's a repeat of what was given earlier in this tutorial, but now that you've been exposed to the essential OpenGL concepts the source code will take on new meaning.

For simplicity, this example has no user interface features (mouse/keyboard event detection). But later in this tutorial we'll see how to add those features.

    #Compile Exe
    #Dim All
    #Include "win32api.inc"
    #Include "gl.inc"
    #Include "glu.inc"
    Global hDlg, hDC, hRC As DWord

    Function PBMain() As Long
      Dialog New Pixels, 0, "OpenGL Example",,, 320, 240, _
                               %WS_OverlappedWindow To hDlg
      Dialog Show Modal hdlg Call dlgproc
    End Function

    CallBack Function dlgproc()
       Select Case CB.Msg
          Case %WM_InitDialog
              GetRenderContext
              InitializeScene
          Case %WM_Size
              ResizeScene Lo(Word, CB.lParam), Hi(Word, CB.lParam)
              DrawScene
          Case %WM_Paint
              DrawScene
          Case %WM_Close
              wglmakecurrent %null, %null 'unselect rendering context
              wgldeletecontext hRC        'delete the rendering context
       End Select
    End Function

    Sub GetRenderContext
       Local pfd As PIXELFORMATDESCRIPTOR, fmt As Long
       pfd.nSize       =  SizeOf(PIXELFORMATDESCRIPTOR)
       pfd.nVersion    =  1
       pfd.dwFlags     = %pfd_draw_to_window Or _
                         %pfd_support_opengl Or %pfd_doublebuffer
       pfd.dwlayermask = %pfd_main_plane
       pfd.iPixelType  = %pfd_type_rgba
       pfd.ccolorbits  = 24
       pfd.cdepthbits  = 24

       hDC = GetDC(hDlg)                'DC for dialog
       fmt = ChoosePixelFormat(hDC, pfd) 'set device context properties
       SetPixelFormat(hDC, fmt, pfd)    'set properties of device context
       hRC = wglCreateContext (hDC)     'get rendering context
       wglMakeCurrent hDC, hRC          'make the RC current
    End Sub

    Sub InitializeScene
       glClearColor 0,0,0,0     'color to be used with glClear
       glClearDepth 1           'zvalue to be used with glClear
       glEnable %gl_depth_test  'enable depth testing
    End Sub

    Sub ResizeScene (w As Long, h As Long)
       glViewport 0, 0, w, h             'resize viewport
       glMatrixMode %gl_projection       'select projection matrix
       glLoadIdentity                    'reset projection matrix
       gluPerspective 45, w/h, 0.1, 100  'set perspective aspect ratio
       glMatrixMode %gl_modelview        'select modelview matrix
    End Sub

    Sub DrawScene
       glClear %gl_color_buffer_bit Or %gl_depth_buffer_bit  'clear buffers
       glLoadIdentity               'clear the modelview matrix
       glBegin %gl_triangles        'select triangles as primitive
          glcolor3ub 255,0,0        'set default vertex color
          glvertex3f  0, 1,  -4     'vertex1
          glvertex3f  -1, 0, -4     'vertex2
          glvertex3f  1, -1, -4     'vertex3
       glEnd
       SwapBuffers hDC              'display the buffer (image)
    End Sub

While complete, this example is fairly limited. It's just a 2D triangle displayed on the screen. But before we get to showing how draw more complicated primitives, let's go over how to improve the minimal code to include user interface capabilities - detecting mouse and keyboard events and using them to modify the display.


Return to top of document

User Interface: Windows Code

You'll recall that OpenGL has no commands to handle user inputs - mouse or keyboard events. So let's take the previous example, and improve on it to make it a more useful template for using OpenGL in your own PowerBASIC applications.

We'll also make the display a little more interesting by using a 3D, 6-sided diamond.

Here are the user interface features missing from the earlier examples, that we'll be adding:

  • Timer - to provide animation (automatic rotation)
  • Mouse Detection - to provide spinning of the display using the mouse
  • Wheel Mouse Detection - to zoom the image using the mousewheel

Many applications also use the arrow keys to rotate 3D objects or to move about in the 3D scene. Though not a part of the code in this section, the code is provided in my gbSnippets library. See the list of gbSnippets OpenGL examples at the top of this tutorial.

Animation (Timer)
To animate the image, we'll simply create a timer and execute the DrawScene each time the timer event fires. The DrawScene rotates the image each time it is called. Here's the timer code:

   Define an equate:
        %ID_Timer = 1000

   On startup create a timer
        SetTimer(hDlg, %ID_Timer, 50, %NULL)
  
   And in the timer event, draw the scene
        %WM_Timer
            DrawScene 1,1,1      'arguments defines axes of rotation

And that's it - all the code your need to add animation to your image.

Mouse Button Detection
It's customary to allow the user to rotate the 3D image using the mouse - dragging the mouse to spin the image. Since OpenGL has no capabilities to detect the mouse, we'll have to do it using PowerBASIC and API commands.

This following code simply detects when the mouse is dragged. During dragging DrawScene is called to rotate the OpenGL image. All of this code goes in the dialog procedure, under %WM_Command.

      Case %WM_SetCursor
         Select Case Hi(Word, CB.lParam)
            Case %WM_LButtonDown
               SpinInWork = 1
               GetCursorPos pt              'screen coordinates
               ScreenToClient hDC, pt       'dialog client coordinates
               XLast = Pt.x
               YLast = Pt.y
            Case %WM_MouseMove
               If SpinInWork Then
                  GetCursorPos pt           'screen coordinates
                  ScreenToClient hDC, pt    'dialog client coordinates
                  XDelta = XLast - Pt.x
                  YDelta = YLast - Pt.y
                  DrawScene -YDelta, -XDelta, 0
                  XLast = pt.x
                  YLast = pt.y
               End If
            Case %WM_LButtonUp
               SpinInWork = 0  
         End Select

Variable declaration are not shown in this example, but are included in an example further down this tutorial.

Mouse Wheel Detection
Another common feature of 3D applications is to allow the user to zoom using the mouse wheel. This is very easy to implement. Place this code in the dialog procedure to detect mousewheel events. CB.wParam tells you which direction the wheel was rolled, which you use to raise/lower the scalefactor that is applied to the image (in DrawScene). So each time %WM_MouseWheel fires, the scalefactor is changed and the image is redrawn.

      Case %WM_MouseWheel
         Select Case Hi(Integer,CB.wParam)
            Case > 0
                ScaleFactor = ScaleFactor + 0.1
                DrawScene 0,0,0
            Case < 0
                ScaleFactor = ScaleFactor - 0.1
                DrawScene 0,0,0

Complete OpenGL Application Template
Now we have all we need to create a very useful OpenGL template. This template creates a rendering context, ready to accept OpenGL commands, and includes basic user interface features.

    'Compilable Example:
    #Compile Exe
    #Dim All
    #Include "win32api.inc"
    #Include "gl.inc"
    #Include "glu.inc"

    %ID_Timer = 1000

    Global hDlg, hDC, hRC As DWord
    Global anglex, angley, anglez, scalefactor As Single

    Function PBMain() As Long
      Dialog New Pixels, 0, "OpenGL Example",,, 320, 240, _ 
                             %WS_OverlappedWindow To hDlg
      Dialog Show Modal hdlg Call dlgproc
    End Function

    CallBack Function dlgproc()
       Local pt As Point
       Local XDelta, YDelta as Single
       Static SpinInWork,XLast,YLast As Long

       Select Case CB.Msg
          Case %WM_InitDialog
              GetRenderContext
              InitializeScene
              SetTimer(hDlg, %ID_Timer, 50, %NULL)
              ScaleFactor = 1
          Case %WM_Timer
              DrawScene 1,1,0  'redraw with rotation on all 3 axes
          Case %WM_Paint
              DrawScene 0,0,0  'redraw with no rotation
          Case %WM_Size
              ResizeScene Lo(Word, CB.lParam), Hi(Word, CB.lParam)
              DrawScene 0,0,0  'redraw with no rotation
          Case %WM_Close
              wglmakecurrent %null, %null 'unselect rendering context
              wgldeletecontext hRC        'delete the rendering context
          Case %WM_MouseWheel
             Select Case Hi(Integer,CB.wParam)
                Case > 0
                    ScaleFactor = ScaleFactor + 0.1 : DrawScene 0,0,0
                Case < 0
                    ScaleFactor = ScaleFactor - 0.1 : DrawScene 0,0,0
             End Select
          Case %WM_SetCursor
             Select Case Hi(Word, CB.lParam)
                Case %WM_LButtonDown
                   SpinInWork = 1
                   GetCursorPos pt          'screen coordinates
                   ScreenToClient hDC, pt   'dialog client coordinates
                   XLast = Pt.x
                   YLast = Pt.y
                Case %WM_MouseMove
                   If SpinInWork Then
                      GetCursorPos pt         'screen coordinates
                      ScreenToClient hDC, pt  'dialog client coordinates
                      XDelta = XLast - Pt.x
                      YDelta = YLast - Pt.y
                      DrawScene -YDelta, -XDelta, 0
                      XLast = pt.x
                      YLast = pt.y
                   End If
                Case %WM_LButtonUp
                   SpinInWork = 0  
             End Select
       End Select
    End Function

    Sub GetRenderContext
       Local pfd As PIXELFORMATDESCRIPTOR, fmt As Long
       pfd.nSize       =  SizeOf(PIXELFORMATDESCRIPTOR)
       pfd.nVersion    =  1
       pfd.dwFlags     = %pfd_draw_to_window Or _
                         %pfd_support_opengl Or %pfd_doublebuffer
       pfd.dwlayermask = %pfd_main_plane
       pfd.iPixelType  = %pfd_type_rgba
       pfd.ccolorbits  = 24
       pfd.cdepthbits  = 24

       hDC = GetDC(hDlg)                  'DC for dialog
       fmt = ChoosePixelFormat(hDC, pfd)  'properties of device context
       SetPixelFormat(hDC, fmt, pfd)      'properties of device context
       hRC = wglCreateContext (hDC)       'get rendering context
       wglMakeCurrent hDC, hRC            'make the RC current
    End Sub

    Sub InitializeScene
       glClearColor 0,0,0,0     'default background color
       glClearDepth 1           'default z-buffer value
       glEnable %gl_depth_test  'enable depth testing   
    End Sub

    Sub ResizeScene (w As Long, h As Long)
       glViewport 0, 0, w, h             'resize viewport
       glMatrixMode %gl_projection       'projection matrix
       glLoadIdentity                    'reset projection matrix
       gluPerspective 45, w/h, 0.1, 100  'set viewport aspect ratio
       glMatrixMode %gl_modelview        'select the modelview matrix
    End Sub

    Sub DrawScene (dx As Single, dy As Single, dz As Single)
       glClear %gl_color_buffer_bit Or _
               %gl_depth_buffer_bit  'clear buffers
       glLoadIdentity               'clear the modelview matrix

       gluLookAt 0,0,4,0,0,0,0,1,0       'set "eye" position
       glScalef scalefactor, scalefactor, scalefactor

       anglex = anglex + dx : glRotatef anglex, 1,0,0
       angley = angley + dy : glRotatef angley, 0,1,0
       anglez = anglez + dz : glRotatef anglez, 0,0,1

       DrawDiamond

       SwapBuffers hDC              'display the buffer (image)
    End Sub 

    Sub DrawDiamond
       glbegin %gl_triangle_fan  'top part
          glcolor3ub 255,   0,   0 : glvertex3f  0, 1.414, 0
          glcolor3ub   0, 255,   0 : glvertex3f  1, 0, 1
          glcolor3ub   0,   0, 255 : glvertex3f  1, 0,-1
          glcolor3ub   0, 255,   0 : glvertex3f -1, 0,-1
          glcolor3ub   0,   0, 255 : glvertex3f -1, 0, 1
          glcolor3ub   0, 255,   0 : glvertex3f  1, 0, 1
       glend
       glbegin %gl_triangle_fan  'bottom part
          glcolor3ub 255,   0,   0 : glvertex3f  0,-1.414, 0
          glcolor3ub   0, 255,   0 : glvertex3f  1, 0, 1
          glcolor3ub   0,   0, 255 : glvertex3f -1, 0, 1
          glcolor3ub   0, 255,   0 : glvertex3f -1, 0,-1
          glcolor3ub   0,   0, 255 : glvertex3f  1, 0,-1
          glcolor3ub   0, 255,   0 : glvertex3f  1, 0, 1
       glend
    End Sub

Looking at the template code, you'll see that I threw in several other commands which we've not yet discussed.


Return to top of document

Primitives: Points, Lines, Triangles, Polygons

Now that we have a more complete template in hand, we're ready to utilize the basic commands of OpenGL to create graphical drawings. In particular, OpenGL provides commands to draw "primitives" - lines, points, triangle, quads - which you use to create more complex graphic drawings.

Here's a reminder of the ten primitives which OpenGl supports. Examples of each will be discussed.

	GL_POINTS		GL_LINE_STRIP 
	GL_LINES		GL_LINE_LOOP 
	GL_POLYGON		GL_TRIANGLE_STRIP 
	GL_TRIANGLES		GL_TRIANGLE_FAN 
	GL_QUADS		GL_QUAD_STRIP 

The left column provide basic one-up primitives. Strictly speaking, these would suffice to create more complex 3D objects. The right column, however, provides some simplifying tools for the programmer - the ability to create multiple lines/triangles/quads with fewer lines of code.

These time-saving OpenGL commands not only reduce then number of lines of code, but also result in faster rendering performance.

glBegin / glEnd
Drawing a primitive always starts with a pair of OpenGL commands - glBegin and glEnd. The commands to draw a primitive must be preceded by a glBegin command, and ended by a glEnd command. The glBegin command further defines which type of primitive is being drawn, as in this example which draws a single point (vertex).

Between glBegin and glEnd, the two most commonly used commands are glVertex3r and glColor3ub. glVertex defines a vertex in the drawing whose color is set by glColor3ub. Here's simple code that creates a red point.

   Sub DrawScene
      glBegin %gl_points
         glColor3ub 255,0,0  'set color of vertex
         glVertex3f(1,1,1)       'create a point at xyz coordinates 1,1,1
      glEnd
   End Sub

Other functions you may use to set attributes of vertrices include:

     glColor*       current vertex color
     glNormal*      current vertex normal (lighting)
     glMaterial*    current material property (lighting)
     glTexCoord*    current texture coordinate
     glEdgeFlag*    edge status (surface primitives)

All of the following examples of creating primitives use just those to commands, glVertex3f and glColor3ub.

GL_POINTS
Each glVertex generates a point. So for each point you want in the image, you'll need to use a glVertex3f command. Here's a simple 3-point example, using the default color of white for the points.

   Sub DrawScene
      glBegin %gl_points
         glVertex3f(-1,-1,-1)
         glVertex3f(0,0,0)
         glVertex3f(1.2,0.5,1)
      glEnd
   End Sub
   

OpenGL also supports assigning a color to a point and setting the size of a point (the standard size of a point is 1 pixel). A large point is drawing as a square, but antialiasing can be applied to render the larger point as a circle. Here's another point example with point attributes modified (large size with antialiasing):

   Sub DrawScene
      glEnable %gl_point_smooth
      glHint %gl_point_smooth_hint,%gl_nicest

      glBegin %gl_points
         glPointSize 20        'size
         glcolor3ub 255,0,0    'color
         glvertex3f  0, 0, -1  'location
      glEnd
   End Sub
 

GL_LINES
Two glVertex lines generate a line. In this example, the glColor statement is used once, so all lines will have the same color. Each pair of glVertex statements represents a separate line

   Sub DrawScene
      glBegin %gl_lines
         glcolor3ub 0,255,255
         glvertex2i  1,  1   'line 1
         glvertex2i -2,  2
         glvertex2i -1, -1   'line 2
         glvertex2i  3,  1 
      glEnd
   End Sub
   

The standard line width is one pixel, but lines can be drawn with different widths and with dot/dash patterns (stippled). Antialiasing may also be applied.

GL_TRIANGLES
Three glVertex lines are needed to generate a triangle. In this example, each vertex is given its own color.

   Sub DrawScene
      glBegin %gl_triangles
         glcolor3ub 0,255,0 : glvertex3f 0,3,-4
         glcolor3ub 255,0,0 : glvertex3f -3,0,-4
         glcolor3ub 0,0,255 : glvertex3f 3,-3,-4
      glEnd
   End Sub
   

With flat shading, the triangle will take on the color of the last vertex. With smooth shading the triangle will be filled with colors interpolated between all vertices (as in this example).

GL_POLYGON

   Sub DrawScene
      glBegin %gl_polygon
         glcolor3ub 255,255,0
         glVertex2f -1.0, -1.0
         glVertex2f -1.0,  1.0
         glVertex2f  2.0,  2.0
         glVertex2f  3.0,  1.0
         glVertex2f  1.0, -1.0 
      glEnd
   End Sub
   

Polygons are typically drawn by filling in all the pixels enclosed within the boundary, but you can also draw them as outlined polygons or simply as points at the vertices. A filled polygon might be solidly filled or stippled with a certain pattern. Antialiasing can also be applied.

OpenGL can directly display only simple convex polygons. A polygon is simple if:

  • The edges intersect only at vertices
  • There are no duplicate vertices
  • Exactly two edges meet at any vertex.
To display simple nonconvex polygons or simple polygons containing holes, you must first tessellate (divide into triangles) the polygon. GLU function are available for that purpose.

GL_QUADS
The image below shows the required sequence of vertices to create a quad primitive.

       

   Sub DrawScene
      glBegin %gl_quads
         glcolor3ub 128,255,0
         glVertex2f  -1, -1
         glVertex2f  -1.5, 1.0
         glVertex2f  2.0, 1.0
         glVertex2f  1.5, -0.5 
      glEnd
   End Sub

GL_LINE_STRIP
The first 2 vertices define the first line segment. Each additional vertex then create a new line segment (a continuation of the line segments before it).

   Sub DrawScene
      glBegin %gl_line_strip
         glcolor3ub 0,255,255
         glvertex2f   2, 2, -4    'vertex1
         glvertex2f  -1, 3, -6    'vertex2
         glvertex2f   -2, -1, -2  'vertex3
         glvertex2f    2, -3, -1  'vertex4 
      glEnd
   End Sub
   

GL_LINE_LOOP
The first 2 vertices define the first line segment. Each additional vertex then creates a new line segment (a continuation of the line segments before it). Once the last line segment is drawn, OpenGL closes the looop for you.

   Sub DrawScene
      glBegin %gl_line_loop
         glcolor3ub 255,0,255
         glvertex2f   2, 2, -4    'vertex1
         glvertex2f  -1, 3, -6    'vertex2
         glvertex2f   -2, -1, -2  'vertex3
         glvertex2f    2, -3, -1  'vertex4  
      glEnd
   End Sub
   

OpenGL then draw a line segment from the last to first line segment, closing off the line_loop structure

GL_TRIANGLE_STRIP
The image below shows the required sequence of vertices to create a triangle_strip primitive. The first 3 vertices define the first triangle, then every additional vertex defines a new triangle added to the stirp.

       

   Sub DrawScene
      glBegin %gl_triangle_strip
         glcolor3ub 0,255,0     : glvertex2f -2,-1     '1st triangle
         glcolor3ub 255,0,0     : glvertex2f -2.2,1 
         glcolor3ub 0,0,255     : glvertex2f  0,-0.8
         glcolor3ub 128,64,255  : glvertex2f  0.8,1    '2nd
         glcolor3ub 128,255,255 : glvertex2f  3,-1.3   '3rd
         glcolor3ub 255,0,128   : glvertex2f  2.5,0.9  '4th
      glEnd
   End Sub

GL_TRIANGLE_FAN
The image below shows the required sequence of vertices to create a triangle_fan primitive. The first 3 vertices define the first triangle, then every additional vertex defines a new triangle added to the fan.

       

   Sub DrawScene
      glBegin %gl_triangle_fan
         glcolor3ub 0,255,0   : glvertex2f  0,2  '1st triangle
         glcolor3ub 255,0,255 : glvertex2f  -2,0
         glcolor3ub 0,255,255 : glvertex2f  -1,-1
         glcolor3ub 128,0,255 : glvertex2f  0,-0.8 '2nd
         glcolor3ub 255,128,0 : glvertex2f  2,-1.1 '3rd
         glcolor3ub 255,128,0 : glvertex2f  3,0    '4th 
      glEnd
   End Sub

GL_QUAD_STRIP
The image below shows the required sequence of vertices to create a quad_strip primitive. The first 4 vertices define the first quad, then every additional 2 vertices defines a new quad added to the strip.

     

   Sub DrawScene
      glBegin %gl_quad_strip
            glcolor3ub 255,255,0 : glVertex2f  -1.8,-1     '1st
            glcolor3ub 0,255,255 : glVertex2f   -2,1
            glcolor3ub 255,0,168 : glVertex2f  -0.5, -0.8
            glcolor3ub 0,255,255 : glVertex2f  -0.2, 1
            glcolor3ub 255,255,0 : glVertex2f   1, -1.1    '2nd
            glcolor3ub 0,255,0   : glVertex2f  0.9, 0.7
            glcolor3ub 255,0,0   : glVertex2f  1.9, -1.2   '3rd
            glcolor3ub 0,255,255 : glVertex2f  2.0, 0.8  
      glEnd
   End Sub


Return to top of document

The Cube

I'm still amazed that OpenGL has no builtin capability to make something as fundamental as a cube (or a rectangular block). For a lot of my applications cubes/blocks are extremely useful.

So here's the code that can make a cube using 6 quad primitives (one for each face of the cube).

    glBegin %GL_QUADS
       glcolor3ub 255,0,0
       glVertex3f  0.5,  0.5, -0.5    ' Top right of the quad (Top)
       glVertex3f -0.5,  0.5, -0.5    ' Top left of the quad (Top)
       glVertex3f -0.5,  0.5,  0.5    ' Bottom left of the quad (Top)
       glVertex3f  0.5,  0.5,  0.5    ' Bottom right of the quad (Top)

       glcolor3ub 255,255,0
       glVertex3f  0.5, -0.5,  0.5    ' Top right of the quad (Bottom)
       glVertex3f -0.5, -0.5,  0.5    ' Top left of the quad (Bottom)
       glVertex3f -0.5, -0.5, -0.5    ' Bottom left of the quad (Bottom)
       glVertex3f  0.5, -0.5, -0.5    ' Bottom right of the quad (Bottom)

       glcolor3ub 255,0,255
       glVertex3f  0.5,  0.5,  0.5    ' Top right of the quad (Front)
       glVertex3f -0.5,  0.5,  0.5    ' Top left of the quad (Front)
       glVertex3f -0.5, -0.5,  0.5    ' Bottom left of the quad (Front)
       glVertex3f  0.5, -0.5,  0.5    ' Bottom right of the quad (Front)

       glcolor3ub 0,0,255
       glVertex3f  0.5, -0.5, -0.5    ' Top right of the quad (Back)
       glVertex3f -0.5, -0.5, -0.5    ' Top left of the quad (Back)
       glVertex3f -0.5,  0.5, -0.5    ' Bottom left of the quad (Back)
       glVertex3f  0.5,  0.5, -0.5    ' Bottom right of the quad (Back)

       glcolor3ub 0,255,255
       glVertex3f -0.5,  0.5,  0.5    ' Top right of the quad (Left)
       glVertex3f -0.5,  0.5, -0.5    ' Top left of the quad (Left)
       glVertex3f -0.5, -0.5, -0.5    ' Bottom left of the quad (Left)
       glVertex3f -0.5, -0.5,  0.5    ' Bottom right of the quad (Left)

       glcolor3ub 255,128,0
       glVertex3f  0.5,  0.5, -0.5    ' Top right of the quad (Right)
       glVertex3f  0.5,  0.5,  0.5    ' Top left of the quad (Right)
       glVertex3f  0.5, -0.5,  0.5    ' Bottom left of the quad (Right)
       glVertex3f  0.5, -0.5, -0.5    ' Bottom right of the quad (Right)
    glEnd


Return to top of document

Threads

A thread can have one current rendering context but a process can have multiple current rendering contexts by means of multithreading. A thread must set a current rendering context before calling any OpenGL functions. Otherwise, all OpenGL calls are ignored.

A rendering context can be current to only one thread at a time. You cannot make a rendering context current to multiple threads. An application can perform multithread drawing by making different rendering contexts current to different threads, supplying each thread with its own rendering context and device context.


Return to top of document

PowerBASIC Forum Exmaples

Here are some of the PowerBASIC forum OpenGL examples that I've found useful:


Return to top of document

References

These would be my recommendations for online sources of information on OpenGL.

Other sources I've used include:

If you have any suggestions on additions to this list, please let me know.