This lab is an introduction to OpenGL programming for Windows
After the lab lecture, you have approximately two weeks of time to:
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.
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 the OpenGL View Class Framework
1. Start Visual C++ Environment
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, you might want to compile the application (which of course dose nothing yet) just to make sure there are no errors in your building process.
3. Compile Your Application
4. Run Your Application
It is quite exciting to run this little window program if it is your first Windows program.
5. Add Member Functions to the COpenGLView Class
The build process creates a minimum number of member functions for each class being generated. We may take a look at what are there by opening up the ClassWizard:
Remember that the system can only create a skeleton for the member function. It is your responsibility to insert computations into the relavent member functions.
6. 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 COpenGLView.h header file:
// Include the OpenGL headersTo add the above contents, you have first to be able to find the desired header file. This is a good time to learn how to navigate around the files of the project.
#include "gl\gl.h" // standard OpenGL library
#include "gl\glu.h" // OpenGL utility library
#include "gl\glaux.h" // OpenGL auxiliary library
The next step is to add the OpenGL libraries to the link step:
D. Backup Your Project before You Leave
You must backup your project before you leave, because you may not use the same PC next time. Even you use the same PC, the contents may be destroyed by others. You must exit Visual C++ system before you do backup. Remember to save all the documents before quitting.
You project should be in the OpenGL folder under C:\Workarea\. I suggest that you copy the entire OpenGL folder to a floppy disk. But, you have to delete all files under OpenGL\Debug folder, because they are too large (>= 6 Mb) to be stored on a floppy disk. Beside, you will get them back next time when you compile your program again. All the source files should be small enough to put on a floppy disk.
Next time when you want to continue the project, you may load the project from the backup floppy disk back to the harddisk, preferably at C:\Workarea\.
Note that, you are to continue with an existing project now. Do the following to open an existing project.
E. Editing Member Functions
Now, we are
ready to modify or add some functions to do real computation. For this lab, you
are concerned with COpenGLView class only. Therefore, you will work with two
files:
Here are the list of the two files. The parts you will work with are highlighted in red color.
COpenGLView.h
// COpenGLView.h : interface of the COpenGLView class // You should have already added these lines. #include "gl\gl.h" #include "gl\glu.h" #include "gl\glaux.h" ...... class COpenGLView : public CView { protected: // create from serialization only ..... // Attributes ...... // Operations public: ...... // Implementation public: ...... protected: // Generated message map functions 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; } ......
// 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]) { ...... } ...... 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; // 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 line follows !!!! // return CView::OnEraseBkgnd(pDC); return TRUE; // tell Windows not to erase the background } 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 ); 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( 20.0f, 1.0f, 0.0f, 0.0f ); return TRUE; } BOOL COpenGLView::RenderScene() { // draw a red wire sphere inside a // light blue cube // 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 ); ::auxWireSphere( .5 ); ::glColor3f( 0.5f, 0.5f, 1.0f ); ::auxWireCube( 1.0 ); 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 modified the above two files. You can compile and run the program. See what
is displayed.
Click HERE to see the result of this program.
Good
luck!
If
your program runs correctly, you might be wondering how exactly the picture is
drawn. That is, you want to understand how each function works. Well, you do
not have to worry 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.