MFC下二維OpenGL環境詳細配置
一直以來,網上有很多關於OpenGL在MFC環境下配置的教程,但是,一般都說的不夠詳細,或者配置過程不夠完整,今天我在自己摸索和學習的基礎了,寫出了這篇文章,儘量說明了座標系的設定、新增深度測試型別以防止顏色變淡或者不純等等。首先說明,本配置教程配置完成的OpenGL環境為二維環境,座標系為中心座標系,客戶區中心為OpenGL座標系的中心,向右為x軸增大方向;向上為y軸增大方向,與其他的配置方法不太一樣,如果想對座標系進行修改,只需修改ReSizeGLScene函式中的下面一句:
//引數分別代表(左下角x座標,右上角x座標,左下角y座標,右上角y座標)——座標全相對於視窗左下角--原點 gluOrtho2D(-cx/2, cx/2, -cy/2, cy/2);
(1) 新建一個MFC單文件應用程式,程式建立完成後,會自動設定一個視類,如CdispView類,OpenGL是圖形操作,所以所有的操作都是在視類上進行的。
(2) 新增OpenGL標頭檔案和庫檔案,1510實驗室使用的是基於OpenGL4.0標準的最新版freeglut3.0開源庫,OpenGL開源庫種類很多,比較出名的有最早的glut庫、OpenLuGL等等,這裡選中這個庫的原因是freeglut原生支援32/64位系統,並且有針對64位系統優化的程式碼,方便後期升級,此外,這個庫在SDK上基本保持了與glut的一致性,方便學習。下面(3)就來詳細的講如何配置
(3) 將FreeGlut_OpenGL_Dll資料夾中標頭檔案資料夾開啟,將其中的四個標頭檔案新增到工程的附加包含目錄下,或直接將其放在工程對應的目錄下;將FreeGlut_OpenGL_Dll資料夾中32位庫資料夾開啟(64位系統也可以用32位庫,目前開發的時候也是按32位庫進行開發的,相容性更好一點),將freeglut.lib和freeglut.dll兩個dll檔案新增到工程的附加庫目錄下,或直接將其放在工程對應的目錄下;
(4) 在視類的標頭檔案中包含OpenGL庫:
//新增OpenGL標頭檔案,呼叫OpenGL庫
#include"glut.h"
如果還需要用牛四強寫的OpenGL的常用畫圖函式,需要新增下面程式碼和相應的標頭檔案、庫檔案
//呼叫我自己寫的OpenGL常用畫圖函式
#include"OpenGLCommonDrawHead.h"
#pragmacomment(lib,"OpenGLCommonDraw.lib")
(5) 在類的屬性欄,為下述訊息加入訊息處理函式:WM_CREATE (for OnCreate), WM_DESTROY (for OnDestroy), WM_SIZE (forOnSize), WM_ERASEBACKGROUND (for OnEraseBkground),如下圖所示:
(6) 設定視窗顯示風格。視窗建立之前我們必須設定視窗風格包含WS_CLIPCHILDREN和WS_CLIPSIBLINGS,從而避免OpenGL繪製到其他視窗中去。這些應該放在PreCreateWindow()中。
程式碼如下:
BOOL CfirstView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: 在此處通過修改
// CREATESTRUCT cs 來修改視窗類或樣式
cs.style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;//Tramp
return CView::PreCreateWindow(cs);
}
(7) 在視類的標頭檔案中新增private型別的變數
/************************************************************************/
/* 設定的變數是Rendering Context(著色描述表)。每一個OpenGL都被連線到一個著
色描述表上。著色描述表將所有的OpenGL呼叫命令連線到Device Context(裝置描述表)上。
我將OpenGL的著色描述表定義為hRC 。要讓您的程式能夠繪製視窗的話,還需要建立一個
裝置描述表,也就是第二行的內容。Windows的裝置描述表被定義為hDC 。DC將視窗連線到
GDI(Graphics Device Interface圖形裝置介面)。而RC將OpenGL連線到DC */
/************************************************************************/
//-----------------------------------------
//-----------------------------------------
// OpenGL及MFC配置引數
//-----------------------------------------
//-----------------------------------------
HGLRC m_hRC; //Rendering Context著色描述表
CDC* m_pDC; //Device Context裝置描述表
//客戶區大小,分別代表x軸和y軸大小,也就是寬和高
int m_cxClient;
int m_cyClient;
(8) 在視類的標頭檔案中定義相應的public初始化函式、縮放函式,並在CPP中填寫函式內容:
標頭檔案宣告:
BOOL InitializeOpenGL(); //初始化OpenGL函式
BOOL SetupPixelFormat(); //設定畫素格式函式
//視窗大小變化時調整的自動縮放函式
GLvoid ReSizeGLScene(int cx,int cy);
//畫圖函式
void RenderScene();
CPP檔案程式碼具體內容:
BOOL CDispWaveShowView::InitializeOpenGL()
{
//獲取客戶區DC
m_pDC = new CClientDC(this);
//判斷獲取是否成功
if(m_pDC == NULL)
{
MessageBox(_T("初始化OpenGL時無法獲取當前DC"));
return FALSE;
}
//設定畫素格式
if(!SetupPixelFormat())
{
MessageBox(_T("初始化OpenGL時無法設定畫素格式"));
return FALSE;
}
//建立著色描述表
m_hRC = ::wglCreateContext (m_pDC->GetSafeHdc ());
//判斷是否成功
if(m_hRC == 0)
{
MessageBox(_T("初始化OpenGL時建立RC失敗"));
return FALSE;
}
//設定當前RC
if(::wglMakeCurrent (m_pDC->GetSafeHdc (), m_hRC)==FALSE)
{
MessageBox(_T("初始化OpenGL時設定當前RC失敗"));
return FALSE;
}
//OpenGL啟用雙緩衝
glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE);
//設定黑色背景並清空
//::glClearColor(0.0f,0.0f,0.0f,0.0f);
//設定緩衝並清空
::glClearDepth(1.0f);
//啟用深度測試
::glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL); // 所作深度測試的型別
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 告訴系統對透視進行修正
glShadeModel(GL_SMOOTH); // 啟用陰影平滑
return TRUE;
}
BOOL CDispWaveShowView::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
0, // no stencil buffer
0, // no auxiliary buffer
PFD_MAIN_PLANE, // main layer
0, // reserved
0, 0, 0 // layer masks ignored
};
//選擇畫素格式
int m_nPixelFormat = ::ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd);
if ( m_nPixelFormat == 0 )
{
MessageBox(_T("選擇畫素格式失敗!"));
return FALSE;
}
//設定畫素格式
if ( ::SetPixelFormat(m_pDC->GetSafeHdc(), m_nPixelFormat, &pfd) == FALSE)
{
MessageBox(_T("設定畫素格式失敗!"));
return FALSE;
}
return TRUE;
}
(9) 在OnCreate中我們將通過建立畫素格式和繪製上下文來初始化OpenGL. 在InitializeOpenGL()中會建立一個裝置上下文(DC),為這個DC選擇一個畫素格式,建立和這個DC相關的繪製上下文(RC),然後選擇這個RC.這個函式會呼叫SetupPixelFormat()來建立畫素格式,程式碼如下:
InitializeOpenGL();//初始化openGL環境
glEnable(GL_BLEND); //啟用混合功能,將圖形顏色同周圍顏色相混合
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
//OpenGL效果設定
glEnable(GL_POLYGON_SMOOTH); //多邊形抗鋸齒
glHint(GL_POLYGON_SMOOTH,GL_NICEST);
glEnable(GL_LINE_SMOOTH); //線抗鋸齒
glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
glEnable(GL_POINT_SMOOTH); //點抗鋸齒
glHint(GL_POINT_SMOOTH,GL_NICEST);
(10) 在OnSize()中一般用來設定視口和視錐,因為這些是和視窗大小相關的。基本操作包括設定視口,選擇投影矩陣,設定模型檢視矩陣。
//獲取視窗大小
m_cxClient = cx;
m_cyClient = cy;
//根據視窗大小縮放
ReSizeGLScene( cx, cy );
其中ReSizeGLScene的內容如下:
GLvoid CDispWaveShowView::ReSizeGLScene(int cx,int cy)
{
//設定視口大小為整個客戶區大小
glViewport(0, 0, cx, cy);
//計算寬度/高度比
// this will keep all dimension scales equal
//W_H_Ratio = (GLdouble)cx/(GLdouble)cy;
// 設定變換模式為投影變換
glMatrixMode(GL_PROJECTION);
// 初始化模型變換矩陣
::glLoadIdentity();
// select the viewing volume
//引數分別代表(左下角x座標,右上角x座標,左下角y座標,右上角y座標)——座標全相對於視窗左下角--原點
gluOrtho2D(-cx/2, cx/2, -cy/2, cy/2);
//設定變換模式為模型變換
glMatrixMode(GL_MODELVIEW);
// 初始化模型變換矩陣
::glLoadIdentity();
}
(11) 在繪製場景時,一般包括如下步驟:1)清空快取。2)繪製場景。3)Flush掉渲染流水線。4)若設定了雙緩衝,則交換前後臺緩衝區。
在視類的OnDraw函式裡面呼叫RenderScene()畫圖函式,畫圖函式的大框架如下:
void CDispWaveShowView::RenderScene()
{
//清空背景和緩衝
::glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
//進行換圖的操作
//操作啊~我畫圖啊~~
// 告訴OpenGL我已經畫完了
::glFinish();
//釋放緩衝區
::SwapBuffers( m_pDC->GetSafeHdc() );
}
(12) 為了使改變視窗大小時嚴重的閃爍,在OnEraseBkgnd裡做一些操作,避免windows自己的視窗重新整理閃爍。把原來的return一堆直接改成return TRUE。
BOOL CDispWaveShowView::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
(13) 為了避免記憶體洩露,我們要將在SetupPixelFormat()中使用了new運算子來為CClientDC物件分配的記憶體在程式關閉時delete掉,即OnDestroy事件。
//取消RC的佔用
if(::wglMakeCurrent (0,0) == FALSE)
{
MessageBox(_T("Could not make RC non-current"));
}
//刪除RC
if(::wglDeleteContext (m_hRC)==FALSE)
{
MessageBox(_T("Could not delete RC"));
}
//刪除DC
if(m_pDC)
{
delete m_pDC;
}
//DC置為NULL
m_pDC = NULL;