文章原創,轉載請註明出處
OpenGL相關的工具庫中的OpenGL程序往往都是在C函數main中初始化和創建的,使用控制檯來完成顯示和控制頗爲不便。如果能夠在MFC中OpenGL函數並創建OpenGL窗口,並且可以將控制參數傳入給OpenGL則可以得到很好的交互性能。自己查找很多文獻資料,貌似都是說要在MFC中顯示OpenGL都是通過微軟的wgl擴展來完成,但是wgl很早就停止更新了的並且自己寫的wgl運行框架儘管有些時候可以使用但在自己的電腦上卻總是發現有運行有內存泄漏的問題並且加載opengl程序也非常慢,也許是顯卡驅動的問題?但自己裝的英偉達的GTX560顯卡並且是最新的顯卡驅動,也還是存在這個問題,看來應該是自己寫的和參考的wgl框架有bug。本來是想用glut的卻發現也是很久就停止更新了,只有freeglut不錯,今年初還出了更新的。於是決定使用glew+freeglut來實現這個想法。
不過有一個問題就是glut的初始化函數往往都是寫的glutInit(&argc,argv);其中argc和argv兩個參數是從控制檯下的C函數的main(int argc,char *argv[])中傳過來的,argc記錄的是命令行中輸入參數的數目,argv是一個擁有argc個元素的字符串數組,每個元素保存一個命令行中輸入的參數。但是在MFC中默認是不會生成控制檯窗口的,而實際上如果蹦出個控制檯來控制opengl也不太好(這是可以實現的,可以參考《GUI程序也能使用控制檯窗口》等文章http://www.cppblog.com/wish/articles/23642.html)偶然看到pfan論壇的eastcowboy的一個帖子《OpenGL入門學習——寫給想用計算機畫圖的朋友》中提到可以在WinMain中初始化並且創建OpenGL窗口,通過自定義argc和argv參數的值達到了欺騙glutInit完成初始化並且不依賴與命令行/控制檯窗口。個人覺得既然可以自定義初始化參數,那麼就實際上可以在MFC中的任何地方初始化和創建OpenGL窗口了。但是爲了不影響MFC正常工作、完成與用戶交互即UI的功能,可以通過多線程來完成,即可以將OpenGL的運行封裝在一個工作線程的run函數中(這實際上真是有趣:這個名義上的工作線程卻是可以創建窗口顯示的)。
這裏給出一個MFC對話框實現的例子:
第一步就是創建一個MFC對話框應用程序,我這裏取名MFCwithOpenGLWindow。第二步添加一個啓動OpenGL按鈕IDC_STARTOPENGL及其對應的消息響應函數OnStartOpengl().
第三步就是添加相應的函數。在對話框實現MFCwithOpenGLWindowDlg.cpp文件頂部添加兩個全局函數聲明(因爲是子線程函數,必須是全局的)
int OpenGLThread(LPVOID lpv);//線程run函數
void DisplayGLView(void);//被線程函數調用以在OpenGL窗口中繪圖顯示的函數
接下來在對話框實現MFCwithOpenGLWindowDlg.cpp文件中給出線程函數的實現(這裏借鑑了《OpenGL入門學習——寫給想用計算機畫圖的朋友》帖子中的程序,只不過添加了顯示繪圖的顏色爲紅色)
void DisplayGLView()//被線程函數調用以在OpenGL窗口中繪圖顯示的函數
{
glClear(GL_COLOR_BUFFER_BIT);//清空顏色緩衝
glColor3f(1.0,0.0,0.0);//設置繪圖爲紅色
glRectf(-0.5f,-0.5f,0.5f,0.5f);//繪製一個正方形
glFlush();//刷新顯示緩衝完成顯示
}
int OpenGLThread(LPVOID lpv)//線程run函數
{
int OpenGLThread(LPVOID lpv)//線程run函數
{
//獲得線程創建時對話框傳入的參數,可以通過這裏傳遞給opengl一些控制參數
CMFCwithOpenGLWindowDlg* pTaskMain = (CMFCwithOpenGLWindowDlg *) lpv;
int argc=1;
char *argv[]={"OpenGL Thread "};
glutInit(&argc,argv);//初始化glut
glutInitDisplayMode(GLUT_RGB|GLUT_SINGLE);//設置顯示模式
glutInitWindowPosition(100,100);//設置opengl窗口顯示位置
glutInitWindowSize(400,400);//設置opengl窗口大小
glutCreateWindow("OpenGL Thread Window");//設置opengl窗口標題
glutDisplayFunc(&DisplayGLView);//調用顯示回調函數繪圖
//調用freeglut中的函數設置opengl窗口被關閉和結束返回時可以繼續執行glutMainLoop後面的部分
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
glutMainLoop();//進入glut循環
//AfxMessageBox("it end");
AfxEndThread(0);//結束線程返回,以免出現意外結束
return 1;//結束時返回
}}
紅色部分非常重要,首先通過調用glutSetOption設置了opengl窗口被關閉時或者其他結束響應事件發生之後可以繼續執行glutMainLoop死循環後面的語句,一方面可以正確地調用AfxEndThread函數結束子線程而不致於opengl對話框關閉時之間調用exit(0)把MFC主對話框也給關閉了即結束了主線程,還可以完成一些清理工作,比如相應的內存釋放等以免造成內存泄漏事件。該函數需要freeglut的支持,在glut中是沒有這個函數的,由此可以看到freeglut對glut的完善和補充,,使得程序更爲可靠和安全了。
最後就是在啓動OpenGL按鈕對應的消息響應函數OnStartOpengl()中創建opengl線程,將opengl窗口顯示出來,這裏比較簡單隻有一句:
CWinThread *TEST1=AfxBeginThread((AFX_THREADPROC)OpenGLThread,this);//創建opengl線程
如此,編譯程序並運行,點擊啓動OpenGL按鈕就可以得到一個繪製了一個紅色正方形的opengl窗口,如下圖所示
爲了使程序編譯通過,需要下載和安裝glew和freeglut庫,並且在MFCwithOpenGLWindowDlg.cpp文件中需要引用對應的頭文件,在VC6.0或者VS平臺中設置工程的頭文件,庫函數以及可執行dll文件的目錄,這裏爲了方便使用已將相關的庫的文件放置在了工程目錄下,僅需要在MFCwithOpenGLWindowDlg.cpp文件中設置如下:
#include "./include/GL/glew.h"
#include "./include/GL/freeglut.h"
#pragma comment(lib,"glew32.lib")
#pragma comment(lib,"freeglut.lib")
爲了驗證可以通過MFC對OpenGL窗口繪圖進行控制,這裏進一步實現了一個旋轉前面繪製出來的紅色正方形。
首先在前面的對話框中再添加一個按鈕IDC_ROTATEGLVIEW用於旋轉GL繪圖。
然後添加該按鈕的消息響應函數OnRotateGLview(),實現如下:
csfortangle.Lock(); //臨界區加鎖
rtangle=rtangle+5.0f; //增加旋轉角度
if (rtangle>180.0f) //處理旋轉角度範圍
{
rtangle=0;
}
csfortangle.Unlock(); //臨界區開鎖
其中csfortangle是rtangle角度對應的臨界區對象用於實現線程之間對rtangle變量的訪問控制使得每時刻僅有一個線程對其操作,在對話框實現文件的頂部定義的:
GLfloat rtangle=0; //旋轉角度
CCriticalSection csfortangle; //控制旋轉角度線程之間訪問同步的臨界區對象
同時要使用臨界區對象,必須加上對應的頭文件:
#include "Afxmt.h" //引入MFC中的線程/進程同步類,這裏使用臨界區對象。
爲了實現真正的旋轉繪製的圖形,在顯示回調函數DisplayGLView中修改如下,即增加紅色部分:
glClear(GL_COLOR_BUFFER_BIT);//清空顏色緩衝
glLoadIdentity(); //復位當前模型視角矩陣
csfortangle.Lock(); //臨界區加鎖
glRotatef(rtangle,0.0f,0.0f,1.0f); //繞Z軸旋轉繪製的圖形
csfortangle.Unlock(); //臨界區開鎖
glColor3f(1.0,0.0,0.0);//設置繪圖爲紅色
glRectf(-0.5f,-0.5f,0.5f,0.5f);//繪製一個正方形
glFlush();//刷新顯示緩衝完成顯示最後編譯運行。測試時創建opengl窗口後首先點擊對話框中的“旋轉GL視圖”按鈕增加旋轉的角度,然後鼠標切換到opengl窗口點擊一下(爲了產生一個點擊事件使得glut能夠調用顯示回調函數DisplayGLView旋轉圖形並刷新顯示,也可以通過定時等事件來達到相應的目的)就可以看到旋轉後的圖形。
如果不想手動去切換,可以通過glut中的定時事件來觸發顯示函數回調。
在前面程序基礎上實現如下:
首先添加一個glut定時timerEvent回調函數,相當於MFC中的ontimer定時器響應函數:
void timerEvent(int value)//GLUT定時回調函數
{
glutPostRedisplay();//發送重新顯示消息,刷新顯示
glutTimerFunc(REFRESH_DELAY, timerEvent, 0);//重置定時器
}
然後修改OpenGLThread函數如下:
int OpenGLThread(LPVOID lpv)//線程run函數
{
//獲得線程創建時對話框傳入的參數,可以通過這裏傳遞給opengl一些控制參數
CMFCwithOpenGLWindowDlg* pTaskMain = (CMFCwithOpenGLWindowDlg *) lpv;
int argc=1;
char *argv[]={"OpenGL Thread "};
glutInit(&argc,argv);//初始化glut
glutInitDisplayMode(GLUT_RGB|GLUT_SINGLE);//設置顯示模式
glutInitWindowPosition(100,100);//設置opengl窗口顯示位置
glutInitWindowSize(400,400);//設置opengl窗口大小
glutCreateWindow("OpenGL Thread Window");//設置opengl窗口標題
glutDisplayFunc(&DisplayGLView);//註冊顯示回調函數繪圖
glutTimerFunc(REFRESH_DELAY, timerEvent, 0);//註冊定時回調函數
//調用freeglut中的函數設置opengl窗口被關閉和結束返回時可以繼續執行glutMainLoop後面的部分
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
glutMainLoop();//進入glut循環,處理響應事件
//AfxMessageBox("it end");
AfxEndThread(0);//結束線程返回,以免出現意外結束
return 1;//結束時返回
}
其中REFRESH_DELAY爲文件頂部的定時時間ms數值宏定義,這裏取200ms,重新編譯並運行程序就可以實現在MFC中點擊“旋轉GL視圖”即可連續地旋轉繪製的正方形了。
爲了驗證glut窗口中也能夠通過按鈕實現正方形旋轉的控制,可以進一步添加一個全局按鈕控制回調函數:
void keyboard(unsigned char key, int /*x*/, int /*y*/)//GLUT按鍵回調函數
{
switch(key)
{
case '+'://+號實現旋轉的逆時針角度增加
{
csfortangle.Lock(); //臨界區加鎖
rtangle=rtangle+5.0f; //增加旋轉角度
if (rtangle>180.0f) //處理旋轉角度範圍
{
rtangle=0;
}
csfortangle.Unlock(); //臨界區開鎖
}
break;
case '-'://-號實現旋轉的順時針角度增加
{
csfortangle.Lock(); //臨界區加鎖
rtangle=rtangle-5.0f; //增加旋轉角度
if (rtangle<-180.0f) //處理旋轉角度範圍
{
rtangle=0;
}
csfortangle.Unlock(); //臨界區開鎖
}
break;
default:
break;
}
}
並且在創建線程時將函數註冊到glut中:
glutKeyboardFunc(keyboard);//註冊鍵盤按鈕控制回調函數
編譯之後運行就可以看到既可以通過glut的窗口中通過鍵盤按鈕控制正方形旋轉,也可以通過MFC對話框按鈕實現。
補充:
如果要通過主線程如UI線程來結束自己創建的opengl線程,則可以在自己定義的顯示函數或者定時器處理函數中加上一個條件變量如Boolean型的變量,當該變量條件爲真時就調用freeglut的退出循環函數glutLeaveMainLoop(),它將直接跳出glutMainLoop,接着執行AfxEndThread()結束線程返回,從而實現提前退出和結束線程。
其實通過閱讀freeglut的源程序可以發現windows平臺上它實際上對窗口的繪製對事件的響應都是對windows API的封裝,尤其對於窗口和圖形的繪製也是對微軟wgl的封裝,如fgSetWindow函數中封裝了wglMakeCurrent函數和ReleaseDC函數,fgWindowProc函數則更是專門封裝了對應win32窗口處理的函數,其中封裝了wglCreateContext和wglGetCurrentContext等等函數。
最後給出本文中的示例源代碼下載地址:
http://download.csdn.net/detail/menglongbor/4268748
參考文獻:
GUI程序也能使用控制檯窗口,http://www.cppblog.com/wish/articles/23642.html
OpenGL入門學習——寫給想用計算機畫圖的朋友,http://bbs.pfan.cn/post-184355.html
OpenGL內存泄漏之主循環函數glutMainLoop()
http://blog.csdn.net/ronggang175/article/details/6068854
坑爹的ssh和glutMainLoop
http://hi.baidu.com/tyxxybd/blog/item/64f6040f698402306159f337.html