龍雲堯個人博客,轉載請註明出處。
CSDN地址:http://blog.csdn.net/michael753951/article/details/71316132
個人blog地址:http://yaoyl.cn/nehexue-xi-bi-ji-wu/
這次我們將嘗試Lesson6和Lesson7的內容。這個部分我們將學習怎麼給一個模型進行紋理映射(其實就是貼圖)。
環境搭建
這次實驗因爲需要使用OpenGL的glaux.h庫頭使用位圖對構建的圖形進行紋理映射。所以我們需要進一步進行環境搭建。(注:環境搭建很麻煩,因爲微軟的VS環境很亂)
如何佈置這個庫頭可以參考【 VS2008無法打開gl/glaux.h頭文件的解決方法】我使用的是方法4,測試能夠正確include庫頭。
在高版本的VS中,因爲VS使用的是自己重新修改過的C++,所以在進行編譯的過程中,可能會出現ERROR LNK2019報錯,無法解析“_sscanf,_sscanf_s”,這個時候我們可以參考【 VS2015 無法解析的外部符號 __vsnwprintf_s】
如果我們在使用AUX_RGBImageRec定義變量的時候,系統沒有報錯的話,就說明我們本次基本的環境已經搭建好了。
另外,因爲我們在實驗中需要使用fopen,而微軟的VS2015中會強行報錯,爲了避免不必要的麻煩,我們需要關掉fopen的報錯。這個部分我們可以參考【百度經驗:VS2013中如何解決error C4996: ‘fopen’問題】
開始實現
如果沒出什麼問題的話,到這裏我們應該能夠正常的編寫這一刻的代碼了。(如果還有什麼報錯請嘗試自行解決或者戳我)。
本次需要在3維圖像上添加紋理映射,首先需要的是讀取位圖像文件。讀取的代碼如下。
AUX_RGBImageRec *LoadBMP(char *Filename) // Loads A Bitmap Image
{
FILE *File=NULL; // File Handle
if (!Filename) // Make Sure A Filename Was Given
{
return NULL; // If Not Return NULL
}
File=fopen(Filename,"r"); // Check To See If The File Exists
if (File) // Does The File Exist?
{
fclose(File); // Close The Handle
return auxDIBImageLoad(Filename); // Load The Bitmap And Return A Pointer
}
return NULL; // If Load Failed Return NULL
}
int LoadGLTextures() // Load Bitmaps And Convert To Textures
{
int Status=FALSE; // Status Indicator
AUX_RGBImageRec *TextureImage[1]; // Create Storage Space For The Texture
memset(TextureImage,0,sizeof(void *)*1); // Set The Pointer To NULL
// Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit
if (TextureImage[0]=LoadBMP("Data/NeHe.bmp"))
{
Status=TRUE; // Set The Status To TRUE
glGenTextures(1, &texture[0]); // Create The Texture
// Typical Texture Generation Using Data From The Bitmap
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
}
if (TextureImage[0]) // If Texture Exists
{
if (TextureImage[0]->data) // If Texture Image Exists
{
free(TextureImage[0]->data); // Free The Texture Image Memory
}
free(TextureImage[0]); // Free The Image Structure
}
return Status; // Return The Status
}
第一個函數LoadBMP不需要解釋,主要功能就是探尋目的位置中是否存在該圖像文件。如果存在就調用auxDIBImageLoad將位圖加載成渲染文件返回出來。
第二個函數LoadGLTextures要稍微注意一下,在本次實驗中是很重要的一個功能函數。
函數中定義了一個LoadGLTextures數組用來存放位圖的句柄,這裏因爲我們只讀取了一張位圖,所以只開了一個大小的數組。
接着調用LoadBMP將位圖轉換成爲紋理渲染文件存進TextureImage, glGenTextures(1, &texture[0]) 告訴OpenGL我們想生成一個紋理名字,glBindTexture將紋理名字 texture[0] 綁定到紋理目標上。
然後我們調用glTexImage2D進行紋理的創建。然後使用glTexParameteri對圖像進行放大和縮小的濾波器進行設置。
最後再紋理穿件完成之後,我們需要釋放掉紋理渲染數組中的內容。
整個紋理渲染工作到這裏也就結束了。我們對InitGL稍作修改,使用LoadGLTextures檢驗位圖是否存在,然後調用glEnable啓用映射
int InitGL(GLvoid) // 此處開始對OpenGL進行所有設置
{
if (!LoadGLTextures()) // 調用紋理載入子例程
{
return FALSE; // 如果未能載入,返回FALSE
}
glEnable(GL_TEXTURE_2D); // 啓用紋理映射
glShadeModel(GL_SMOOTH); // 啓用陰影平滑
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // 黑色背景
glClearDepth(1.0f); // 設置深度緩存
glEnable(GL_DEPTH_TEST); // 啓用深度測試
glDepthFunc(GL_LEQUAL); // 所作深度測試的類型
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 真正精細的透視修正
return TRUE; // 初始化 OK
}
最後我們按照慣例,修改DrawGLScene方法,需要注意的是,在將紋理貼上模型的時候,需要調用glTexCoord2f方法,第一個參數是X座標,0.0是紋理的左側,0.5是紋理的中點,1.0是紋理的右側。第二個參數是Y座標,0.0是紋理的底部,0.5是紋理的中點,1.0是紋理的頂部。將4個點全部綁定在張芳行上面之後,便能夠正常的顯示了。需要注意的是,在glTexCoord2f方法中,參考系是以圖像的右下角作爲原點,左邊爲X軸正方向,上方爲Y軸正方向(和繪圖中的直角座標系的設定相似)。
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The View
glTranslatef(0.0f,0.0f,-5.0f);
glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);
glRotatef(zrot,0.0f,0.0f,1.0f);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glBegin(GL_QUADS);
// Front Face
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
// Back Face
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
// Top Face
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
// Bottom Face
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
// Right face
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
// Left Face
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd();
xrot+=0.3f;
yrot+=0.2f;
zrot+=0.4f;
return TRUE; // Keep Going
}
方法中,在begin之前,我們先預先定義好圖像的旋轉方式(不一定非要放在begin之前)。
接着調用glBindTexture進行綁定。我們就能夠開始進行紋理渲染的綁定操作了。
操作中我們在每次定義點的同時,將紋理的四個角也固定在對應的點上,便能夠完成綁定工作。
注意,用原引博客的話說,當您想改變紋理時,應該綁定新的紋理。有一點值得指出的是,您不能在 glBegin() 和 glEnd() 之間綁定紋理,必須在 glBegin() 之前或 glEnd() 之後綁定。
到這一步,這一課內容也就結束了。
課程7的實現
我們接下來嘗試課程7的內容。
課程7將帶領我們建立一個光照系統,我們也將在這個課程中嘗試添加按鍵控制。通過這個課程,我們會對OpenGL中的光照系統的實現、控制以及如何實現按鍵控制有一個入門的瞭解。本次實驗基於課程6的內容進行進一步擴展。
實驗中,因爲我們需要首先添加一些羣居變量用來對按鍵狀態進行識別,同時也還要添加一些全局變量用來記錄圖像的旋轉角度。
因此我們在全局變量中添加如下變量。
GLfloat xrot; // X 旋轉角度
GLfloat yrot; // Y 旋轉角度
GLfloat xspeed; // X 旋轉速度
GLfloat yspeed; // Y 旋轉速度
GLfloat z=-5.0f; // Depth Into The Screen
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };
GLuint filter; // 濾波器類型
GLuint texture[3]; // 3中紋理文件指針
上面代碼中,中間3行數組分別表示環境光變量數組,漫射光變量數組,以及光源位置數組。
其中,我們需要了解一下環境光和漫射光的意義是什麼。環境光來自於四面八方。所有場景中的對象都處於環境光的照射中;漫射光由特定的光源產生,並在您的場景中的對象表面上產生反射。處於漫射光直接照射下的任何對象表面都變得很亮,而幾乎未被照射到的區域就顯得要暗一些。
LightAmbient和LightDiffuse創建光源的過程和顏色的創建完全一致。前三個參數分別是RGB三色分量,最後一個是alpha通道參數。
而LightPosition中,前三個參數表示光源的XYZ左座標,最後一個參數將告訴OpenGL這裏指定的座標就是光源的位置。
然後我們在上一次實驗的基礎上稍稍稍修改LoadGLTextures函數。
int LoadGLTextures() // Load Bitmaps And Convert To Textures
{
int Status=FALSE; // Status Indicator
AUX_RGBImageRec *TextureImage[1]; // Create Storage Space For The Texture
memset(TextureImage,0,sizeof(void *)*1); // Set The Pointer To NULL
// Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit
if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))
{
Status=TRUE; // Set The Status To TRUE
glGenTextures(3, &texture[0]); // Create Three Textures
// Create Nearest Filtered Texture
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
// Create Linear Filtered Texture
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
// Create MipMapped Texture
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
}
if (TextureImage[0]) // If Texture Exists
{
if (TextureImage[0]->data) // If Texture Image Exists
{
free(TextureImage[0]->data); // Free The Texture Image Memory
}
free(TextureImage[0]); // Free The Image Structure
}
return Status; // Return The Status
}
在讀過上一次的代碼之後,我們可以很容易的知道這段代碼就是將Data/Crate.bmp這張位圖轉換成紋理並且存放在texture數組中,並且3次渲染的畫面都一致(基於這一點,其實我們也可以自己實現不同的3張圖片做出3種不同的,不同的是3章圖像的glTexParameteri進行濾波的方式並不相同,第一張渲染使用最鄰近過濾(GL_NEAREST),第二張使用線性濾波(GL_LINEAR),第三張使用選擇最鄰近的mip層,使用線性過濾(GL_LINEAR_MIPMAP_NEAREST)。然後就可以在立方體上渲染出不同的團了)。其他的部分改變不大。
上面對紋理映射的濾波器的選擇可以參考【OpenGL超級寶典筆記——紋理映射Mipmap】這篇博客,在上面有很詳細的解釋和說明。
然後在濾波器設置結束之後,最後一行還添加了一個gluBuild2DMipmaps方法,這個在上面的【OpenGL超級寶典筆記——紋理映射Mipmap】中也有提到,是一個能夠將任意圖像正常縮放到適當大小的方法函數,這樣就不用我們費心進行圖像的預處理工作了。
好了,到這裏LoadGLTextures就設置完畢了。
接着就到InitGL函數了,我們在第六課中已經設置了GLEnable以及glHint進行紋理綁定工作了。我們需要在後面添加新的方法,用來實現光源的初始化。具體參考代碼如下:
int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
if (!LoadGLTextures()) // Jump To Texture Loading Routine
{
return FALSE; // If Texture Didn't Load Return FALSE
}
glEnable(GL_TEXTURE_2D); // Enable Texture Mapping
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient); // Setup The Ambient Light
glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse); // Setup The Diffuse Light
glLightfv(GL_LIGHT1, GL_POSITION,LightPosition); // Position The Light
glEnable(GL_LIGHT1); // Enable Light One
return TRUE; // Initialization Went OK
}
新增了一個1號光源GL_LIGHT1。在對GL_LIGHT新增的3個glLightfv方法中,第一個表示設置環境光(Ambient Light),傳入我們已經定義好的全局變量LightAmbient;第二個是漫射光(Diffuse Light),傳入我們已經定義好的全局變量LightDiffuse;第三個是光源位置,傳入我們定義好的LightPosition。最後使用glEnable啓用光源。
到這裏點光源的初始化操作就結束了。和設置渲染一樣,我們接着要在DrawGLScene中添加光源的某些設置,
在OpenGL中,法線是指經過面(多邊形)上的一點且垂直於這個面(多邊形)的直線。使用光源的時候必須指定一條法線。法線告訴OpenGL這個多邊形的朝向,並指明多邊形的正面和背面。如果沒有指定法線,什麼怪事情都可能發生:不該照亮的面被照亮了,多邊形的背面也被照亮….。對了,法線應該指向多邊形的外側。
所以我們在DrawGLScene中繪製圖像的時候,也要同時設置法線。一般來說在繪製一個平面之前,我們就要預先定義好這個平面的法線。參考代碼如下。
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The View
glTranslatef(0.0f,0.0f,z);
glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);
glBindTexture(GL_TEXTURE_2D, texture[filter]);
glBegin(GL_QUADS);
// Front Face
glNormal3f( 0.0f, 0.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
// Back Face
glNormal3f( 0.0f, 0.0f,-1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
// Top Face
glNormal3f( 0.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
// Bottom Face
glNormal3f( 0.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
// Right face
glNormal3f( 1.0f, 0.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
// Left Face
glNormal3f(-1.0f, 0.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd();
xrot+=xspeed;
yrot+=yspeed;
return TRUE; // Keep Going
}
每新定義一個平面,我們都需要調用glNormal3f定義好一個法向量。法向量的3個參數分別表示其在X軸,Y軸,Z軸上的分量的長度。法向量的值是由面上的點計算出來的。
到這一步,我們編譯運行以後就已經能夠看到在點光源下的一個木箱子的圖案,接下來我們將爲其添加鍵盤控制功能。這部分在WinMain內。
我們在課程1上已經知道,nehe教程中,按鍵的反饋是在WinMain的一個while循環中,不斷檢測事件,然後對不同事件進行相應的反饋。裏面已經添加了F1進行全屏控制的時間控制,我們要做的就是模仿F1的工作機制,添加其他按鍵的反饋。
在以前的while循環中,按鍵的觸發設置代碼如下。
while(!done) // Loop That Runs While done=FALSE
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Is There A Message Waiting?
{
if (msg.message==WM_QUIT) // Have We Received A Quit Message?
{
done=TRUE; // If So done=TRUE
}
else // If Not, Deal With Window Messages
{
TranslateMessage(&msg); // Translate The Message
DispatchMessage(&msg); // Dispatch The Message
}
}
else // If There Are No Messages
{
// Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Active? Was There A Quit Received?
{
done=TRUE; // ESC or DrawGLScene Signalled A Quit
}
else // Not Time To Quit, Update Screen
{
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
}
if (keys[VK_F1]) // Is F1 Being Pressed?
{
keys[VK_F1]=FALSE; // If So Make Key FALSE
KillGLWindow(); // Kill Our Current Window
fullscreen=!fullscreen; // Toggle Fullscreen / Windowed Mode
// Recreate Our OpenGL Window
if (!CreateGLWindow("NeHe's Texture Mapping Tutorial",640,480,16,fullscreen))
{
return 0; // Quit If Window Was Not Created
}
}
}
}
在確認ESC按鍵未被按下之前,OpenGL將會不停的繪製圖片。在這個窗口刷新工作結束以後,我們開始了F1的按鍵判斷。由此,我們可以在這個基礎上,進一步擴展了。擴展的代碼如下。
while (!done) // Loop That Runs While done=FALSE
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) // Is There A Message Waiting?
{
if (msg.message == WM_QUIT) // Have We Received A Quit Message?
{
done = TRUE; // If So done=TRUE
}
else // If Not, Deal With Window Messages
{
TranslateMessage(&msg); // Translate The Message
DispatchMessage(&msg); // Dispatch The Message
}
}
else // If There Are No Messages
{
// Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Active? Was There A Quit Received?
{
done = TRUE; // ESC or DrawGLScene Signalled A Quit
}
else // Not Time To Quit, Update Screen
{
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
}
if (keys['L'] && !lp)
{
lp = TRUE;
light = !light;
if (!light)
{
glDisable(GL_LIGHTING);
}
else
{
glEnable(GL_LIGHTING);
}
}
if (!keys['L'])
{
lp = FALSE;
}
if (keys['F'] && !fp)
{
fp = TRUE;
filter += 1;
if (filter>2)
{
filter = 0;
}
}
if (!keys['F'])
{
fp = FALSE;
}
if (keys[VK_PRIOR])
{
z -= 0.02f;
}
if (keys[VK_NEXT])
{
z += 0.02f;
}
if (keys[VK_UP])
{
xspeed -= 0.01f;
}
if (keys[VK_DOWN])
{
xspeed += 0.01f;
}
if (keys[VK_RIGHT])
{
yspeed += 0.01f;
}
if (keys[VK_LEFT])
{
yspeed -= 0.01f;
}
if (keys[VK_F1]) // Is F1 Being Pressed?
{
keys[VK_F1] = FALSE; // If So Make Key FALSE
KillGLWindow(); // Kill Our Current Window
fullscreen = !fullscreen; // Toggle Fullscreen / Windowed Mode
// Recreate Our OpenGL Window
if (!CreateGLWindow("NeHe's Textures, Lighting & Keyboard Tutorial", 640, 480, 16, fullscreen))
{
return 0; // Quit If Window Was Not Created
}
}
}
}
如果按下L建,就對光源GL_LIGHTING進行改變(我們在初始化InitGL的時候,就曾經調用過glEnable,不知道你是否還有印象)。其他部分都不難,這裏就不一一解釋了。
另外需要說明的是,因爲在NEHE教程的前文中,按鍵反饋是在刷新屏幕if ((active && !DrawGLScene()) || keys[VK_ESCAPE])
這個判斷結束之後才進行的按鍵反饋,而在其之後的代碼中,卻又將ESC之外的按鍵觸發部分放進了判斷條件之內。我這裏模仿的是作者以前的按鍵觸發設置,所以代碼和作者lesson7的代碼稍有不同。不過最終效果其實一致——只不過我的會在按下ESC之後延遲一幀才推出,而作者按下ESC立刻退出,因爲刷新頻率很快,所以其實想過一隻。
最後,實驗正確運行的話,你應該能夠在窗口中正常的運行你的第一個有按鍵反饋的窗口遊戲了。L打開燈光,F設置不同的濾波器,上下左右方向鍵分別設置不同的旋轉方向,PageUp和PageDown分別將圖形靠近或者遠離我們。