Graphics 版 (精华区)

发信人: jun (☆子夜☆), 信区: Graphics
标  题: OpenGL programming in MFC(3)
发信站: 哈工大紫丁香 (Sat Jan  9 15:09:47 1999), 转信

发信人: lanhaitao (蓝天-大海-波涛-你和我), 信区: Graphics
发信站: BBS 水木清华站 (Sat Nov 21 16:09:54 1998)

欢迎大家到bbs.gznet.edu.cn的3D版共同讨论OpenGL
////////////////////////////////////////
Using OpenGL in Visual C++
Version 4.x 

Transformations and the Matrix Stack
/////////////////////////////////////
By N. Alan Oursland (naoursla@iftech.com) 


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() 
        {
                CPaintDC dc(this); // device context for painting
                
                CGLSample3Doc* pDoc = GetDocument();
                pDoc->RenderScene();
        }

        void CGLSample3Doc::RenderScene(void)
        {
                glClear(GL_COLOR_BUFFER_BIT);

                glFlush();
        }

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
        {
                ArmPart=1
        };
        
        double m_transY;
        double m_transX;
        double m_angle2;
        double m_angle1;

        CGLSample3Doc::CGLSample3Doc()
        {
                m_transY=100;
                m_transX=100;
                m_angle2=15;    
                m_angle1=15;    
        }

     ArmPart - This is a identifier for the display list that we will be creating to draw the
     parts of the arm. 
     m_transY - This is the y offset of the arm from the world coordinate system origin 
     m_transX - This is the x offset of the arm from the world coordinate system origin 
     m_angle2 - This is the angle of the second part of the arm with respect to the first
     part. 
     m_angle1 - This is the angle of the first part of the arm with respect to the world
     coordinate axis. 

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()
        {
                if (!CDocument::OnNewDocument())
                        return FALSE;

                glNewList(ArmPart);
                        glBegin(GL_POLYGON);
                                glVertex2f(-10.0f,  10.0f);
                                glVertex2f(-10.0f, -10.0f);
                                glVertex2f(100.0f, -10.0f);
                                glVertex2f(100.0f,  10.0f);
                        glEnd();
                glEndList();

                return TRUE;
        }

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)
        {
                glClear(GL_COLOR_BUFFER_BIT);

                glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
                glCallList(ArmPart);

                glFlush();
        }

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);
        glRotated( m_angle1, 0, 0, 1);

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)
        {
                glClear(GL_COLOR_BUFFER_BIT);

                glPushMatrix();
                        glTranslated( m_transX, m_transY, 0);
                        glRotated( m_angle1, 0, 0, 1);
                        glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
                        glCallList(ArmPart);
                glPopMatrix();

                glFlush();
        }

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)
        {
                glClear(GL_COLOR_BUFFER_BIT);

                glPushMatrix();
                        glTranslated( m_transX, m_transY, 0);
                        glRotated( m_angle1, 0, 0, 1);
                        glPushMatrix();
                                glTranslated( 90, 0, 0);
                                glRotated( m_angle2, 0, 0, 1);
                                glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
                                glCallList(ArmPart);
                        glPopMatrix();
                        glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
                        glCallList(ArmPart);
                glPopMatrix();

                glFlush();
        }

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. 

   1.Add the following member variables to the view class: 

        CPoint m_RightDownPos;  // Initialize to (0,0)
        CPoint m_LeftDownPos;   // Initialize to (0,0)
        BOOL m_RightButtonDown; // Initialize to FALSE
        BOOL m_LeftButtonDown;  // Initialize to FALSE

   1.Add member functions responding to WM_LBUTTONDOWN,
     WM_LBUTTONUP, WM_RBUTTONDOWN, and WM_RBUTTONUP. Edit
     them as shown below: 

        void CGLSample3View::OnLButtonUp(UINT nFlags, CPoint point) 
        {
                m_LeftButtonDown = FALSE;
                CView::OnLButtonUp(nFlags, point);
        }

        void CGLSample3View::OnLButtonDown(UINT nFlags, CPoint point) 
        {
                m_LeftButtonDown = TRUE;
                m_LeftDownPos = point;
                CView::OnLButtonDown(nFlags, point);
        }

        void CGLSample3View::OnRButtonUp(UINT nFlags, CPoint point) 
        {
                m_RightButtonDown = FALSE;
                CView::OnRButtonUp(nFlags, point);
        }

        void CGLSample3View::OnRButtonDown(UINT nFlags, CPoint point) 
        {
                m_RightButtonDown = TRUE;
                m_RightDownPos = point;
                CView::OnRButtonDown(nFlags, point);
        }

   1.Add a member function responding to WM_MOUSEMOVE and edit it as shown
     below. 

        void CGLSample3View::OnMouseMove(UINT nFlags, CPoint point) 
        {
                if (m_RightButtonDown)
                {
                        CGLSample3Doc* pDoc = GetDocument();
                        CSize rotate = m_RightDownPos - point;
                        m_RightDownPos = point;

                        pDoc->m_angle1 += rotate.cx/3;
                        pDoc->m_angle2 += rotate.cy/3;
                        InvalidateRect(NULL);
                }

                if (m_LeftButtonDown)
                {
                        CGLSample3Doc* pDoc = GetDocument();
                        CSize translate = m_LeftDownPos - point;
                        m_LeftDownPos = point;
                        pDoc->m_transX -= translate.cx/3;
                        pDoc->m_transY += translate.cy/3;
                        InvalidateRect(NULL);
                }

                CView::OnMouseMove(nFlags, 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, not 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 | 
                                PFD_SUPPORT_OPENGL | 
                                PFD_DOUBLEBUFFER | 
                                PFD_STEREO_DONTCARE;  

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. 

--
※ 来源:·BBS 水木清华站 bbs.net.tsinghua.edu.cn·[FROM: 202.38.220.50]

--
☆ 来源:.哈工大紫丁香 bbs.hit.edu.cn.[FROM: sunup.bbs@bbs.net.ts]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:208.468毫秒