![]() |
Using OpenGL in Visual C++: Part III Transformations and the Matrix Stack by Alan Oursland [Download the example source] Transformations and the Matrix Stack The sample program presented in this section will show you how to use display lists, basic transforms, the matrix stack, and double buffering. Once again, follow the above steps to get to a starting point for this third sample program (or continue to modify the same program). In this program we will be creating a "robot arm" that you can control with your mouse. This "arm" will actually be two rectangles where one rectangle rotates about a point on the other rectangle. Begin by adding the public member function "void RenderScene(void)" to the CGLSample3Doc class. Modify CGLSample3View::OnPaint and CGLSample3Doc:: RenderScene so that they look like this: void CGLSample3View::OnPaint() At this time our program generates a black screen. We will do something about that in a minute, but first we need to add some state variables to the CGLSample3Doc class. Add the following enumerated types and variables to the document class. Then initialize them in the document constructor. enum GLDisplayListNames
We will be using what is known as a display list to draw the parts of our arm. A display list is simply a list of OpenGL commands that have been stored and named for future processing. Display lists are often preprocessed, giving them a speed advantage over the same commands called out of a display list. Once a display list is created, its commands may be executed by calling glCallList with the integer name of the list. Edit CGLSample3Doc::OnNewDocument to look like this: BOOL CGLSample3Doc::OnNewDocument() Note: Microsoft has changed the OpenGL API since this was written. If you are using a newer version of the API, you will need to make the following call to glNewList: glNewList(ArmPart, GL_COMPILE); GL_COMPILE tells OpenGL to just build the display list. Alternatively, you can pass GL_COMPILE_AND_EXECUTE into glNewList. This will cause the commands to be executed as the display list is being built! Now edit CGLSample3Doc::RenderScene to look like this: void CGLSample3Doc::RenderScene(void) If you were to run the program now, all you would see is a small red rectangle in the lower left hand corner of the screen. Now add the following lines just before the call to glCallList: glTranslated( m_transX, m_transY, 0); These two commands affect the ModelView matrix, causing our rectangle to rotate the number of degrees stored in m_angle1 and translate by the distance defined by (m_transX, m_transY). Run the program now to see the results. Notice that every time the program gets a WM_PAINT event the rectangle moves a little bit more ( you can trigger this by placing another window over the GLSample3 program and then going back to GLSample3 ). The effect occurs because we keep changing the ModelView matrix each time we call glRotate and glTranslate. Note that resizing the window resets the rectangle to its original position ( OnSize clears the matrix to an identity matrix, as you can see in the code) We need to leave the matrix in the same state in which we found it. To do this we will use the matrix stack. Edit CGLSample3Doc::RenderScene to look like the code below. Then compile and run the program again. void CGLSample3Doc::RenderScene(void) glPushMatrix takes a copy of the current matrix and places it on a stack. When we call glPopMatrix, the last matrix pushed is restored as the current matrix. Our glPushMatrix call preserves the initial identity matrix, and glPopMatrix restores it after we dirtied up the matrix. We can use this technique to position objects with respect to other objects. Once again, edit RenderScene to match the code below. void CGLSample3Doc::RenderScene(void) When you run this you will see a red rectangle overlapping a green rectangle. The translate commands actually move the object's vertex in the world coordinates. When the object is rotated, it still rotates around its own vertex, thus allowing the green rectangle to rotate around the end of the red one. Follow the steps below to add controls so that you can move these rectangles.
CPoint m_RightDownPos; // Initialize to (0,0)
void CGLSample3View::OnLButtonUp(UINT nFlags, CPoint point)
void CGLSample3View::OnMouseMove(UINT nFlags, CPoint point) Build and run the program. You may now drag with the left mouse button anywhere on the screen to move the arm, and drag with the right button to rotate the parts of the arm. The above code uses the Windows interface to change data. The OpenGL code then draws a scene based on that data. The only problem with the program now is that annoying flicker from the full screen refreshes. We will add double buffering to the program and then call it complete. Double buffering is a very simple concept used in most high performance graphics programs. Instead of drawing to one buffer that maps directly to the screen, two buffers are used. One buffer is always displayed ( known as the front buffer ), while the other buffer is hidden ( known as the back buffer ). We do all of our drawing to the back buffer and, when we are done, swap it with the front buffer. Because all of the updates happen at once we don't get any flicker. The only drawback to double buffering is that it is incompatible with GDI. GDI was not designed with double buffering in mind. Because of this, GDI commands will not work in an OpenGL window with double buffering enable. That being said, we first need to change all of the "InvalidateRect(NULL);" calls to "InvalidateRect(NULL, FALSE);". This will solve most of our flicker problem (the rest of the flicker was mainly to make a point). To enable double buffering for the pixel format, change the pixelDesc.dwFlags definition in CGLSample3View::SetWindowPixelFormat to the following: pixelDesc.dwFlags = PFD_DRAW_TO_WINDOW | There are no checks when we set the pixel format to make sure that ours has double buffering. I will leave this as an exercise for the reader. First we need to tell OpenGL to draw only onto the back buffer. Add the following line to the end of CGLSample3View::OnSize: glDrawBuffer(GL_BACK); Each time we draw a new scene we need to swap the buffer. Add the following line to the end of CGLSample3View::OnPaint: SwapBuffers(dc.m_ps.hdc); When you compile and run the program now you should see absolutely no flicker. However, the program will run noticeably slower. If you still see any flicker then ChoosePixelFormat is not returning a pixel format with double buffering. Remember that ChoosePixelFormat returns an identifier for the pixel format that it believes is closest to the one you want. Try forcing different indices when you call SetPixelFormat until you find a format that supports double buffering. In the final sample program, we will construct a three dimensional cube. There may be some 3-D graphics concepts in this section that those uninitiated to graphics will not understand. Part IV will include a few recommended books on 3D graphics.
|
![]() |