第六課
     
  學會如何材質貼圖會有很多好處. 當你想要畫一個飛彈飛過畫面, 在上一個課程之前, 我們會用彩色的多邊形來畫這個飛彈. 用了貼圖材質後, 你就可以用真實的圖片做出飛彈, 而讓圖片飛過畫面. 你想這樣看起來會比較好嗎? 用一張照片或是用三角形和正方形建出的物件? 使用貼圖材質, 不只是看起來更好, 也會讓程式執行起來更快. 用貼圖材質的飛彈只要用一個四方形越過畫面即可. 用多邊形做出的飛彈需要用數千百個多邊形才能做出來. 一個貼圖材質的四方形只會用到一點點的處理的功能.

讓我們先加入五行新的程式碼到第一課的程式開頭裡. 第一行新的程式碼是 #include <stdio.h>. 加入這個標頭檔可以讓我們用到一些檔案. 為了在以後的程式碼用 fopen(), 我們需要包含這一行. 然後我們加入三個新的浮點變數... xrot, yrot, 和 zrot. 這些變數用來將立方體旋轉 x 軸, y 軸, 和 z 軸. 最後一行 GLuint texture[1] 設定一個貼圖材質的儲存空間. 如果你要載入一個以上的貼圖材質, 那就把數字 1 設成你所要載入貼圖數量的值.
 
     
#include <windows.h>								// Header File For Windows
#include <stdio.h>								// Header File For Standard Input/Output ( NEW )
#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

GLfloat	xrot;									// X Rotation ( NEW )
GLfloat	yrot;									// Y Rotation ( NEW )
GLfloat	zrot;									// Z Rotation ( NEW )

GLuint	texture[1];								// Storage For One Texture ( NEW )

LRESULT	CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);				// Declaration For WndProc
     
  現在馬上在上面程式碼的後面, ReSizeGLScene() 的前面, 加入這一段的程式碼. 這個程式碼的工作就是載入點陣圖檔. 如果檔案不存在, 就傳回 NULL, 表示貼圖材質無法載入. 在我開始解釋這個程式碼之前, 有一些很重要的事, 就是你必須知道有關於你計畫用的貼圖材質圖檔的限制. 圖形的高度和寬度必須要是 2 的倍數. 高度和寬度最少必須是 64 個像素, 而為了相容性的原因, 也不要大於 256 個像素. 如果你要用的圖像寬高不是 64, 128 或 256 個像素, 那就用影像處理軟體先更改圖像的大小. 因為有這些限制, 以至於我們用到的貼圖材質都會遵循標準的大小.

建立一個檔案的 handle 是第一件事. 一個 handle 是一個用來識別資源的值, 所以我們的程式就可以使用它了. 我們會在一開始把 handle 設為 NULL.
 
     
AUX_RGBImageRec *LoadBMP(char *Filename)					// Loads A Bitmap Image
{
	FILE *File=NULL;							// File Handle
     
  接下來我們檢查來確認檔名是否正確被指定了. 人們可能會用 LoadBMP() 但卻沒有指定載入的檔名, 因此我們要檢查這個. 我們可不想試著去載入沒有的東西 :)  
     
	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
     
  如果我們可以開啟這個檔案, 那很明顯的檔案是存在的. 我們用 fclose(File) 關閉檔案, 然後傳回影像資料. auxDIBImageLoad(Filename) 會讀取資料.  
     
	if (File)								// Does The File Exist?
	{
		fclose(File);							// Close The Handle
		return auxDIBImageLoad(Filename);				// Load The Bitmap And Return A Pointer
	}
     
  如果我們無法開啟檔案, 那就傳回 NULL. 這表示檔案無法被載入. 隨後我們在程式中檢查看看檔案是否被載入. 如果沒有載入, 那我們就離開程式並顯示出錯誤訊息.  
     
	return NULL;								// If Load Failed Return NULL
}
     
  這一段程式碼會載入點陣圖 (呼叫上面的程式碼), 並把它轉入貼圖材質.  
     
int LoadGLTextures()								// Load Bitmaps And Convert To Textures
{
     
  我們將會設定一個叫做 Status 的變數. 我們會用這一個變數來追蹤在載入點陣圖和建立貼圖材質是否成功. 我們把 Status 的預設值設為 FALSE (也就是說還沒有貼圖被載入與建立).  
     
	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/NeHe.bmp") 會進入 LoadBMP() 的程式碼中. 檔案 NeHe.bmp 在 Data 目錄下的就會被載入了. 如果一切正常的話, 圖形資料就會被存入 TextureImage[0]. Status 就設為 TRUE, 接著就開始建立我們的貼圖材質.  
     
	// 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
     
  現在我們載入圖形資料到 TextureImage[0], 我們將用這個資料建立一個貼圖材質. 第一行 glGenTextures(1, &texture[0]) 會告訴 OpenGL 說我們想要建立一個貼圖材質 (增加這個數字當你有一個以上的貼圖材質時), 我們會把貼圖材質存入 texture[] 第 0 個位置. 記住在這個課程的一開始, 我們就用 GLuint texture[1] 建立了一個空間給貼圖材質. 雖然你也許會認為第一個貼圖材質應存放在 &texture[1] 而非 &texture[0], 不過並不是這樣的. 第一個正確的存放位置是 0. 如果我們想要兩個貼圖材質, 那就用 GLuint texture[2], 而第二個貼圖材質就存放在texture[1].

第二行 glBindTexture(GL_TEXTURE_2D, texture[0]) 告訴 OpenGL, texture[0] (第一個貼圖材質) 會是 2D 的貼圖材質. 2D 的貼圖材質會有高度 (Y 軸上的) 以及寬度 (X 軸上的). glBindTexture 的主要功能是指向 OpenGL 可用的記憶體. 在這個例子中, 我們將告訴 OpenGL 在 &texture[0] 有可用的記憶體. 當我們建立貼圖材質時, 它將會存放在這個記憶空間中. 基本上 glBindTexture() 指向的記憶體會存放貼圖材質.
 
     
		glGenTextures(1, &texture[0]);					// Create The Texture

		// Typical Texture Generation Using Data From The Bitmap
		glBindTexture(GL_TEXTURE_2D, texture[0]);
     
  接著我們會建立真正的貼圖材質. 以下這一行會告訴 OpenGL 貼圖材質將會是 2D 材質 (GL_TEXTURE_2D). 零表示圖像的精細等級, 這通常會設為 0. 第三個是資料元素數目. 因為圖形是由紅色, 綠色以及藍色資料所構成的, 那就有三個元素. TextureImage[0]->sizeX 是貼圖材質的寬度值. 如果你知道寬度, 你可以設在這兒, 但是讓電腦來算出這個值會簡單些. TextureImage[0]->sizey 是貼圖材質的高度值. 零是邊框值, 它通常會設為零. GL_RGB 告訴 OpenGL 我們所要用的圖像資料是依照紅色, 綠色, 藍色資料順序所構成的. GL_UNSIGNED_BYTE 是指圖像資料是用無號位元所構成的, 而最後... TextureImage[0]->data 是告訴 OpenGL 從哪裡來抓取貼圖材質的資料. 這個例子中, 它會指向的資料是存放在 TextureImage[0] 紀錄中.  
     
		// Generate The Texture
		glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
     
  下面兩行告訴 OpenGL 當圖像放大 (GL_TEXTURE_MAG_FILTER) 時或是延伸出原本的貼圖大小時要使用哪一種濾鏡, 或是當圖像縮小 (GL_TEXTURE_MIN_FILTER) 於原本的貼圖材質時. 我通常在這兩種都使用 GL_LINEAR. 這會讓貼圖在遠處看起來比較平順些, 而且在接近畫面上看也很平順. 使用 GL_LINEAR 需要花費許多處理器/顯示卡的工作, 所以如果你的系統很慢的話, 你或許該改用 GL_NEAREST. 一個使用 GL_NEAREST 濾鏡的貼圖材質, 在放大時看起來會一塊一塊的. 你也可以試著將這兩種結合使用. 在近處時用濾鏡, 而遠處時則不用.  
     
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);	// Linear Filtering
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);	// Linear Filtering
	}
     
  現在我們釋放任何之前已經存放點陣資料的記憶體. 我們會檢查看是否有點陣資料已經存放在 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
}
     
  我已經在 InitGL 加入幾行程式碼. 我將會重新貼上這整段的程式碼, 所以很容易看出來我加了哪幾行, 以及它們被加在何處. 第一行 if (!LoadGLTextures()) 會跳到上面的程序, 載入點陣圖檔並且由它建立一個貼圖材質. 如果 LoadGLTextures() 因為任何理由失敗, 那下一行的程式碼就會傳回 FALSE. 如果一切 OK, 貼圖材質會被建立, 我們就啟動 2D 貼圖. 如果你忘記啟動 2D 貼圖, 那你的物件通常看起來會是白色實體, 那絕對是不對的.  
     
int InitGL(GLvoid)								// All Setup For OpenGL Goes Here
{
	if (!LoadGLTextures())							// Jump To Texture Loading Routine ( NEW )
	{
		return FALSE;							// If Texture Didn't Load Return FALSE ( NEW )
	}

	glEnable(GL_TEXTURE_2D);						// Enable Texture Mapping ( NEW )
	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
	return TRUE;								// Initialization Went OK
}
     
  現在我們畫出貼圖立方體. 你可以用以下的程式碼來取代 DrawGLScene, 或是你可以新的程式碼到原本第一課的程式碼中. 這一段將有一堆的註解, 所以它會很容易懂. 前兩行 glClear() 和 glLoadIdentity() 是原本第一課的程式碼. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 會用 InitGL() 中所選的顏色來清除畫面. 這個例子中, 畫面會被清成藍色. 深度緩衝區也會被清除. 接著攝影機觀點會用 glLoadIdentity() 來重新設定.  
     
int DrawGLScene(GLvoid)								// Here's Where We Do All The Drawing
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			// Clear Screen And Depth Buffer
	glLoadIdentity();							// Reset The Current Matrix
	glTranslatef(0.0f,0.0f,-5.0f);						// Move Into The Screen 5 Units
     
  接下來的三行程式碼, 將立方體旋轉 x 軸, y 軸, 以及 z 軸. 旋轉每一軸的量值則由 xrot, yrot, 和 zrot 值來決定.  
     
	glRotatef(xrot,1.0f,0.0f,0.0f);						// Rotate On The X Axis
	glRotatef(yrot,0.0f,1.0f,0.0f);						// Rotate On The Y Axis
	glRotatef(zrot,0.0f,0.0f,1.0f);						// Rotate On The Z Axis
     
  下一行選擇我們所要用的貼圖材質. 如果在你的場景中會用到一個以上的貼圖材質, 那就用 glBindTexture(GL_TEXTURE_2D, texture[使用的貼圖材質編號]). 如果你要改變貼圖材質, 你就連結到新的貼圖材質. 有一點要注意的是, 你不可以在 glBegin() 和 glEnd() 之間做材質的連結動作, 你必須在 glBegin() 之前或是 glEnd() 之後做才可以. 注意到我們如何使用 glBindTextures 來指定選用哪一個貼圖材質.  
     
	glBindTexture(GL_TEXTURE_2D, texture[0]);				// Select Our Texture
     
  要正確的貼圖到方形上, 你必須確定貼圖的右上角要對應到方形的右上角, 貼圖的左上角要對應到方形的左上角, 貼圖的右下角要對應到方形的右下角, 然後貼圖的左下角要對應到方形的左下角. 如果貼圖的角落沒有對應到方形的相同角落時, 那圖像可能看起來會上下顛倒, 左右相反, 或是怪怪的.

glTexCoord2f 的第一個值是 X 軸座標. 0.0f 是貼圖材質的左界. 0.5f 是貼圖材質的中間. 1.0f 是貼圖材質的右界. 第二個值是 Y 軸座標. 0.0f 是貼圖材質的下界. 0.5f 是貼圖材質的中間. 1.0f 是貼圖材質的上界.

所以現在我們知道貼圖材質的左上座標 X 是 0.0f, Y 是 1.0f, 方形左上點的 X 是 -1.0f, Y 是 1.0f. 現在你所要做的就是把貼圖材質上其他三個點的座標對應到方形剩餘的三個角落上.

試著玩玩 glTexCoord2f 的 x, y 值. 把 1.0f 改為 0.5f, 那麼就只會畫出貼圖材質的左半部, 從 0.0f (左界) 到 0.5f (中間). 如果是把 0.0f 改為 0.5f, 那麼就只會畫出貼圖材質的右半部, 從 0.5f (中間) 到 1.0f (右界).
 
     
	glBegin(GL_QUADS);
		// Front Face
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// Top Left Of The Texture and Quad
		// Back Face
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// Bottom Left Of The Texture and Quad
		// Top Face
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
		// Bottom Face
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// Top Left Of The Texture and Quad
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
		// Right face
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// Top Left Of The Texture and Quad
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
		// Left Face
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// Bottom Left Of The Texture and Quad
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
	glEnd();
     
  現在我們增加 xrot, yrot 和 zrot 的值. 試著改變每個變數的值, 讓立方體轉得快或慢, 或是改變符號 + 為 -, 讓立方體往另一個方向旋轉.  
     
	xrot+=0.3f;								// X Axis Rotation
	yrot+=0.2f;								// Y Axis Rotation
	zrot+=0.4f;								// Z Axis Rotation
	return true;								// Keep Going
}
     
  現在你應該對材質貼圖有更多的了解. 你應該可以把任何方形表面貼上你所選的圖像材質. 只要你覺得對於 2D 貼圖的認識有足夠的信心, 就試著在立方體上加入六種不同的貼圖材質.

材質貼圖不會很難了解的, 只要你了解貼圖座標. 如果你在這個課程中任何地方有什麼問題, 讓我知道. 或許我會重寫這個課程的段落, 或許我會回電子郵件給你. 開心的建立你自己的貼圖場景吧 :)

Jeff Molofee (NeHe)

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