Whitepapers
Product Development

Calsoft Labs is a leading technology partner for companies, helping them develop new products and modernize existing ones using emerging technologies. more

Download PDF: Open Gl Graphics Programming & MFC Application Bookmark and Share

Graphics Programming Using OpenGL and MFC

Introduction

OpenGL is a library of graphics routines available on a wide variety of hardware platforms and operating systems, including Windows 95,Windows NT, OS/2, Digital's Open VMS, and X Windows.

OpenGL was developed by Silicon Graphics Incorporated in the year 1992, and was eventually accepted as an industrial standard for hardcore 3D graphics. The OpenGL Architecture Review Board maintains the library ensuring that OpenGL is implemented properly on various platforms. This makes porting the programs across the platforms much easier. OpenGL routines are well structured, highly stable, intuitive, scalable from PCs to Super Computers and guaranteed to produce consistent visual displays across various platforms.

This article intents to introduce a VC++ programmer, to the concepts and usage of OpenGL library on Windows NT 3.5 (or higher) platform.

Click here to go to Top

OpenGL Support On WINDOWS NT

Following libraries are packed with Windows NT to support OpenGL.

  • The main OpenGL library Contains the core APIs that are guaranteed to be implemented on any platform that supports OpenGL. APIs in this library has a prefix ' gl '. These APIs are used to draw shapes, perform transformations, produce lighting effects, map textures etc.
  • OpenGL Utility Library Contains higher-level helper functions that internally use the core APIs to work. APIs in this library has a prefix ' glu ' and helps the programmer in polygon tessellation, texture scaling etc. These APIs are also guaranteed to be implemented on all platforms that supports OpenGL.
  • OpenGL Auxiliary Library Contains some special APIs that are platform dependent and may not be available on all platforms.
  • Apart from the APIs in the above libraries, there are some APIs that are unique to Windows NT implementation of OpenGL. These APIs have a prefix ' wgl ' and acts as an interface between Windows and OpenGL.

To use these APIs in your application, you have to set links to OPENGL32.LIB (core APIs), GLU32.LIB (utilities) and GLAUX.LIB (auxiliary library functions). The header files to be included are gl\gl.h (core APIs), gl\glu.h (utility library APIs) and gl\glaux.h (auxiliary library functions).

Click here to go to Top

What Can OpenGL Do For Me

Well... Almost everything in 3D graphics !!!

OpenGL functionality ranges from drawing simple shapes like points, lines and polygons to creating graphic marvels like 'Jurassic Park' with near photographic quality. OpenGL APIs relieves you from the complex mathematics involved in lighting, shading, blending, antialiasing, texture mapping and animation. OpenGL also supports features like Fog, Motion blur, Gauraud shading, non-uniform rational B-spline (NURBS) curves and surfaces etc. We shall see more about them later in the article.

However, OpenGL is more of a renderer than a modeler. Even though you can draw points, lines and polygons using OpenGL, combining these basic shapes to build the required 3D image is the applications responsibility. The OpenGL Utility Library packed with Windows NT can assist with modeling tasks, but for all practical purposes modeling is the application's responsibility.

Click here to go to Top

A Graphics Primer

Before starting to use OpenGL APIs, let us look into some of the relevant concepts you may not be familiar with. This is just a random listing without any order. A brief understanding of these terms will surely be helpful to you while reading any article on OpenGL.

Color systems and Logical Palettes

Let us take a look at how pixel colors are represented in various color systems.

  • Monochrome - Here, 1 bit represents a pixel. The pixel is turned on or off depending on the value of the bit.
  • 16 Color - Here, 4 bits represents a pixel. Each nibble holds an index of a color in a 16-color palette.
  • 256 Color - Here each pixel is represented by a Byte, which holds an index of a color in a 256-color palette.
  • 16 Million Color - In this system, 24 bits are used to represent a pixel. Each byte in the 24 bits holds the actual Red, Green, and Blue components of the color of the pixel.

Among the above mentioned color schemes, we can see that 16 color and 256 color systems use indexes to find the color of a pixel from a palette. There are two types of palettes, System palette and logical palette. System palette is provided by the operating system and most of the applications use this palette for displaying purposes. But there is a pitfall here. The system palette used by Windows NT on a 256-color system has only 20 colors. OpenGL cannot accurately render 3D objects with just 20 colors. The solution is either to upgrade the system to support more colors or to set up a logical palette that contains a reasonable number of colors and map it into Windows system palette.

Device Dependent and Independent Bitmaps

Device Dependent bitmaps use system palette to convert the color indexes to the pixel colors. Since system palettes may vary for different devices, these bitmaps will be displayed differently on each device. Device Independent bitmaps (DIBs) carry the color table along. Hence they can give consistent output with any device. Any bitmap that you use as an OpenGL primitive should be a DIB.

Front and Back Buffers

OpenGL supports front and back video buffers. The front buffer holds the current display information. In animations, we may have to draw many frames on the screen one after the other. If you don't want the user to see a frame as it is being drawn, you can send the drawing commands to a hidden buffer (called back buffer) and swap the buffers when the frame is finished.

Depth Buffer

The Z-Depth buffer is used by OpenGL to keep track of the proximity of the viewer's object. It is also crucial for hidden surface removal.

Stencil Buffer

Stencil Planes are used by OpenGL to restrict the drawing to certain portions of the screen. Useful for constructive solid geometry, interference, mirror reflection, and shadow algorithms

Accumulation Buffer

Useful for image post-processing, creating motion blur, and depth-of-field effects

Alpha Buffer

Useful for Alpha blending which described later in this section.

Pixel Formats

Pixel format contains the attributes for a drawing device. OpenGL gives us almost total control over the pixel format. These attributes include color depth, double buffering, whether the drawing surface uses RGBA or Indexed color mode, number of bits used in depth or stencil buffers, whether to draw to a window or to a bitmap etc.

Rendering Context

Just like the normal Windows programs need to create a GDI Device Context before drawing to a window, OpenGL programs need to create a Rendering Context and be made current before any drawing. The Rendering context holds the OpenGL state variables (See OpenGL State Variables). You can have many rendering contexts in your program with different pixel formats but only one rendering context can be made current at any specific time. All the OpenGL drawing commands are routed to the current rendering context. If your application is multithreaded, you can have only one rendering context current per thread at any specific time.

Gouraud shading

Gouraud shading is a technique used to apply smooth shading to a 3D object by interpolating color differences between the vertices across its surfaces.

Texture Mapping

OpenGL allows you to 'paste' an image onto a surface. This feature if effectively used can make your 3D object look like natural objects. For example, you can map a DIB representing wood grain onto the faces of a cube to make it look like a wooden box.

Anti-Aliasing

Lines drawn on a computer display have jagged edges. This effect is predominant when lines are drawn at low resolution. Anti-aliasing is a common computer graphics technique that modifies the color and intensity of the pixels near the line in order to produce smooth lines. OpenGL supports antialiasing.

Alpha Blending

Alpha blending uses the Alpha value of the RGBA code, allowing one to combine the color of the fragment (see OpenGL Process Pipeline) being processed with that of the pixel already stored in the frame buffer. Imagine, for example, drawing a transparent light blue window in front of a red box. Alpha blending allows simulating the transparency of the window object so the box seen through the glass will appear with a magenta tone.

Fog

In OpenGL, Fog is a term that really describes an algorithm that simulates the effect of air, making the farther points of an object less sharp and more realistic.

Display Lists

A display list is a group of OpenGL commands that have been stored for later execution. When a display list is invoked, the commands in it are executed in the order in which they were issued. One common use is creating a display list for an object that is to be drawn more than once. If the vertices of the object must be calculated, then the calculations need only be performed rather than each time the object is drawn.

Transformation Matrices

You will already know that Matrices are used extensively in graphics for transformation purposes. OpenGL provides the You with Three stacks of 4*4 Matrices, which can be used for performing any desired transformations (scaling, translation and rotation) on the OpenGL primitives. They are,

  • MODELVIEW Matrix Stack - Use these matrices to define transformations for individual 3D objects
  • PROJECTION Matrix Stack - Use these matrices to define the clipping volumes, perspective ratio etc
  • TEXTURE Matrix Stack - Use these matrices to define transformations for the texture coordinates

The usage of these matrices is discussed later.

Click here to go to Top

OpenGL Process Pipeline

The series of processes performed by OpenGL for rendering a 3D scene on the screen are listed below in their order of execution.

  • The user defined vertices are sent to a vertex buffer
  • When vertices for an entire OpenGL primitive are buffered, the Model-View matrix transformations are applied to the Vertices and to the current Normal.
  • Texture Matrix transformations are applied to texture coordinates
  • Lighting and fog factors are calculated for the vertices using the current color settings
  • The combined information from the above processes are used to assemble a Primitive
  • The Projection-View matrix transformations are applied to the primitive
  • Clipping and culling of the primitive is performed
  • Light intensities at vertices of the objects are evaluated
  • Sub-pixel accurate slopes and offsets are generated for color, depth, alpha, fog, and texture coordinates
  • Edge walking and span interpolations are performed for surface primitives. The resulting interpolated data is referred to as Fragments
  • The Scissor test is done to eliminate the fragments outside the Scissor rectangle on the screen specified by the user
  • The alpha test (performed only in RGBA mode) compares the fragment's Alpha value with a reference value set by the user and discards or accepts the fragment according to the comparison mode set by the user
  • Stencil test compares the fragment's Stencil value with a reference value set by the user and discards or accepts the fragment according to the comparison mode set by the user
  • Depth test compares the fragment's Z-value value with a reference value set by the user and discards or accepts the fragment according to the comparison mode set by the user
  • A fragment produced by rasterization modifies the corresponding pixel in the frame buffer only if it passes the above four tests
  • Alpha Blending is done where, Alpha value (diffuse-material value) of the RGBA code, is used for combining the color of the fragment being processed with that of the pixel already stored in the frame buffer to produce an effects like transparency.
  • Dithering applied to the fragment's color or color-index value
  • User defined logical operation can be applied between the fragment and the value stored at the corresponding location in the frame buffer; the result replaces the current frame buffer value. This is done only in the color index mode.

Click here to go to Top

OpenGL Command SYNTAX

OpenGL commands use the prefix 'gl'. Some OpenGL command names have a number at the end that indicates the number of values that must be presented to the command. The character that follows the number indicates the specific type of the arguments like signed short integer (s), signed integer (i), character (b), single-precision floating-point (f) or double precision floating-point (d). If the final character is 'v', indicating that the command takes a pointer to an array (a vector) of values. OpenGL defined constants begin with GL_, use all capital letters, and use underscores to separate words.

Click here to go to Top

OpenGL Primitives

OpenGL supports the following primitives

  • Points
  • Lines
  • Line Loops
  • Polygons
  • Triangles
  • Triangle Fans
  • Triangle Strips
  • Quads and
  • Quad Strips

The syntax for defining an n vertex primitive is as given below

glBegin (primitive type);
       glVertex3f (x1, y1, z1);     
       glVertex3f (x2, y2, z2);
       .....
       glVertex3f(xn, yn, zn);
glEnd();

Click here to go to Top

OpenGL State Variables

As a geometric primitive is drawn, each of its vertices is affected by OpenGL "state" variables associated with the current RC. These state variables specify information such as line width, line stipple pattern, color, shading method, fog, polygon culling, etc . . . They can be broadly categorized as follows.

  • Some variables hold the on-off states of OpenGL capabilities that can be turned on or off. Examples of such capabilities are Lighting, Alpha blending, Depth test, etc. You can turn these capabilities On and Off using the commands glEnable() and glDisable(). For example,
    glEnable(GL_LIGHTING);  // enables lighting
    
  • Some state variables specify the modes for certain OpenGL functionality that can operate in various modes. Examples are Shading Model, Lighting Model etc. Mode state variables require commands specific to the state variable being accessed in order to change its value. For example,
    glShadeModel(GL_SMOOTH); 
    // Sets the shade mode to Gouraud 
    // Shading
    
  • Some state variables hold the information regarding the current color, current normal, line width etc. Value state variables require commands specific to the state variable being accessed in order to change it value. For example,
    glColor3f(1.0f, 0.0f, 0.0f);
    //Sets the current drawing color to Red
    

    All OpenGL state variables have a default value.

Click here to go to Top

OpenGL Application Layout

The 'View' class of a MFC program that uses OpenGL APIs to render will have the following layout.

PreCreateWindow

Set up the correct window style

WM_CREATE message

Create a device context
Choose a pixel format
Set up palette if needed
Create an OpenGL Rendering context (RC)

Make the Rendering Context current
Set the OpenGL state variables
Make the Rendering context not current

WM_SIZE message

Set up Aspect ratio
Set up perspective viewing volume

WM_ERASEBACKGROUND

Do not allow windows to erase the background (which may cause flicker)

WM_PAINT message

Make the Rendering Context current
Draw something
Make the Rendering context not current

WM_DESTROY message

Make the Rendering context not current
Destroy the Rendering Context

Click here to go to Top

Lets Write Simple OpenGL Application

Let's write a simple OpenGL application to display two cubes rotating in different directions and illuminated by a spotlight. I have carefully selected this example to familiarize you the OpenGL transformations. Before getting into the code, lets see the major parts of the above layout in little more detail

Setting up a proper Window style

In the PreCreateWindow member function, modify the default window style so that WS_CLIPSIBLINGS and WS_CLIPCHILDREN bits are set. This is to prevent OpenGL from displaying output in child windows.

Choosing a pixel format

The PIXELFORMATDESCRIPTOR structure describes a pixel format used by a DC. Since this is the most error prone area for a beginner, I have listed the members of the PIXELFORMATDESCRIPTOR structure and their typical values for a beginners application in Appendix A. The reader is advised to refer it before further reading.

For choosing a Pixel Format, you have to fill in a PIXELFORMATDESCRIPTOR structure with the desired format and passing it to the system using the ChoosePixelFormat() API function. The quality of the output depends on the values you choose here. The system gives a close match depending on the resources available on that particular system. You can examine the granted pixel format using the API function DescribePixelFormat(). You have to loop through this cycle until you get the most appropriate pixel format for your application on that system. Set the pixel format using the API SetPixelFormat().

Setting up a logical palette if needed

Examine the dwFlags member of the PIXELFORMATDESCRIPTOR and check if the PFD_NEED_PALETTE flag is set. If so, set up a logical palette with a reasonable number of colors.

Creating the RC and making it current

The API function wglCreateContext() takes the DC (for which we have already set the pixelformat) as the argument, creates the RC and returns the handle. You can use wglMakeCurrent() to make the RC current and not current.

Setting the state variables

Since our application is very simple and straightforward, default values will do for most of the state variables. I have set the shading model to be Smooth, enabled Lighting, Vertex winding direction of the faces of the cube to be Counter Clockwise, drawing color, etc in this application.

Setting the aspect ratio and viewing frustum

While the user changes the size of the window, the aspect ratio also should be changed dynamically to avoid distortion of the displayed elements. Viewing frustum can be defined by defining the View-Angle, distance to the front-clipping plane, dimensions of the front clipping plane and the distance to the back-clipping plane. This also implicitly defines the perspective ratio. Any object or the parts of object that goes out of this viewing frustum is clipped. As I mentioned earlier, the PROJECTION Matrix stack is used to hold these transformations. These transformations can be applied to the PROJECTION Matrix using the APIs gluPerspective() and glViewPort().

Drawing Something

Drawing a 3D Object.

OpenGL can draw only its primitives. It is application's responsibility to split the target entities into OpenGL primitives. For example, in our application, target entity is a Cube. Since cube is not a primitive, we have to split it into the six Faces. Face is a primitive (Polygon) and can be defined as mentioned earlier (see OpenGL primitives). While defining vertices, keep in mind that OpenGL's default grid is a cube of side 2 units and centered at origin. This means that X, Y, Z coordinates can range from 1.0 to -1.0.

Applying Transformations

As I have mentioned earlier, MODELVIEW Matrix is used to transform the primitives. A general layout for applying transformations to primitives is given below.

Set MODELVIEW Matrix as the current matrix
Initialize MODELVIEW Matrix

Define Transformation T1
Define Transformation T2
....
Define Transformation Tn

Define Primitive P1
Define Primitive P2
...
Define Primitive Pn

The thumb rule is, any primitive is affected by the transformations defined above its definition and below the initialization of the matrix. Hence in the above case, all the transformations are applied to all the primitives. Suppose we want to apply separate transformations for the two cubes in our application, the layout will be as shown below.

Set MODELVIEW Matrix as the current 

Initialize MODELVIEW Matrix

Define Transformation Ta
Define Transformation Tb
...
Define Transformation Tn

Define Cube1 Primitives 

Initialize MODELVIEW Matrix

Define Transformation TA
Define Transformation TB
...
Define Transformation TN

Define Cube2 Primitives 

Now, consider the case where some transformations are common and some are separate for the cubes. In that case, we have to retain the matrix that holds common transformations. Fortunately, we can use the matrix stack. The layout becomes

Set MODELVIEW Matrix as the current 
Initialize MODELVIEW Matrix

Define CommonTransformation Tc1
Define CommonTransformation Tc2
...
Define CommonTransformation Tcn

Push Matrix
        Define Transformation Ta
        Define Transformation Tb
        ....
        Define Transformation Tn
      
        Define Cube1 Primitives 
Pop Matrix
Push Matrix
        Define Transformation TA
        Define Transformation TB
        ....
        Define Transformation TN

        Define Cube2 Primitives 
Pop Matrix

Wondering what is happening? The idea is simple. When you push a matrix, you are actually putting a copy of the current matrix on the top of the matrix stack, leaving you with two identical matrices on the top. When you pop, OpenGL removes one copy from the stack, leaving the original one on the top.

Applying Lighting

In OpenGL, Light has ambient, diffusion and specular properties. You can set RGB values of each component. You can also specify the position, direction, cut-off angle and attenuation factors for the light. These property vectors are passed to OpenGL using the API glLightfv().

Apart from specifying light properties, OpenGL allows you to specify the properties of the surface materials of the Objects you are drawing. These properties are Ambient light reflection, Diffusion light reflection, Specular light reflection, Emission, and Shininess. These property vectors are passed to OpenGL using the API glMaterialfv().

The appearance of an object depends on the properties of light falling on it and the reflective properties of the surfaces.

In order for OpenGL's lighting model to work, you should also specify the Normals for each vertex of the object using the API glNormal3f(). Note that one RC can support only upto 8 lights.

Click here to go to Top

Building The Application

Now its time to see the above concepts in action. Crank up your MFC AppWizard. Create a simple SDI application. Add Links to OPENGL32.LIB, GLU32.LIB and GLAUX.LIB

In the View class,

Include gl/gl.h, gl/glu.h and gl/glaux.h

Override PrecreateWindow()

Add handlers for WM_CREATE,
WM_DESTROY, WM_SIZE,
WM_ERASEBACKGROUND,
And WM_TIMER

Add the following member variables

HGLRC m_hRC;
GLfloat m_Angle;
GLfloat ambmat1 [4]; 
GLfloat specmat1 [4];
GLfloat ambmat2 [4]; 
GLfloat specmat2 [4];
GLfloat amblight1 [4];
GLfloat difflight1 [4]; 
GLfloat speclight1 [4]; 
GLfloat poslight1 [4];
GLfloat dirlight1[4];

Add the following member functions

void DrawNextFrame()    
void CalcNormal ( double*, double* , double* ,double* )
void DrawCube()
void SetupLogicalPalette()

Now, modify the View.cpp file to look like the one given below

// OGLAnimationView.cpp: 
// implementation of the COGLAnimationView class 
// #include "stdafx.h" 
#include "OGLAnimation.h"
#include "OGLAnimationDoc.h" 
#include "OGLAnimationView.h" 
#ifdef _DEBUG 
#define new DEBUG_NEW 
#undef THIS_FILE static char THIS_FILE[] = __FILE__; 
#endif 
///////////////////////////// //
 COGLAnimationView 
 IMPLEMENT_DYNCREATE(COGLAnimationView, CView) 
 BEGIN_MESSAGE_MAP(COGLAnimationView, CView) 
 //{{AFX_MSG_MAP(COGLAnimationView) 
 ON_WM_CREATE() ON_WM_DESTROY() 
 ON_WM_SIZE() ON_WM_TIMER() ON_WM_ERASEBKGND() 
 //}}AFX_MSG_MAP END_MESSAGE_MAP()
 ///////////////////////////////////// // 
 COGLAnimationView construction/destruction 
 COGLAnimationView::COGLAnimationView() 
 { 
 m_Angle=0.0f; // Ambient, diffusion, and specular 
 // light reflective properties of the 
 // surfaces of the cube1 (Green) 
 ambmat1 [0]=0.0f; ambmat1 [1]=0.3f; 
 ambmat1 [2]=0.0f; ambmat1 [3]=1.0f; 
 specmat1 [0]=0.0f; specmat1 [1]=0.8f; 
 specmat1 [2]=0.0f; specmat1 [3]=1.0f; 
 // Ambient, diffusion, and specular 
 // light reflective properties of the 
 // surfaces of the cube2 (Red) ambmat2 [0]=0.3f; 
 ambmat2 [1]=0.0f; ambmat2 [2]=0.0f; 
 ambmat2 [3]=1.0f; specmat2 [0]=0.8f; 
 specmat2 [1]=0.0f; specmat2 [2]=0.0f; 
 specmat2 [3]=1.0f; // Ambient, diffusion, specular, position, 
 // and direction properties of the light1 amblight1 
 [0]=0.8f; amblight1 [1]=0.8f; 
 amblight1 [2]=0.8f; amblight1 [3]=1.0f; 
 difflight1 [0]=0.2f; difflight1 [1]=0.2f; 
 difflight1 [2]=0.2f; difflight1 [3]=1.0f; 
 speclight1 [0]=0.1f; speclight1 [1]=0.1f; 
 speclight1 [2]=0.1f; speclight1 [3]=1.0f; 
 dirlight1 [0]=0.0f; dirlight1 [1]=0.0f; 
 dirlight1 [2]=0.0f; dirlight1 [3]=1.0f; 
 poslight1 [0]=0.0f; poslight1 [1]=0.5f; 
 poslight1 [2]=0.0f; poslight1 [3]=1.0f; 
 } 
 COGLAnimationView::~COGLAnimationView() 
 { } BOOL COGLAnimationView::PreCreateWindow(CREATESTRUCT& cs) 
 { 
 //set window style 
 //cs.style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS ; 
 return CView::PreCreateWindow(cs); 
 } 
 ///////////////////////////////////////// // 
 COGLAnimationView drawing 
 void COGLAnimationView::OnDraw(CDC* pDC) 
 { 
 COGLAnimationDoc* pDoc = GetDocument();
  ASSERT_VALID(pDoc); DrawNextFrame();
 } 
 ////////////////////////////////////////////////////////// // 
 COGLAnimationView diagnostics 
 #ifdef _DEBUG void COGLAnimationView::AssertValid() 
 const 
 { 
 CView::AssertValid(); 
 } 
 void COGLAnimationView::Dump(CDumpContext& dc) 
 const 
 { 
 CView::Dump(dc); 
 } 
 COGLAnimationDoc* COGLAnimationView::GetDocument() 
 { 
 ASSERT(m_pDocument->  IsKindOf(RUNTIME_CLASS(COGLAnimationDoc))); 
 return (COGLAnimationDoc*)m_pDocument; 
 } 
 #endif 
 //_DEBUG 
 ////////////////////////////////////////////////////////// // 
 COGLAnimationView message 
 handlers int COGLAnimationView::OnCreate
 (LPCREATESTRUCT lpCreateStruct) 
 { 
 if (CView::OnCreate(lpCreateStruct) == -1)
 return -1; 
 //Create an appropriate Rendering Context which 
 is needed by OGL //to draw anything. 
 PIXELFORMATDESCRIPTOR pfd= 
 { sizeof(PIXELFORMATDESCRIPTOR),
 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
 PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 24, 
 //color depth 0,0,0,0,0,0, 0,0,0,0,0,0,0, 32, 
 //number of Depth buffer bits per pixel 0,0, 
 PFD_MAIN_PLANE, 0, 0,0,0 }; CClientDC clientDC(this); 
 int pixelformat=ChoosePixelFormat(clientDC.m_hDC,&pfd);
 SetPixelFormat(clientDC.m_hDC,pixelformat,&pfd); 
 // Cross-check the pixel format 
 //Here, I'm checking only whether palette is needed 
 DescribePixelFormat
 (clientDC.m_hDC,pixelformat,sizeof(pfd),&pfd); 
 if(pfd.dwFlags & PFD_NEED_PALETTE)  
 SetupLogicalPalette(); 
 // Create RC m_hRC= wglCreateContext(clientDC.m_hDC); 
 //Set the state variables 
 wglMakeCurrent(clientDC.m_hDC,m_hRC); 
 //Make RC current glEnable(GL_DEPTH_TEST); 
 //Enable Depth Test glEnable(GL_LIGHTING); 
 //Enable Lighting glShadeModel(GL_SMOOTH); 
 //Gauraud shading glClearColor(0.0f,0.0f,0.0f,0.0f); 
 //The color used to 
 //clear the screen is black 
 glFrontFace(GL_CCW); 
 //Vertices of the primitives are wound 
 //in counter clock direction 
 wglMakeCurrent(clientDC.m_hDC,NULL); 
 //Make the RC not-current 
 //Initialize Timer for animation SetTimer(1,200,0); return 0; 
 } 
 void COGLAnimationView::OnDestroy() 
 {
  CView::OnDestroy(); KillTimer(1); wglDeleteContext(m_hRC); 
 } 
 void COGLAnimationView::OnSize(UINT nType, int cx, int cy) 
 { 
  CView::OnSize(nType, cx, cy); GLdouble ar; 
 // aspect ratio 
 // ReInitialize Projection and 
 // ModelView Matrices to adapt to 
 // new window size. CClientDC cdc(this);
 wglMakeCurrent(cdc.m_hDC,m_hRC); 
 glMatrixMode(GL_PROJECTION); 
 glLoadIdentity(); 
 //initialize the PROJECTION 
 matrix if(cy!=0)ar=(GLdouble)cx / 
 (GLdouble)cy; 
 gluPerspective(40.0, //View Angle ar, 
 //aspect ratio 1.0, //distance to 
 front clipping plane 40);
 /distance to back clipping plane glViewport(0,0,cx,cy); 
 //Set the view port to be entire screen 
 glMatrixMode(GL_MODELVIEW); 
 glLoadIdentity(); 
 //initialize the MODELVIEW matrix 
 wglMakeCurrent(NULL,NULL); 
 } 
 void COGLAnimationView::OnTimer(UINT nIDEvent) 
{ 
 // Change orientation angle of objects m_Angle += 10; 
 if(m_Angle>355)m_Angle=0.0f; 
 // Redraw the Frame DrawNextFrame(); 
 CView::OnTimer(nIDEvent); 
} 
 void COGLAnimationView::
CalcNormal (double * p1, double * p2, double * p3, 
double * n)
{ 
 //Find the cross product of the vectors p1p2 and 
 p1p3 double a[3],b[3]; a[0]=p2[0]-p1[0]; a[1]=p2[1]-p1[1]; 
 a[2]=p2[2]-p1[2]; b[0]=p3[0]-p1[0]; 
 b[1]=p3[1]-p1[1]; b[2]=p3[2]-p1[2]; 
 n[0]=a[1]*b[2]-a[2]*b[1]; n[1]=a[2]*b[0]-a[0]*b[2]; 
 n[2]=a[0]*b[1]-a[1]*b[0]; 
 //make it a unit vector 
 double len=sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]); 
 if(len==0)return; n[0]=n[0]/len; 
 n[1]=n[1]/len; n[2]=n[2]/len; 
} 
 BOOL COGLAnimationView::OnEraseBkgnd(CDC* pDC) 
 { 
  // Do not erase the background, just return 
  //return CView::OnEraseBkgnd(pDC); 
  return TRUE; 
 } 
 void COGLAnimationView::SetupLogicalPalette() 
 { 
  struct 
  { 
   WORD VersionNumber; WORD NumberOfEntries; 
   PALETTEENTRY PalEntryArr[256];
  } 
  MyPalette = { 0x300, 256} 
  // Code for setting RGB values in 
  // each of the 256 PALETTEENTRY structure of 
  // MyPalette goes here. I have not included 
  // it for it is too lengthy and doesn't 
  // convey much. But If you get the following 
  // message box, you will have to add it here. 
  AfxMessageBox(" Add colors to the Logical palette"); 
  m_hPalette = CreatePalette ((LOGPALETTE*) &MyPalette); 
  } 
  void COGLAnimationView::DrawCube() 
  { 
   //Since cube is not a primitive, we draw each face 
   using OpenGL 
   //Note that vertices are wound in Counter ClockWise 
   Direction and 
   //The Direction of the normal is out ward 
   double v1[3],v2[3],v3[3],n[3]; 
   //front face v1[0]=-0.5f;v1[1]=0.5f;
   v1[2]=0.5f; v2[0]=-0.5f;v2[1]=-0.5f;
   v2[2]=0.5f; v3[0]=0.5f;v3[1]=-0.5f;
   v3[2]=0.5f; CalcNormal(v1,v2,v3,n); 
   glBegin(GL_POLYGON); 
   glNormal3f((GLfloat)n[0],(GLfloat)n[1],(GLfloat)n[2]); 
   glVertex3f(-0.5f,0.5f,0.5f); 
   glVertex3f(-0.5f,-0.5f,0.5f); 
   glVertex3f(0.5f,-0.5f,0.5f); 
   glVertex3f(0.5f,0.5f,0.5f); glEnd(); 
   //right face v1[0]=0.5f;v1[1]=0.5f;v1[2]=0.5f; 
   v2[0]=0.5f;v2[1]=-0.5f;v2[2]=0.5f; v3[0]=0.5f;
   v3[1]=-0.5f;v3[2]=-0.5f; 
   CalcNormal(v1,v2,v3,n); 
   glBegin(GL_POLYGON); 
   glNormal3f((GLfloat)n[0],(GLfloat)n[1],(GLfloat)n[2]);
   glVertex3f(0.5f,0.5f,0.5f); 
   glVertex3f(0.5f,-0.5f,0.5f); 
   glVertex3f(0.5f,-0.5f,-0.5f); 
   glVertex3f(0.5f,0.5f,-0.5f); 
   glEnd(); //back face v1[0]=0.5f;
   v1[1]=0.5f;v1[2]=-0.5f; v2[0]=0.5f;
   v2[1]=-0.5f;v2[2]=-0.5f; v3[0]=-0.5f;
   v3[1]=-0.5f;v3[2]=-0.5f; 
   CalcNormal(v1,v2,v3,n); glBegin(GL_POLYGON);
   glNormal3f((GLfloat)n[0],(GLfloat)n[1],(GLfloat)n[2]); 
   glVertex3f(0.5f,0.5f,-0.5f); glVertex3f(0.5f,-0.5f,-0.5f);
   glVertex3f(-0.5f,-0.5f,-0.5f); glVertex3f(-0.5f,0.5f,-0.5f); 
   glEnd(); //left face v1[0]=-0.5f;v1[1]=0.5f;v1[2]=-0.5f;
   v2[0]=-0.5f;v2[1]=-0.5f;v2[2]=-0.5f; v3[0]=-0.5f;v3[1]=-0.5f;
   v3[2]=0.5f; CalcNormal(v1,v2,v3,n); glBegin(GL_POLYGON);
   glNormal3f((GLfloat)n[0],(GLfloat)n[1],(GLfloat)n[2]);
   glVertex3f(-0.5f,0.5f,-0.5f); glVertex3f(-0.5f,-0.5f,-0.5f); 
   glVertex3f(-0.5f,-0.5f,0.5f); glVertex3f(-0.5f,0.5f,0.5f); 
   glEnd(); 
   //bottom face v1[0]=-0.5f;v1[1]=-0.5f;v1[2]=0.5f; 
   v2[0]=-0.5f;v2[1]=-0.5f;v2[2]=-0.5f; 
   v3[0]=0.5f;v3[1]=-0.5f;v3[2]=-0.5f; 
   CalcNormal(v1,v2,v3,n); 
   glBegin(GL_POLYGON); 
   glNormal3f((GLfloat)n[0],(GLfloat)n[1],(GLfloat)n[2]); 
   glVertex3f(-0.5f,-0.5f,0.5f); glVertex3f(-0.5f,-0.5f,-0.5f); 
   glVertex3f(0.5f,-0.5f,-0.5f); glVertex3f(0.5f,-0.5f,0.5f); 
   glEnd(); 
   //top face v1[0]=-0.5f;v1[1]=0.5f;
   v1[2]=-0.5f; v2[0]=-0.5f;v2[1]=0.5f;
   v2[2]=0.5f; v3[0]=0.5f;v3[1]=0.5f;v3[2]=0.5f;
   CalcNormal(v1,v2,v3,n); 
   glBegin(GL_POLYGON); 
   glNormal3f((GLfloat)n[0],(GLfloat)n[1],(GLfloat)n[2]); 
   glVertex3f(-0.5f,0.5f,-0.5f); glVertex3f(-0.5f,0.5f,0.5f); 
   glVertex3f(0.5f,0.5f,0.5f); glVertex3f(0.5f,0.5f,-0.5f); 
   glEnd(); } void COGLAnimationView::DrawNextFrame() 
   { 
    m_Angle += 10.0f; // increment the angle of rotations
    of the cubes CClientDC cdc(this); 
	wglMakeCurrent(cdc.m_hDC,m_hRC); 
	//Make the RC current
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
	//Clear the Color and the Depth buffers 
	glMatrixMode(GL_MODELVIEW); glLoadIdentity(); 
	//Initialize matrix glTranslatef(0.0, 0.0,-4.0); 
	//translate both cubes and the light into 
	//Viewing frustum glPushMatrix(); 
	//Draw the Green Cube 
	// Set current material properties 
    glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,ambmat1); 
	glMaterialfv(GL_FRONT,GL_SPECULAR,specmat1); 
    // Apply transformations specific to this cube 
	glTranslatef(-2.0, 0.0,0.0); 
	//translate to left glRotatef(m_Angle,0.0,1.0,0.0); 
	//rotate about local Y axis 
	// Define the primitives forming the cube 
	DrawCube(); 
	glPopMatrix(); 
	glPushMatrix(); 
	//Draw the Red Sphere 
	//Set current material properties 
	glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,ambmat2); 
	glMaterialfv(GL_FRONT,GL_SPECULAR,specmat2); 
	// Apply transformations specific to this cube 
	glTranslatef(0.0f,0.0f ,-6.0f); 
	//the light is above the Red Sphere and directed 
	downward float co=160.0; 
	//Cut Off Angle 
	//Pass on the light properties to 
	OpenGL glLightfv(GL_LIGHT1,GL_AMBIENT,amblight1); 
	glLightfv(GL_LIGHT1,GL_DIFFUSE,difflight1); 
	glLightfv(GL_LIGHT1,GL_SPECULAR,speclight1); 
	glLightfv(GL_LIGHT1,GL_POSITION,poslight1); 
	glLightfv(GL_LIGHT1,GL_SPOT_CUTOFF,&co); 
	glLightfv(GL_LIGHT1,GL_SPOT_DIRECTION,dirlight1); 
	glEnable(GL_LIGHT1); 
	// Turn On the Light glRotatef(m_Angle,1.0,0.0,0.0); 
	//rotate the cube about // local X axis DrawCube(); 
	glPopMatrix(); glFlush(); 
	// Make sure all the commands 
	//are executed SwapBuffers(cdc.m_hDC); 
	//Swap the buffers wglMakeCurrent(NULL,NULL); 
	//Make the RC not-current }

Click here to go to Top

Performance Tips

Here are some of the general tips that can improve the performance of your OpenGL applications. I have not applied them to the above code to keep it simple and readable.

  • Always use triangles, triangle strips and perhaps triangle fans as your primitives. Other primitives such as polygons and quads are decomposed into triangles before rasterization. So if the same model is to be drawn multiple times, considerable time may be lost decomposing the primitives each time it is drawn.
  • Rather than drawing independent lines and triangles with distinct glBegin()-glEnd() pairs, use the GL_LINES, GL_TRIANGLES, and GL_QUADS tokens to specify as many vertices as possible between glBegin()-glEnd() pairs.
  • For any 3D object, we can see that some edges and vertices are common for two or more faces. In OpenGL, while constructing the Object with its faces as primitives, there is no guarantee that the shared edge will share the same pixels since the two edges may be rasterized differently. This may result in formation of narrow cracks along the edges while animating the object. In order to avoid the problem, shared edges should share the same vertex coordinates so that the edge equations are the same.
  • Similarly, all the coplanar Faces of the object should share the same Normal for proper lighting effect.
  • If a surface is intended to be closed, it should share the same vertex coordinates where the surface specification starts and ends.
  • Use unit-length Normals instead of using GL_NORMALIZE.
  • Performance can be improved by avoiding unnecessary state changes. Avoid redundantly re-specify the color or material for each line and polygon, if it has not changed since the last line or polygon.
  • Beware of invalid data like Zero-length vectors and NaN (Not a Number) colors and coordinates which can slow down your application considerably.
  • Use Flat shading wherever smooth shading isn't required.
  • Use display lists to encapsulate the rendering of objects that will be drawn repeatedly.
  • Use specific matrix calls such as glLoadIdentity() glRotate, glTranslate() and glScale(), rather than composing your own rotation, translation, and scale matrices and calling glMultMatrix().

Click here to go to Top

Get Little Help From OpenGL Utility LIBRARY

The OpenGL core API is dedicated to the basic rendering functionality. The Utility Library assists you in performing many tasks that are common in graphics. The prefix for Utility library functions is "glu". Utility Library helps you in

  • Transforming coordinates
  • Performing Selection and feedback from an OpenGL screen
  • Tessellating polygons
  • OpenGL renders only convex polygons whereas your polygon may be concave and may even contain holes. You can use the Utility functions to tessellate the polygon into convex polygons.
  • Manipulating images for texture applications The dimensions of the images used for texture mapping in OpenGL should be a power of two. If your texture has some arbitrary dimensions, you can use Utility functions to scale the image to a nearest power of two.
  • Rendering canonical shapes like spheres, cylinders and disks, which you will need extensively
  • Support for Non-uniform rational B-spline (NURBS) curves and surfaces
  • Error reporting

Clearly knowing how to use the Utility Library is as important as learning OpenGL.

Click here to go to Top

Conclusion

As you may have guessed, there is still a reasonably large learning curve to take full advantage of OpenGL. Even though 'Texture mapping' is commonly used and a handy feature of OpenGL, lack of time and space didn't allow me to include it in this article. A lot of experimentation on the sample code given above can give you a better insight of OpenGL transformations.Exhaustive helps are available at www.OpenGL.org and www.sgi.com.

Click here to go to Top

APPENDIX A

PIXELFORMATDESCRIPTOR structure

The members, their possible values and explanations are given below

Member
Possible Values
Description

NSize

sizeof (PIXELFORMATDESCRIPTOR)

Size of the PIXELFORMATDESCRIPTOR Structure

NVersion

1

Version number of the structureCurrently,1

DwFlags

PFD_DRAW_TO_WINDOW


Set this flag if you want to draw the output to a window

 

PFD_DRAW_TO_BITMAP


Set this flag if you want to draw the output to memory bitmap.

 

PFD_DOUBLEBUFFER


Enables double buffering. Set this flag if your output is going to be animated.

 

PFD_GENERIC_FORMAT








PFD_GENERIC_
ACCELERATED







These two flags can be used to identify the underlying OpenGL driver model implemented in the system. If both the flags are not set, then it's a fully hardware accelerated card running an ICD (Installable Client Driver). This is the fastest implementation. If both the flags are set, then it's a partially accelerated card running a MCD (Mini Client Driver). This is the second fastest and most common implementation. If PFD_GENERIC_FORMAT alone is set, then it's a generic software driver.

 

PFD_NEED_PALETTE

If set, a logical palette is needed

 

PFD_NEED_SYSTEM_PALETTE



If set, the system uses OpenGL hardware that supports only one hardware palette

 

PFD_STEREO




Enables buffers for holding the left and right eye parallaxes of the same image. Not supported under NT.

 

PFD_SUPPORT_GDI




Set this flag if you want to use the familiar GDI drawing functions. Cannot be used when double buffering is enabled.s

 

PFD_SUPPORT_OPENGL

If set, the buffer supports OpenGL drawing functions

IpixelType

PFD_TYPE_RGBA






Color coding in RGBA (Red-Green-Blue-Alpha). The Alpha values are used in blending. Running in this mode allows OpenGL to render the output more realistically.

 

PFD_TYPE_INDEX






Color coding in the familiar color index-palette mode. Use this mode only if you are running in less than 15-bit color and you are not using blending, lighting, texture mapping etc.s

CcolorBits

8, 16, 24 etc


Number of bits used to represent a color (color depth).

CredBits

0 will do for normal applications

Number of red bits in the RGBA buffer. Valid only in RGBA mode

CredShift

0 will do for normal applications

The shift count for red bits in the RGBA color buffer Valid only in RGBA modes

CgreenBits

0 will do for normal applications

Number of Green bits in the RGBA buffer Valid only in RGBA mode

CgreenShift

0 will do for normal applications

The shift count for Green bits in the RGBA color buffer Valid only in RGBA mode

CblueBits

0 will do for normal applications

Number of Blue bits in the RGBA buffer Valid only in RGBA mode

CblueShift

0 will do for normal applications

The shift count for Blue bits in the RGBA color buffer Valid only in RGBA mode

CalphaBits

0 will do for normal applications

Number of Alpha bits in the RGBA buffer. Valid only in RGBA mode

CalphaShift

0 will do for normal applications

The shift count for Alpha bits in the RGBA color buffer Valid only in RGBA mode

CaccumBits

0 will do for normal applications

Number of bits per pixel in the accumulation buffer

CaccumRedBits

0 will do for normal applications

Number of Red bits per pixel in the accumulation buffer

CaccumGreenBits

0 will do for normal applications

Number of Greenbits per pixel in the accumulation buffer

CaccumBlueBits

0 will do for normal applications

Number of Blue bits per pixel in the accumulation buffer

CaccumAlphaBits

0 will do for normal applications

Number of Alpha bits per pixel in the accumulation buffer

CdepthBits

16, 32 etc

Number of bits per pixel in the Depth buffer

CstencilBits

0 will do for normal applications

Number of bits per pixel in the Stencil buffer

CauxBuffers

0



Number of bits per pixel in the Auxiliary buffer. Not supported under Windows NT

IlayerType

PFD_MAIN_PLAIN


Specifies the layer type. Only PFD_MAIN_PLAIN is supported under NT

Breserved

0

Not supported under Windows NT

DwLayerMask

0

Not supported under Windows NT

DwVisibleMask

0

Not supported under Windows NT

DwDamageMask

0

Not supported under Windows NT

Click here to go to Top