明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

用Delphi做一個(gè)OpenGL控件

[摘要]OpenGL是一個(gè)獨(dú)立于窗口的圖形庫,而圖形最終是在窗口系統(tǒng)里繪制出來的,那么OpenGL的繪圖命令是怎么在窗口里生成輸出的呢?   這就是各個(gè)系統(tǒng)上的OpenGL實(shí)現(xiàn)者需要做的工作了。在Windo...
OpenGL是一個(gè)獨(dú)立于窗口的圖形庫,而圖形最終是在窗口系統(tǒng)里繪制出來的,那么OpenGL的繪圖命令是怎么在窗口里生成輸出的呢?
  這就是各個(gè)系統(tǒng)上的OpenGL實(shí)現(xiàn)者需要做的工作了。在Windows里是通過wgl庫完成的,在X-Windows里是通過glx服務(wù)器來完成的,至于這些OpenGL實(shí)現(xiàn)具體是怎么工作的,請參考sgi發(fā)布的sample implement源碼,不過那個(gè)代碼是用C寫的。
  在MS-Windows里,wgl庫負(fù)責(zé)將OpenGL的繪制設(shè)備RenderContext與GDI的DeviceContext聯(lián)系起來,使得發(fā)到OpenGL的RC里的命令生成的位圖能夠在GDI DC里繪制出來,你可以把它想象成OpenGL在RC里有一個(gè)FrameBuffer,記錄著生成的圖案,而wgl則負(fù)責(zé)把FrameBuffer的內(nèi)容BitBlt到DC上。當(dāng)然,這并不是它實(shí)際的工作方法,如果想了解更多請參考SGI發(fā)布的SDK資料或聯(lián)系MS公司。
  為了使GDI DC能夠接受OpenGL RC的輸出,必須為DC選定特別的像素格式,然后建立RC,再用wglMakeCurrent把當(dāng)前要使用的RC和DC聯(lián)系起來。此后我們就可以用OpenGL命令正常工作了。在一個(gè)程序里可以創(chuàng)建多個(gè)RC和多個(gè)DC,程序中的OpenGL命令會發(fā)到被wglMakeCurrent指定為當(dāng)前的那一組合中。
  我并不認(rèn)為這個(gè)初始化過程是個(gè)很有意思的工作,這個(gè)世界上有很多聰明的程序員也這么想,所以他們發(fā)明了glaux庫和glut庫。glaux是在著名的OpenGL Programmer Guide里提出的,這本書是OpenGL編程的官方文檔,因?yàn)樗姆馄な羌t色的,所以通常簡稱為RedBook。故名思意,glaux是一套輸助庫,它使得你無須關(guān)心在具體窗口系統(tǒng)里初始化、消息響應(yīng)的細(xì)節(jié),而是使用傳統(tǒng)的c/dos程序風(fēng)格編制OpenGL程序。



int main(int argc, char** argv)
{ auxInitDisplayMode( AUX_SINGLE AUX_RGB AUX_DEPTH16);//使用單緩沖、RGB彩色模式、16位濃度
auxInitPosition(0,0,250,250);
auxInitWindow("Title");//以上兩行在(0,0)片建立了一個(gè)大小為250X250的窗口,其標(biāo)題為"Title"。
myinit();//建立OpenGL透視投影環(huán)境
auxReshapeFunc(myReshape);//指定窗口大小變化的響應(yīng)函數(shù)
auxMainLoop(display);//指定繪制函數(shù)
return 0; }


  由于glaux是為教學(xué)目的開發(fā)的,所以實(shí)用價(jià)值很限,所以又有程序員開發(fā)了glut,這套庫被廣泛使用,它的工作方式與glaux極為類似,但功能完善得多,特別是對交互、全屏等的支持要理想得多,所以許多的OpenGL演示程序使用它,比如SGI網(wǎng)站 峁┑畝嗍菔境絳蚨夾枰褂盟M閉馓卓庖丫灰浦駁蕉嘀制教ㄉ,所以要矢[胗眉虻サ姆椒ǹ⒃趙indows/macos/os2/xwindows等系統(tǒng)上都能使用的程序,那么應(yīng)該選擇這套庫。
  我并不認(rèn)為一個(gè)Delphi程序員會喜歡glaux或glut,因?yàn)槟且馕吨悴荒芾肈elphi的可視開發(fā)能力,另外任何真正實(shí)用的Delphi程序想直接在其它操作系統(tǒng)上編譯運(yùn)行好象也不現(xiàn)實(shí),即glut的跨平臺能力也沒有什么吸引力。我們應(yīng)該開發(fā)一個(gè)VCL控件,把初始化工作封裝起來。
  我認(rèn)為從TCustomPanel派生一個(gè)子類比較方便,讓我們稱它為TGLPanel吧。初始化過程要在WMCreate里完成,之所以要放在這里是因?yàn)檫@個(gè)時(shí)候Window Handle已經(jīng)建立,但還沒啟用。
  在WMCreate中會
 、僬{(diào)用initDC完成DC調(diào)整工作,initDC會以本窗口使用的DC調(diào)用PreparePixelFormat,而PixelFormat則真正完成像素格式調(diào)整。
  ②然后WMCreate會調(diào)用InitGL完成OpenGL透視投影環(huán)境的設(shè)定。
 、圩詈笳{(diào)用OnInit給用戶一個(gè)調(diào)整透視投影環(huán)境的機(jī)會。
注意,如果要在MDI環(huán)境中的子窗體中使用OpenGL,還有些附加工作要做,這就是給窗口類的Params.Style加上WS_CLIPCHILDREN和WS_CLIPSIBLINGS屬性,這得在Window Handle建立之前就完成,因此要寫在CreateParams里。由于SDI應(yīng)用并不需要該代碼,所以應(yīng)該定義OnPreInit事件,讓用戶在需要的時(shí)候自己加上,在Create里調(diào)用OnPreInit。以下代碼定義了OnPreInit,但并沒有定義CreateParams,如果需要自己加上吧。
  在TGLPanel類中實(shí)際所做工作的詳細(xì)說明(按成員可見性組織):
私有
1、加入DC/RC/Pal私有變量
2、定義初始化DC/RC的私有方法

保護(hù):
3、加入FOnPaint,FOnResize,FOnInit,FOnPreInit四個(gè)事件響應(yīng)變量。
4、繼承/重載虛方法CreateParams,Paint,Resize
5、響應(yīng)以下消息
WM_CREATE, TWMCreate, WMCreate
WM_DESTROY, TWMDestroy, WMDestroy
WM_PALETTECHANGED, TWMPaletteChanged, WMPaletteChanged
WM_QUERYNEWPALETTE, TWMQueryNewPalette, WMQueryNewPalette
WM_ERASEBKGND, TWMEraseBkgnd, WMEraseBkgnd

公開:
6、定義建構(gòu)與析構(gòu)方法
7、定義必要的其它方法以提供各種特性

發(fā)布:
8、以下繼承來的屬性
__property Alignment;
__property Align;
__property DragCursor;
__property DragMode;
__property Enabled;
__property ParentFont;
__property ParentShowHint;
__property PopupMenu;
__property ShowHint;
__property TabOrder;
__property TabStop;
__property Visible;
9、以下繼承來的方法
__property OnClick;
__property OnDblClick;
__property OnDragDrop;
__property OnDragOver;
__property OnEndDrag;
__property OnEnter;
__property OnExit;
__property OnMouseDown;
__property OnMouseMove;
__property OnMouseUp;
__property OnStartDrag;
10、加入以下事件
//初始化OpenGL狀態(tài)
__property TNotifyEvent OnInit = {read=FOnInit,write=FOnInit};
//專用于修改顯示BPP模式
__property TNotifyEvent OnPreInit = {read=FOnPreInit,write=FOnPreInit};
11、重載以下事件
__property TNotifyEvent OnResize = {read=FOnResize,write=FOnResize};
__property TNotifyEvent OnPaint = {read=FOnPaint,write=FOnPaint};
12、將消息與其響應(yīng)函數(shù)連接起來(Delphi中這一步是在定義函數(shù)時(shí)指定的)
源代碼
unit GLPanel;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls,OpenGL;

type
TGLPanel = class(TCustomPanel)
private
{ Private declarations }
DC: HDC;
RC: HGLRC;
procedure initDC;
procedure initGL;
procedure PreparePixelFormat(var DC: HDC);

protected
{ Protected declarations }
FOnPaint:TNotifyEvent;
FOnInit:TNotifyEvent;
FOnPreInit:TNotifyEvent;
FOnResize:TNotifyEvent;

procedure Paint;override;
procedure Resize;override;
procedure WMDestroy(var Msg: TWMDestroy);message WM_DESTROY;
procedure WMCreate(var Msg:TWMCreate); message WM_CREATE;


public
{ Public declarations }
constructor Create(Owner:TComponent);override;

published
{ Published declarations }

property Alignment;
property Align;
property DragCursor;
property DragMode;
property Enabled;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property TabOrder;
property TabStop;
property Visible;

property OnClick;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnStartDrag;

property OnInit:TNotifyEvent read FOnInit write FOnInit;
property OnPreInit:TNotifyEvent read FOnPreInit write FOnPreInit;

property OnResize:TNotifyEvent read FOnResize write FOnResize;
property OnPaint:TNotifyEvent read FOnPaint write FOnPaint;

end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents(’Samples’, [TGLPanel]);
end;
//---------------------------------------------
constructor TGLPanel.Create;
begin
inherited;
end;
//---------------------------------------------
procedure TGLPanel.WMDestroy(var Msg: TWMDestroy);
begin
wglMakeCurrent(0, 0);
wglDeleteContext(RC);
ReleaseDC(Handle, DC);
end;
//---------------------------------------------------
procedure TGLPanel.initDC;
begin
DC := GetDC(Handle);
PreparePixelFormat(DC);
end;
//---------------------------------------------------
procedure TGLPanel.initGL;
begin
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity;
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glOrtho(-1, 1, -1, 1, -1, 50);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
glEnable(GL_DEPTH_TEST);
//注意下面這一行是為了做練習(xí)程序時(shí)可以直接用glColor指定材質(zhì)而加的,
// 可能使得光照或表面材質(zhì)發(fā)生意想不到的變化,
// 如果不需要可以去掉或在程序中用glDisable(GL_COLOR_MATERIAL);關(guān)閉
glEnable(GL_COLOR_MATERIAL);
glShadeModel(GL_SMOOTH);
gluLookAt(2, 4, 6, 0, 0, 0, 0, 1, 0);
SwapBuffers(DC);
end;
//---------------------------------------------
procedure TGLPanel.PreparePixelFormat(var DC: HDC);
var
PFD : TPixelFormatDescriptor;
ChosenPixelFormat : Integer;
begin
  FillChar(PFD, SizeOf(TPixelFormatDescriptor), 0);

with PFD do
begin
  nSize := SizeOf(TPixelFormatDescriptor);
  nVersion := 1;
  dwFlags := PFD_DRAW_TO_WINDOW or
  PFD_SUPPORT_OPENGL or
  PFD_DOUBLEBUFFER;
  iPixelType := PFD_TYPE_RGBA;
  cColorBits := 16; // 16位顏色
  cDepthBits := 32; // 32位深度緩沖
  iLayerType := PFD_MAIN_PLANE;
{ Should be 24, but we must allow for the clunky WKU boxes }
end;

ChosenPixelFormat := ChoosePixelFormat(DC, @PFD);
if ChosenPixelFormat = 0 then
  Raise Exception.Create(’ChoosePixelFormat failed!’);
  SetPixelFormat(DC, ChosenPixelFormat, @PFD);
end;

procedure TGLPanel.WMCreate(var Msg:TWMCreate);
begin
//在這里做初始化工作
//修改DC的象素格式,使之支持OpenGL繪制
initDC;
RC := wglCreateContext(DC);
wglMakeCurrent(DC, RC);
//初始化GL繪制系統(tǒng)
initGL;
if Assigned(FOnInit) then
begin
 if (wglMakeCurrent(DC,RC)=false) then
  ShowMessage(’wglMakeCurrent:’ + IntToStr(GetLastError));
  FOnInit(self);
 end;
end;
//
procedure TGLPanel.Paint;
begin
//TCustomPanel::Paint();
if Assigned(FOnPaint) then
 begin
  wglMakeCurrent(DC,RC);
  FOnPaint(self);
  SwapBuffers(DC);
 end;
end;
//
procedure TGLPanel.Resize;
begin
  inherited;
if Assigned(FOnResize) then
 begin
  wglMakeCurrent(DC,RC);
  glViewport(0,0,ClientWidth,ClientHeight);
  FOnResize(self);
  end;
 end;
end.

  以上代碼僅用來說明原理及建立一個(gè)基本的練習(xí)環(huán)境,您可以自由使用,轉(zhuǎn)載請注明出處。如果使用從本人主頁下載的TGLPanel請遵守內(nèi)附使用說明的版權(quán)申明。如果實(shí)際做東西,建議使用Mike Lischke的GLScene控件組(http://www.lischke-online.de/)。

  end

  else

  刪除注冊表項(xiàng)....................... end;初始化擴(kuò)展是通過IShellExtInit實(shí)現(xiàn)的,當(dāng)外殼調(diào)用IShellExtInit.Initialize時(shí),它傳遞一個(gè)數(shù)據(jù)對象包含來文件對應(yīng)的目錄的PIDL標(biāo)識符。Initialize方法需要從數(shù)據(jù)對象中提取文件名,并把文件名和PIDL標(biāo)識符保存起來為了以后使用。

  

  function TCXPropSheet.SEIInitialize(pidlFolder: PItemIDList;

    lpdobj: IDataObject; hKeyProgID: HKEY): HResult;

  var

    StgMedium: TStgMedium;

    FormatEtc: TFormatEtc;

    szFile: array[0..MAX_PATH+1]of Char;

    filecount: integer;begin

    Result:=E_FAIL;

  if(lpdobj=nil)then

  begin

    Result:=E_INVALIDARG;

    messagebox(0, ’1’, ’錯(cuò)誤’, mb_ok);

    Exit;

  end;

  with FormatEtc do

  begin

    cfFormat:=CF_HDROP;

    ptd:=nil;

    dwAspect:=DVASPECT_CONTENT;

    lindex:=-1;

    tymed:=TYMED_HGLOBAL;

  end;

  Result:=lpdobj.GetData(FormatEtc, StgMedium);

  if Failed(Result)then

  Exit;

  //如果只有一個(gè)文件被選中,獲得文件名并保存。

  filecount:=DragQueryFile(stgmedium.hGlobal, $FFFFFFFF, nil, 0);

  if filecount=1 then

  begin

    Result:=NOERROR;

    DragQueryFile(stgmedium.hGlobal, 0, szFile, SizeOf(szFile));

    FFilename:=strpas(szFile);

  end;

  ReleaseStgMedium(StgMedium);end;添加頁面的操作是通過IShellPropSheetExt接口來實(shí)現(xiàn)的。如果屬性頁是和文件相關(guān)聯(lián),外殼會調(diào)用IShellPropSheetExt.AddPages給屬性頁添加一個(gè)頁面。如果屬性頁同控制面板程序相關(guān)聯(lián),外殼調(diào)用IShellPropSheetExt.ReplacePage來替換頁面。

  IShellPropSheetExt.AddPages方法有兩個(gè)參數(shù),lpfnAddPage是一個(gè)指向AddPropSheetPageProc回調(diào)函數(shù)的指針,回調(diào)函數(shù)用來提供要添加的頁面信息給外殼。lParam是一個(gè)用戶自定義的值,這里我們用它來返回給回調(diào)函數(shù)對象。

  一般的IShellPropSheetExt.AddPages方法實(shí)現(xiàn)步驟是:

  給PROPSHEETPAGE結(jié)構(gòu)設(shè)定正確的值,特別是:

  把擴(kuò)展的對象引用記數(shù)變量付值給pcRefParent成員,這可以防止頁面還在顯示時(shí),擴(kuò)展對象被卸載。

  實(shí)現(xiàn)PropSheetPageProc回調(diào)函數(shù)來處理頁面創(chuàng)建和銷毀的情況。

  調(diào)用CreatePropertySheetPage函數(shù)來創(chuàng)建頁面。

  調(diào)用lpfnAddPage指向的函數(shù)來來添加創(chuàng)建好的頁面。

  function TCXPropSheet.AddPages(lpfnAddPage: TFNADDPROPSHEETPAGE;

  lParam: LPARAM): HResult;var

  PSP: TPropSheetPage;

  HPSP: HPropSheetPage;begin

  result:=E_FAIL;

  try

  psp.dwSize:=SizeOf(psp);

  psp.dwFlags:=PSP_USEREFPARENT or PSP_USETITLE or PSP_USECALLBACK;

  psp.hInstance:=hInstance;

  //這里我們使用了事先儲存在wave.res中的對話框模板,模板是用delphi5自帶的

  //resource workshop編輯的,使用delphi5\bin\brcc32.exe編譯的。

  psp.pszTemplate:=MakeIntResource(100);

  //標(biāo)題名

  psp.pszTitle:=’波文件信息’;

  //設(shè)定回調(diào)函數(shù)

  psp.pfnDlgProc:=@DialogProc;

  psp.pfnCallBack:=@PropCallback;

  //設(shè)定對象引用記數(shù)變量

  psp.pcRefParent:=@comserver.objectcount;

  //用lParam向回調(diào)函數(shù)傳遞對象

  psp.lParam:=integer(self);

  HPSP:=CreatePropertySheetPage(psp);

  if HPSP$#@60;$#@62;nil then begin

  if not lpfnAddPage(HPSP, lParam)then begin

  DestroyPropertySheetPage(HPSP);

  end else begin

  _addref;//增加引用記數(shù),否則一脫離這個(gè)方法的作用域,delphi自動(dòng)釋放對象。

  result:=S_OK;

  end

  end

  except

  on e: exception do begin

  e.message:=’添加頁面’+e.message;

  messagebox(0, pchar(e.message), ’錯(cuò)誤’, mb_ok);

  end;

  end;end;

  function TCXPropSheet.ReplacePage(uPageID: UINT;

  lpfnReplaceWith: TFNADDPROPSHEETPAGE; lParam: LPARAM): HResult;begin

  Result:=E_NOTIMPL;//同文件關(guān)聯(lián)時(shí),外殼不調(diào)用ReplacePage,所以不用實(shí)現(xiàn)end;回調(diào)函數(shù)處理屬性頁的消息,主要要響應(yīng)WM_INITDIALOG消息來初始化頁面顯示信息,響應(yīng)WM_COMMAND消息來處理用戶交互,響應(yīng)WM_NOTIFY消息來處理頁面切換或關(guān)閉后處理操作結(jié)果。

  

  function DialogProc(hwndDlg: HWnd; Msg: UINT; wParam: wParam;

  lParam: LPARAM): Bool; stdcall;

  var

    PageObj: TCXPropSheet;

    filename: string;

    displayName : string;

    SheetHWnd: HWnd;

  begin

    result:=false;

    try

    if Msg=WM_INITDIALOG then begin//初始化界面

  //獲得lparam傳遞過來的對象

    pageObj:=TCXPropSheet(PPropSheetPage(lParam)^.lParam);

  //保存對象信息

    SetWindowLong(hwndDlg, DWL_USER, integer(pageObj));

  //設(shè)置界面顯示波文件信息

    SetDlgItemText(hwndDlg, 100, PChar(ExtractFileName(PageObj.FFileName)));

    OpenMedia(PageObj.FFileName);

  SetDlgItemText(hwndDlg, 101, PChar(IntToStr(GetWavStatus(MCI_WAVE_STATUS_AVGBYTESPERSEC))));

  SetDlgItemText(hwndDlg, 102, PChar(IntToStr(GetWavStatus(MCI_WAVE_STATUS_BITSPERSAMPLE))));

  SetDlgItemText(hwndDlg, 103, PChar(IntToStr(GetWavStatus(MCI_WAVE_STATUS_CHANNELS))));

  CloseMedia;

    SetWindowLong(hwndDlg, DWL_MSGRESULT, 0);

    Result:=TRUE;

  end

  else if(Msg=WM_COMMAND)then begin

  if Lo(wParam)=110 then//用戶點(diǎn)擊了關(guān)于按鈕(id=110)

    MessageBox(0,’作者:hubdog’+#13#10+’email:hubdog@263.net’,’關(guān)于...’,MB_OK);

  end else if(msg=WM_NOTIFY)then begin

    sheetHwnd:=getparent(hwndDlg);//獲得屬性頁的窗口句柄

    case PNMHdr(lparam)^.code of

  //頁面失去焦點(diǎn)

    PSN_KILLACTIVE:

  begin

    SetWindowLong(hwndDlg, DWL_MSGRESULT, 0);

    Result:=TRUE;

  end;

  end;

  end;

    except

    on e: exception do begin

    e.message:=’回調(diào)處理’+e.message;

    messagebox(0, pchar(e.message), ’錯(cuò)誤’, mb_ok);

  end;

  end;

  end;

  

  建立同驅(qū)動(dòng)器相關(guān)聯(lián)的屬性頁擴(kuò)展用

  同上面講的有兩點(diǎn)不同:

  IShellExtInit.Initialize方法傳遞過來的數(shù)據(jù)對象包含的驅(qū)動(dòng)器路徑可能是CFSTR_MOUNTEDVOLUME格式而不是CF_HDROP格式的。標(biāo)準(zhǔn)驅(qū)動(dòng)器是CF_HDROP格式的,而在NTFS文件系統(tǒng)中映射的遠(yuǎn)程設(shè)備則是CFSTR_MOUNTEDVOLUME格式的。

  注冊表項(xiàng)是HKEY_CLASSES_ROOT\Drive\Shellex\PropertySheetHandlers子鍵。

  建立控制面板屬性頁擴(kuò)展

  同上面講的有兩點(diǎn)不同:

  控制面板程序調(diào)用IShellPropSheetExt.ReplacePage方法來替換頁面,它不調(diào)用IShellPropSheetExt。AddPages方法。

  注冊方式:子鍵可以在不同位置創(chuàng)建,這依賴于擴(kuò)展是針對用戶還是針對機(jī)器的。對用戶方式子鍵是HKEY_CURRENT_USER\REGSTR_PATH_CONTROLPANEL,否則子鍵是HKEY_LOCAL_MACHINE\REGSTR_PATH_CONTROLSFOLDER。

  本程序在Delphi5,Win NT 4.0,K6-233系統(tǒng)下調(diào)試成功。例子程序可以到http://chaozhi.com/lgc去下載