1. 程式人生 > >NeHe OpenGL教程第一課 建立一個OpenGL視窗(Wiin32)

NeHe OpenGL教程第一課 建立一個OpenGL視窗(Wiin32)

(以下內容為我個人按照NeHe英文教程原文,以及一些中文資料做的翻譯,由於自己的英語水平有限,可能有些地方翻譯的不是很正確,歡迎指正,共同提高,希望以下文件能夠給你一些幫助。

注:以下程式碼均是在Visual c++ 6.0下編寫的,如果希望程式碼成功編譯通過,請在Visual c++ 6.0下除錯程式碼。

以下程式碼已在Visual c++ 6.0下成功編譯執行。如果在編譯過程中出現錯誤,請仔細檢查是否某處書寫不正確。或者到NeHe的Tutorial網站中下載所需程式碼。

以下所有資料的版權為NeHe所有,如需轉載或使用,請標註原始碼出處。)

在這個教程裡,我將教你在Windows環境中建立一個OpenGL程式。它顯示一個空的OpenGL視窗,可以在視窗和全屏模式下切換,按ESC退出。它是我們以後應用程式的框架。

在這裡我將教你如何設定一個OpenGL視窗。它可以是一個視窗,或者是全屏,它可以是你想要的任意大小,任意的解析度,任意的色彩深度。這部分程式碼是很靈活的,可以在你的所有的OpenGL工程中使用。所有的教程都是基於這部分程式碼的。這部分程式碼不僅很靈活,而且健壯性很好。所有的錯誤都可以提出。(All errors are reported.原文,可能我翻譯的不是很準確。)這些程式碼沒有記憶體洩漏的問題,而且這些程式碼非常容易閱讀和修改。

現在我們就直接從程式碼開始吧。首先,你應該在Visual C++中建立一個工程。如果你不知道怎麼做,那你因該先學習如何使用Visual C++,然後再學習OpenGL。可以下載的程式碼是Visual C++ 6.0的程式碼。一些Visual C++的版本需要使用BOOL代替bool,使用TRUE代替true,使用FALSE代替false。按照上面提到的做相應的替換,你可以在Visual C++ 4.0 和Visual C++ 5.0中成功地編譯程式碼。

在Visual C++ 6.0中建立一個新的Win32 Application (而不是一個 console application) 之後,你需要連結OpenGL的libraries。在Visual C++ 6.0中,選擇Project->Setting,然後點選LINK選項卡。在"Object/Library Modules" 選項的最開始(在kernel32.lib之前),新增OpenGL32.lib 、GLu32.lib 和 GLaux.lib.。然後點選OK。現在你可以準備編寫一個OpenGL視窗程式了。

注 #1:很多的編譯器沒有定義CDS_FULLSCREEN。如果你的程式遇到一個和CDS_FULLSCREEN有關的錯誤,你需要在你的程式的上面新增 #define CDS_FULLSCREEN 4這段程式碼。

注 #2:過去寫的教程中,GLAUX是可以使用的。現在不支援GLAUX了。這個網站上的很多教程還在使用舊的GLAUX程式碼。如果你的編譯器不支援GLAUX或者你不怎麼想使用它,在主頁中下載 GLAUX REPLACEMENT CODE 。

程式碼的前四行包含了我們使用的庫的標頭檔案。如下所示:

#include <windows.h>  // Windows標頭檔案 #include <gl\gl.h>    // OpenGL32庫標頭檔案 #include <gl\glu.h>   // GLu32庫標頭檔案 #include <gl\glaux.h> // GLaux庫標頭檔案 接下來你需要定義所有你想要在你的程式中使用的變數。這個程式只是建立一個空白的OpenGL視窗,所以我們現在不需要設定太多的變數。我們下面定義的這幾個變數是非常重要的,在你的所有的OpenGL程式中都會使用到。

第一行定義了一個渲染用到的上下文(Rendering Context)。每一個OpenGL程式都會連結到這個渲染上下文。一個渲染上下文就是把一個OpenGL呼叫連結到一個裝置上下文(Device Context)。渲染上下文定義為hRC變數。你的程式需要定義一個裝置上下文來畫一個視窗,第二行定義了這樣一個變數。這個Windows裝置上下文定義為hDC。hDC變數把Window連線到GDI(Graphics Device Interface)。hRC把OpenGL連線到hDC。

第三行的變數hWnd通過Windows系統獲得分配到我們的OpenGL視窗的控制代碼,最後,在第四行的變數建立了一個我們程式的例項。

HGLRC      hRC=NULL;         // 永久的渲染上下文( Rendering Context) HDC                hDC=NULL;         // 私有的GDI裝置上下文( GDI Device Context) HWND              hWnd=NULL;        // 獲得我們視窗的控制代碼 HINSTANCE   hInstance;        // 獲得應用程式的例項 下面的第一行定義了一個用來監視鍵盤點選事件的陣列。有很多的方法可以獲得鍵盤的點選事件,我使用這種方法。這種方法很可靠,它可以同時處理多個點選事件。

active變數用來通知我們的程式我們的OpenGL視窗是否已經最小化到工作列了。如果我們的OpenGL已經最小化了,我們可以做從暫停到退出程式的任何事情。我比較喜歡暫停程式。在這種情況下,當我們的OpenGL視窗最小化時,我們的程式不會在後臺執行。

fullscreen變數相當的明顯。(感覺翻譯不太通順,原文:The variable fullscreen is fairly obvious. )。如果我們的程式執行在全屏模式下,fullscreen變數為TRUE,如果我的程式執行在視窗模式下,fullscreen變數為FALSE。定義這個全域性變數非常重要,它使得每一個步驟都知道程式是否執行在全屏模式下。

bool   keys[256];              // 用於鍵盤行為的陣列 bool   active=TRUE;            // 視窗活動標記,預設設定為TRUE
bool   fullscreen=TRUE;        // 全屏標記,預設設定為全屏
現在我們要宣告 WndProc()函式。之所以這麼做是因為CreateGLWindow() 函式需要呼叫WndProc() 函式,但是WndProc() 函式是在CreateGLWindow()函式之後定義的。在C語言中如果我們想要訪問一個出現在當前程式碼段之後的程式或者是一段程式碼,我們必須在我們的程式開始對這部分程式碼進行宣告。 所以下面的這行宣告WndProc() 函式的程式碼使得CreateGLWindow() 函式能夠訪問 WndProc()函式。
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);//宣告WndProc()函式 下面這部分程式碼的作用是無論何時視窗(假設你使用視窗模式而不是全屏模式)的尺寸改變時重新設定你的OpenGL場景的尺寸。即使你不能改變你的視窗的大小(例如,你執行在全屏模式下),這段程式碼在你第一次執行程式來設定你的透視檢視(原文:perspective view)時至少也會被呼叫一次。OpenGL場景會依據你的顯示視窗的寬和高來重新設定它的尺寸。 GLvoid ReSizeGLScene(GLsizei width, GLsizei height)// 初始化和設定OpenGL視窗
{ if(height==0)                          // 防止被0除 { height=1;                          // 把height設定為1
} glViewport(0, 0, width, height);       // 重新設定當前的檢視視窗
glMatrixMode(GL_PROJECTION) 下面的程式碼把螢幕設定為透視檢視。意味著遠處的物體要比近處的物體小。它建立了一個更逼真的場景。根據視窗的寬和高,用45度的視角來計算透視。0.1f和100.0f是我們可以在螢幕上繪製的深度的起始點和終止點。

glMatrixMode(GL_PROJECTION)函式表示下面的兩行程式碼將會影響投影矩陣(projection matrix)。投影矩陣負責新增透視到我們的場景中。glLoadIdentity()函式可以簡單地完成重置功能。它重新儲存我們選擇的矩陣到它的原始狀態。呼叫glLoadIdentity()函式之後,我們把透視檢視新增到我們的場景中。glMatrixMode(GL_MODELVIEW)函式表示任何新的改變都將影響模型檢視矩陣。模型檢視矩陣是我們的物件資訊儲存的地方。最後,我們重置了模型檢視矩陣。如果你不理解這些也沒關係。在後面的教程中將會對這些做出解釋。只需要知道如果你想得到一個更好的透視場景,這些工作是必需的。

    glMatrixMode(GL_PROJECTION); //選擇投影矩陣

glLoadIdentity();   //重置投影矩陣 //計算視窗的比率 gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f); glMatrixMode(GL_MODELVIEW); //選擇模型檢視矩陣 glLoadIdentity();    //重設模型檢視矩陣 }

所有有關OpenGL的設定由下面的這部分程式碼完成。我們設定用什麼顏色來清空螢幕,開啟深度緩衝區,啟用平滑陰影(原文: smooth shading)等等。這段程式直到OpenGL視窗建立時才會呼叫。這段程式會有一個返回值,但是因為我們的初始化並不複雜,所以暫且不用考慮這個返回值。

int InitGL(GLvoid)//在這裡做所有有關OpenGL的設定 { 下面這行程式碼用來啟用平滑陰影。平滑陰影能夠在一個多邊形上出色地實現顏色過度,和光照過度。(原文:Smooth shading blends colors nicely across a polygon, and smoothes out lighting.)我們將在後面的教程中對這部分內容做更詳細的解釋。 glShadeModel(GL_SMOOTH);                       //啟用平滑陰影 接下來的程式碼設定了清屏的顏色。如果你不是很瞭解這些顏色有什麼用處,我會很快做出解釋。這些顏色值的範圍是從0.0f到1.0f,從最暗到最亮。glClearColor函式的第一個引數是紅色的強度,第二個引數是綠色的強度,第三個引數是藍色的強度。這個值越接近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.5f);   //黑色背景 接下來的三行程式碼是為了處理深度快取。可以把深度快取想象為螢幕裡面的層。深度快取能跟蹤物體在螢幕裡的深度。我們在這一個教程中不會用到深度快取,但是幾乎每一個在螢幕上繪製3D圖形的OpenGL程式都會用到深度快取。它用來排列哪一個物件先繪製,這樣你就不會把一個圓形後面的一個正方形繪製到圓形上來。深度快取是OpenGL中非常重要的組成部分。 glClearDepth(1.0f);    //設定深度快取 glEnable(GL_DEPTH_TEST);  //啟用深度測試 glDepthFunc(GL_LEQUAL); //深度測試的型別 下面我們通知OpenGL,我們想要進行最好的透視修正。這會或多或少地影響一些效能,但是會讓透視檢視看起來更好一點。 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);   //出色的透視計算 最後,函式返回TRUE。如果我們想測試是否初始化正常進行了,我們可以檢查函式返回的是TRUE還是FALSE。如果有錯誤發生,你可以自己新增返回FALSE的程式碼。現在我們不需要考慮這個。

return TRUE;//初始化成功

} 下一段是你所有的繪圖程式碼部分。任何你想要在螢幕上顯示出來的效果都在這部分程式碼裡實現。後面的教程會在程式的這部分加入一些新的程式碼。如果你已經理解了OpenGL的相關知識,在可以嘗試在glLoadIdentity()函式和return TURE語句之間加入一些程式碼來繪製一些基本的圖形。如果你還是一個OpenGL初學者,可以在以後的教程中完成這些。現在我們將用我們開始設定的顏色來清屏,清除深度快取,然後重新設定場景。我們暫時不會繪製任何圖形。

return TRUE語句通知我們的程式這裡沒有錯誤。如果你因為某種原因想要程式停止,在return TRUE語句之前的某個位置新增一個return FALSE語句,通知我們的程式繪製過程失敗。程式這時會退出。

int DrawGLScene(GLvoid) //我們繪製圖形的地方 { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除顏色和深度快取 glLoadIdentity(); //重置模型檢視矩陣 return TRUE;// 所有的程式碼正確執行
}

下面這部分程式碼是在程式退出之前呼叫的。KillGLWindow()函式的作用是釋放渲染上下文,裝置上下文,最後釋放視窗控制代碼。我加入了很多的錯誤檢查。如果程式不能銷燬視窗的任何部分,一個錯誤資訊的訊息對話方塊將會彈出,告訴你哪裡出錯了。它使得在你的程式碼中查詢錯誤更加容易。 GLvoid KillGLWindow(GLvoid)   //徹底關閉視窗 { KillGLWindow()函式中首先要做的是檢查我們的視窗是否為全屏模式。如果是,我們將切換回桌面。我們應該在禁用全屏模式前銷燬視窗,但是在某些顯示卡上如果我們在禁用全屏模式前銷燬視窗,桌面會崩潰。所以我們首先禁用全屏模式。這樣可以防止桌面崩潰,而且在Nvidia和3dfx顯示卡上效果很好。 if(fullscreen)    //判斷是否為全屏模式 { 我們使用 ChangeDisplaySettings(NULL,0)函式返回原始桌面。傳遞NULL作為第一個引數,0作為第二個引數強制windows系統使用當前儲存在windows登錄檔中的值(預設的解析度,色彩深度,重新整理率等等)有效地恢復到原始桌面。我們切換回原始桌面後,顯示滑鼠。

ChangeDisplaySettings(NULL,0);  //如果切換回桌面

ShowCursor(TRUE);   //顯示滑鼠 } 下面的程式碼檢查是我們是否獲得了一個渲染上下文(hRC)。如果我們還沒有獲得,程式會跳轉到下面檢查我們是否獲得了一個裝置上下文的程式碼段。 if(hRC)  //我們是否獲得了一個渲染上下文 { 如果我們已經獲得了渲染上下文,下面的程式碼會檢查是否我們可以釋放它(將hRC從hDC分開),注意,我檢查錯誤的方式。基本上我只是通知程式嘗試去釋放它(利用wglMakeCurrent(NULL,NULL)函式),然後我檢查是否釋放了。非常完美地將幾行程式碼合併成一行。 if(!wglMakeCurrent(NULL,NULL))    //是否我們可以釋放渲染和裝置上下文 { 如果我們不能釋放渲染和裝置上下文,會有一個錯誤訊息的訊息框彈出,通知我們渲染和裝置上下文不能釋放。NULL意味著訊息框沒有父視窗。NULL右邊的文字將在訊息框中顯示。"SHUTDOWN ERROR"是訊息框標題欄的文字。MB_OK意味著我們希望訊息框有一個"OK"按鈕。MB_ICONINFORMATION是讓訊息框裡顯示一個帶圓圈的小寫的i(看上去更正式一些)。

MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

} 然後我們嘗試刪除渲染上下文。如果不成功,一個錯誤訊息框將會彈出。 if(!wglDeleteContext(hRC))  //我們能否刪除渲染上下文 { 如果我們不能刪除渲染上下文,下面的程式碼會彈出一個錯誤訊息框通知我們刪除渲染上下文失敗。hRC將被設定為NULL。

MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

} hRC=NULL;   // hRC設定為NULL
} 然後我們檢查是否我們的程式已經獲得了一個裝置上下文,如果獲得了,我們嘗試去釋放它。如果我們不能釋放裝置上下文,一個錯誤訊息框將會彈出hDC將被設定為NULL。
if(hDC && !ReleaseDC(hWnd,hDC))  //是否能夠釋放hDC { MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hDC=NULL;  //hDC設定為NULL } 然後我們檢查是否有一個視窗控制代碼,如果有,我們嘗試用DestroyWindow(hWnd)函式來銷燬視窗。如果我們不能銷燬視窗,一個錯誤訊息框將會彈出,hWnd將被設定為NULL。 if(hWnd && !DestroyWindow(hWnd))   //是否能夠銷燬視窗 { MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hWnd=NULL;    //hWnd設定為NULL } 最後,取消視窗類的註冊資訊。這使得我們可以正確地殺死視窗,接著開啟其他視窗時不會出現"Windows Class already registered"的錯誤訊息框。

if(!UnregisterClass("OpenGL",hInstance))   //能否取消註冊資訊

{ MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION); hInstance=NULL;    // hInstance設定為NULL } }

下面的程式碼是建立我們的OpenGL視窗。我花了很長時間考慮是否應該建立一個不需要很多額外程式碼的固定的全屏視窗。最後我還是決定用更多的程式碼來建立一個使用者友好的視窗,這應該是最好的選擇。我一直在郵件裡詢問下面的問題:我怎樣建立一個不使用全屏模式的視窗?我應該怎樣改變視窗的標題?我應該怎麼樣改變視窗的解析度或者是畫素格式?下面的程式碼做了這些工作。因此,最好學習一些材質方面的知識,這對於你寫一個自己的OpenGL程式會更加容易些。

這個函式返回一個BOOL(TRUE or FALSE)型別值,它有5個形參:視窗的標題,視窗的寬度,視窗的高度,顏色深度(原文:bits (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;                      // 設定全域性變數fullscreen

下面的這個程式碼段,我們獲得了一個我們視窗的例項,然後我們宣告視窗類。CS_HREDRAW 和 CS_VREDRAW風格會在視窗的尺寸方法變化時強制重新繪製視窗。CS_OWNDC 為視窗建立一個私有的DC。意思是說DC在程式中不是共享的。WndProc 是在我們的程式中的訊息處理程式。沒有額外的視窗資料可用,所以我們把這兩個欄位設定為0。然後我們設定例項。接下來我們把 hIcon 設定為NULL,我們不想在視窗中有一個ICON,我們使用標準的滑鼠箭頭。我們不需要關心背景顏色(我們在GL中設定了)。我們在視窗中不想要一個選單,所以我們把它設定為NULL,你可以為這個類設定任何名字。為了簡便,我使用"OpenGL"。

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);          // 載入預設的ICON

wc.hCursor      = LoadCursor(NULL, IDC_ARROW);          // 載入滑鼠箭頭

wc.hbrBackground    = NULL;        // 不需要為GL設定背景顏色

wc.lpszMenuName     = NULL;       // 不需要選單

wc.lpszClassName    = "OpenGL";      // 設定類名

現在我們註冊類。如果出現錯誤,一個錯誤訊息框會彈出。點選訊息框的OK按鈕退出程式。

if (!RegisterClass(&wc))                        // 嘗試註冊視窗類

{
    MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
    return FALSE;                           // 退出,返回FALSE

}

現在我們檢查程式是應該執行在全屏模式還是視窗模式。如果應該執行在全屏模式,我們嘗試去設定全屏模式。

if (fullscreen)                             // 嘗試設定全屏模式

{

下一部分用來切換到全屏模式的程式碼很多人會有很多疑問。這裡有一些你應該牢記的用來切換到全屏模式的重要的東西。確認你在全屏模式下使用的寬和高和你想要的視窗的一樣,更重要的是在你建立你的視窗之前設定全屏模式。在這部分程式碼中,你不需要擔心寬和高,全屏和視窗模式都會設定為需要的尺寸。

DEVMODE dmScreenSettings;           // 裝置模式

memset(&dmScreenSettings,0,sizeof(dmScreenSettings));       // 確保記憶體清空為零

dmScreenSettings.dmSize=sizeof(dmScreenSettings);       // 裝置模式結構的尺寸

dmScreenSettings.dmPelsWidth    = width;        // 選擇螢幕寬頻

dmScreenSettings.dmPelsHeight   = height;      // 選擇螢幕高度

dmScreenSettings.dmBitsPerPel   = bits;      // 選擇每個畫素的色彩深度(原文:Selected Bits Per Pixel )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,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{

如果使用者決定使用視窗模式,變數fullscreen設定為FALSE,程式繼續執行。

    fullscreen=FALSE;      // 選擇視窗模式 (Fullscreen=FALSE)
}
else
{

如果使用者決定退出程式,一個提示使用者程式將要關閉的訊息框彈出。返回FALSE通知我們的程式視窗建立失敗。然後程式退出。

 //彈出一個提示使用者程式將要關閉的訊息框           

 MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
            return FALSE;                   // 退出,返回FALSE

        }
    }
}

因為上面的全屏模式可能失敗,使用者可能決定在視窗模式下執行程式,在我們設定螢幕/視窗型別前再檢查一次我們的程式是執行在全屏模式下,還是視窗模式下。

if (fullscreen)                             // 是否為全屏模式

{

如果執行在全屏模式下,設定擴充套件風格為WS_EX_APPWINDOW,一旦我們的視窗可見,所有螢幕上的視窗都會強制最小化到工作列。對於視窗風格,我們將建立一個WS_POPUP 風格的視窗。這種型別的視窗沒有邊框,在全屏模式下效果很好。

最後,我們禁用滑鼠箭頭。如果我們的程式沒有互動,在全屏模式下禁用滑鼠效果很好。這由你決定。

    dwExStyle=WS_EX_APPWINDOW;                  // 視窗擴充套件風格

    dwStyle=WS_POPUP;                       // 視窗風格

    ShowCursor(FALSE);                      // 隱藏滑鼠箭頭

}
else
{

如果是在視窗模式下,增加WS_EX_WINDOWEDGE 引數到擴充套件模式。這將增強視窗的3D效果。視窗風格我們使用WS_OVERLAPPEDWINDOW 代替WS_POPUP。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 風格在我們的視窗風格中。WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN對於OpenGL的正確執行都是必須的。這些風格防止其他的視窗在我們的OpenGL視窗上面和裡面繪圖。

if (!(hWnd=CreateWindowEx(  dwExStyle,              // 擴充套件風格

                "OpenGL",               // 類名

                title,                  // 視窗標題

                WS_CLIPSIBLINGS |           // 視窗風格

                WS_CLIPCHILDREN |           // 視窗風格

                dwStyle,                // 選擇視窗風格

                0, 0,                   // 視窗的左上角座標

                WindowRect.right-WindowRect.left,   // 視窗的寬

                WindowRect.bottom-WindowRect.top,   // 視窗的高             

                NULL,                   // 不需要一個父視窗

                NULL,                   // 沒有選單

                hInstance,              // 視窗例項 
                NULL)))                 // WM_CREATE為NULL

然後我們檢查是否我們的視窗是否正確地建立了。如果我們的視窗建立成功,hWnd 獲得視窗的控制代碼。如果視窗沒有建立成功,下面的程式碼會彈出一個錯誤訊息框,然後程式退出。

{
    KillGLWindow();                         // 重置顯示區域

    MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
    return FALSE;                           // 返回 FALSE
}

下面這部分程式碼描述了一個畫素格式。我們選擇了通過RGBA(紅、綠、藍、alpha通道)支援OpenGL和雙快取的格式。我們試圖找到匹配我們選定的色彩深度(16位、24位、32位)的象素格式。最後我們設定16位的Z-Buffer。剩下的引數或者沒有使用,或者不太重要(模板快取和累積快取除外。原文:aside from the stencil buffer and the (slow) accumulation buffer)。

static  PIXELFORMATDESCRIPTOR pfd=  // pfd 通知視窗我們想要的效果(pfd Tells Windows How We

                                                                    // Want Things To Be ) 括號內為原文,下同。

{
    sizeof(PIXELFORMATDESCRIPTOR),       // 畫素格式描述器的尺寸(Size Of This Pixel Format Descriptor )

    1,                                               // 版本號(Version Number )

    PFD_DRAW_TO_WINDOW |        // 格式必須支援視窗(Format Must Support Window )

    PFD_SUPPORT_OPENGL |       // 格式必須支援OpenGL(Format Must Support OpenGL )

    PFD_DOUBLEBUFFER,        // 必須支援雙重快取(Must Support Double Buffering )

    PFD_TYPE_RGBA,        // 一個RGBA格式(Request An RGBA Format )

    bits,                 // 選擇我們的色彩位(Select Our Color Depth )

    0, 0, 0, 0, 0, 0,        // 忽略的色彩位(Color Bits Ignored )

    0,              // 沒有Alpha快取(No Alpha Buffer )

    0,          // 忽略切換色彩位   (Shift Bit Ignored )

    0,            // 沒有累積快取(No Accumulation Buffer )

    0, 0, 0, 0,     // 忽略累積色彩位(Accumulation Bits Ignored )

    16,         // 16位Z-Buffer (16Bit Z-Buffer (Depth Buffer) )
    0,              // 沒有模板快取(No Stencil Buffer)
    0,          // 沒有輔助快取(No Auxiliary Buffer)
    PFD_MAIN_PLANE,            // 主繪圖層(Main Drawing Layer )
    0,          // 儲存(Reserved )
    0, 0, 0          //忽略層遮罩()Layer Masks Ignored
};

如果建立視窗時沒有錯誤發生,我們將嘗試去獲得一個OpenGL裝置上下文。如果我們不能獲得一個裝置上下文,一個錯誤訊息框將彈出,然後程式退出(返回FALSE)。

if (!(hDC=GetDC(hWnd)))      // 能否獲得裝置上下文

{
    KillGLWindow();         // 重置顯示區域

    MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
    return FALSE;          // 返回FALSE
}

如果我們成功獲得了一個OpenGL視窗的裝置上下文,我們將嘗試獲得一個和上面描述的匹配的畫素格式。如果Windows系統無法獲得一個匹配的畫素格式,一個錯誤訊息框將彈出,然後程式退出(返回FALSE)。

if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))             // Windows系統能夠獲得匹配的畫素格式

{
    KillGLWi