實驗原理:
OpenGL在MFC下編程原理---- Windows操作系統對OpenGL的支持
在Windows下用GDI作圖必須通過設備上下文(DeviceContext簡寫DC)調用相應的函數;用OpenGL作圖也是類似,OpenGL函數是通過"渲染上下文"(RenderingContext簡寫RC)完成三維圖形的繪制。Windows下的窗口和設備上下文支持"位圖格式"(PIXELFORMAT)屬性, 和RC有着位圖結構上的一致。只要在創建RC時與一個DC建立聯系(RC也只能通過已經建立了位圖格式的DC來創建),OpenGL的函數就以通過RC對應的DC畫到相應的顯示設備上。這里還有以下需要注意的方面:
1.一個線程只能擁有一個渲染上下文RC,也就是說,用戶如果在一個線程內對不同設備作圖,只能通過更換與RC對應的DC來完成,而RC在線程保持不變(當然,刪除舊的RC后再創建新的是可以的)。與此對應,一個RC也只能屬於一個線程,不能被不同線程同時共享。
2.設定DC位圖格式等於設定了相應的窗口的位圖格式,並且DC和窗口的位圖格式一旦確定就不能再改變。
3.一個RC雖然可以更換DC,在任何時刻只能利用一個DC(這個DC稱為RC的當前DC),但由於一個窗口可以讓多個DC作圖從而可以讓多個線程利用多個RC在該窗口上執行OpenGL操作。
4.現在的Windows下的OpenGL版本對OpenGL和GDI在同一個DC上作圖有一定的限制。當使用雙緩存用OpenGL產生動畫時,不能使用GDI函數向該DC作圖。
5.不建議用ANSI C在Windows下編寫OpenGL程序。這樣的程序雖然具有跨平台的可移植性(比如很多SGI的例子程序),但是它們不能利用Windows操作系統的很多特性,實用價值不大。
---- 用VC來編寫OpenGL程序
經過上面的分析,用VC來調用OpenGL作圖的方法流程如下:
1.先設置顯示設備DC的位圖格式(PIXELFORMAT)屬性。這通過填充一個PIXELFORMATDESCRIPTOR的結構來完成,該結構決定了OpenGL作圖的物理設備的屬性,比如該結構中的數據項dwFlags中PFD_DOUBLEBUFFER位如果沒有設置(置1),通過該設備的DC上作圖的OpenGL命令就不可能使用雙緩沖來做動畫。有一些位圖格式(PIXELFORMAT)是DC支持的,而有一些DC就不支持了。所以程序必須先用ChoosePixelFormat來選擇DC所支持的與指定位圖格式最接近的位圖格式,然后用SetPixelFormat設置DC的位圖格式。
2.利用剛才的設備DC建立渲染上下文RC(wglCreateContext),使得RC與DC建立聯系(wglMakeCurrent)。
3.調用OpenGL函數作圖。由於線程與RC一一對應,OpenGL函數的參數中都不指明本線程RC的句柄。
4.作圖完畢以后,先通過置當前線程的RC為NULL(::wglMakeCurrent(NULL,NULL);),斷開當前線程和該渲染上下文的聯系,由此斷開與DC的聯系。所以在后面刪除RC的時候要先判斷以下RC句柄的有效性(if (m_hrc) ::wglDeleteContext(m_hrc);)。再根據情況釋放(ReleaseDC)或者刪除(DeleteDC)DC。
實驗步驟:
1:新建一個MFC的工程,單文檔的工程。
2:工程建好之后,可以先編譯運行一下。下面就是要把View的窗口初始化為OpenGL的編程環境。當然以下所有的操作都是在View類中進行的。
先在Project->Settings->Link中,加上opengl32.lib glu32.lib glut.lib glaux.lib,
然后在View.h的類定義view.cpp中加上如下引用。這個大家都知道。
#include <gl\gl.h>
#include <gl\glu.h>
#include <gl\glut.h>
#include <gl\glaux.h>
3:在PreCreateWindow(CREATESTRUCT& cs)這個函數中可以修改一下窗口的風格,比如說窗口的名稱背景什么的,當然也可以不修改,如果想修改的話,需要對WNDCLASSEX和CREATESTRUCT這兩個結構比較熟悉。在這里,我不進行修改。采用默認的風格。
4:下面開始正題,首先要讓窗口支持OpenGL,那就必須要對PIXELFORMATDESCRIPTOR這個結構有所了解,先在View類view.cpp中新建一個函數SetupPixFormat(CDC *pDC),公有私有無所謂,如下:
BOOL CTestGLInitialView::SetupPixFormat(CDC*pDC) //比如工程名叫TestGLInitial
{
static PIXELFORMATDESCRIPTOR pfd = //定義像素格式
{
sizeof(PIXELFORMATDESCRIPTOR),// 上述格式描述符的大小
1, // 版本號
PFD_DRAW_TO_WINDOW | // 格式支持窗口
PFD_SUPPORT_OPENGL | // 格式必須支持OpenGL
PFD_DOUBLEBUFFER, // 必須支持雙緩沖
PFD_TYPE_RGBA, // 申請 RGBA 格式
24, // 24位色彩深度,即1.67千萬的真彩色
0,0, 0, 0, 0, 0, // 忽略的色彩位
0, // 無Alpha緩存
0, // 忽略Shift Bit
0, // 無累加緩存
0,0, 0, 0, // 忽略聚集位
32, // 32位 Z-緩存 (深度緩存)
0, // 無蒙板緩存
0, // 無輔助緩存
PFD_MAIN_PLANE, // 主繪圖層
0, // Reserved
0,0, 0 // 忽略層遮罩
};
int nIndex =ChoosePixelFormat(pDC->GetSafeHdc(), &pfd); //選擇剛剛定義的像素格式
if( nIndex == 0 ) return FALSE;
return SetPixelFormat(pDC->GetSafeHdc(),nIndex, &pfd); //設置像素格式
}
這個函數的主要目的就是設置窗口的像素格式,使之支持OpenGL,明白這點就行了。在創建窗口的時候,調用這個函數。
5:剛剛那個函數是用來在創建窗口是調用的,在創建窗口時,還需要對OpenGL的環境做一些初始化,再定義一個函數InitialGL(),如下:
BOOL CTestGLInitialView::InitialGL()
{
glShadeModel(GL_SMOOTH); // 啟用陰影平滑
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); // 告訴系統對透視進行修正
return TRUE; // 初始化 OK
}
6:現在可以捕獲WM_CREATE消息了。但是,還要先定義一個CClientDC*的成員,這個成員指向View窗口自己,是用來傳遞給SetupPixFormat(CDC *pDC)函數的,沒別的意思。現在,來捕獲WM_CREATE消息,寫上如下代碼:
intCTestGLInitialView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
m_pDC = new CClientDC(this);
SetupPixFormat(m_pDC);
HGLRC hrc = wglCreateContext(m_pDC->GetSafeHdc());
wglMakeCurrent(m_pDC->GetSafeHdc(), hrc);
InitialGL();
return 0;
}
當然,當窗口關閉的時候,還應該要釋放一些資源。捕獲WM_DESTROY消息,寫下如下代碼:
void CTestGLInitialView::OnDestroy()
{
CView::OnDestroy();
// TODO: Add your message handler code here
HGLRC hrc = wglGetCurrentContext();
wglMakeCurrent(NULL, 0);
wglDeleteContext(hrc);
delete m_pDC;
}
現在可以編譯一下了,沒有錯誤。
7:現在,OpenGL的環境已經初始化差基本完成,可以開始做圖了。先定義一個作圖的函數DrawScene(),寫上如下的代碼:
BOOL CTestGLInitialView::DrawScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度緩存
glLoadIdentity(); // 重置當前的模型觀察矩陣
SwapBuffers(m_pDC->GetSafeHdc()); // 交換緩沖區
return TRUE;
}
然后,要在OnDraw中,調用這個函數:
void CTestGLInitialView::OnDraw(CDC* pDC)
{
CTestGLInitialDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
DrawScene();
}
8:運行一下,黑色的背景出來了。
9:這時,可以修改DrawScene()這個作圖函數,作圖。畫出那個三角形和正方形來。寫代碼如下:
BOOL CTestGLInitialView::DrawScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度緩存
glLoadIdentity(); // 重置當前的模型觀察矩陣
glTranslatef(-1.5f,0.0f,-6.0f); // 左移 1.5 單位,並移入屏幕 6.0
glBegin(GL_TRIANGLES); // 繪制三角形
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f( 0.0f, 1.0f, 0.0f); // 上頂點
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f,-1.0f,0.0f); // 左下
glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f( 1.0f,-1.0f,0.0f); // 右下
glEnd(); // 三角形繪制結束
glTranslatef(3.0f,0.0f,0.0f); // 右移3單位
glColor3f(0.0f, 0.0f, 1.0f);
glBegin(GL_QUADS); //繪制正方形
glVertex3f(-1.0f, 1.0f, 0.0f); // 左上
glVertex3f( 1.0f, 1.0f, 0.0f); // 右上
glVertex3f( 1.0f,-1.0f,0.0f); // 左下
glVertex3f(-1.0f,-1.0f,0.0f); // 右下
glEnd();
SwapBuffers(m_pDC->GetSafeHdc()); // 交換緩沖區
return TRUE;
}
運行一下,發現圖形沒有出現,這個怎么回事呢。原來是因為還沒有定義投影方式和視口。即用正交投影還是透視投影。定義投影,還要捕獲WM_SIZE消息。寫如下代碼:
void CTestGLInitialView::OnSize(UINT nType,int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// TODO: Add your message handler code here
if (0 == cy) // 防止被零除
{
cy = 1; // 將Height設為1
}
glViewport(0, 0, cx, cy); // 重置當前的視口
glMatrixMode(GL_PROJECTION); // 選擇投影矩陣
glLoadIdentity(); // 重置投影矩陣
// 設置視口的大小
gluPerspective(45.0f,(GLfloat)cx/(GLfloat)cy,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW); // 選擇模型觀察矩陣
glLoadIdentity(); // 重置模型觀察矩陣
}
再運行一下,圖形已經出來了。以后,就可以在DrawScene()寫任何畫圖的代碼了,當窗口重繪的時候,都可以自動適應。如果要做一段可以運動的3D圖畫,可以再捕獲WM_TIMER消息,通過在OnCreate的時候定義一個時鍾,再配合一些變量,就可以做簡單的動畫了。
讀者可以在上面學習的基礎上,繼續完成下面的內容,以便增加對知識的掌握程度:
建議必做功能:包括基本的基於OpenGL的顯示,在OpenGL初始化過的視圖區繪制一個基本圖形。
建議選做功能:繪制復雜例如曲線、曲面、不規則多面體以及其他實物的三維圖形,圖形的旋轉,平移,放大、縮小等動畫功能。
搬家於CSDN 2015-05-10