前言
NeHe OpenGL第一課:OpenGL視窗

建立一個OpenGL視窗:
在這個教程裡,我将教你在Windows環境中建立OpenGL程式.它将顯示一個空的OpenGL視窗,可以在視窗和全屏模式下切換,按ESC退出.它是我們以後應用程式的架構.
了解OpenGL如何工作非常重要,你可以在教程的末尾下載下傳源程式,但我強烈建議你至少讀一遍教程,然後再開始程式設計.
歡迎來到我的 OpenGL教程。我是個對 OpenGL充滿激情的普通男孩! 我第一次聽說 OpenGL是 3Dfx 釋出 Voodoo1 卡的 OpenGL硬體加速驅動的時候。我立刻意識到 OpenGL是那種必須學習的東西。不幸的是當時很難從書本或網絡上找到關于 OpenGL的訊息。我花了 N 個 小時來調試自己書寫的代碼,甚至在 IRC和 EMail 上花更多的時間來懇求别人幫忙。但我發現那 些懂得 OpenGL 高手們保留了他們的精華,對共享知識也不感興趣。實在讓人灰心 !
我建立這個網站的目的是為了幫助那些對 OpenGL有興趣卻又需要幫助的人。在我的每個教程中,我都會盡可能詳細的來解釋每一行代碼的作用。我會努力讓我的代碼更簡單(您無需學習 MFC代碼)!就算您是個VC 、OPENGL的絕對新手也應該可以讀通代碼,并清楚的知道發生了什麼。我的站點隻是許多提供 OpenGL教程的站點中的一個。如果您是 OpenGL的進階程式員的話,我的站點可能太簡單了,但如果您才開始的話,我想這個站點會教會您許多東西!
教程的這一節在2000年一月徹底重寫了一遍。将會教您如何設定一個 OpenGL視窗。它可以隻是一個視窗或是全螢幕的、可以任意 大小、任意色彩深度。此處的代碼很穩定且很強大,您可以在您所有的OpenGL項目中使用。我所有的教程都将基于此節的代碼!所有的錯誤都有被報告。是以應該沒有記憶體洩漏,代碼也很容易閱讀和修改。感謝Fredric Echols對代碼所做的修改!
現在就讓我們直接從代碼開始吧。第一件事是打開VC然後建立一個新工程。如果您不知道如何建立的話,您也許不該學習OpenGL,而應該先學學VC。某些版本的VC需要将 bool 改成 BOOL , true 改成 TRUE , false 改成 FALSE ,請自行修改。
在您建立一個新的Win32程式(不是console控制台程式)後,您還需要連結OpenGL庫檔案。在VC中操作如下:Project-> Settings,然後單擊LINK标簽。在"Object/Library Modules"選項中的開始處(在 kernel32.lib 前)增加 OpenGL32.lib GLu32.lib 和 GLaux.lib 後單擊OK按鈕。現在可以開始寫您的OpenGL程式了。
代碼的前4行包括了我們使用的每個庫檔案的頭檔案。如下所示:
Cpp代碼
接下來您需要設定您計劃在您的程式中使用的所有變量。本節中的例程将建立一個空的OpenGL視窗,是以我們暫時還無需設定大堆的變量。餘下需要設定的變量不多,但十分重要。您将會在您以後所寫的每一個OpenGL程式中用到它們。
第一行設定的變量是Rendering Context(着色描述表)。每一個OpenGL都被連接配接到一個着色描述表上。着色描述表将所有的OpenGL調用指令連接配接到Device Context(裝置描述表)上。我将OpenGL的着色描述表定義為 hRC 。要讓您的程式能夠繪制視窗的話,還需要建立一個裝置描述表,也就是第二行的内容。Windows的裝置描述表被定義為 hDC 。DC将視窗連接配接到GDI(Graphics Device Interface圖形裝置接口)。而RC将OpenGL連接配接到DC。第三行的變量 hWnd 将儲存由Windows給我們的視窗指派的句柄。最後,第四行為我們的程式建立了一個Instance(執行個體)。
下面的第一行設定一個用來監控鍵盤動作的數組。有許多方法可以監控鍵盤的動作,但這裡的方法很可靠,并且可以處理多個鍵同時按下的情況。
active 變量用來告知程式視窗是否處于最小化的狀态。如果視窗已經最小化的話,我們可以做從暫停代碼執行到退出程式的任何事情。我喜歡暫停程式。這樣可以使得程式不用在背景保持運作。
fullscreen 變量的作用相當明顯。如果我們的程式在全屏狀态下運作, fullscreen 的值為TRUE,否則為FALSE。這個全局變量的設定十分重要,它讓每個過程都知道程式是否運作在全屏狀态下。
現在我們需要先定義WndProc()。必須這麼做的原因是CreateGLWindow()有對WndProc()的引用,但WndProc()在CreateGLWindow()之後才出現。在C語言中,如果我們想要通路一個目前程式段之後的過程和程式段的話,必須在程式開始處先申明所要通路的程式段。是以下面的一行代碼先行定義了WndProc(),使得CreateGLWindow()能夠引用WndProc()。
下面的代碼的作用是重新設定OpenGL場景的大小,而不管視窗的大小是否已經改變(假定您沒有使用全屏模式)。甚至您無法改變視窗的大小時(例如您在全屏模式下),它至少仍将運作一次--在程式開始時設定我們的透視圖。OpenGL場景的尺寸将被設定成它顯示時所在視窗的大小。
<a></a>
下面幾行為透視圖設定螢幕。意味着越遠的東西看起來越小。這麼做建立了一個現實外觀的場景。此處透視按照基于視窗寬度和高度的45度視角來計算。0.1f,100.0f是我們在場景中所能繪制深度的起點和終點。
glMatrixMode(GL_PROJECTION)指明接下來的兩行代碼将影響projection matrix(投影矩陣)。投影矩陣負責為我們的場景增加透視。 glLoadIdentity()近似于重置。它将所選的矩陣狀态恢複成其原始狀态。調用 glLoadIdentity()之後我們為場景設定透視圖。
glMatrixMode(GL_MODELVIEW)指明任何新的變換将會影響 modelview matrix(模型觀察矩陣)。模型觀察矩陣中存放了我們的物體訊息。最後我們重置模型觀察矩陣。如果您還不能了解這些術語的含義,請别着急。在以後的教程裡,我會向大家解釋。隻要知道如果您想獲得一個精彩的透視場景的話,必須這麼做。
接下的代碼段中,我們将對OpenGL進行所有的設定。我們将設定清除螢幕所用的顔色,打開深度緩存,啟用smooth shading(陰影平滑),等等。這個例程直到OpenGL視窗建立之後才會被調用。此過程将有傳回值。但我們此處的初始化沒那麼複雜,現在還用不着擔心這個傳回值。
下一行啟用smooth shading(陰影平滑)。陰影平滑通過多邊形精細的混合色彩,并對外部光進行平滑。我将在另一個教程中更詳細的解釋陰影平滑。
下一行設定清除螢幕時所用的顔色。如果您對色彩的工作原理不清楚的話,我快速解釋一下。色彩值的範圍從0.0f到1.0f。0.0f代表最黑的情況,1.0f就是最亮的情況。glClearColor 後的第一個參數是Red Intensity(紅色分量),第二個是綠色,第三個是藍色。最大值也是1.0f,代表特定顔色分量的最亮情況。最後一個參數是Alpha值。當它用來清除螢幕的時候,我們不用關心第四個數字。現在讓它為0.0f。我會用另一個教程來解釋這個參數。
通過混合三種原色(紅、綠、藍),您可以得到不同的色彩。希望您在學校裡學過這些。是以,當您使用glClearColor(0.0f,0.0f,1.0f,0.0f),您将用亮藍色來清除螢幕。如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的話,您将使用中紅色來清除螢幕。不是最亮(1.0f),也不是最暗 (0.0f)。要得到白色背景,您應該将所有的顔色設成最亮(1.0f)。要黑色背景的話,您該将所有的顔色設為最暗(0.0f)。
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// 黑色背景
接下來的三行必須做的是關于depth buffer(深度緩存)的。将深度緩存設想為螢幕後面的層。深度緩存不斷的對物體進入螢幕内部有多深進行跟蹤。我們本節的程式其實沒有真正使用深度緩存,但幾乎所有在螢幕上顯示3D場景OpenGL程式都使用深度緩存。它的排序決定那個物體先畫。這樣您就不會将一個圓形後面的正方形畫到圓形上來。深度緩存是OpenGL十分重要的部分。
接着告訴OpenGL我們希望進行最好的透視修正。這會十分輕微的影響性能。但使得透視圖看起來好一點。
最後,我們傳回TRUE。如果我們希望檢查初始化是否OK,我們可以檢視傳回的 TRUE或FALSE的值。如果有錯誤發生的話,您可以加上您自己的代碼傳回FALSE。目前,我們不管它。
return TRUE;// 初始化 OK
}
下一段包括了所有的繪圖代碼。任何您所想在螢幕上顯示的東東都将在此段代碼中出現。以後的每個教程中我都會在例程的此處增加新的代碼。如果您對OpenGL已經有所了解的話,您可以在 glLoadIdentity()調用之後,傳回TRUE值之前,試着添加一些OpenGL代碼來建立基本的形。如果您是OpenGL新手,等着我的下個教程。目前我們所作的全部就是将螢幕清除成我們前面所決定的顔色,清除深度緩存并且重置場景。我們仍沒有繪制任何東東。
傳回TRUE值告知我們的程式沒有出現問題。如果您希望程式因為某些原因而中止運作,在傳回TRUE值之前增加傳回FALSE的代碼告知我們的程式繪圖代碼出錯。程式即将退出。
int DrawGLScene(GLvoid)// 從這裡開始進行所有的繪制
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// 清除螢幕和深度緩存
glLoadIdentity();// 重置目前的模型觀察矩陣
return TRUE;// 一切 OK
下一段代碼隻在程式退出之前調用。KillGLWindow() 的作用是依次釋放着色描述表,裝置描述表和視窗句柄。我已經加入了許多錯誤檢查。如果程式無法銷毀視窗的任意部分,都會彈出帶相應錯誤消息的訊息視窗,告訴您什麼出錯了。使您在您的代碼中查錯變得更容易些。
GLvoid KillGLWindow(GLvoid)// 正常銷毀視窗
{
我們在KillGLWindow()中所作的第一件事是檢查我們是否處于全屏模式。如果是,我們要切換回桌面。我們本應在禁用全屏模式前先銷毀視窗,但在某些顯示卡上這麼做可能會使得桌面崩潰。是以我們還是先禁用全屏模式。這将防止桌面出現崩潰,并在Nvidia和3dfx顯示卡上都工作的很好!
if (fullscreen)// 我們處于全屏模式嗎?
我們使用ChangeDisplaySettings(NULL,0)回到原始桌面。将NULL作為第一個參數,0作為第二個參數傳遞強制Windows使用目前存放在系統資料庫中的值(預設的分辨率、色彩深度、重新整理頻率,等等)來有效的恢複我們的原始桌面。切換回桌面後,我們還要使得滑鼠指針重新可見。
ChangeDisplaySettings(NULL,0);// 是的話,切換回桌面
ShowCursor(TRUE); // 顯示滑鼠指針
接下來的代碼檢視我們是否擁有着色描述表(hRC)。如果沒有,程式将跳轉至後面的代碼檢視是否擁有裝置描述表。
if (hRC)// 我們擁有OpenGL渲染描述表嗎?
如果存在着色描述表的話,下面的代碼将檢視我們能否釋放它(将 hRC從hDC分開)。這裡請注意我使用的的查錯方法。基本上我隻是讓程式嘗試釋放着色描述表(通過調用wglMakeCurrent(NULL,NULL),然後我再檢視釋放是否成功。巧妙的将數行代碼結合到了一行。
if (!wglMakeCurrent(NULL,NULL))// 我們能否釋放DC和RC描述表?
如果不能釋放DC和RC描述表的話,MessageBox()将彈出錯誤消息,告知我們DC和RC無法被釋放。NULL意味着消息視窗沒有父視窗。其右的文字将在消息視窗上出現。"SHUTDOWN ERROR"出現在視窗的标題欄上。MB_OK的意思消息視窗上帶有一個寫着OK字樣的按鈕。
MB_ICONINFORMATION将在消息視窗中顯示一個帶圈的小寫的i(看上去更正式一些)。
MessageBox(NULL,"釋放DC或RC失敗。","關閉錯誤",MB_OK | MB_ICONINFORMATION);
下一步我們試着删除着色描述表。如果不成功的話彈出錯誤消息。
if (!wglDeleteContext(hRC))// 我們能否删除RC?
如果無法删除着色描述表的話,将彈出錯誤消息告知我們RC未能成功删除。然後hRC被設為NULL。
MessageBox(NULL,"釋放RC失敗。","關閉錯誤",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL;// 将RC設為 NULL
現在我們檢視是否存在裝置描述表,如果有嘗試釋放它。如果不能釋放裝置描述表将彈出錯誤消息,然後hDC設為NULL。
if (hDC && !ReleaseDC(hWnd,hDC))// 我們能否釋放 DC?
MessageBox(NULL,"釋放DC失敗。","關閉錯誤",MB_OK | MB_ICONINFORMATION);
hDC=NULL;// 将 DC 設為 NULL
現在我們來檢視是否存在視窗句柄,我們調用 DestroyWindow( hWnd )來嘗試銷毀視窗。如果不能的話彈出錯誤視窗,然後hWnd被設為NULL。
if (hWnd && !DestroyWindow(hWnd))// 能否銷毀視窗?
MessageBox(NULL,"釋放視窗句柄失敗。","關閉錯誤",MB_OK | MB_ICONINFORMATION);
hWnd=NULL;// 将 hWnd 設為 NULL
最後要做的事是登出我們的視窗類。這允許我們正常銷毀視窗,接着在打開其他視窗時,不會收到諸如"Windows Class already registered"(視窗類已注冊)的錯誤消息。
if (!UnregisterClass("OpenG",hInstance))// 能否登出類?
MessageBox(NULL,"不能登出視窗類。","關閉錯誤",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // 将 hInstance 設為 NULL
接下來的代碼段建立我們的OpenGL視窗。我花了很多時間來做決定是否建立固定的全屏模式這樣不需要許多額外的代碼,還是建立一個容易定制的友好的視窗但需要更多的代碼。當然最後我選擇了後者。我經常在EMail中收到諸如此類的問題:怎樣建立視窗而不使用全螢幕?怎樣改變視窗的标題欄?怎樣改變視窗的分辨率或pixel format(象素格式)?以下的代碼完成了所有這一切!盡管最好要學學材質,這會讓您寫自己的OpenGL程式變得容易的多!
正如您所見,此過程傳回布爾變量(TRUE 或 FALSE)。他還帶有5個參數:視窗的标題欄,視窗的寬度,視窗的高度,色彩位數(16/24/32),和全屏标志(TRUE --全屏模式, FALSE--視窗模式 )。傳回的布爾值告訴我們視窗是否成功建立。
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
當我們要求Windows為我們尋找相比對的象素格式時,Windows尋找結束後将模式值儲存在變量PixelFormat中。
GLuint PixelFormat;// 儲存查找比對的結果
wc用來儲存我們的視窗類的結構。視窗類結構中儲存着我們的視窗資訊。通過改變類的不同字段我們可以改變視窗的外觀和行為。每個視窗都屬于一個視窗類。當您建立視窗時,您必須為視窗注冊類。
WNDCLASS wc;// 視窗類結構
dwExStyle和dwStyle存放擴充和通常的視窗風格資訊。我使用變量來存放風格的目的是為了能夠根據我需要建立的視窗類型(是全螢幕下的彈出視窗還是視窗模式下的帶邊框的普通視窗);來改變視窗的風格。
DWORD dwExStyle;// 擴充視窗風格
DWORD dwStyle;// 視窗風格
下面的5行代碼取得矩形的左上角和右下角的坐标值。我們将使用這些值來調整我們的視窗使得其上的繪圖區的大小恰好是我們所需的分辨率的值。通常如果我們建立一個640x480的視窗,視窗的邊框會占掉一些分辨率的值。
RECT WindowRect;// 取得矩形的左上角和右下角的坐标值
WindowRect.left=(long)0;// 将Left設為 0
WindowRect.right=(long)width;// 将Right 設為要求的寬度
WindowRect.top=(long)0;// 将Top設為 0
WindowRect.bottom=(long)height;// 将Bottom 設為要求的高度
下一行代碼我們讓全局變量fullscreen等于fullscreenflag。如果我們希望在全螢幕下運作而将fullscreenflag設為TRUE,但沒有讓變量fullscreen等于fullscreenflag的話,fullscreen變量将保持為FALSE。當我們在全螢幕模式下銷毀視窗的時候,變量fullscreen的值卻不是正确的TRUE值,計算機将誤以為已經處于桌面模式而無法切換回桌面。上帝啊,但願這一切都有意義。就是一句話,fullscreen的值必須永遠fullscreenflag的值,否則就會有問題。
fullscreen=fullscreenflag;// 設定全局全屏标志
下一部分的代碼中,我們取得視窗的執行個體,然後定義視窗類。
CS_HREDRAW 和 CS_VREDRAW 的意思是無論何時,隻要視窗發生變化時就強制重畫。CS_OWNDC為視窗建立一個私有的DC。這意味着DC不能在程式間共享。WndProc是我們程式的消息處理過程。由于沒有使用額外的視窗資料,後兩個字段設為零。然後設定執行個體。接着我們将hIcon設為NULL,因為我們不想給視窗來個圖示。滑鼠指針設為标準的箭頭。背景色無所謂(我們在GL中設定)。我們也不想要視窗菜單,是以将其設為NULL。類的名字可以您想要的任何名字。出于簡單,我将使用"OpenG"。
hInstance = GetModuleHandle(NULL);// 取得我們視窗的執行個體
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;// 移動時重畫,并為視窗取得DC
wc.lpfnWndProc = (WNDPROC) WndProc;// WndProc處理消息
wc.cbClsExtra = 0;// 無額外視窗資料
wc.cbWndExtra = 0;// 無額外視窗資料
wc.hInstance = hInstance;// 設定執行個體
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);// 裝入預設圖示
wc.hCursor = LoadCursor(NULL, IDC_ARROW);// 裝入滑鼠指針
wc.hbrBackground = NULL;// GL不需要背景
wc.lpszMenuName = NULL;// 不需要菜單
wc.lpszClassName = "OpenG";// 設定類名字
現在注冊類名字。如果有錯誤發生,彈出錯誤消息視窗。按下上面的OK按鈕後,程式退出
if (!RegisterClass(&wc))// 嘗試注冊視窗類
MessageBox(NULL,"注冊視窗失敗","錯誤",MB_OK|MB_ICONEXCLAMATION);
return FALSE;// 退出并傳回FALSE
檢視程式應該在全屏模式還是視窗模式下運作。如果應該是全屏模式的話,我們将嘗試設定全屏模式。
if (fullscreen)// 要嘗試全屏模式嗎?
下一部分的代碼看來很多人都會有問題要問關于.......切換到全屏模式。在切換到全屏模式時,有幾件十分重要的事您必須牢記。必須確定您在全屏模式下所用的寬度和高度等同于視窗模式下的寬度和高度。最最重要的是要在建立視窗之前設定全屏模式。這裡的代碼中,您無需再擔心寬度和高度,它們已被設定成與顯示模式所對應的大小。
DEVMODE dmScreenSettings;// 裝置模式
memset(&dmScreenSettings,0,sizeof(dmScreenSettings));// 確定記憶體清空為零
dmScreenSettings.dmSize=sizeof(dmScreenSettings);// Devmode 結構的大小
dmScreenSettings.dmPelsWidth = width;// 所選螢幕寬度
dmScreenSettings.dmPelsHeight = height;// 所選螢幕高度
dmScreenSettings.dmBitsPerPel = bits;// 每象素所選的色彩深度
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
上面的代碼中,我們配置設定了用于存儲視訊設定的空間。設定了螢幕的寬,高,色彩深度。下面的代碼我們嘗試設定全屏模式。我們在dmScreenSettings中儲存了所有的寬,高,色彩深度訊息。下一行使用ChangeDisplaySettings來嘗試切換成與dmScreenSettings所比對模式。我使用參數CDS_FULLSCREEN來切換顯示模式,因為這樣做不僅移去了螢幕底部的狀态條,而且它在來回切換時,沒有移動或改變您在桌面上的視窗。
// 嘗試設定顯示模式并傳回結果。注: CDS_FULLSCREEN 移去了狀态條。
if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
如果模式未能設定成功,我們将進入以下的代碼。如果不能比對全屏模式,彈出消息視窗,提供兩個選項:在視窗模式下運作或退出。
// 若模式失敗,提供兩個選項:退出或在視窗内運作。
if (MessageBox(NULL,"全屏模式在目前顯示卡上設定失敗!\n使用視窗模式?","NeHe G",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
如果使用者選擇視窗模式,變量fullscreen 的值變為FALSE,程式繼續運作。
fullscreen=FALSE;// 選擇視窗模式(Fullscreen=FALSE)
else
如果使用者選擇退出,彈出消息視窗告知使用者程式将結束。并傳回FALSE告訴程式視窗未能成功建立。程式退出。
// 彈出一個對話框,告訴使用者程式結束
MessageBox(NULL,"程式将被關閉","錯誤",MB_OK|MB_ICONSTOP);
return FALSE;// 退出并傳回 FALSE
由于全屏模式可能失敗,使用者可能決定在視窗下運作,我們需要在設定螢幕/視窗之前,再次檢查fullscreen的值是TRUE或FALSE。
if (fullscreen)// 仍處于全屏模式嗎?
如果我們仍處于全屏模式,設定擴充窗體風格為WS_EX_APPWINDOW,這将強制我們的窗體可見時處于最前面。再将窗體的風格設為WS_POPUP。這個類型的窗體沒有邊框,使我們的全屏模式得以完美顯示。
最後我們禁用滑鼠指針。當您的程式不是互動式的時候,在全屏模式下禁用滑鼠指針通常是個好主意。
dwExStyle=WS_EX_APPWINDOW;// 擴充窗體風格
dwStyle=WS_POPUP;// 窗體風格
ShowCursor(FALSE);// 隐藏滑鼠指針
如果我們使用視窗而不是全屏模式,我們在擴充窗體風格中增加了 WS_EX_WINDOWEDGE,增強窗體的3D感觀。窗體風格改用 WS_OVERLAPPEDWINDOW,建立一個帶标題欄、可變大小的邊框、菜單和最大化/最小化按鈕的窗體。
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;// 擴充窗體風格
dwStyle=WS_OVERLAPPEDWINDOW;// 窗體風格
下一行代碼根據建立的窗體類型調整視窗。調整的目的是使得視窗大小正好等于我們要求的分辨率。通常邊框會占用視窗的一部分。使用AdjustWindowRectEx 後,我們的OpenGL場景就不會被邊框蓋住。實際上視窗變得更大以便繪制邊框。全屏模式下,此指令無效。
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);// 調整視窗達到真正要求的大小
視窗并檢查視窗是否成功建立。我們将傳遞CreateWindowEx()所需的所有參數。如擴充風格、類名字(與您在注冊視窗類時所用的名字相同)、視窗标題、窗體風格、窗體的左上角坐标(0,0 是個安全的選擇)、窗體的寬和高。我們沒有父視窗,也不想要菜單,這些參數被設為NULL。還傳遞了視窗的執行個體,最後一個參數被設為NULL。
注意我們在窗體風格中包括了 WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN。要讓OpenGL正常運作,這兩個屬性是必須的。他們阻止别的窗體在我們的窗體内/上繪圖。
if (!(hWnd=CreateWindowEx( dwExStyle,// 擴充窗體風格
"OpenG", // 類名字
title,// 視窗标題
WS_CLIPSIBLINGS | // 必須的窗體風格屬性
WS_CLIPCHILDREN |// 必須的窗體風格屬性
dwStyle,// 選擇的窗體屬性
0, 0,// 視窗位置
WindowRect.right-WindowRect.left,// 計算調整好的視窗寬度
WindowRect.bottom-WindowRect.top,// 計算調整好的視窗高度
NULL,// 無父視窗
NULL,// 無菜單
hInstance,// 執行個體
NULL)))// 不向WM_CREATE傳遞任何東西
下來我們檢檢視視窗是否正常建立。如果成功, hWnd儲存視窗的句柄。如果失敗,彈出消息視窗,并退出程式。
KillGLWindow();// 重置顯示區
MessageBox(NULL,"不能建立一個視窗裝置描述表","錯誤",MB_OK|MB_ICONEXCLAMATION);
return FALSE;// 傳回 FALSE
下面的代碼描述象素格式。我們選擇了通過RGBA(紅、綠、藍、alpha通道)支援OpenGL和雙緩存的格式。我們試圖找到比對我們標明的色彩深度(16位、24位、32位)的象素格式。最後設定16位Z-緩存。其餘的參數要麼未使用要麼不重要(stencil buffer模闆緩存和accumulation buffer聚集緩存除外)。
static PIXELFORMATDESCRIPTOR pfd= //pfd 告訴視窗我們所希望的東東,即視窗使用的像素格式
sizeof(PIXELFORMATDESCRIPTOR),//上述格式描述符的大小
1,// 版本号
PFD_DRAW_TO_WINDOW |// 格式支援視窗
PFD_SUPPORT_OPENGL |// 格式必須支援OpenGL
PFD_DOUBLEBUFFER,// 必須支援雙緩沖
PFD_TYPE_RGBA,// 申請 RGBA 格式
bits,// 標明色彩深度
0, 0, 0, 0, 0, 0,// 忽略的色彩位
0,// 無Alpha緩存
0,// 忽略Shift Bit
0,// 無累加緩存
0, 0, 0, 0,// 忽略聚集位
16,// 16位 Z-緩存 (深度緩存)
0,// 無蒙闆緩存
0,// 無輔助緩存
PFD_MAIN_PLANE,// 主繪圖層
0,// Reserved
0, 0, 0// 忽略層遮罩
};
如果前面建立視窗時沒有錯誤發生,我們接着嘗試取得OpenGL裝置描述表。若無法取得DC,彈出錯誤消息程式退出(傳回FALSE)。
if (!(hDC=GetDC(hWnd)))// 取得裝置描述表了麼?
MessageBox(NULL,"不能建立一種相比對的像素格式","錯誤",MB_OK|MB_ICONEXCLAMATION);
設法為OpenGL視窗取得裝置描述表後,我們嘗試找到對應與此前我們標明的象素格式的象素格式。如果Windows不能找到的話,彈出錯誤消息,并退出程式(傳回FALSE)。
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))// Windows 找到相應的象素格式了嗎?
MessageBox(NULL,"不能設定像素格式","錯誤",MB_OK|MB_ICONEXCLAMATION);
Windows 找到相應的象素格式後,嘗試設定象素格式。如果無法設定,彈出錯誤消息,并退出程式(傳回FALSE)。
if(!SetPixelFormat(hDC,PixelFormat,&pfd))// 能夠設定象素格式麼?
正常設定象素格式後,嘗試取得着色描述表。如果不能取得着色描述表的話,彈出錯誤消息,并退出程式(傳回FALSE)。
if (!(hRC=wglCreateContext(hDC)))// 能否取得着色描述表?
MessageBox(NULL,"不能建立OpenGL渲染描述表","錯誤",MB_OK|MB_ICONEXCLAMATION);
如果到現在仍未出現錯誤的話,我們已經設法取得了裝置描述表和着色描述表。接着要做的是激活着色描述表。如果無法激活,彈出錯誤消息,并退出程式(傳回FALSE)。
if(!wglMakeCurrent(hDC,hRC)) // 嘗試激活着色描述表
MessageBox(NULL,"不能激活目前的OpenGL渲然描述表","錯誤",MB_OK|MB_ICONEXCLAMATION);
一切順利的話,OpenGL視窗已經建立完成,接着可以顯示它啦。将它設為前端視窗(給它更高的優先級),并将焦點移至此視窗。然後調用ReSizeGLScene 将螢幕的寬度和高度設定給透視OpenGL螢幕。
ShowWindow(hWnd,SW_SHOW);// 顯示視窗
SetForegroundWindow(hWnd);// 略略提高優先級
SetFocus(hWnd);// 設定鍵盤的焦點至此視窗
ReSizeGLScene(width, height);// 設定透視 GL 螢幕
跳轉至 InitGL(),這裡可以設定光照、紋理、等等任何需要設定的東東。您可以在 InitGL()内部自行定義錯誤檢查,并傳回 TRUE (一切正常)或FALSE (有什麼不對)。例如,如果您在InitGL()内裝載紋理并出現錯誤,您可能希望程式停止。如果您傳回 FALSE的話,下面的代碼會彈出錯誤消息,并退出程式。
if (!InitGL())// 初始化建立的GL視窗
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
到這裡可以安全的推定建立視窗已經成功了。我們向WinMain()傳回TRUE,告知WinMain()沒有錯誤,以防止程式退出。
return TRUE;// 成功
下面的代碼處理所有的視窗消息。當我們注冊好視窗類之後,程式跳轉到這部分代碼處理視窗消息。
LRESULT CALLBACK WndProc( HWND hWnd, // 視窗的句柄
UINT uMsg, // 視窗的消息
WPARAM wParam, // 附加的消息内容
LPARAM lParam) // 附加的消息内容
下面的代碼比對uMsg的值,然後轉入case處理,uMsg 中儲存了我們要處理的消息名字。
switch (uMsg) // 檢查Windows消息
如果uMsg等于WM_ACTIVE,檢視視窗是否仍然處于激活狀态。如果視窗已被最小化,将變量active設為FALSE。如果視窗已被激活,變量active的值為TRUE。
case WM_ACTIVATE:// 監視視窗激活消息
if (!HIWORD(wParam))// 檢查最小化狀态
{
active=TRUE;// 程式處于激活狀态
}
else
active=FALSE; // 程式不再激活
return 0; // 傳回消息循環
如果消息是WM_SYSCOMMAND(系統指令),再次比對wParam。如果wParam 是 SC_SCREENSAVE 或 SC_MONITORPOWER的話,不是有螢幕保護要運作,就是顯示器想進入節電模式。傳回0可以阻止這兩件事發生。
case WM_SYSCOMMAND: // 系統中斷指令
switch (wParam) // 檢查系統調用
{
case SC_SCREENSAVE: // 屏保要運作?
case SC_MONITORPOWER: // 顯示器要進入節電模式?
return 0; // 阻止發生
}
break; // 退出
如果 uMsg是WM_CLOSE,視窗将被關閉。我們發出退出消息,主循環将被中斷。變量done被設為TRUE,WinMain()的主循環中止,程式關閉。
case WM_CLOSE: // 收到Close消息?
PostQuitMessage(0); // 發出退出消息
return 0; // 傳回
如果鍵盤有鍵按下,通過讀取wParam的資訊可以找出鍵值。我将鍵盤數組keys[ ]相應的數組組成員的值設為TRUE。這樣以後就可以查找key[ ]來得知什麼鍵被按下。允許同時按下多個鍵。
case WM_KEYDOWN: // 有鍵按下麼?
keys[wParam] = TRUE; // 如果是,設為TRUE
同樣,如果鍵盤有鍵釋放,通過讀取wParam的資訊可以找出鍵值。然後将鍵盤數組keys[ ]相應的數組組成員的值設為FALSE。這樣查找key[ ]來得知什麼鍵被按下,什麼鍵被釋放了。鍵盤上的每個鍵都可以用0-255之間的一個數來代表。舉例來說,當我們按下40所代表的鍵時,keys[40]的值将被設為TRUE。放開的話,它就被設為FALSE。這也是key數組的原理。
case WM_KEYUP:// 有鍵放開麼?
keys[wParam] = FALSE; // 如果是,設為FALSE
return 0;// 傳回
當調整視窗時,uMsg 最後等于消息WM_SIZE。讀取lParam的LOWORD 和HIWORD可以得到視窗新的寬度和高度。将他們傳遞給ReSizeGLScene(),OpenGL場景将調整為新的寬度和高度。
case WM_SIZE: // 調整OpenGL視窗大小
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Width,HiWord=Height
}
其餘無關的消息被傳遞給DefWindowProc,讓Windows自行處理。
// 向 DefWindowProc傳遞所有未處理的消息。
return DefWindowProc(hWnd,uMsg,wParam,lParam);
下面是我們的Windows程式的入口。将會調用視窗建立例程,處理視窗消息,并監視人機互動。
int WINAPI WinMain( HINSTANCE hInstance, // 目前視窗執行個體
HINSTANCE hPrevInstance, // 前一個視窗執行個體
LPSTR lpCmdLine, // 指令行參數
int nCmdShow) // 視窗顯示狀态
我們設定兩個變量。msg 用來檢查是否有消息等待處理。done的初始值設為FALSE。這意味着我們的程式仍未完成運作。隻要程式done保持FALSE,程式繼續運作。一旦done的值改變為TRUE,程式退出。
MSG msg; // Windowsx消息結構
BOOL done=FALSE; // 用來退出循環的Bool 變量
這段代碼完全可選。程式彈出一個消息視窗,詢問使用者是否希望在全屏模式下運作。如果使用者單擊NO按鈕,fullscreen變量從預設的TRUE改變為FALSE,程式也改在視窗模式下運作。
// 提示使用者選擇運作模式
if (MessageBox(NULL,"你想在全屏模式下運作麼?", "設定全屏模式",MB_YESNO|MB_ICONQUESTION)==IDNO)
fullscreen=FALSE; // FALSE為視窗模式
接着建立OpenGL視窗。CreateGLWindow函數的參數依次為标題、寬度、高度、色彩深度,以及全屏标志。就這麼簡單!我很欣賞這段代碼的簡潔。如果未能建立成功,函數傳回FALSE。程式立即退出。
// 建立OpenGL視窗
if (!CreateGLWindow("NeHe's OpenGL程式架構",640,480,16,fullscreen))
return 0; // 失敗退出
下面是循環的開始。隻要done保持FALSE,循環一直進行。
while(!done) // 保持循環直到 done=TRUE
我們要做的第一件事是檢查是否有消息在等待。使用PeekMessage()可以在不鎖住我們的程式的前提下對消息進行檢查。許多程式使用GetMessage(),也可以很好的工作。但使用GetMessage(),程式在收到paint消息或其他别的什麼視窗消息之前不會做任何事。
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // 有消息在等待嗎?
{
下面的代碼檢視是否出現退出消息。如果目前的消息是由PostQuitMessage(0)引起的WM_QUIT,done變量被設為TRUE,程式将退出。
if (msg.message==WM_QUIT) // 收到退出消息?
done=TRUE; // 是,則done=TRUE
else // 不是,處理視窗消息
如果不是退出消息,我們翻譯消息,然後發送消息,使得WndProc() 或 Windows能夠處理他們。
TranslateMessage(&msg); // 翻譯消息
DispatchMessage(&msg); // 發送消息
else // 如果沒有消息
如果沒有消息,繪制我們的OpenGL場景。代碼的第一行檢視視窗是否激活。如果按下ESC鍵,done變量被設為TRUE,程式将會退出。
// 繪制場景。監視ESC鍵和來自DrawGLScene()的退出消息
if (active) // 程式激活的麼?
if (keys[VK_ESCAPE]) // ESC 按下了麼?
done=TRUE; // ESC 發出退出信号
else // 不是退出的時候,重新整理螢幕
{
如果程式是激活的且ESC沒有按下,我們繪制場景并交換緩存(使用雙緩存可以實作無閃爍的動畫)。我們實際上在另一個看不見的"螢幕"上繪圖。當我們交換緩存後,我們目前的螢幕被隐藏,現在看到的是剛才看不到的螢幕。這也是我們看不到場景繪制過程的原因。場景隻是即時顯示。
DrawGLScene(); // 繪制場景
SwapBuffers(hDC); // 交換緩存 (雙緩存)
下面的一點代碼是最近新加的(05-01-00)。允許使用者按下F1鍵在全屏模式和視窗模式間切換。
if (keys[VK_F1]) // F1鍵按下了麼?
{
keys[VK_F1]=FALSE; // 若是,使對應的Key數組中的值為 FALSE
KillGLWindow(); // 銷毀目前的視窗
fullscreen=!fullscreen; // 切換 全屏 / 視窗 模式
// 重建 OpenGL 視窗
if (!CreateGLWindow("NeHe's OpenGL 程式架構",640,480,16,fullscreen))
return 0; // 如果視窗未能建立,程式退出
}
如果done變量不再是FALSE,程式退出。正常銷毀OpenGL視窗,将所有的記憶體釋放,退出程式。
// 關閉程式
KillGLWindow(); // 銷毀視窗
return (msg.wParam); // 退出程式
原文及其個版本源代碼下載下傳:
<a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=01">http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=01</a>
沒有整理與歸納的知識,一文不值!高度概括與梳理的知識,才是自己真正的知識與技能。 永遠不要讓自己的自由、好奇、充滿創造力的想法被現實的架構所束縛,讓創造力自由成長吧! 多花時間,關心他(她)人,正如别人所關心你的。理想的騰飛與實作,沒有别人的支援與幫助,是萬萬不能的。
本文轉自wenglabs部落格園部落格,原文連結:http://www.cnblogs.com/arxive/p/6238969.html,如需轉載請自行聯系原作者