1. 程式人生 > >OpenGL 紋理濾波方式

OpenGL 紋理濾波方式

原 文:Lesson 7: Texture Filters, Lighting & Keyboard Control
譯 者:CKER

  這一課我會教您如何使用三種不同的紋理濾波方式。教您如何使用鍵盤來移動場景中的物件,還會教您在OpenGL場景中應用簡單的光照。這一課包含了很多內容,如果您對前面的課程有疑問的話,先回頭複習一下。進入後面的程式碼之前,很好的理解基礎知識十分重要。我們還是在第一課的程式碼上加以修改。跟以前不一樣的是,只要有任何大的改動,我都會寫出整段程式碼。程式開始,我們先加上幾個新的變數。

  #include <windows.h>                    // Windows的標頭檔案
  #include <stdio.h>                     // 標準輸入/輸出庫的標頭檔案 (新增)
  #include <gl/gl.h>                     // OpenGL32庫的標頭檔案
  #include <gl/glu.h>                     // GLu32庫的標頭檔案
  #include <gl/glaux.h>                    // GLaux庫的標頭檔案

  HGLRC hRC=NULL;                       // 永久著色描述表
  HDC hDC=NULL;                        // 私有GDI裝置描述表
  HWND hWnd=NULL;                       // 儲存我們的視窗控制代碼
  HINSTANCE hInstance;                    // 儲存程式的例項

  bool keys[256];                       // 用於鍵盤例程的陣列
  bool active=TRUE;                      // 視窗的活動標誌,預設為TRUE
  bool fullscreen=TRUE;                    // 全屏標誌預設設定成全屏模式

  下面幾行是新的。我們增加三個布林變數。light變數跟蹤光照是否開啟。變數lp和fp用來儲存‘L’和‘F’鍵是否按下的狀態。後面我會解釋這些變數的重要性。現在,先放在一邊吧。

  BOOL light;                         // 光源的開/關
  BOOL lp;                          // L鍵按下了麼?
  BOOL fp;                          // F鍵按下了麼?

  現在設定5個變數來控制繞x軸和y軸旋轉角度的步長,以及繞x軸和y軸的旋轉速度。另外還建立了一個z變數來控制進入螢幕深處的距離。

  GLfloat xrot;                        // X 旋轉
  GLfloat yrot;                        // Y 旋轉
  GLfloat xspeed;                       // X 旋轉速度
  GLfloat yspeed;                       // Y 旋轉速度

  GLfloat z=-5.0f;                      // 深入螢幕的距離

  接著設定用來建立光源的陣列。我們將使用兩種不同的光。第一種稱為環境光。環境光來自於四面八方。所有場景中的物件都處於環境光的照射中。第二種型別的光源叫做漫射光。漫射光由特定的光源產生,並在您的場景中的物件表面上產生反射。處於漫射光直接照射下的任何物件表面都變得很亮,而幾乎未被照射到的區域就顯得要暗一些。這樣在我們所建立的木板箱的稜邊上就會產生的很不錯的陰影效果。
  建立光源的過程和顏色的建立完全一致。前三個引數分別是RGB三色分量,最後一個是alpha通道引數。
  因此,下面的程式碼我們得到的是半亮(0.5f)的白色環境光。如果沒有環境光,未被漫射光照到的地方會變得十分黑暗。

  GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };     // 環境光引數 (新增)

  下一行程式碼我們生成最亮的漫射光。所有的引數值都取成最大值1.0f。它將照在我們木板箱的前面,看起來挺好。

  GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };     // 漫射光引數 (新增)

  最後我們儲存光源的位置。前三個引數和glTranslate中的一樣。依次分別是XYZ軸上的位移。由於我們想要光線直接照射在木箱的正面,所以XY軸上的位移都是0.0f。第三個值是Z軸上的位移。為了保證光線總在木箱的前面,所以我們將光源的位置朝著觀察者(就是您哪。)挪出螢幕。我們通常將螢幕也就是顯示器的螢幕玻璃所處的位置稱作Z軸的0.0f點。所以Z軸上的位移最後定為2.0f。假如您能夠看見光源的話,它就浮在您顯示器的前方。當然,如果木箱不在顯示器的螢幕玻璃後面的話,您也無法看見箱子。最後一個引數取為1.0f。這將告訴OpenGL這裡指定的座標就是光源的位置,以後的教程中我會多加解釋。

  GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };    // 光源位置 ( 新增 )

  filter 變數跟蹤顯示時所採用的紋理型別。第一種紋理(texture 0)使用gl_nearest(不光滑)濾波方式構建。第二種紋理(texture 1)使用gl_linear(線性濾波)方式,離螢幕越近的影象看起來就越光滑。第三種紋理(texture 2)使用mipmapped濾波方式,這將建立一個外觀十分優秀的紋理。根據我們的使用型別,filter變數的值分別等於0,1或2。下面我們從第一種紋理開始。
  GLuint texture[3]為三種不同紋理分配儲存空間。它們分別位於在texture[0]、texture[1]、texture[2]中。

  GLuint filter;                       // 濾波型別
  GLuint texture[3];                     // 3種紋理的儲存空間
  LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    // WndProc定義

  現在載入一個位圖,並用它建立三種不同的紋理。這一課使用glaux輔助庫來載入點陣圖,因此在編譯時您應該確認是否包含了glaux庫。我知道Delphi和VC++都包含了glaux庫,但別的語言不能保證都有。(譯者:glaux是OpenGL輔助庫,根據OpenGL的跨平臺特性,所有平臺上的程式碼都應通用。但輔助庫不是正式的OpenGL標準庫,沒有出現在所有的平臺上。但正好在Win32平臺上可用。呵呵,BCB當然也沒問題了。)這裡我只對新增的程式碼做註解。如果您對某行程式碼有疑問的話,請檢視教程六。那一課很詳細的解釋了載入、建立紋理的內容。
  在上一段程式碼後面及ReSizeGLScene()之前的位置,我們增加了下面的程式碼。這和第六課中載入點陣圖的程式碼幾乎相同。

  AUX_RGBImageRec *LoadBMP(char *Filename)          // 載入點陣圖
  {
      FILE *File=NULL;                  // 檔案控制代碼
      if (!Filename)                   // 確認檔名已初始化
      {
          return NULL;                // 沒有返回 NULL
      }
      File=fopen(Filename,"r");              // 檢查檔案是否存在
      if (File)                      // 存在麼?
      {
          fclose(File);                // 關閉檔案控制代碼
          return auxDIBImageLoad(Filename);      // 載入點陣圖並返回一個指標
      }
      return NULL;                    // 載入失敗返回 NULL
  }

  這段程式碼呼叫前面的程式碼載入點陣圖,並將其轉換成3個紋理。Status變數跟蹤紋理是否已載入並被建立了。

  int LoadGLTextures()                    // 載入點陣圖並轉換成紋理
  {
      int Status=FALSE;                  // Status 指示器
      AUX_RGBImageRec *TextureImage[1];          // 建立紋理的儲存空間
      memset(TextureImage,0,sizeof(void *)*1);      // 將指標設為 NULL

  現在載入點陣圖並轉換成紋理。TextureImage[0]=LoadBMP("Data/Crate.bmp")呼叫我們的LoadBMP()函式。Data目錄下的“Crate.bmp”將被載入。如果一切正常,影象資料將存放在TextureImage[0]。Status變數被設為TRUE,我們將開始建立紋理。

      // 載入點陣圖,檢查有錯,或點陣圖不存在的話退出。
      if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))
      {
          Status=TRUE;                // Status 設為 TRUE

  現在我們已經將影象資料載入TextureImage[0]。我們將用它來建立3個紋理。下面的行告訴OpenGL我們要建立三個紋理,它們將存放在texture[0]、texture[1]、texture[2] 中。

          glGenTextures(3, &texture[0]);       // 建立紋理

  第六課中我們使用了線性濾波的紋理貼圖。這需要機器有相當高的處理能力,但它們看起來很不錯。這一課中,我們接著要建立的第一種紋理使用 GL_NEAREST方式。從原理上講,這種方式沒有真正進行濾波。它只佔用很小的處理能力,看起來也很差。唯一的好處是這樣我們的工程在很快和很慢的機器上都可以正常執行。
  您會注意到我們在MIN和MAG時都採用了GL_NEAREST,你可以混合使用GL_NEAREST和GL_LINEAR。紋理看起來效果會好些,但我們更關心速度,所以全採用低質量貼圖。MIN_FILTER在影象繪製時小於貼圖的原始尺寸時採用。MAG_FILTER在影象繪製時大於貼圖的原始尺寸時採用。

          // 建立 Nearest 濾波貼圖
          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);

  下個紋理與第六課的相同,線性濾波。唯一的不同是這次放在了texture[1]中。因為這是第二個紋理。如果放在texture[0]中的話,他將覆蓋前面建立的GL_NEAREST紋理。

          // 建立線性濾波紋理
          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);

  下面是建立紋理的新方法:Mip-mapping(紋理細化)。您可能會注意到當影象在螢幕上變得很小的時候,很多細節將會丟失。剛才還很不錯的圖案變得很難看。當您告訴OpenGL建立一個mipmapped的紋理後,OpenGL將嘗試建立不同尺寸的高質量紋理。當您向螢幕繪製一個mipmapped紋理的時候,OpenGL將選擇它已經建立的外觀最佳的紋理(帶有更多細節)來繪製,而不僅僅是縮放原先的影象(這將導致細節丟失)。
  我曾經說過有辦法可以繞過OpenGL對紋理寬度和高度所加的限制 — 64、128、256,等等。辦法就是 gluBuild2DMipmaps。據我的發現,您可以使用任意的點陣圖來建立紋理。OpenGL將自動將它縮放到正常的大小。因為是第三個紋理,我們將它存到texture[2]。這樣本課中的三個紋理全都建立好了。

          // 建立 MipMapped 紋理
          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); // (新增)

  下面一行生成mipmapped紋理。我們使用三種顏色(紅,綠,藍)來生成一個2D紋理。TextureImage[0]->sizeX 是點陣圖寬度,extureImage[0]->sizeY是點陣圖高度,GL_RGB意味著我們依次使用RGB色彩。GL_UNSIGNED_BYTE意味著紋理資料的單位是位元組。TextureImage[0]->data指向我們建立紋理所用的點陣圖。

          gluBuild2DMipmaps(GL_TEXTURE_2D, 3,
              TextureImage[0]->sizeX, TextureImage[0]->sizeY,
              GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);  // ( 新增 )
      }

  現在釋放用來存放點陣圖資料的記憶體。我們先檢視點陣圖資料是否存放在TextureImage[0]中,如果有,刪掉。然後釋放點陣圖結構以確保記憶體被釋放。

      if (TextureImage[0])                // 紋理是否存在
      {
          if (TextureImage[0]->data)         // 紋理影象是否存在
          {
              free(TextureImage[0]->data);    // 釋放紋理影象佔用的記憶體
          }
          free(TextureImage[0]);           // 釋放影象結構
      }

  最後我們返回status變數。如果一切OK,status變數的值為TRUE。否則為FALSE。

      return Status;                   // 返回 Status 變數
  }

  接著應該載入紋理並初始化OpenGL設定了。InitGL函式的第一行使用上面的程式碼載入紋理。建立紋理之後,我們呼叫glEnable(GL_TEXTURE_2D)啟用2D紋理對映。陰影模式設為平滑陰影(smooth shading)。背景色設為黑色,我們啟用深度測試,然後我們啟用優化透視計算。

  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); // 高度優化的透視投影計算

  現在開始設定光源。下面下面一行設定環境光的發光量,光源light1開始發光。這一課的開始處我們我們將環境光的發光量存放在LightAmbient陣列中。現在我們就使用此陣列(半亮度環境光)。

      glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);  // 設定環境光

  接下來我們設定漫射光的發光量。它存放在LightDiffuse陣列中(全亮度白光)。

      glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);  // 設定漫射光

  然後設定光源的位置。位置存放在 LightPosition 陣列中(正好位於木箱前面的中心,X-0.0f,Y-0.0f,Z方向移向觀察者2個單位[位於螢幕外面])。

      glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);  // 光源位置

  最後,我們啟用一號光源。我們還沒有啟用GL_LIGHTING,所以您看不見任何光線。記住:只對光源進行設定、定位、甚至啟用,光源都不會工作。除非我們啟用GL_LIGHTING。

      glEnable(GL_LIGHT1);                // 啟用一號光源
      return TRUE;                    // 初始化 OK
  }

  下一段程式碼繪製貼圖立方體。我只對新增的程式碼進行註解。如果您對沒有註解的程式碼有疑問,回頭看看第六課。

  int DrawGLScene(GLvoid)                   // 從這裡開始進行所有的繪製
  {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除螢幕和深度快取
      glLoadIdentity();                  // 重置當前的模型觀察矩陣

  下三行程式碼放置並旋轉貼圖立方體。glTranslatef(0.0f,0.0f,z)將立方體沿著Z軸移動Z單位。glRotatef(xrot,1.0f,0.0f,0.0f)將立方體繞X軸旋轉xrot。glRotatef(yrot,0.0f,1.0f,0.0f)將立方體繞Y軸旋轉yrot。

      glTranslatef(0.0f,0.0f,z);             // 移入/移出螢幕 z 個單位

      glRotatef(xrot,1.0f,0.0f,0.0f);          // 繞X軸旋轉
      glRotatef(yrot,0.0f,1.0f,0.0f);          // 繞Y軸旋轉

  下一行與我們在第六課中的類似。有所不同的是,這次我們繫結的紋理是texture[filter],而不是上一課中的texture[0]。任何時候,我們按下F鍵,filter的值就會增加。如果這個數值大於2,變數filter 將被重置為0。程式初始時,變數filter的值也將設為0。使用變數filter我們就可以選擇三種紋理中的任意一種。

      glBindTexture(GL_TEXTURE_2D, texture[filter]);   // 選擇由filter決定的紋理
      glBegin(GL_QUADS);                 // 開始繪製四邊形

  glNormal3f是這一課的新東西。Normal就是法線的意思,所謂法線是指經過面(多邊形)上的一點且垂直於這個面(多邊形)的直線。使用光源的時候必須指定一條法線。法線告訴OpenGL這個多邊形的朝向,並指明多邊形的正面和背面。如果沒有指定法線,什麼怪事情都可能發生:不該照亮的面被照亮了,多邊形的背面也被照亮....。對了,法線應該指向多邊形的外側。
  看著木箱的前面您會注意到法線與Z軸正向同向。這意味著法線正指向觀察者-您自己。這正是我們所希望的。對於木箱的背面,也正如我們所要的,法線背對著觀察者。如果立方體沿著X或Y軸轉個180度的話,前側面的法線仍然朝著觀察者,背面的法線也還是背對著觀察者。換句話說,不管是哪個面,只要它朝著觀察者這個面的法線就指向觀察者。由於光源緊鄰觀察者,任何時候法線對著觀察者時,這個面就會被照亮。並且法線越朝著光源,就顯得越亮一些。如果您把觀察點放到立方體內部,你就會法線裡面一片漆黑。因為法線是向外指的。如果立方體內部沒有光源的話,當然是一片漆黑。

          // 前側面
          glNormal3f( 0.0f, 0.0f, 1.0f);       // 法線指向觀察者
          glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Point 1 (Front)
          glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Point 2 (Front)
          glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Point 3 (Front)
          glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Point 4 (Front)

          // 後側面
          glNormal3f( 0.0f, 0.0f,-1.0f);      // 法線背向觀察者
          glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Point 1 (Back)
          glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Point 2 (Back)
          glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Point 3 (Back)
          glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Point 4 (Back)

          // 頂面
          glNormal3f( 0.0f, 1.0f, 0.0f);      // 法線向上
          glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Point 1 (Top)
          glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // Point 2 (Top)
          glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Point 3 (Top)
          glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Point 4 (Top)

          // 底面
          glNormal3f( 0.0f,-1.0f, 0.0f);     // 法線朝下
          glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Point 1 (Bottom)
          glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Point 2 (Bottom)
          glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Point 3 (Bottom)
          glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Point 4 (Bottom)

          // 右側面
          glNormal3f( 1.0f, 0.0f, 0.0f);     // 法線朝右
          glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Point 1 (Right)
          glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // Point 2 (Right)
          glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);  // Point 3 (Right)
          glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // Point 4 (Right)

          // 左側面
          glNormal3f(-1.0f, 0.0f, 0.0f);    // 法線朝左
          glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Point 1 (Left)
          glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // Point 2 (Left)
          glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);  // Point 3 (Left)
          glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // Point 4 (Left)
      glEnd();                   // 四邊形繪製結束

  下兩行程式碼將xot和yrot的旋轉值分別增加xspeed和yspeed個單位。xspeed和yspeed的值越大,立方體轉得就越快。

      xrot+=xspeed;                // xrot 增加 xspeed 單位
      yrot+=yspeed;                // yrot 增加 yspeed 單位
      return TRUE;
  }

  現在轉入WinMain()主函式。我們將在這裡增加開關光源、旋轉木箱、切換過濾方式以及將木箱移近移遠的控制程式碼。在接近WinMain()函式結束的地方你會看到SwapBuffers(hDC)這行程式碼。然後就在這一行後面新增如下的程式碼。程式碼將檢查L鍵是否按下過。如果L鍵已按下,但lp的值不是false的話,意味著L鍵還沒有鬆開,這時什麼都不會發生。

                       SwapBuffers(hDC);   // 交換快取 (雙快取)
                       if (keys['L'] && !lp) // L 鍵已按下並且鬆開了?
                       {

  如果lp的值是false的話,意味著L鍵還沒按下,或者已經鬆開了,接著lp將被設為TRUE。同時檢查這兩個條件的原因是為了防止L鍵被按住後,這段程式碼被反覆執行,並導致窗體不停閃爍。
  lp設為true之後,計算機就知道L鍵按過了,我們則據此可以切換光源的開/關:布林變數light控制光源的開關。
                           lp=TRUE;   // lp 設為 TRUE
                           light=!light; // 切換光源的 TRUE/FALSE

  下面幾行來檢查光源是否應該開啟,並根據light變數的值。

                            if (!light)  // 如果沒有光源
                           {
                               glDisable(GL_LIGHTING); //禁用光源
                           }
                           else     // Otherwise
                           {
                               glEnable(GL_LIGHTING); //啟用光源
                           }
                        }

  下面的程式碼檢視是否鬆開了“L”鍵。如果鬆開,變數lp將設為false。這意味著“L”鍵沒有按下。如果不作此檢查,光源第一次開啟之後,就無法再關掉了。計算機會以為“L”鍵一直按著呢。

                       if (!keys['L'])    // L鍵鬆開了麼?
                       {
                           lp=FALSE;   // 若是,則將lp設為FALSE
                       }

  然後對“F”鍵作相似的檢查。如果有按下“F”鍵並且“F”鍵沒有處於按著的狀態或者它就從沒有按下過,將變數fp設為true。這意味著這個鍵正被按著呢。接著將filter變數加一。如果filter變數大於2(因為這裡我們的使用的陣列是texture[3],大於2的紋理不存在),我們重置filter變數為0。

                       if (keys['F'] && !fp) // F鍵按下了麼?
                       {
                           fp=TRUE;   // fp 設為 TRUE
                           filter+=1;  // filter的值加一
                           if (filter>2) // 大於2了麼?
                           {
                                filter=0; // 若是重置為0
                           }
                       }
                       if (!keys['F'])    // F鍵放開了麼?
                       {
                           fp=FALSE;  // 若是fp設為FALSE
                       }

  這四行檢查是否按下了PageUp鍵。若是的話,減少z變數的值。這樣DrawGLScene函式中包含的glTranslatef(0.0f,0.0f,z)呼叫將使木箱離觀察者更遠一點。

                       if (keys[VK_PRIOR])  // PageUp按下了?
                       {
                            z-=0.02f;  // 若按下,將木箱移向螢幕內部。
                       }

  接著四行檢查PageDown鍵是否按下,若是的話,增加z變數的值。這樣DrawGLScene函式中包含的glTranslatef(0.0f,0.0f,z)呼叫將使木箱向著觀察者移近一點。

                       if (keys[VK_NEXT])  // PageDown按下了麼?
                       {
                           z+=0.02f;  //若按下的話,將木箱移向觀察者。
                       }

  現在檢查方向鍵。按下左右方向鍵xspeed相應減少或增加。按下上下方向鍵yspeed相應減少或增加。記住在以後的教程中如果xspeed、yspeed的值增加的話,立方體就轉的更快。如果一直按著某個方向鍵,立方體會在那個方向上轉的越快。

                       if (keys[VK_UP])    // Up方向鍵按下了麼?
                       {
                           xspeed-=0.01f; // 若是,減少xspeed
                       }
                       if (keys[VK_DOWN])   // Down方向鍵按下了麼?
                       {
                           xspeed+=0.01f; // 若是,增加xspeed
                       }
                       if (keys[VK_RIGHT])   // Right方向鍵按下了麼?
                       {
                            yspeed+=0.01f; // 若是,增加yspeed
                       }
                       if (keys[VK_LEFT])    // Left方向鍵按下了麼?
                       {
                            yspeed-=0.01f; // 若是, 減少yspeed
                       }

  像前幾課一樣,我們最後還需要更正窗體的標題。

                       if (keys[VK_F1])        // F1鍵按下了麼?
                       {
                           keys[VK_F1]=FALSE;
                           KillGLWindow();     // 銷燬當前的視窗
                           fullscreen=!fullscreen; // 切換 全屏/視窗 模式
                           // 重建 OpenGL 視窗(修改)
                           if (!CreateGLWindow("NeHe's Textures, Lighting & Keyboard Tutorial",640,480,16,fullscreen))
                           {
                               return 0; // 如果視窗未能建立,程式退出
                           }
                       }
                   }
               }
           }
      // 關閉
      KillGLWindow();                   // 銷燬視窗
      return (msg.wParam);                // 退出程式
  }

  這一課完了之後,您應該學會建立和使用這三種不同的紋理對映過濾方式。並使用鍵盤和場景中的物件互動。最後,您應該學會在場景中應用簡單的光源,使得場景看起來更逼真。