Graphics Programming Using OpenGL and MFC

CALIFORNIA SOFTWARE LABS

R E A L I Z E Y O U R I D E A S

California Software Labs 6800 Koll Center Parkway, Suite 100 Pleasanton CA 94566, USA. Phone (925) 249 3000 Fax (925) 426 2556 [email protected] http://www.cswl.com www.cswl.com

Graphics Programming Using OpenGL and MFC

A Technical Report

Technical Expertise Level : Intermediate Requires knowledge of : MFC

INDEX

INTRODUCTION...... 3 OPENGL SUPPORT ON WINDOWS NT...... 3 WHAT CAN OPENGL DO FOR ME...... 4 A GRAPHICS PRIMER...... 5 OPENGL PROCESS PIPELINE...... 9 OPENGL COMMAND SYNTAX...... 11 OPENGL PRIMITIVES...... 11 OPENGL STATE VARIABLES...... 12 OPENGL APPLICATION LAYOUT...... 13 LETS WRITE A SIMPLE OPENGL APPLICATION...... 15 BUILDING THE APPLICATION...... 20 PERFORMANCE TIPS...... 38 GET LITTLE HELP FROM OPENGL UTILITY LIBRARY...... 40 CONCLUSION...... 41 APPENDIX A...... 41

In this article, I have tried to familiarise the reader, the most commonly used concepts and techniques in OpenGL Programming. Eventhough the focus is on

CSWL Inc, Pleasanton, California - 2 - www.cswl.com

building simple 3D graphic programmes, many of the techniques presented here are also applicable to advanced applications.

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 guarantied 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.

OPENGL SUPPORT ON WINDOWS NT

Following libraries are packed with Windows NT to support OpenGL.

1. The main OpenGL library Contains the core APIs that are guarantied 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. 2. 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

CSWL Inc, Pleasanton, California - 3 - www.cswl.com

APIs are also guarantied to be implemented on all platforms that supports OpenGL. 3. OpenGL Auxiliary Library Contains some special APIs that are platform dependent and may not be available on all platforms. 4. 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).

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.

CSWL Inc, Pleasanton, California - 4 - www.cswl.com

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.

1. Monochrome Here, 1 bit represents a pixel. The pixel is turned on or off depending on the value of the bit. 2. 16 Color Here, 4 bits represents a pixel. Each nibble holds an index of a color in a 16-color palette. 3. 256 Color Here each pixel is represented by a Byte, which holds an index of a color in a 256-color palette. 4. 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

CSWL Inc, Pleasanton, California - 5 - www.cswl.com

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

CSWL Inc, Pleasanton, California - 6 - www.cswl.com

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

CSWL Inc, Pleasanton, California - 7 - www.cswl.com

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.

CSWL Inc, Pleasanton, California - 8 - www.cswl.com

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,

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

The usage of these matrices is discussed later.

CSWL Inc, Pleasanton, California - 9 - www.cswl.com

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.

1. The user defined vertices are sent to a vertex buffer 2. When vertices for an entire OpenGL primitive are buffered, the Model- View matrix transformations are applied to the Vertices and to the current Normal. 3. Texture Matrix transformations are applied to texture coordinates 4. Lighting and fog factors are calculated for the vertices using the current color settings 5. The combined information from the above processes are used to assemble a Primitive 6. The Projection-View matrix transformations are applied to the primitive 7. Clipping and culling of the primitive is performed 8. Light intensities at vertices of the objects are evaluated 9. Sub-pixel accurate slopes and offsets are generated for color, depth, alpha, fog, and texture coordinates 10. Edge walking and span interpolations are performed for surface primitives. The resulting interpolated data is referred to as Fragments 11. The Scissor test is done to eliminate the fragments outside the Scissor rectangle on the screen specified by the user 12. 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 13. 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

CSWL Inc, Pleasanton, California - 10 - www.cswl.com

14. 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 15. A fragment produced by rasterization modifies the corresponding pixel in the frame buffer only if it passes the above four tests 16. 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. 17. Dithering applied to the fragment's color or color-index value 18. 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.

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.

CSWL Inc, Pleasanton, California - 11 - www.cswl.com

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();

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.

CSWL Inc, Pleasanton, California - 12 - www.cswl.com

1. 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

2. 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

3. 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.

CSWL Inc, Pleasanton, California - 13 - www.cswl.com

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

CSWL Inc, Pleasanton, California - 14 - www.cswl.com

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

LETS WRITE A SIMPLE OPENGL APPLICATION

Let's write a simple OpenGL application to display two cubes rotating in diffrent 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

1. 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.

2. 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.

CSWL Inc, Pleasanton, California - 15 - www.cswl.com

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().

3. 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.

4. 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.

5. 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.

6. Setting the aspect ratio and viewing frustum

CSWL Inc, Pleasanton, California - 16 - www.cswl.com

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().

7. 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

CSWL Inc, Pleasanton, California - 17 - www.cswl.com

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

CSWL Inc, Pleasanton, California - 18 - www.cswl.com

... 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

CSWL Inc, Pleasanton, California - 19 - www.cswl.com

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.

CSWL Inc, Pleasanton, California - 20 - www.cswl.com

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];

CSWL Inc, Pleasanton, California - 21 - www.cswl.com

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

CSWL Inc, Pleasanton, California - 22 - www.cswl.com

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;

CSWL Inc, Pleasanton, California - 23 - www.cswl.com

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;

CSWL Inc, Pleasanton, California - 24 - www.cswl.com

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); }

/////////////////////////////////////////

CSWL Inc, Pleasanton, California - 25 - www.cswl.com

// 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

CSWL Inc, Pleasanton, California - 26 - www.cswl.com

////////////////////////////////////////////////////////// // 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);

CSWL Inc, Pleasanton, California - 27 - www.cswl.com

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);

CSWL Inc, Pleasanton, California - 28 - www.cswl.com

//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

CSWL Inc, Pleasanton, California - 29 - www.cswl.com

// 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) {

CSWL Inc, Pleasanton, California - 30 - www.cswl.com

// 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];

CSWL Inc, Pleasanton, California - 31 - www.cswl.com

//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

CSWL Inc, Pleasanton, California - 32 - www.cswl.com

// 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);

CSWL Inc, Pleasanton, California - 33 - www.cswl.com

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);

CSWL Inc, Pleasanton, California - 34 - www.cswl.com

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();

CSWL Inc, Pleasanton, California - 35 - www.cswl.com

//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

CSWL Inc, Pleasanton, California - 36 - www.cswl.com

//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);

CSWL Inc, Pleasanton, California - 37 - www.cswl.com

//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

}

CSWL Inc, Pleasanton, California - 38 - www.cswl.com

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.

1. 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.

2. 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.

3. 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.

4. Similarly, all the coplanar Faces of the object should share the same Normal for proper lighting effect.

5. If a surface is intended to be closed, it should share the same vertex coordinates where the surface specification starts and ends.

6. Use unit-length Normals instead of using GL_NORMALIZE.

CSWL Inc, Pleasanton, California - 39 - www.cswl.com

7. 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.

8. Beware of invalid data like Zero-length vectors and NaN (Not a Number) colors and coordinates which can slow down your application considerably.

9. Use Flat shading wherever smooth shading isn't required.

10. Use display lists to encapsulate the rendering of objects that will be drawn repeatedly.

11. Use specific matrix calls such as glLoadIdentity() glRotate, glTranslate() and glScale(), rather than composing your own rotation, translation, and scale matrices and calling glMultMatrix().

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

CSWL Inc, Pleasanton, California - 40 - www.cswl.com

 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.

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.

APPENDIX A

PIXELFORMATDESCRIPTOR structure

The members, their possible values and explanations are given below

Member Possible Values Description Size of the PIXELFORMATDESCRIPTOR Nsize sizeof( PIXELFORMATDESCRIPTOR) Structure Version number of the NVersion 1 structureCurrently, 1 DwFlags PFD_DRAW_TO_WINDOW Set this flag if you want to draw the output to a window

CSWL Inc, Pleasanton, California - 41 - www.cswl.com

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

Enables double buffering. Set this flag if your output is going to be PFD_DOUBLEBUFFER animated. 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 PFD_GENERIC_FORMAT (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 PFD_GENERIC_ACCELERATED driver.

PFD_NEED_PALETTE If set, a logical palette is needed

If set, the system uses OpenGL hardware that supports only one PFD_NEED_SYSTEM_PALETTE hardware palette Enables buffers for holding the left and right eye parallaxes of the same image. Not supported PFD_STEREO under NT.

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

If set, the buffer supports PFD_SUPPORT_OPENGL 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.

CSWL Inc, Pleasanton, California - 42 - www.cswl.com

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, PFD_TYPE_INDEX texture mapping etc.

Number of bits used to represent CcolorBits 8, 16, 24 etc a color (color depth).

Number of red bits in the RGBA CredBits 0 will do for normal applications buffer. Valid only in RGBA mode

The shift count for red bits in the RGBA color buffer Valid only in CredShift 0 will do for normal applications RGBA mode

Number of Green bits in the RGBA buffer Valid only in RGBA CgreenBits 0 will do for normal applications mode

The shift count for Green bits in the RGBA color buffer Valid only CgreenShift 0 will do for normal applications in RGBA mode

Number of Blue bits in the RGBA CblueBits 0 will do for normal applications buffer Valid only in RGBA mode

The shift count for Blue bits in the RGBA color buffer Valid only in CblueShift 0 will do for normal applications RGBA mode

Number of Alpha bits in the RGBA CalphaBits 0 will do for normal applications buffer. Valid only in RGBA mode

The shift count for Alpha bits in the RGBA color buffer Valid only CalphaShift 0 will do for normal applications in RGBA mode

Number of bits per pixel in the CaccumBits 0 will do for normal applications accumulation buffer

Number of Greenbits per pixel in CaccumGreenBits 0 will do for normal applications the accumulation buffer

Number of Blue bits per pixel in CaccumBlueBits 0 will do for normal applications the accumulation buffer

Number of Alpha bits per pixel in CaccumAlphaBits 0 will do for normal applications the accumulation buffer

Number of bits per pixel in the CdepthBits 16, 32 etc Depth buffer

CSWL Inc, Pleasanton, California - 43 - www.cswl.com

Number of bits per pixel in the CstencilBits 0 will do for normal applications Stencil buffer

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

Specifies the layer type. Only PFD_MAIN_PLAIN is supported IlayerType PFD_MAIN_PLAIN 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

------Copyright Notice:

2002 California Software Labs. All rights Reserved. The contents on the document are not to be reproduced or duplicated in any form or kind, either in part or full, without the written permission of California Software labs. Product and company names mentioned here in are the trademarks of their respective companies.

CSWL Inc, Pleasanton, California - 44 -