基於OpenGL三維軟件開發


 

實驗原理:

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM