學(xué)習(xí)紋理貼圖是十分有用的。 假如我們想使一個導(dǎo)彈飛過屏幕, 用前面的知識我們大概只能用多邊形來構(gòu)造。 而使用紋理映射, 我們完全可以讓一個真實(shí)的導(dǎo)彈圖片飛過屏幕。 一張照片和一個用多邊形構(gòu)造的物體, 你認(rèn)為哪一個會更好呢? 使用紋理貼圖, 不僅能得到更好的視覺效果, 而且還能得到更好的運(yùn)行效率。 因?yàn)槭褂眉y理貼圖制作的導(dǎo)彈可以僅僅是一個四邊形, 而如果我們完全使用多邊形來構(gòu)造的話, 就有可能需要成百上千的多邊形了。 所以使用紋理映射的四邊形將為我們節(jié)省大量的運(yùn)算。
我們以第一篇教程的代碼作為基礎(chǔ), 首先在開始處增加5行新的代碼。 第一行新代碼是 #include <stdio.h>, 因?yàn)槲覀冎笠褂?/SPAN>fopen()。 還有三行新代碼是增加了三個浮點(diǎn)變量: xrot, yrot 和 zrot, 它們分別用于控制立方體在x,y和z軸上的旋轉(zhuǎn)。 最后一行新代碼是 GLuint texture[1], 用于存儲一個紋理對象(譯注:紋理對象是一個非零的無符號整數(shù))。 如果有更多的紋理對象要保存, 就需要修改數(shù)組元素的數(shù)目為相應(yīng)的紋理對象的數(shù)目。
#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
下面我們增加了一段新的代碼, 其作用是讀入位圖文件。 如果文件不存在的話, 將返回NULL值代表文件不能被讀取。 在我解釋代碼之前, 還要講一些關(guān)于紋理圖像的很重要的事情: 紋理圖像的寬度和高度必須是2的冪。 寬度或高度最小應(yīng)是64個像素, 為了兼容性,最多應(yīng)是256個像素。 如果圖像的寬度或高度不是64,128或256個像素, 就應(yīng)該重新調(diào)整圖像尺寸, 這是解決這一限制的方法。 但現(xiàn)在我們只使用標(biāo)準(zhǔn)的紋理尺寸。
(譯注: 紋理的最小尺寸限制和最大尺寸限制其實(shí)取決于具體的OpenGL實(shí)現(xiàn))
首先我們創(chuàng)建一個文件句柄, 并初始化為 NULL。
AUX_RGBImageRec *LoadBMP(char *Filename) // Loads A Bitmap Image
{
FILE *File=NULL; // File Handle
接下來我們檢測文件名是否合法, 因?yàn)槭褂?/SPAN>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
如果文件能打開證明其一定存在。 我們關(guān)閉文件, 然后返回 auxDIBImageLoad(Filename) 讀入的圖像數(shù)據(jù)。
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
}
下面這段代碼的作用是讀取位圖 (通過調(diào)用上面的代碼) 并轉(zhuǎn)換成紋理。
int LoadGLTextures() // Load Bitmaps And Convert To Textures
{
我們設(shè)置一個 Status 變量, 它用于記錄我們是否成功地讀取了位圖并建造了紋理。 我們將其初始化為FLASE (代表什么都沒有讀取和建造)。
int Status=FALSE; // Status Indicator
現(xiàn)在我們創(chuàng)建一個用于保存位圖的圖像記錄, 它將持有圖像的寬度, 高度和數(shù)據(jù)。
AUX_RGBImageRec *TextureImage[1]; // Create Storage Space For The Texture
首先清空圖像記錄。
memset(TextureImage,0,sizeof(void *)*1); // Set The Pointer To NULL
現(xiàn)在我們讀入位圖。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 將調(diào)用上面的LoadBMP(), 讀入 Data 目錄中的 NeHe.bmp。 如果順利, 圖像數(shù)據(jù)會保存在TextureImage[0] 中, Status 被設(shè)置為 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
現(xiàn)在我們已經(jīng)把圖像數(shù)據(jù)讀入了 TextureImage[0], 我們將用這個數(shù)據(jù)來建造紋理。 下面的第一行代碼glGenTextures(1, &texture[0]) 用于獲得一個未使用的紋理名稱。
第二行代碼glBindTexture(GL_TEXTURE_2D, texture[0]) 綁定一個紋理名稱到一個紋理對象。
(譯注:glBindTexture 即創(chuàng)建紋理對象, 又綁定紋理對象, 又使用紋理對象。 詳細(xì)內(nèi)容請看OpenGL紅皮書)
glGenTextures(1, &texture[0]); // Create The Texture
// Typical Texture Generation Using Data From The Bitmap
glBindTexture(GL_TEXTURE_2D, texture[0]);
現(xiàn)在我們建造實(shí)際的紋理(譯注:這一步是指定紋理數(shù)據(jù))。 下面這行代碼告知OpenGL 我們將定義一個2D紋理。 0代表圖像的詳細(xì)級別, 這通常為0 (譯注:這與多紋理貼圖有關(guān))。 3 指定數(shù)據(jù)成分, 因?yàn)槲覀兊膱D像是紅,綠,藍(lán)組成的, 所以為3。 TextureImage[0]->sizeX 是紋理寬度, 如果你知道具體的寬度數(shù)值, 可以直接寫在這兒, 不過我們這樣做更簡單。 TextureImage[0]->sizeY 是紋理高度。 0 是紋理邊界, 通常為 0。 GL_RGB 告知 OpenGL 我們的圖像數(shù)據(jù)是紅,綠,藍(lán)順序存儲的。GL_UNSIGNED_BYTE 表示圖像的數(shù)據(jù)類型是8位無符號整數(shù)。 最后TextureImage[0]->data 指定紋理數(shù)據(jù), 在這里指向TextureImage[0] 中保存的數(shù)據(jù)。
(譯注:詳細(xì)內(nèi)容請看OpenGL紅皮書)
// Generate The Texture
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
下面兩行代碼分別用于設(shè)置在放大 (GL_TEXTURE_MAG_FILTER) 和縮小 (GL_TEXTURE_MIN_FILTER) 紋理貼圖的時候所使用的過濾。 我通常將它們都設(shè)置為GL_LINEAR, 這使紋理貼圖在距離屏幕很遠(yuǎn)和很近的時候都能看起來很平滑。 當(dāng)然, 使用 GL_LINEAR 是要犧牲一些效率的, 所以如果你的系統(tǒng)較慢, 你可以使用 GL_NEAREST, 不過使用 GL_NEAREST 可能會出現(xiàn)一些鋸齒。 當(dāng)然你可以把它們結(jié)合起來使用, 放大用一種, 縮小用一種。
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);// Linear Filtering
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);// Linear Filtering
}
現(xiàn)在我們釋放掉存儲位圖數(shù)據(jù)的所有內(nèi)存。 首先檢查是否有位圖保存在TextureImage[0], 有的話檢查是否有數(shù)據(jù), 有即釋放掉, 最后釋放掉圖像結(jié)構(gòu), 確保所有內(nèi)存都釋放干凈了。
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
}
最后返回狀態(tài)。 如果一切順利, 變量Status 將為 TRUE; 否則為 FALSE。
return Status; // Return The Status
}
在InitGL中我添加了幾行新的代碼。 第一行 if (!LoadGLTextures()) 調(diào)用上面的代碼, 讀取位圖并建造紋理。 如果由于一些原因LoadGLTextures() 失敗了, 那么下面一行代碼將返回 FALSE。 如果讀入紋理成功, 那么我們就啟用(激活)紋理映射。 如果你忘記了啟用紋理映射, 物體看起來將是白色的。
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
}
現(xiàn)在我們繪制貼圖立方體。 頭兩行代碼都是原來的代碼, glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 用于清除屏幕為指定顏色和清除深度緩沖區(qū), 在這個例子中屏幕將被清除為黑色。 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
接下來的3行代碼用于在x,y和z軸上旋轉(zhuǎn)立方體, 旋轉(zhuǎn)的角度有分別由變量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
下面一行代碼用于選擇我們要使用的紋理。 如果想變換紋理, 就需要綁定另一個紋理。 有一點(diǎn)很重要, 那就是綁定紋理操作不能在glBegin() 和 glEnd() 之間進(jìn)行。 請注意我們是如何使用glBindTextures 來創(chuàng)建和選擇一個指定的紋理的。
glBindTexture(GL_TEXTURE_2D, texture[0]); // Select Our Texture
想要適當(dāng)?shù)赜成湟粋€紋理(貼圖)到一個四邊形上, 你必須確保它們的右上角,左上角,右下角和左下角都能正確地一一對應(yīng), 否則紋理有可能被倒置過來, 也有可能歪斜了, 或者根本沒貼上。
glTexCoord2f 的第一個參數(shù)是x坐標(biāo)(譯注:紋理坐標(biāo)。為了與物體坐標(biāo)相區(qū)分,紋理坐標(biāo)一般使用字母s和t代替x和y), 0.0f 就是紋理的左邊, 0.5f 就是紋理的中間, 1.0f 就是紋理的右邊。glTexCoord2f 的第二個參數(shù)是y坐標(biāo)(譯注:紋理坐標(biāo)), 0.0f 就是紋理的底邊, 0.5f 是中間, 而1.0f 就是紋理的頂邊(最上邊)。
(譯注:紋理坐標(biāo)范圍是0到1,詳細(xì)請看紅皮書)
現(xiàn)在我們知道, 紋理左上角的坐標(biāo)是0.0f, 1.0f, 而四邊形左上角的頂點(diǎn)坐標(biāo)是 -1.0f, 1.0f。 然后我們要做的就是匹配四邊形剩下的3個角。
你可以試著修改glTexCoord2f 的參數(shù), 把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();
現(xiàn)在我們給xrot,yrot 和 zrot 增量。 你可以試著修改這些增量值, 讓立方體旋轉(zhuǎn)的更快或者更慢。 或者, 把+改為-,讓其向反方向旋轉(zhuǎn)。
xrot+=0.3f; // X Axis Rotation
yrot+=0.2f; // Y Axis Rotation
zrot+=0.4f; // Z Axis Rotation
return true; // Keep Going
}
你應(yīng)該已經(jīng)較好地理解了紋理映射, 可以給自己的四邊形貼圖了。 如果你對2D紋理映射感覺有了信心, 你可以試著給立方體的6個面映射不同的紋理貼圖。
如果你理解了紋理坐標(biāo), 那么紋理映射是不難理解的。 如果此篇教程的某些地方你不明白, 告訴我, 我會重寫那一部分教程,或者,在email里給你答復(fù)。 祝你貼圖愉快:)
Jeff Molofee (NeHe)
(譯著) 作者在紋理坐標(biāo)這個關(guān)鍵的地方并沒有做多少敘述,但是由于這是非常基礎(chǔ)的,所以并無大礙。 如果你發(fā)現(xiàn)了什么問題或者疏漏, 請即時反饋給我, 這樣我就能做出相應(yīng)的補(bǔ)救或者更正。 十分歡迎你的支持和鼓勵, 那將會使我更有動力。