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為
接下來要做的,就是新增用來繪製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