[MFC]使用Picture Control來當做DrawScene

參考來源:
1.http://blog.sina.com.cn/s/blog_6ac675410100o0t6.html
2.http://www.codeguru.com/cpp/cpp/cpp_mfc/tutorials/article.php/c10975__2/Setting-Up-OpenGL-in-an-MFC-Control.htm
PS.內部參數皆先參考範例內資料。

這邊使用的為Dialog-based的FormView。因此在Dialog上拉出一個Picture Control即可。
ID可以隨便設定,但是Visible的屬性要設成false
根據作者的說法,有些狀況下,如果把Visible的屬性設成True的話,那麼在畫面重新繪製的時候,OpenGL繪製的畫面,會被Windows繪製PictureControl的畫面蓋掉。
目前測試手頭機器的確會有這樣的狀況。

需載入的header為以及。並引用opengl32.lib。

接下來要做的,就是新增用來繪製OpenGL用的Class。名稱也是一樣可以自由設定,這邊是使用COpenGLDrawer。
1.需要預先宣告的是Timer的指標以及相關需用來繪圖的成員。
 class COpenGLDrawer :    public CWnd  
 {  
 //================ Data member ==================  
     public:  
         UINT_PTR m_unpTimer;      //timer  
     private:  
 //Window Information - for draw scene  
         CWnd *m_hWnd;           //Handle of parent window  
         HDC m_hdc;              //Handle of DC for OpenGL using  
         HGLRC    m_hrc;         //Handle of RC for OpenGL using  
         int m_nPixelFormat;     //Pixel format  
         CRect m_rect;           //For getting window rect  
         CRect m_rectOldWindow;  //rect of previous displayed window  
         CRect m_rectOrgRect;    //rect of original displayed window  
 };  
相關成員也要記得先初始化,避免使用到錯誤的數值。

2.建立glCreate()。此函式是用來向系統註冊Class、建立OpenGL使用之視窗屬性以及記錄視窗的初始設定。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
 //============== Member Function =================  
     public:  
         COpenGLDrawer(void);  
         ~COpenGLDrawer(void);  
         void glCreate(CRect rect, CWnd *parent);  
 ....  
 };  

CPP:
 void COpenGLDrawer::glCreate(CRect rect, CWnd *parent)  
 {  
     CString cstrClassName = ::AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_OWNDC,  
                                                                                 NULL,  
                                                                                 (HBRUSH)::GetStockObject(BLACK_BRUSH),  
                                                                                 NULL);  
     CreateEx(0, cstrClassName, "OpenGL", WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN, rect, parent, 0);  
     //Set initail variable's value  
     this->m_rectOldWindow = rect;  
     this->m_rectOrgRect = rect;  
     this->m_hWnd = parent;  
 }  

3.由於OpenGL是使用Timer來進行畫面的繪製,因此就不適用於MFC所提供的繪製畫面的方法。因此需要跳過Windows繪圖的函式 - OnPaint()(WM_PAINT)。在OnPaint()內,使用ValidateRect()來讓Picture Control不被更新。
 void COpenGLDrawer::OnPaint()  
 {  
     //CPaintDC dc(this); // device context for painting  
     // TODO: Add your message handler code here  
     // Do not call CWnd::OnPaint() for painting messages  
     ValidateRect(NULL);  //Remove all of region of the client from the updatr region
 }  

4.建立產生OpenGL視窗的函式。裡面呼叫用來初始化OpenGL視窗的相關參數。這邊則改寫WM_CREATE之內容。
 int COpenGLDrawer::OnCreate(LPCREATESTRUCT lpCreateStruct)  
 {  
     if (CWnd::OnCreate(lpCreateStruct) == -1)  
         return -1;  
     // TODO: Add your specialized creation code here  
     this->glInitialize();  
     return 0;  
 }  

5.建立初始化OpenGL之函式。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
 //============== Member Function =================  
     public:  
         COpenGLDrawer(void);  
         ~COpenGLDrawer(void);  
         void glCreate(CRect rect, CWnd *parent);  
     protected:  
         void glInitialize(void);  
 };  

CPP:
 void COpenGLDrawer::glInitialize(void)  
 {  
 //Inital Setup  
     static PIXELFORMATDESCRIPTOR pfd =  
     {  
         sizeof(PIXELFORMATDESCRIPTOR),  
         1,  
         PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER,  
         PFD_TYPE_RGBA,  
         32,                                //bit depth  
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  
         16,                                //z-buffer depth  
         0, 0, 0, 0, 0, 0, 0  
     };  
 //Get device context only once  
     this->m_hdc = GetDC()->m_hDC;  
 //Pixel format  
     this->m_nPixelFormat = ::ChoosePixelFormat(this->m_hdc, &pfd);  
     ::SetPixelFormat(this->m_hdc, this->m_nPixelFormat, &pfd);  
 //Create the OpenGL Rendering Context  
     this->m_hrc = ::wglCreateContext(this->m_hdc);  
     ::wglMakeCurrent(this->m_hdc, this->m_hrc);  
 //Basisc Setup:  
     //Set color to use when clearing the background  
     ::glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  
     ::glClearDepth(1.0f);  
     //Turn on backface culling  
     ::glFrontFace(GL_CCW);  
     ::glCullFace(GL_BACK);  
     //Turn on depth testing  
     ::glEnable(GL_DEPTH_TEST);  
     ::glDepthFunc(GL_LEQUAL);  
     //Send draw request  
     this->OnDraw(NULL);  
 }  

6.建立OnDraw函式。此函式內處理圖形的移動以及旋轉,也就是鏡頭的控制。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
 // View information variables if camera - for mouse move  
         float m_fLastX;  
         float m_fLastY;  
         float m_fPosX;  
         float m_fPosY;  
         float m_fZoom;  
         float m_fRotX;  
         float m_fRotY;  
 //============== Member Function =================  
 ....  
     public:  
         afx_msg void OnDraw(CDC *pDC);  
 ....  
 };  

CPP:
 void COpenGLDrawer::OnDraw(CDC *pDC)  
 {  
   // TODO: Camera controls.  
     glLoadIdentity();  
     glTranslatef( 0.f, 0.f, -this->m_fZoom );  
     glTranslatef( this->m_fPosX, this->m_fPosY, 0.f);  
     glRotatef( this->m_fRotX, 1.f, 0.f, 0.f );  
     glRotatef( this->m_fRotY, 0.f, 1.f, 0.f);  
 }  

7.新增圖像更新用的OnTimer()(WM_TIMER)。
 void COpenGLDrawer::OnTimer(UINT_PTR nIDEvent)  
 {  
     // TODO: Add your message handler code here and/or call default  
     switch (nIDEvent)  
     {  
         case 1:          
         //Clear color and depth buffer bits  
             ::glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);  
         //Draw OpenGL scene  
             this->glDrawScene();  
         //Swap buffers  
             ::SwapBuffers(this->m_hdc);  
             break;  
         default:  
             break;  
     }    //End of switch (nIDEvent)  
     CWnd::OnTimer(nIDEvent);  
 }  

8.當視窗有需要自由變更大小時,則需改寫OnSize()(WM_SIZE)。
 void COpenGLDrawer::OnSize(UINT nType, int cx, int cy)  
 {  
     CWnd::OnSize(nType, cx, cy);  
     // TODO: Add your message handler code here  
     if ( 0>= cx || 0 >= cy || nType == SIZE_MINIMIZED ) {  
         return; };  
 //Resize  
     switch (nType)  
     {  
         case SIZE_MAXIMIZED:        // If widow resize token is "maximize"  
             //get the current window rect  
             GetWindowRect(m_rect);  
             //Move the window accordingly  
             MoveWindow(6, 6, (cx-14), (cy-14));  
             //Get the new window rect  
             GetWindowRect(this->m_rect);  
             //Store our old window as the new rect  
             this->m_rectOldWindow = this->m_rect;  
             break;  
         case SIZE_RESTORED:        //if window resize token is "restore"  
             //If the window is currently maximized  
             if ( this->m_bIsMaximized )  
             {  
                 //Get the current window rect  
                 GetWindowRect(this->m_rect);  
                 //Move the window accordingly ( to our stored old window)  
                 MoveWindow(this->m_rectOldWindow.left, (this->m_rectOldWindow.top-18), (this->m_rectOrgRect.Width()-4), (this->m_rectOrgRect.Height()-4));  
                 //Get the new window rect  
                 GetWindowRect(this->m_rect);  
                 //Store our old window as the new rect  
                 this->m_rectOldWindow = this->m_rect;  
             }    //End of if ( this->m_bIsMaximized )  
             break;  
     }    //End of switch (nType)  
 //Map the OpengGL coordinates  
     glViewport(0, 0, cx, cy);  
 //Projection view  
     glMatrixMode(GL_PROJECTION);  
     glLoadIdentity();  
 //Set our current view perspective  
     gluPerspective(35.0f, (float)cx / (float)cy, 0.01f, 2000.0f);  
 //Mode view  
     glMatrixMode(GL_MODELVIEW);  
 }  

當然也是要改寫Dialog的OnSize()。再由Dialog來呼叫OpenGL class來進行重新繪製。
 void CMainDlg::OnSize(UINT nType, int cx, int cy)  
 {  
     CDialog::OnSize(nType, cx, cy);  
     // TODO: Add your message handler code here  
     switch(nType)  
     {  
         case SIZE_RESTORED:  
             if ( this->m_glWindow.m_bIsMaximized ) {  
                 this->m_glWindow.OnSize(nType, cx, cy);  
                 this->m_glWindow.m_bIsMaximized = false;  
             }    //End of if ( this->m_glWindow.m_bIsMaximized )  
             break;  
         case SIZE_MAXIMIZED:  
             this->m_glWindow.OnSize(nType, cx, cy);  
             this->m_glWindow.m_bIsMaximized = true;  
             break;  
     }    //End of switch(ntype)  
 }  

9.當Timer設定完成後,即可定期的去做任何的更新動作。所以如有需要於畫面上繪製圖形,則需自行新增繪圖函式,並在OnTimer()內呼叫。此處宣告的繪圖函式為glDrawScene()。此處繪製一六面體。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
     protected:  
         void glInitialize(void);  
         void glDrawScene(void);  
 };  

CPP:
 void COpenGLDrawer::glDrawScene(void)  
 {  
 //Wireframe Mode  
     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);  
     glBegin(GL_QUADS);  
 //Top side  
     glVertex3f( 1.0f, 1.0f, 1.0f );  
     glVertex3f( 1.0f, 1.0f, -1.0f );  
     glVertex3f( -1.0f, 1.0f, -1.0f );  
     glVertex3f( -1.0f, 1.0f, 1.0f );  
 //Bottom side  
     glVertex3f( -1.0f, -1.0f, -1.0f );  
     glVertex3f( 1.0f, -1.0f, -1.0f );  
     glVertex3f( 1.0f, -1.0f, 1.0f );  
     glVertex3f( -1.0f, -1.0f, 1.0f );  
 //Front side  
     glVertex3f( 1.0f, 1.0f, 1.0f );  
     glVertex3f( -1.0f, 1.0f, 1.0f );  
     glVertex3f( -1.0f, -1.0f, 1.0f );  
     glVertex3f( 1.0f, -1.0f, 1.0f );  
 //Back side  
     glVertex3f( -1.0f, -1.0f, -1.0f );  
     glVertex3f( -1.0f, 1.0f, -1.0f );  
     glVertex3f( 1.0f, 1.0f, -1.0f );  
     glVertex3f( 1.0f, -1.0f, -1.0f );  
 //Left side  
     glVertex3f( -1.0f, -1.0f, -1.0f );  
     glVertex3f( -1.0f, -1.0f, 1.0f );  
     glVertex3f( -1.0f, 1.0f, 1.0f );  
     glVertex3f( -1.0f, 1.0f, -1.0f );  
 //Right side  
     glVertex3f( 1.0f, 1.0f, 1.0f );  
     glVertex3f( 1.0f, -1.0f, 1.0f );  
     glVertex3f( 1.0f, -1.0f, -1.0f );  
     glVertex3f( 1.0f, 1.0f, -1.0f );  
     glEnd();  
 }  

10.如有需控制物體的選轉、移動,則需改寫OnMouseMove()(WM_MOUSEMOVE)。
 void COpenGLDrawer::OnMouseMove(UINT nFlags, CPoint point)  
 {  
     // TODO: Add your message handler code here and/or call default  
     int diffX = (int)(point.x - this->m_fLastX);  
     int diffY = (int)(point.y - this->m_fLastY);  
     this->m_fLastX = (float)point.x;  
     this->m_fLastY = (float)point.y;  
 //Left mouse button  
     if ( nFlags & MK_LBUTTON )  
     {  
         this->m_fRotX += (float)0.5f * diffY;  
         if ( (this->m_fRotX > 360.0f) || (this->m_fRotX < -360.0f) )    {  
             this->m_fRotX = 0.f;    };  
         this->m_fRotY += (float)0.5f * diffX;  
         if ( (this->m_fRotY > 360.f) || (this->m_fRotY < -360.f) ) {  
             this->m_fRotY = 0.f; };  
 //Right mouse button  
     }else if ( nFlags & MK_RBUTTON ){  
         this->m_fZoom -= (float)0.1f * diffY;  
 //Middle mouse button  
     }else if ( nFlags & MK_MBUTTON ){  
         this->m_fPosX += (float)0.05f * diffX;  
         this->m_fPosY -= (float)0.05f * diffY;  
     }    //End of if ( nFlags & MK_LBUTTON )  
     this->OnDraw(NULL);  
     CWnd::OnMouseMove(nFlags, point);  
 }  

如果希望可以透過滑鼠滾輪來縮放大小,則一樣要改寫OnMouseWheel()(WM_MOUSEWHEEL)。但目前測試結果為WM_MOUSEWHEEL會先被Dialog所接收,所以這邊改寫的OnMouseWheel()並不會被message queue呼叫。
因此,也一併改寫Dialog的OnMouseWheel(),當收到message時,則呼叫COpenGLDrawer的OnMouseWheel()來達到縮放大小之結果。
COpenGLDrawer.cpp
 BOOL COpenGLDrawer::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)  
 {  
     // TODO: Add your message handler code here and/or call default  
     int diffX = (int)(pt.x - this->m_fLastX);  
     int diffY = (int)(pt.y - this->m_fLastY);  
     this->m_fLastX = (float)pt.x;  
     this->m_fLastY = (float)pt.y;  
     if ( zDelta == WHEEL_DELTA )                    //Rolls to forward - Zoom in  
     {  
         this->m_fZoom -= (float)0.05f * diffY;  
     }else if (zDelta == -WHEEL_DELTA) {        //Rolls to back - Zoom out  
         this->m_fZoom += (float)0.05f * diffY;  
     }    //End of if ( zDelta > 0 )   
     this->OnDraw(NULL);  
     return CWnd::OnMouseWheel(nFlags, zDelta, pt);  
 }  

CMainDlg.cpp
 BOOL CMainDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)  
 {  
     // TODO: Add your message handler code here and/or call default  
     this->m_glWindow.OnMouseWheel(nFlags, zDelta, pt);  
     return CDialog::OnMouseWheel(nFlags, zDelta, pt);  
 }  

不使用時,也是要釋放記憶體。
 COpenGLDrawer::~COpenGLDrawer(void)  
 {  
     wglMakeCurrent(NULL, NULL);  
     wglDeleteContext(this->m_hrc);
     DeleteDC(this->m_hdc);
 }  

到上述步驟完成後,OpenGL繪圖用的Class就已經完成了。接下來就要到主要的Dialog來使用此class。

11.建立OpenGL物件,以及設定參數。
Header:
 protected:  
     COpenGLDrawer m_glWindow;  

CPP:
 BOOL CMainDlg::OnInitDialog()  
 {  
     CDialog::OnInitDialog();  
     // Add "About..." menu item to system menu.  
     // IDM_ABOUTBOX must be in the system command range.  
     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);  
     ASSERT(IDM_ABOUTBOX < 0xF000);  
     CMenu* pSysMenu = GetSystemMenu(FALSE);  
     if (pSysMenu != NULL)  
     {  
         CString strAboutMenu;  
         strAboutMenu.LoadString(IDS_ABOUTBOX);  
         if (!strAboutMenu.IsEmpty())  
         {  
             pSysMenu->AppendMenu(MF_SEPARATOR);  
             pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);  
         }  
     }  
     // Set the icon for this dialog. The framework does this automatically  
     // when the application's main window is not a dialog  
     SetIcon(m_hIcon, TRUE);            // Set big icon  
     SetIcon(m_hIcon, FALSE);        // Set small icon  

     // TODO: Add extra initialization here  
     CRect rect;  
 //Get size and position of the picture control  
     GetDlgItem(IDC_PIC_DRAWDC)->GetWindowRect(rect);  
 //Convert screen coordinates to client coordinate  
     ScreenToClient(rect);  
 //Create OpenGL Control window  
     this->m_glWindow.glCreate(rect, this);  
 //Setup the OpenGL Window's timer to render  
     this->m_glWindow.m_unpTimer = this->m_glWindow.SetTimer(1, 1, 0);  
     return TRUE; // return TRUE unless you set the focus to a control  
 }  

上述完成後,就可以在Picture Control裡繪製圖形了,至於效能部分,就待之後之測試再進行說明。

PS.如有要將FormView當做繪圖區,則只要在進行ScreenToClient()時,塞入FormView的rect即可。

No comments:

Post a Comment

Build docker image from multiple build contexts

Build docker image from multiple build contexts ...