CS405 Lab 1: Introduction to OpenGL Programming for Windows


Highlights of this lab:

This lab is an introduction to OpenGL programming for Windows. You will see:

Exercise:

After the lab lecture, you have approximately two weeks to:

Online OpenGL Manual

Seminar Notes

A. OpenGL Architecture on Windows

                    APPLICATION
                         |
                         V
           +-------------+------------------+
           |             |                  |
           V             V                  V
     OpenGL DLL<-------GLU32 DLL         GDI DLL
     |        |<--------------------------[wgl]
     V        V                             |
  OpenGL     Generic                        |
Installable  OpenGL                         |
  Client     Driver                         |
  Driver       |                            |
    |          |                            |
    V          V                            V
+-----------------------------------------------+
|                 DISPLAY DRIVER                |
| [OpenGL Driver]                 [GDI Driver]  |
+-----------------------------------------------+

B. Overview of Visual C++ Environment

Architecture of the Model-View-Controller

This is the basic concept in object-oriented programming.

Microsoft Foundation Classes (MFC) is an example of this architecture. MFC programs consist of:

How does an MFC Application Program Work?

After a MFC application is started, it enters its main loop, called Windows Message Loop. This main loop will wait for a message. When a message arrives, appropriate member functions in appropriate classes will be called to perform computation. This is briefly illustrated by the following diagram:

                               +------------------+
                               | Get DC           |
                  WM_CREATE    | Set Pixel Format |
                 +-----------> | Create RC from DC|
                 |             | Make RC Current  |
                 |             | using DC         |
                 |             +------------------+
                 |
    +---------+  |WM_PAINT     +-------------------+
    | Windows |  +-----------> | Make OpenGL Calls |
    | Message +->|             +-------------------+
    | Loop    |  |
    +---------+  |             +--------------------+
                 |WM_DESTROY   | Make RC Noncurrent |
                 +-----------> | Delete RC          |
                               | Release DC         |
                               +--------------------+


For example, when an application starts running, a WM_CREATE message will be automatically generated. When Windows Message Loop receives this message, it will call the handling functions associated with message, names Get Dc, etc.

A message is usually triggered by a window event. For example, when you use mouse to drag the window to a different location, a WM_PAINT message will be generated by this event, and Window Message Loop receives it and call the handling function to redraw the scene.

User's responsibility is to develop these handling functions corresponding concerned messages.

C. Building a Simple OpenGL View Class with MFC Framework

1. Start Visual C++ Environment

You will see a Start Screen similar to this one:

2. Create a New Project

The following show you how to create a framework for the OpenGL viewing class. You will need to perform the following steps.

At this point, we have created a framework for the OpenGL program we are to develop. Your main window has changed. It now has three main regions.

The Solution Explorer shows several Source Files and Header Files that have been automatically generated. There are two tabs under the Solution Explorer: Click on the "Class View" tab, and the class hierarchy will be displayed. There should be five classes (CAboutDlg, CMainFrame, COpenGLApp, COpenGLDoc, and COpenGLView) automatically created for the OpenGL project. One of them, called "COpenGLView", will be particularly interesting to you because the main task of this lab is to add functions into this class.

At this point, you might want to compile the application (which of course does nothing yet) just to make sure there are no errors in your building process.

3. Add Necessary Member Functions to the COpenGLView Class

The MFC Project Wizard creates the minimum number of member functions for each class being generated. We may take a look at them by looking at the Class View:

To examine COpenGLView class: We need to add functions for the following additional messages: To add functions for these messages, first ensure that you have selected "COpenGLView" in the "Class View". You will notice that in the Properties window, the properties of "COpenGLView" are listed. The goal is to list appropriate messages and select functions. Follow the below example for all four messages.

You may notice that, OnCreate is added into the "Class View" window.

Remember that the system can only create a skeleton for the member function. It is your responsibility to insert computations into the relevent member functions.

Click on COpenGLView again to add the next function and repeat as needed.

4. Compile Your Application

It will take a little while to compile and link the program. You may look at the output from the compiling process being displayed in the Output region at the bottom. If the project has compiled successfully, you will see a message like this:
---------------------- Done ----------------------
    Build: 1 succeeded, 0 failed, 0 skipped

It is very exciting to run this little window's program if it is your first Windows program.

A new window should be displayed in a few seconds. There are three menus: "File", "Edit", and "Help". These are the minimum options automatically created by your VC++ system. Don't be surprised; the inside window is totally blank, because we haven't written any functions yet.

5. Linking OpenGL Libraries

So far what we have created is all standard MFC stuff. It has nothing to do with OpenGL yet. To connect to OpenGL, we have do a few things first. Then we may use functions from OpenGL library.

First, we need to insert the following lines into OpenGLView.h header file:

// Include the OpenGL headers
#include "gl\gl.h"    // standard OpenGL library
#include "gl\glu.h"   // OpenGL utility library

To add the above contents, you have to first find the desired header file. This is a good time to learn how to navigate around the files of the project.

Now, you may go ahead to insert the above contents at the very beginning of this header file. Remember to save the document before you close this Editing window.

The next step is to add the OpenGL libraries to the link line:

At this point, you might want compile your application again to make sure that everything is right so far.

D. Editing Member Functions

Now that the OpenGL libraries have been added to our project, we are ready add some real OpenGL code. For this lab, you are concerned with the COpenGLView class only. Therefore, you will work with two files:

The code you will add will:

  1. Create and set up a Device Context
  2. Establish a Rendering Context
  3. Handle window events
  4. Draw a scene
  5. Clean up after itself

You will learn more about all this in Lab 2. For now here are the changes you need to make to the two files. The parts you will work with are highlighted in red.

File: OpenGLView.h
// OpenGLView.h : interface of the COpenGLView class


// You should have already added these lines.
#include "gl\gl.h"
#include "gl\glu.h"
...... class COpenGLView : public CView { protected: // create from serialization only ..... // Attributes ...... // Operations public: ...... // Implementation public: ...... protected:
      // You will add the following stuff!!!

      virtual BOOL SetupPixelFormat( void );
      virtual BOOL SetupViewport( int cx, int cy );
      virtual BOOL SetupViewingFrustum( GLdouble aspect_ratio );
      virtual BOOL SetupViewingTransform( void );
      virtual BOOL PreRenderScene( void ) { return TRUE; }
      virtual void RenderStockScene( void );
      virtual BOOL RenderScene( void );

   private:

      BOOL InitializeOpenGL();
      void SetError( int e );

      HGLRC     m_hRC;
      CDC*      m_pDC;

      static const char* const _ErrorStrings[];
      const char* m_ErrorString;
      GLfloat   sceneRotate;
// Generated message map functions protected: DECLARE_MESSAGE_MAP() public: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg BOOL OnEraseBkgnd(CDC* pDC); afx_msg void OnSize(UINT nType, int cx, int cy); }; ......
OpenGLView.cpp


// OpenGLView.cpp : implementation of the COpenGLView class

#include "stdafx.h"
......

// COpenGLView

IMPLEMENT_DYNCREATE(COpenGLView, CView)

BEGIN_MESSAGE_MAP(COpenGLView, CView)
      ......
END_MESSAGE_MAP()

// You will add stuff here!!!!
const char* const COpenGLView::_ErrorStrings[]= {
    {"No Error"},                     // 0
    {"Unable to get a DC"},           // 1
    {"ChoosePixelFormat failed"},     // 2
    {"SelectPixelFormat failed"},     // 3
    {"wglCreateContext failed"},      // 4
    {"wglMakeCurrent failed"},        // 5
    {"wglDeleteContext failed"},      // 6
    {"SwapBuffers failed"},           // 7
};
// COpenGLView construction/destruction COpenGLView::COpenGLView()
   // You will add the following line !!!
    : m_hRC(0), m_pDC(0), m_ErrorString(_ErrorStrings[0]) // Call constructors
{ ...... } ...... BOOL COpenGLView::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs
    // You will add stuff here !!!
    // An OpenGL window must be created with the following flags and must not
    // include CS_PARENTDC for the class style.
    cs.style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
return CView::PreCreateWindow(cs); } // COpenGLView drawing void COpenGLView::OnDraw(CDC* pDC) { COpenGLDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here
    // You are to add a lot stuff here !!!!!!

     ::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    PreRenderScene();

    ::glPushMatrix();
    RenderStockScene();
    ::glPopMatrix();

    ::glPushMatrix();
    RenderScene();
    ::glPopMatrix();

    ::glFinish();

    if ( FALSE == ::SwapBuffers( m_pDC->GetSafeHdc() ) )
    {
        SetError(7);
    }
} ...... // COpenGLView message handlers int COpenGLView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here
    // You will add stuff here!!!
     InitializeOpenGL();
return 0; } void COpenGLView::OnDestroy() { CView::OnDestroy(); // TODO: Add your message handler code here
    // You will add some stuff here!!!!
    if ( FALSE == ::wglDeleteContext( m_hRC ) )
    {
        SetError(6);
    }

    if ( m_pDC )
    {
        delete m_pDC;
    }
} BOOL COpenGLView::OnEraseBkgnd(CDC* pDC) { // TODO: Add your message handler code here and/or call default
    // You are to modify the following line!!!!
    //sceneRotate += 5;
return CView::OnEraseBkgnd(pDC); } void COpenGLView::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); // TODO: Add your message handler code here
    // You are to add a lot of stuff here !!!!
     GLdouble aspect_ratio; // width/height ratio

    if ( 0 >= cx || 0 >= cy )
    {
        return;
    }


    SetupViewport( cx, cy );

    // select the projection matrix and clear it
    ::glMatrixMode( GL_PROJECTION );
    ::glLoadIdentity();

    // compute the aspect ratio
    // this will keep all dimension scales equal
    aspect_ratio = (GLdouble)cx/(GLdouble)cy;
   
    // select the viewing volumn
    SetupViewingFrustum( aspect_ratio );

    // switch back to the modelview matrix
    ::glMatrixMode( GL_MODELVIEW );
    ::glLoadIdentity();

    // now perform any viewing transformations
    SetupViewingTransform();
}
/////////////////////////////////////////////////////////////////////////////
// GL helper functions
// You are to add all new functions here !!!!!!!!!!
// They include:
//    void COpenGLView::SetError( int e )
//    BOOL COpenGLView::InitializeOpenGL()
//    BOOL COpenGLView::SetupPixelFormat()
//    BOOL COpenGLView::SetupViewport( int cx, int cy )
//    BOOL COpenGLView::SetupViewingFrustum( GLdouble aspect_ratio )
//    BOOL COpenGLView::SetupViewingTransform()
//    BOOL COpenGLView::RenderScene()
//    void COpenGLView::RenderStockScene()

void COpenGLView::SetError( int e )
{
    // if there was no previous error,
    // then save this one
    if ( _ErrorStrings[0] == m_ErrorString )
    {
        m_ErrorString = _ErrorStrings[e];
    }
}

BOOL COpenGLView::InitializeOpenGL()
{
    // Can we put this in the constructor?
    m_pDC = new CClientDC(this);

    if ( NULL == m_pDC ) // failure to get DC
    {
        SetError(1);
        return FALSE;
    }

    if (!SetupPixelFormat())
    {
        return FALSE;
    }

    //n = ::GetPixelFormat(m_pDC->GetSafeHdc());
    //::DescribePixelFormat(m_pDC->GetSafeHdc(), n, sizeof(pfd), &pfd);

    //  CreateRGBPalette();

    if ( 0 == (m_hRC = ::wglCreateContext( m_pDC->GetSafeHdc() ) ) )
    {
        SetError(4);
        return FALSE;
    }

    if ( FALSE == ::wglMakeCurrent( m_pDC->GetSafeHdc(), m_hRC ) )
    {
        SetError(5);
        return FALSE;
    }

    // specify black as clear color
    ::glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
    // specify the back of the buffer as clear depth
    ::glClearDepth( 1.0f );
    // enable depth testing
    ::glEnable( GL_DEPTH_TEST );

    sceneRotate = 20.0;

    return TRUE;
}

BOOL COpenGLView::SetupPixelFormat()
{
    static PIXELFORMATDESCRIPTOR pfd =
       {
 	 sizeof(PIXELFORMATDESCRIPTOR),  // size of this pfd
 	 1,                              // version number
 	 PFD_DRAW_TO_WINDOW |            // support window
 	 PFD_SUPPORT_OPENGL |          // support OpenGL
 	 PFD_DOUBLEBUFFER,             // double buffered
 	 PFD_TYPE_RGBA,                  // RGBA type
 	 24,                             // 24-bit color depth
 	 0, 0, 0, 0, 0, 0,               // color bits ignored
 	 0,                              // no alpha buffer
 	 0,                              // shift bit ignored
 	 0,                              // no accumulation buffer
 	 0, 0, 0, 0,                     // accum bits ignored
 	 // 32,                          // 32-bit z-buffer
 	 16,    // NOTE: better performance with 16-bit z-buffer
 	 0,                              // no stencil buffer
 	 0,                              // no auxiliary buffer
 	 PFD_MAIN_PLANE,                 // main layer
 	 0,                              // reserved
 	 0, 0, 0                         // layer masks ignored
       };
    int pixelformat;
 
    if ( 0 == (pixelformat = ::ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd)) )
    {
        SetError(2);
        return FALSE;
    }

    if ( FALSE == ::SetPixelFormat(m_pDC->GetSafeHdc(), pixelformat, &pfd) )
    {
        SetError(3);
        return FALSE;
    }

    return TRUE;
}

BOOL COpenGLView::SetupViewport( int cx, int cy )
{
    // select the full client area
    ::glViewport(0, 0, cx, cy);
 
    return TRUE;
}

BOOL COpenGLView::SetupViewingFrustum( GLdouble aspect_ratio )
{
    // select a default viewing volumn
    ::gluPerspective( 40.0f, aspect_ratio, .1f, 20.0f );
    return TRUE;
}


BOOL COpenGLView::SetupViewingTransform()
{
    // select a default viewing transformation
    // of a 20 degree rotation about the X axis
    // then a -5 unit transformation along Z
    ::glTranslatef( 0.0f, 0.0f, -5.0f );
    ::glRotatef( sceneRotate, 1.0f, 0.0f, 0.0f );
    return TRUE;
}


BOOL COpenGLView::RenderScene()
{
    // draw a red wire sphere inside a
    // light blue cone (cylinder with one raduis set to 0)
 
    //Quadrics are part of glu library that make parameterized shapes
    GLUquadricObj *qobj;
    qobj = gluNewQuadric();
    gluQuadricDrawStyle(qobj, GLU_LINE); /* wireframe */

    // rotate the wire sphere so it's vertically
    // oriented
    ::glRotatef( 90.0f, 1.0f, 0.0f, 0.0f );
    ::glColor3f( 1.0f, 0.0f, 0.0f );
    ::gluSphere( qobj, 0.5, 20, 10);

    ::glColor3f( 0.5f, 0.5f, 1.0f );
    ::glTranslatef(0,0,-1.0); /* Try to align cylinder "up" on Z axis */
    ::gluCylinder(qobj, 0.0, 1.0f, 1.5f, 20, 5);

    return TRUE;
}

// Draw a square surface that looks like a
// black and white checkerboard
void COpenGLView::RenderStockScene()
{
    // define all vertices   X     Y     Z
    GLfloat v0[3], v1[3], v2[3], v3[3], delta;
    int color = 0;
 
    delta = 0.5f;
 
    // define the two colors
    GLfloat color1[3] = { 0.9f, 0.9f, 0.9f };
    GLfloat color2[3] = { 0.05f, 0.05f, 0.05f };
 
    v0[1] = v1[1] = v2[1] = v3[1] = 0.0f;
 
    ::glBegin( GL_QUADS );
     
    for ( int x = -5 ; x <= 5 ; x++ )
    {
        for ( int z = -5 ; z <= 5 ; z++ )
        {
      ::glColor3fv( (color++)%2 ? color1 : color2 );
      	  
      v0[0] = 0.0f+delta*z;
      v0[2] = 0.0f+delta*x;
      	  
      v1[0] = v0[0]+delta;
      v1[2] = v0[2];
      	  
      v2[0] = v0[0]+delta;
      v2[2] = v0[2]+delta;
      
      v3[0] = v0[0];
      v3[2] = v0[2]+delta;
      
      ::glVertex3fv( v0 );
      ::glVertex3fv( v1 );
      ::glVertex3fv( v2 );
      ::glVertex3fv( v3 );
        }
    }
    ::glEnd();
}

Once you have modified the above two files. You can compile and run the program. See what is displayed.

It should look like this:

Good luck!

If your program runs correctly, you might be wondering how exactly the picture is drawn. That is, you might want to understand how each function works. Well, you do not have to worry too much about this in the first lab. The next four labs will go through these subjects one-by-one in detail. Of course, we will learn a lot of more advanced functions and features.

E. Backup Your Project before You Leave

It's a good idea to backup your project before you leave. You can copy your work to the UDML H: drive, hercules (scp or sftp using, for eg., WinSCP in the URCOMM folder), a floppy, or a USB drive. You must exit Visual C++ system before you do a backup. Remember to save all the documents before quitting.

Your project should be in the OpenGL folder under "D:\VS2008\Projects". We suggest that you copy the entire OpenGL folder to a floppy disk. But, you will have to delete all files under OpenGL\Debug folder and the OpenGL\OpenGL.ncb file. They are too large (> 2 Mb) to be stored on a floppy disk. Besides, you will get them back the next time you compile your program. All the source files should be small enough to put on a floppy disk. Another option is to ftp the OpenGL folder (minus the OpenGL\Debug folder) up to hercules.

Note: Opening Existing projects
To open an existing project, on the hard drive, you can change to that directory (For instance, My Documents\Visual Studio 2008\Projects\OpenGL) and double click on the solution file (OpenGL.sln), which looks like the following:
However, this approach will not give you the "Start Page" Tab.

Another way to open an existing project is:

The project should be up and ready to go (just as you left it).

*Warning: if saving to the H: drive, make a copy to "My Documents" folder before working with it. If you don't, you may get strange messages and inconsistencies when building.

*You have extra space on your H: drive just for this lab. The extra space will disappear at the end of this semester.


EXERCISE

To be completed and shown to your lab instructor during your next scheduled lab time.

Play with OpenGL

Think about MFC

Experiment with SDL (Self-Learning)

Now that you have created a project with MFC let's create a project using SDL.

Downloading SDL (Relevant if Working at Home)

If you want to work on SDL at home, you will have to download it.
Click here for information on doing that.

Code Needed

Here is the code to get you started: main.cpp.

Project Settings

The project that you create will be a basic "Win32 Project". You will have to modify some project properties. The following link tells you how:
Information on creating the Win32 Project and setting the properties

Deliverables

  1. MFC project with OpenGL
  2. Answer to: What is an event?
  3. Answer to: What is SDL?
  4. SDL project with same image
  5. BONUS: to be announced in lab

References