第七課
     
  這一課我會教你如何使用三種不同的貼圖濾鏡. 我會教你如何用鍵盤按鍵來移動一個物件, 我也會教你應用一個簡單的光源在你的 OpenGL 場景中. 這個課程包含了一堆東東, 所以如果你在之前的課程有什麼問題的話, 回去複習之前的課程. 在你進入下面的程式碼之前, 對一些基本觀念有好的了解是很重要的.

我們將再次修改第一課的程式碼. 和平常一樣, 如果有任何主要的修改, 我就會把有修改的程式碼整段寫出來. 我們將會在程式開始的地方加入一些新的變數.
 
     
#include <windows.h>								// Header File For Windows
#include <stdio.h>								// Header File For Standard Input/Output ( ADD )
#include <gl\gl.h>								// Header File For The OpenGL32 Library
#include <gl\glu.h>								// Header File For The GLu32 Library
#include <gl\glaux.h>								// Header File For The GLaux Library

HDC		hDC=NULL;							// Private GDI Device Context
HGLRC		hRC=NULL;							// Permanent Rendering Context
HWND		hWnd=NULL;							// Holds Our Window Handle
HINSTANCE	hInstance;							// Holds The Instance Of The Application

bool	keys[256];								// Array Used For The Keyboard Routine
bool	active=TRUE;								// Window Active Flag
bool	fullscreen=TRUE;							// Fullscreen Flag
     
  下面這幾行是新加入的. 我們將新增三個布林變數. BOOL 表示變數只能設為 TRUE 或 FALSE. 我們建立一個叫 light 的變數來持續追蹤光源是否被開啟或是關閉. lp 變數和 fp 變數用來存放 'L' 或是 'F' 按鍵被按下. 我將解釋為何我們待會需要這些變數在程式碼中. 現在只要知道它們很重要就好了.  
     
BOOL	light;									// Lighting ON / OFF
BOOL	lp;									// L Pressed?
BOOL	fp;									// F Pressed?
     
  現在我們將會設定五個變數, 控制 x 軸的角度 (xrot), y 軸的角度 (yrot), 木箱 x 軸的旋轉速度是 (xspeed), 木箱 y 軸的旋轉速度是 (yspeed). 我們也將建一個叫做 z 的變數來控制木箱在畫面上 (在 z 軸上) 的深度值.  
     
GLfloat	xrot;									// X Rotation
GLfloat	yrot;									// Y Rotation
GLfloat	xspeed;									// X Rotation Speed
GLfloat	yspeed;									// Y Rotation Speed

GLfloat	z=-5.0f;								// Depth Into The Screen
     
  現在我們設定陣列來建立光源. 我們將使用兩種不同的光源. 第一種光源叫做環境 (ambient) 光源. 環境光源是指不是來自任何特定方向的光源. 第二種光源叫做散射 (diffuse) 光. 散射光是由你的光源所產生的, 它反射自場景中物件的表面. 任何物件的表面被光線直射就會很亮, 而只有少亮光線的區域就會比較暗. 這會產生很好的濃淡效果在我們的木箱上.

光的建立和顏色的建立是相同方式的. 如果第一個值是 1.0f, 接著兩個值都是 0.0f, 那我們建立的就是亮紅光. 如果第三個值是 1.0f, 前兩個值是 0.0f, 我們就有一個亮藍光. 最後一個值是透明度. 現在把它給值 1.0f.

所以下面這一行, 我們存放數值給白色環境光源, 強度值為一半 (0.5f). 因為所有的數值都是 0.5f, 我們將光的值給定為全暗 (黑色) 和全亮 (白色) 的一半. 紅色, 藍色, 和綠色給定相同的數值會建立出濃淡色系由黑色 (0.0f) 到白色 (1.0f). 沒有一個環境光源, 場景就沒有散射光, 看起來就會很暗.
 
     
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; 				// Ambient Light Values ( NEW )
     
  下一行我們會存放一個很亮的值, 全亮度的散射光. 所有的數值都是 1.0f. 這表示這個光源是我們所能給的最亮值了. 一個散射光會好好照亮木箱的前面.  
     
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };				 // Diffuse Light Values ( NEW )
     
  最後我們存放光源的位置. 前三個數值和 glTranslate 的三個值一樣. 第一個值沿著 x 軸左右移動, 第二個值沿著 y 軸上下移動, 第三個值沿著 z 軸進出畫面. 因為我們想要光源直接打在木箱的前面, 我們就不去左右移動, 所以第一個值就會是 0.0f (不在 x 軸上移動), 我們也不上下移動, 所以第二個值也會是 0.0f. 至於第三個值, 我們要確保光源一值在木箱的前面. 所以我們把光源放在畫面外, 朝向觀察者. 我們說螢幕玻璃就是 z 軸上 0.0f 的位置. 我們將光的位置放在 z 軸上 2.0f 的位置. 也就是如果你真的看得到這個光源的話, 它會浮在螢幕玻璃的前面. 這麼一來, 要讓光源放在木箱後方的唯一方式就是也把木箱放置在螢幕玻璃的前面. 當然啦, 如果木箱不放置在螢幕玻璃的後方, 那你就看不到木箱, 所以光源在哪兒也就無所謂了. 不過這樣有意義嗎?

並沒有真正簡單的方法來解釋第三個參數的意義. 你應該知道 -2.0f 會比 -5.0f 更靠近你. 而 -100.0f 會更深入畫面中. 當你設為 0.0f, 圖像會很大, 它會填滿整個螢幕. 一但你開始把它設為正值時, 圖像就不會顯示在畫面中, 因為它已經 "穿過螢幕". 這就是我所指的畫面之外的意義. 物件還是在那兒, 你只是看不到罷了.

把最後一個值給 1.0f. 這會告訴 OpenGL 指定座標是光源的位置. 稍後的課程會有更多的解釋.
 
     
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };				 // Light Position ( NEW )
     
  下列 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;									// Which Filter To Use
GLuint	texture[3];								// Storage for 3 textures

LRESULT	CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);				// Declaration For WndProc
     
  現在我們載入一個點陣圖, 並且由它來建立出三個不同的貼圖材質. 這一課會用到 glaux 函式庫來載入點陣圖, 所以先確定在你試著編譯程式碼之前, 你有把 glaux 函式庫包含進來. 我知道 Delphi 和 Visual C++ 都有 glaux 函式庫. 我不確定其他的程式語言有沒有. 我將會解釋新的那幾行程式碼做什麼用的, 如果你看到一行我沒有給註解的程式碼, 而你懷疑它的用處時, 請查閱第六課. 第六課很詳細的解釋載入, 以及由點陣圖建立出貼圖材質.

很快的在上述的程式碼後, 以及 ReSizeGLScene() 之前, 我們想要加入下列的這段程式碼. 這和第六課中用來載入點陣圖檔的程式碼相同. 沒有什麼改變. 如果你不確定下列這幾行程式碼做什麼的話, 請閱讀第六課. 它很詳細的解釋這個程式碼.
 
     
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
}
     
  這一段程式碼會載入點陣圖 (藉由呼叫上列的程式碼) 並且把它轉換為三個貼圖材質. Status 是用來持續追蹤是否貼圖材質已經被載入和建立.  
     
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
     
  現在我們載入點陣圖, 並且把它轉換為貼圖材質. TextureImage[0]=LoadBMP("Data/Crate.bmp") 將會跳到我們的 LoadBMP() 程式碼中. 在 Data 目錄中檔名叫做 Crate.bmp 的檔案會被載入. 如果一切順利, 圖像資料會存放在 TextureImage[0], Status 會設定為 TRUE, 我們就開始建立我們的貼圖材質.  
     
	// 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
     
  現在我們已經把圖像資料載入 TextureImage[0], 我們會用這個資料建立出三個貼圖材質. 下列這一行告訴 OpenGL 我們要建立三個貼圖材質, 而且把貼圖材質存放入 texture[0], texture[1] 和 texture[2].  
     
		glGenTextures(3, &texture[0]);					// Create Three Textures
     
  在第六課中, 我們對貼圖材質用線性濾鏡. 這會需要較大量的處理器能力, 但是看起來真的比較好看. 這一課中的第一種貼圖材質我們將使用 GL_NEAREST. 基本上這種貼圖材質沒有使用任何濾鏡. 它只需少量的處理器能力, 而且它看起來真的比較差. 如果你玩過一個遊戲的貼圖材質看起來一塊一塊的, 它大概就是用這類的貼圖材質. 這類貼圖材質的好處, 就是當專案計劃使用這類的貼圖材質時, 在慢速的電腦也可以執行的很順暢.

你會注意到我們在縮小 (MIN) 和放大 (MAG) 都使用 GL_NEAREST. 你可以混著用 GL_NEAREST 與 GL_LINEAR (在縮小時用 GL_NEAREST, 在放大時用GL_LINEAR), 那貼圖材質看起來會好一些, 但是我們在意執行速度, 所以我們在這兩個時候都使用較差的畫面品質. MIN_FILTER 是指當要畫的圖像比原本貼圖材質小時的濾鏡. MIN_FILTER 是指當要畫的圖像比原本貼圖材質大時的濾鏡.
 
     
		// Create Nearest Filtered Texture
		glBindTexture(GL_TEXTURE_2D, texture[0]);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); ( NEW )
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); ( NEW )
		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], 因為它是我們第二個材質. 如果我們還是把它存放在 texture[0], 那就會覆蓋掉第一個材質的 GL_NEAREST.  
     
		// 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);
     
  現在有一個新的方法來做貼圖材質. 就是 Mipmapping! 你可能有注意到當圖像畫面上顯示很小時, 很多精細的地方會消失. 圖案直接看很好看, 但是實際在場景卻變得很糟. 當你告訴 OpenGL 要建立 mipmapped 材質, 那 OpenGL 就會試著對高品質的貼圖材質建立出一些不同大小的材質. 當你在畫面上畫 mipmapped 材質時, OpenGL 會從已產生的材質中 (由最精細的材質中所產生2的), 選擇最好看的貼圖材質, 並把它畫在畫面上, 而不是去改變最原始的圖像大小 (這會導致精細度被遺失).

我在第六課中說過, OpenGL 限制貼圖材質寬高必須是 64, 128, 256, 等等. 用 gluBuild2DMipmaps 就對了. 我發現你可以用任何你想用的點陣圖像 (任何寬高值), 當要建立 mipmapped 貼圖材質時. OpenGL 會自動的改變它的大小為適當的寬高.

因為這個貼圖材質編號是三, 我們就把它存放在 texture[2]. 所以現在我們有 texture[0] 是不用濾鏡的, texture[1] 使用線性濾鏡, texture[2] 使用 mipmapped 貼圖材質. 我們建立完成這一課的貼圖材質了.
 
     
		// 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); ( NEW )
     
  下面這一行建立 mipmapped 貼圖材質. 我們正用三原色 (紅綠藍) 建立一個 2D 貼圖材質. TextureImage[0]->sizeX 是點陣圖的寬度, TextureImage[0]->sizeY 點陣圖的高度, GL_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); ( NEW )
	}
     
  現在我們釋放任何用來存放點陣資料的記憶體. 我們檢查看看是否點陣資料已被存放入 TextureImage[0]. 如果是的話, 那就刪除它. 接著釋放圖像結構, 以確定任何已被使用的記憶體有釋放掉.  
     
	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
	}
     
  最後我們傳回 status. 如果一切 OK, 那變數 Status 就會設為 TRUE. 如果有錯的話, Status 就會設為 FALSE.  
     
	return Status;								// Return The Status
}
     
  現在我們載入貼圖材質, 並且初始化 OpenGL 的設定. InitGL 的第一行用以上的程式碼載入貼圖材質. 在貼圖材質被建立之後, 我們用 glEnable(GL_TEXTURE_2D) 啟動 2D 材質貼圖. 著色模式設定為平滑著色, 背景顏色設為黑色, 接著啟動深度測試, 然後啟動較佳的透視計算.  
     
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
     
  現在我們設定光源. 下面這一行會設定一組環境光, light1 會被用到. 在這一課的一開始我們存放了一組環境光在 LightAmbient. 我們存放在陣列的數值就會被用到了 (半強度的環境光)  
     
	glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);				// Setup The Ambient Light
     
  接著我們設定一組散射光, light1 會被用到. 我們存放了一組散射光在 LightDiffuse. 我們存放在陣列的數值就會被用到 (全強度的白色光)  
     
	glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);				// Setup The Diffuse Light
     
  現在我們設定光源的位置. 我們存放位置在 LightPosition. 我們存放在陣列的資料就會被用到 (在畫面中央前面的位置, x 是 0.0f, y 是 0.0f, 而朝向觀察者兩個單位 {往畫面外面} 的 z 軸處).  
     
	glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);			// Position The Light
     
  最後我們啟動光源編號一號. 我們還沒有啟動 GL_LIGHTING, 所以你還看不到任何光. 即使光已經設定及其位置, 不過在我們啟動 GL_LIGHTING 之前, 光源是不會運作的.  
     
	glEnable(GL_LIGHT1);							// Enable Light One
	return TRUE;								// Initialization Went OK
}
     
  下一段程式碼中, 我們將畫出材質貼圖的立方體. 我將在幾行加上註解, 只要它們是新的程式碼時. 如果你不確定沒有加註解的那幾行在幹嘛, 就看看第六課吧.  
     
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) 移動立方體在 z 軸上的 z 的值 (遠離或朝向觀察者). glRotatef(xrot,1.0f,0.0f,0.0f) 使用變數 xrot 來旋轉立方體在 x 軸上的量值. glRotatef(yrot,1.0f,0.0f,0.0f) 使用變數 yrot 來旋轉立方體在 y 軸上的量值.  
     
	glTranslatef(0.0f,0.0f,z);						// Translate Into/Out Of The Screen By z

	glRotatef(xrot,1.0f,0.0f,0.0f);						// Rotate On The X Axis By xrot
	glRotatef(yrot,0.0f,1.0f,0.0f);						// Rotate On The Y Axis By yrot
     
  下一行類似我們在第六課所用到的, 不過不是連結 texture[0], 而是連結 texture[filter]. 任何在我們按下 'F' 按鍵時, filter 的值就會增加. 如果數值大於二, filter 變數就會設為零. 這跟 glBindTexture(GL_TEXTURE_2D, texture[0]) 是相同的. 如果在按一次 'F', filter 變數就會等於一, 那麼跟 glBindTexture(GL_TEXTURE_2D, texture[1]) 是相同的. 使用 filter 這個變數, 我們就可以任意選擇所建立的三張貼圖.  
     
	glBindTexture(GL_TEXTURE_2D, texture[filter]);				// Select A Texture Based On filter

	glBegin(GL_QUADS);							// Start Drawing Quads
     
  glNormal3f 在教學中是新的東西. 一個法向量是指一條線由一個多邊形中指出來, 並且和多邊形成 90 度的垂直. 當你使用光源, 你就得指定法向量. 法向量會告訴 OpenGL 這個多邊形面向何處... 這是一個方法啦. 如果你不指定法向量, 就會有怪事發生. 該被照亮的表面沒有被照亮, 錯誤的多邊形邊緣卻被照亮, 等等的怪事. 法向量應該指向多邊形外側.

看著前面的面時, 你會注意到法向量會朝向 z 軸正值的方向. 這是指法向量方向指向觀察者. 我們要把它指向正確的方向. 在背面的面, 法向量方向會遠離觀察者, 進入畫面. 再次指向我們所要的正確方向. 如果立方體繞著 x 或 y 軸旋轉 180 度, 則原本正面的面就會朝向畫面裡, 原本背面的面就會朝向觀察者. 不論哪一個面朝向觀察者, 那個平面的法向量也就會指向觀察者. 因為光源靠近觀察者, 任何時候當法向量指向觀察者時, 它也會同時指向光源. 這時候, 這個面就會被照亮. 法向量越是指向光源時, 這個面就會更加的亮. 如果你移進立方體中心時, 你會注意到它是暗的. 法向量是指向外面, 而不是裡面, 所以立方體內部是沒有光線的, 這應該是正確的.
 
     
		// Front Face
		glNormal3f( 0.0f, 0.0f, 1.0f);					// Normal Pointing Towards Viewer
		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)
		// Back Face
		glNormal3f( 0.0f, 0.0f,-1.0f);					// Normal Pointing Away From Viewer
		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)
		// Top Face
		glNormal3f( 0.0f, 1.0f, 0.0f);					// Normal Pointing Up
		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)
		// Bottom Face
		glNormal3f( 0.0f,-1.0f, 0.0f);					// Normal Pointing Down
		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)
		// Right face
		glNormal3f( 1.0f, 0.0f, 0.0f);					// Normal Pointing Right
		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)
		// Left Face
		glNormal3f(-1.0f, 0.0f, 0.0f);					// Normal Pointing Left
		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();								// Done Drawing Quads
     
  下面這兩行藉由 xspeedyspeed 來增加 xrotyrot 的值. 如果 xspeedyspeed 的值很高的話, xrotyrot 就會增加的很快. xrotyrot 增加的很快時, 立方體就會旋轉的很快.  
     
	xrot+=xspeed;								// Add xspeed To xrot
	yrot+=yspeed;								// Add yspeed To yrot
	return TRUE;								// Keep Going
}
     
  現在我們進入 WinMain(). 增加程式碼來開關光源, 旋轉木箱, 改變濾鏡, 以及移動木箱進出畫面. 在靠近 WinMain() 尾端的地方, 你將看到一個指令 SwapBuffers(hDC). 緊接著在這行的後面, 加入以下的程式碼.

這個程式碼會檢查看看是否鍵盤上的 'L' 字母被按下. 如果 'L' 被按下, 但是 lp 不是 false 時, 那表示 'L' 已經被按下過了, 或是它一直被按下, 那就不會發生任何事.
 
     
				SwapBuffers(hDC);				// Swap Buffers (Double Buffering)
				if (keys['L'] && !lp)				// L Key Being Pressed Not Held?
				{
     
  如果 lp 是 false, 表示 'L' 鍵還沒有被按下, 或是它已經被放開了, lp 就會設為 true. 這會強迫使用者在程式再次執行時已放掉 'L' 鍵. 如果我們不檢查這個鍵是否正在被按下, 那光源就會閃爍著一開一關, 因為程式執行到這兒時, 會認為你每次一再的按著 'L' 鍵.

一但 lp 被設為 true, 告訴電腦說 'L' 正被按下, 我們就開關光源. light 變數只可以設定為 true 或 false. 所以假設我們寫 light=!light, 實際上的說法就是 light 等於非 light. 用國語說就是如果 light 等於 true 時, 那就讓 light 為非 true (false), 如果 light 等於 false 時, 那就讓 light 為非 false (true). 所以如果 light 是 true, 它就會變為 false, 如果 light 是 false, 它就會變為 true.
 
     
					lp=TRUE;				// lp Becomes TRUE
					light=!light;				// Toggle Light TRUE/FALSE
     
  現在我們檢查 light 到底是什麼值. 第一行翻譯成國語就是: 如果 light 等於 false, 就取消光源. 它會關掉所有的光源. 指令 'else' 翻譯成國語就是: 如果不是 false. 所以如果 light 不是 false, 那它就是 true, 所以我們就開啟光源了.  
     
					if (!light)				// If Not Light
					{
						glDisable(GL_LIGHTING);		// Disable Lighting
					}
					else					// Otherwise
					{
						glEnable(GL_LIGHTING);		// Enable Lighting
					}
				}
     
  下面這一行檢查我們是否不再按下 'L' 按鍵. 如果是的話, 就把 lp 變數設為 false, 也就是指 'L' 按鍵沒有被按下了. 如果我們不檢查看看這個按鍵是否已經被放開了, 那只要一但光源被打開, 電腦就會認為 'L' 一直被按下, 所以我們就不能把光源關掉.  
     
				if (!keys['L'])					// Has L Key Been Released?
				{
					lp=FALSE;				// If So, lp Becomes FALSE
				}
     
  現在我們對 'F' 做類似的工作. 如果這個按鍵被按下, 而且它還沒有正在被按下或是它之前從沒有被按下過, 那麼 fp 變數就會等於 true, 表示這個按鍵正被按下. 它就會增加變數 filter 的值. 如果 filter 大於二 (也就是 texture[3], 不過這個編號的貼圖並不存在), 我們就重新設定 filter 變數為零.  
     
				if (keys['F'] && !fp)				// Is F Key Being Pressed?
				{
					fp=TRUE;				// fp Becomes TRUE
					filter+=1;				// filter Value Increases By One
					if (filter>2)				// Is Value Greater Than 2?
					{
						filter=0;			// If So, Set filter To 0
					}
				}
				if (!keys['F'])					// Has F Key Been Released?
				{
					fp=FALSE;				// If So, fp Becomes FALSE
				}
     
  下列這四行檢查看看我們是否壓下 'Page Up' 按鍵. 如果有的話, 就減少 z 的值. 如果這個變數減少時, 立方體就會移入畫面的遠處, 因為在 DrawGLScene 程序中用了 glTranslatef(0.0f,0.0f,z) 命令.  
     
				if (keys[VK_PRIOR])				// Is Page Up Being Pressed?
				{
					z-=0.02f;				// If So, Move Into The Screen
				}
     
  下列這四行檢查看看我們是否壓下 'Page Down' 按鍵. 如果有的話, 就增加 z 的值, 而立方體就會移向觀察者, 因為在 DrawGLScene 程序中用了 glTranslatef(0.0f,0.0f,z) 命令.  
     
				if (keys[VK_NEXT])				// Is Page Down Being Pressed?
				{
					z+=0.02f;				// If So, Move Towards The Viewer
				}
     
  現在我們必須檢查方向鍵. 按下左右鍵時, xspeed 會增加或減少. 記得在這課的前半部我有提到過, 如果 xspeedyspeed 的值很高時, 立方體就會轉的較快. 方向鍵按的越久, 立方體就會往那個方向轉的越快.  
     
				if (keys[VK_UP])				// Is Up Arrow Being Pressed?
				{
					xspeed-=0.01f;				// If So, Decrease xspeed
				}
				if (keys[VK_DOWN])				// Is Down Arrow Being Pressed?
				{
					xspeed+=0.01f;				// If So, Increase xspeed
				}
				if (keys[VK_RIGHT])				// Is Right Arrow Being Pressed?
				{
					yspeed+=0.01f;				// If So, Increase yspeed
				}
				if (keys[VK_LEFT])				// Is Left Arrow Being Pressed?
				{
					yspeed-=0.01f;				// If So, Decrease yspeed
				}
     
  就像前面幾個課程一樣, 要確定視窗上面的標題列是正確的.  
     
				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
					}
				}
			}
		}
	}

	// Shutdown
	KillGLWindow();								// Kill The Window
	return (msg.wParam);							// Exit The Program
}
     
  在這一課的最後, 你應該能夠建立出有互動性, 高品質, 看起來很真實, 用四邊形建構的具有材質貼圖的物件了. 你應該了解這一課中三種濾鏡的優點. 以按下鍵盤上特定的按鍵, 你應該可以和畫面中的物件做出互動, 而最後你應該知道怎麼樣在場景中套用簡單的光源, 已使得建構出的場景看起來更加的真實.

Jeff Molofee (NeHe)

* 下載 Visual C++ 程式碼給本課程的.
* 下載 Visual Fortran 程式碼給本課程的. ( Conversion by Jean-Philippe Perois )
* 下載 Delphi 程式碼給本課程的. ( Conversion by Brad Choate )
* 下載 Linux 程式碼給本課程的. ( Conversion by Richard Campbell )
* 下載 Irix 程式碼給本課程的. ( Conversion by Lakmal Gunasekara )
* 下載 Solaris 程式碼給本課程的. ( Conversion by Lakmal Gunasekara )
* 下載 Mac OS 程式碼給本課程的. ( Conversion by Anthony Parker )
* 下載 Power Basic 程式碼給本課程的. ( Conversion by Angus Law )
* 下載 BeOS 程式碼給本課程的. ( Conversion by Chris Herborth )
* 下載 Java 程式碼給本課程的. ( Conversion by Darren Hodges )
* 下載 MingW32 & Allegro 程式碼給本課程的. ( Conversion by Peter Puck )
* 下載 Borland C++ Builder 4.0 程式碼給本課程的. ( Conversion by Patrick Salmons )
 
     
 
Back To NeHe Productions!
回到 OpenGL 教學索引
中文版由 Macbear 翻譯