姑且不談論OpenGL的名貴背景和光明前途,單憑其實用性和有效性就足以使其成為我們圖形輸出編程的首選。但是在實際工程應用中程序員沒必要仔細地深究OpenGL的運作機制,也往往不需要掌握各種高級的效果制作,真正需要的是最簡捷地利用這個得力的工具實現輸出數據的可視化,如波形、譜圖、立體統計圖表等的顯示。有鑒於此,本文總結出了在C++Builder中OpenGL編程的實用框架。筆者經過實踐,對於一般的圖形輸出的應用,此框架足以應付。對於復雜些的程序可以在本框架的基礎上進行擴充。
在Windows環境下用OpenGL編程至少要了解如下預備知識:
OpenGL本身:對於一般的應用,我們可以認為OpenGL就是一套與窗口系統和操作系統無關的三維圖形函數庫。
DC和RC:每個Win32應用程序都有一個設備描述表(Device Context)簡稱DC,在這個設備描述表中包含了圖形怎樣顯示在窗口的設置( GDI)信息。調用OpenGL函數必須使用設備描述表才能正確地在窗口輸出。圖形操作描述表(Rendering Context)簡稱RC,是一種設備描述表的形式,在圖形操作描述表中存放一些OpenGL和操作系統相聯系的信息。圖形操作描述表是傳遞所有OpenGL命令的端口。
像素格式:像素格式定義了OpenGL繪圖的屬性,創建圖形操作描述表首先要定義象素格式。它由這樣一個數據結構實現
typedef struct tagPIXELFORMATDESCRIPTOR
{
WORD nSize; //結構大小
WORD nVersion; //版本
DWORD dwFlags; //象素緩沖的位標志
BYTE iPixelType; //RGBA模式或顏色索引模式
BYTE cColorBits; //顏色位數
BYTE cRedBits; //RGBA模式下R所占位數
BYTE cRedShift; //RGBA模式下R位數偏移
BYTE cGreenBits; //RGBA模式下G所占位數
BYTE cGreenShift; //RGBA模式下G位數偏移
BYTE cBlueBits; //RGBA模式下B所占位數
BYTE cBlueShift; //RGBA模式下B位數偏移
BYTE cAlphaBits; //RGBA模式下Alpha所占位數
BYTE cAlphaShift; //RGBA模式下Alpha位數偏移
BYTE cAccumBits; //累計緩沖區位面總數
BYTE cAccumRedBits; //累計緩沖區R位面總數
BYTE cAccumGreenBits; //累計緩沖區G位面總數
BYTE cAccumBlueBits; //累計緩沖區B位面總數
BYTE cAccumAlphaBits; //累計緩沖區Alpaha位面總數
BYTE cDepthBits; //深度緩沖位數
BYTE cStencilBits; //模板緩沖位數
BYTE cAuxBuffers; //Win32 下不支持
BYTE iLayerType; //不再使用
BYTE bReserved; //0
DWORD dwLayerMask; //不再使用
DWORD dwVisibleMask; //0
DWORD dwDamageMask; //不再使用
} PIXELFORMATDESCRIPTOR;
雙緩沖技術:OpenGL支持一個顯示緩沖和一個非顯示緩沖。缺省的情況是所有的OpenGL繪制命令在非顯示緩沖中繪制,繪制完成後再將其內容拷貝到顯示緩沖區中(使用SwapBuffers函數)。雙緩沖使圖象轉換更平滑,這就是在快速動畫(如波形等的實時輸出)時沒有屏幕閃爍的奧妙所在。
反走樣技術:實際中需要畫出的往往是曲線,由於計算機以離散點生成圖形,曲線上會有鋸齒,這就是一種走樣現象。在用一般語言畫圖時,這一現象是難以避免的。OpenGL中利用混合技術,把原來邊界的鋸齒部分用低飽和度的點補上從而實現反走樣,達到平滑的邊界效果。
好,我們現在可以啟用下面的程序框架了。
在*.h文件的類聲明中添加private成員:
private:
HGLRC hRC;
HDC hDC;
以下是相應*.cpp文件
首先加上兩個包含文件:
#include <gl\gl.h> //程序使用OpenGL的核心函數
#include <gl\glu.h> //程序使用實用庫中的函數
一、在FormCreate()函數中完成OpenGL的初始化
使用OpenGL必須首先進行一些初始化工作,具體包含以下步驟:
1、創建DC
hDC=GetDC(Handle);
此句獲取一個設備描述表,TForm1->Handle中保存有Form的窗口句柄;很多情況下我們希望在一個Panel中輸出圖形,那麼可以用Panel1->Handle作為此函數的參數。
2、創建RC
(1)定義像素格式
static PIXELFORMATDESCRIPTOR pfd={
sizeof(PIXELFORMATDESCRIPTOR), //此結構的大小
1, //此結構的版本
PFD_DRAW_TO_WINDOW| //在窗口上繪圖(而不是在位圖上)
PFD_SUPPORT_OPENGL| //在窗口中支持使用OpenGL
PFD_DOUBLEBUFFER, //使用雙緩沖模式
PFD_TYPE_RGBA, //使用RGBA色彩模式
24, //存儲顏色數據的位數
0,0,0,0,0,0,
0,0,0,0,0,0,0,
32, //深度緩沖區大小
0,0,
PFD_MAIN_PLANE, //在主平面上繪圖
0,
0,0,0
};
(2)選擇最佳像素格式
int iPixelFormat=ChoosePixelFormat(hDC,&pfd);
選擇一最適合上述pfd結構的像素格式,並把保存索引號。
SetPixelFormat(hDC,iPixelFormat,&pfd);
按選擇的索引號設置設備描述表的像素格式。
(3)用DC創建RC
hRC=wglCreateContext(hDC);
用指定的設備描述表產生一個圖形操作描述表,使它在該設備描述表上繪圖,並且有與此設備描述表相同的像素格式。
3、指定當前的DC、RC
wglMakeCurrent(hDC,hRC);
把產生的圖形操作描述表置為當前的,程序此後的所有OpenGL函數都通過此圖形操作描述表執行,並將圖形繪制在設備描述表引用的設備上。
到此就完成了初始化工作,這些步驟基本上是固定的(像素格式的參數設置也是如此),對於一般的應用可以直接使用上述語句。
二、在FormDestroy()中作清理工作以釋放資源
1、清屏
glClearColor(0.0,0.0,0.0,1.0);
設置背景色為黑色。
glClear(GL_COLOR_BUFFER_BIT);
清屏以防止對以後窗口操作的影響。
2、當前DC、RC置空
wglMakeCurrent(NULL,NULL);
使不再有當前的圖形操作描述表。
3、刪除DC、RC
wglDeleteContext(hRC);
刪除該圖形操作描述表。
DeleteObject(hDC);
刪除該設備描述表。
如果在同一個程序裡對多個窗體用繪圖必須嚴格進行清理,否則輸出會出現混亂。
三、在FormPaint()中實施繪圖的相關操作
每當窗體重畫時進行繪圖的動作。OnPaint事件可能由系統觸發例如置為當前窗口;也可以由程序觸發即在需要改變繪圖時調用TForm->FormPaint(Sender)。把所有繪圖操作統一歸入FormPaint()事件的響應函數中使我們很容易控制繪圖的時機,程序變得很有條理。
1、繪圖准備
(1)指定DC
HDC hDC;
hDC=wglGetCurrentDC();
在多個設備描述表如多個繪圖面板時有必要指定此後OpenGL命令輸出的目標。
(2)清屏
動態的圖形輸出必須有清屏的操作將上此繪圖的結果以背景色覆蓋掉,以便畫新的圖形。
glClearColor(0.0,0.0,0.0,1.0);
背景色以黑色為例。
glClear(GL_COLOR_BUFFER_BIT);
清屏命令。
(3)啟動反走樣功能(可選)
glEnable(GL_BLEND);
啟動混合。
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
指定混合的屬性。
glEnable(GL_LINE_SMOOTH);
啟動線反走樣。
glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
指定為線反走樣且為最好質量。
注意在繪圖結束後要有相應的關閉反走樣的操作,以防止影響程序以後的操作,語句如下
glDisable(GL_LINE_SMOOTH);
glDisable(GL_BLEND);
2、繪圖
調用自定義的繪圖函數RenderSence()。
3、緩沖操作
glFlush();
強制完成繪圖工作。
SwapBuffers(hDC);
完成兩個圖形緩沖區的交換,把畫完的非顯示緩沖圖形顯示出來。
四、編寫繪圖函數
即定義RenderSence()函數,完成真正的繪圖操作。實際上是把工程計算的結果作為參數調用OpenGL的各種繪圖庫函數。具體函數與畫法請參閱有關書籍。我們把單純的繪圖和效果設定單列出來作為一個函數供FormPaint()調用,這樣的結構使畫圖編程變得非常靈活。更復雜的情況可以寫多個自定義繪圖函數,也可以引入參數列表。
五、視口變換
OpenGL中有多種圖形變換,其中視口變換是比較簡單而且常用的變換方式,所以也歸為本框架的一部分。事實上只需要使用一個函數
glViewport(0,0, ClientWidth, ClientHeight);
此函數可以指定全部圖形最後投影的一個矩形區域,上句以整個窗體的客戶區為例。在FormResize()事件響應函數中調用這個函數可以使圖形在窗體大小形狀變化時保持相同比例的縮放。