紋理映射:Jeff Molofee(NeHe) 的 OPENGL 教程-第六課

 

Jeff Molofee(NeHe) 的 OPENGL 教程

第六課

Translated by
CKER

Lesson 6 第六課

學習 texture map 紋理映射(貼圖)有很多好處。比方說您想讓一顆導彈飛過屏幕。根據前幾課的知識,我們最可行的辦法可能是很多個多邊形來構建導彈的輪廓並加上有趣的顏色。使用紋理映射,您可以使用真實的導彈圖像並讓它飛過屏幕。您覺得哪個更好看?照片還是一大堆三角形和四邊形?使用紋理映射的好處還不止是更好看,而且您的程序運行會更快。導彈貼圖可能只是一個飛過窗口的四邊形。一個由多邊形構建而來的導彈卻很可能包括成百上千的多邊形。很顯然,貼圖極大的節省了CPU時間。
現在我們在第一課的代碼開始處增加五行新代碼。新增的第一行是 #include <stdio.h> 。它允許我們對文件進行操作,爲了在後面的代碼中使用 fopen() ,我們增加了這一行。然後我們增加了三個新的浮點變量... xrot , yrot zrot 。這些變量用來使立方體繞X、Y、Z軸旋轉。最後一行 GLuint texture[1] 爲一個紋理分配存儲空間。如果您需要不止一個的紋理,應該將數字1改成您所需要的數字。
#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;
// 全屏標誌缺省設定成全屏模式

GLfloat xrot; // X 旋轉量 ( 新增 )
GLfloat yrot;
// Y 旋轉量 ( 新增 )
GLfloat zrot;
// Z 旋轉量 ( 新增 )

GLuint texture[1];
// 存儲一個紋理 ( 新增 )  

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// WndProc的定義

緊跟上面的代碼在 ReSizeGLScene() 之前,我們增加了下面這一段代碼。這段代碼用來加載位圖文件。如果文件不存在,返回 NULL 告知程序無法加載位圖。在我開始解釋這段代碼之前,關於用作紋理的圖像我想有幾點十分重要,並且您必須明白。此圖像的寬和高必須是2的n次方;寬度和高度最小必須是64象素;並且出於兼容性的原因,圖像的寬度和高度不應超過256象素。如果您的原始素材的寬度和高度不是64,128,256象素的話,使用圖像處理軟件重新改變圖像的大小。可以肯定有辦法能繞過這些限制,但現在我們只需要用標準的紋理尺寸。

首先,我們創建一個文件句柄。句柄是個用來鑑別資源的數值,它使程序能夠訪問此資源。我們開始先將句柄設爲 NULL
AUX_RGBImageRec *LoadBMP(char *Filename) // 載入位圖圖象
{
FILE *File=NULL;
// 文件句柄
接下來檢查文件名是否已提供。因爲 LoadBMP() 可以無參數調用,所以我們不得不檢查一下。您可不想什麼都沒載入吧.....:)
if (!Filename) // 確保文件名已提供。
{
return NULL;
// 如果沒提供,返回 NULL
}
接着檢查文件是否存在。下面這一行嘗試打開文件。
File=fopen(Filename,"r"); //嘗試打開文件
如果我們能打開文件的話,很顯然文件是存在的。使用 fclose(File) 關閉文件。 auxDIBImageLoad(Filename) 讀取圖象數據並將其返回。
if (File) // 文件存在麼?
{
fclose(File);
// 關閉句柄
return auxDIBImageLoad(Filename);
//載入位圖並返回指針
}
如果我們不能打開文件,我們將返回NULL。這意味着文件無法載入。程序在後面將檢查文件是否已載入。如果沒有,我們將退出程序並彈出錯誤消息。
return NULL; // 如果載入失敗,返回 NULL
}
下一部分代碼載入位圖(調用上面的代碼)並轉換成紋理。
int LoadGLTextures() // 載入位圖(調用上面的代碼)並轉換成紋理
{
然後設置一個叫做 Status 的變量。我們使用它來跟蹤是否能夠載入位圖以及能否創建紋理。 Status 缺省設爲 FALSE (表示沒有載入或創建任何東東)。
int Status=FALSE; // Status 狀態指示器
現在我們創建存儲位圖的圖像記錄。次記錄包含位圖的寬度、高度和數據。
AUX_RGBImageRec *TextureImage[1]; // 創建紋理的存儲空間
清除圖像記錄,確保其內容爲空。
memset(TextureImage,0,sizeof(void *)*1); // 將指針設爲 NULL
現在載入位圖,並將其轉換爲紋理。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 調用 LoadBMP() 的代碼。載入 Data 目錄下的 NeHe.bmp 位圖文件。如果一切正常,圖像數據將存放在 TextureImage[0] 中, Status 被設爲 TRUE ,然後我們開始創建紋理。
// 載入位圖,檢查有無錯誤,如果位圖沒找到則退出。
if (TextureImage[0]=LoadBMP("Data/NeHe.bmp"))
{
Status=TRUE;
// 將 Status 設爲 TRUE
現在使用中 TextureImage[0] 的數據創建紋理。第一行 glGenTextures(1, &texture[0]) 告訴OpenGL我們想生成一個紋理名字(如果您想載入多個紋理,加大數字)。值得注意的是,開始我們使用 GLuint texture[1] 來創建一個紋理的存儲空間,您也許會認爲第一個紋理就是存放在 &texture[1] 中的,但這是錯的。正確的地址應該是 &texture[0] 。同樣如果使用 GLuint texture[2] 的話,第二個紋理存放在 texture[1] 中。『譯者注:學C的,在這裏應該沒有障礙,數組就是從零開始的嘛。』

第二行 glBindTexture(GL_TEXTURE_2D, texture[0]) 告訴OpenGL將紋理名字 texture[0] 綁定到紋理目標上。2D紋理只有高度(在 Y 軸上)和寬度(在 X 軸上)。主函數將紋理名字指派給紋理數據。本例中我們告知OpenGL, &texture[0] 處的內存已經可用。我們創建的紋理將存儲在 &texture[0] 指向的內存區域。
glGenTextures(1, &texture[0]); // 創建紋理

// 使用來自位圖數據生成 的典型紋理

glBindTexture(GL_TEXTURE_2D, texture[0]);
下來我們創建真正的紋理。下面一行告訴OpenGL此紋理是一個2D紋理 ( GL_TEXTURE_2D )。數字代表圖像的詳細程度,通常就由它爲零去了。數字是數據的成分數。因爲圖像是由紅色數據,綠色數據,藍色數據三種組分組成。 TextureImage[0]->sizeX 是紋理的寬度。如果您知道寬度,您可以在這裏填入,但計算機可以很容易的爲您指出此值。 TextureImage[0]->sizey 是紋理的高度。數字是邊框的值,一般就是零。 GL_RGB 告訴OpenGL圖像數據由紅、綠、藍三色數據組成。
GL_UNSIGNED_BYTE 意味着組成圖像的數據是無符號字節類型的。最後... TextureImage[0]->data 告訴OpenGL紋理數據的來源。此例中指向存放在 TextureImage[0] 記錄中的數據。
// 生成紋理
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 )時OpenGL採用的濾波方式。通常這兩種情況下我都採用 GL_LINEAR 。這使得紋理從很遠處到離屏幕很近時都平滑顯示。使用 GL_LINEAR 需要CPU和顯卡做更多的運算。如果您的機器很慢,您也許應該採用 GL_NEAREST 。過濾的紋理在放大的時候,看起來斑駁的很『譯者注:馬賽克啦』。您也可以結合這兩種濾波方式。在近處時使用 GL_LINEAR ,遠處時 GL_NEAREST
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // 線形濾波
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
// 線形濾波
}
現在我們釋放前面用來存放位圖數據的內存。我們先查看位圖數據是否存放在處。如果是的話,再查看數據是否已經存儲。如果已經存儲的話,刪了它。接着再釋放 TextureImage[0] 圖像結構以保證所有的內存都能釋放。
if (TextureImage[0]) // 紋理是否存在
{
if (TextureImage[0]->data)
// 紋理圖像是否存在
{
free(TextureImage[0]->data);
// 釋放紋理圖像佔用的內存
}

free(TextureImage[0]);
// 釋放圖像結構
}
最後返回狀態變量。如果一切OK,變量 Status 的值爲 TRUE 。否則爲 FALSE
return Status; // 返回 Status
}
我只在 InitGL 中增加很少的幾行代碼。但爲了方便您查看增加了哪幾行,我這段代碼全部重貼一遍。 if (!LoadGLTextures()) 這行代碼調用上面講的子例程載入位圖並生成紋理。如果因爲任何原因 LoadGLTextures() 調用失敗,接着的一行返回FALSE。如果一切OK,並且紋理創建好了,我們啓用2D紋理映射。如果您忘記啓用的話,您的對象看起來永遠都是純白色,這一定不是什麼好事。
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
}
現在我們繪製貼圖『譯者注:其實貼圖就是紋理映射。將術語換來換去不好,我想少打倆字。^_^』過的立方體。這段代碼被狂註釋了一把,應該很好懂。開始兩行代碼 glClear() glLoadIdentity() 是第一課中就有的代碼。 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 清除屏幕並設爲我們在 InitGL() 中選定的顏色,本例中是黑色。深度緩存也被清除。模型觀察矩陣也使用glLoadIdentity()重置。
int DrawGLScene(GLvoid) // 從這裏開始進行所有的繪製
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 清除屏幕和深度緩存
glLoadIdentity();
// 重置當前的模型觀察矩陣
glTranslatef(0.0f,0.0f,-5.0f);
// 移入屏幕 5 個單位
下面三行使立方體繞X、Y、Z軸旋轉。旋轉多少依賴於變量 xrot yrot zrot 的值。
glRotatef(xrot,1.0f,0.0f,0.0f); // 繞X軸旋轉
glRotatef(yrot,0.0f,1.0f,0.0f);
// 繞Y軸旋轉
glRotatef(zrot,0.0f,0.0f,1.0f);
// 繞Z軸旋轉
下一行代碼選擇我們使用的紋理。如果您在您的場景中使用多個紋理,您應該使用來 glBindTexture(GL_TEXTURE_2D, texture[ 所使用紋理對應的數字 ]) 選擇要綁定的紋理。當您想改變紋理時,應該綁定新的紋理。有一點值得指出的是,您不能 glBegin() glEnd() 之間綁定紋理,必須在 glBegin() 之前或 glEnd() 之後綁定。注意我們在後面是如何使用 glBindTexture 來指定和綁定紋理的。
glBindTexture(GL_TEXTURE_2D, texture[0]); // 選擇紋理
爲了將紋理正確的映射到四邊形上,您必須將紋理的右上角映射到四邊形的右上角,紋理的左上角映射到四邊形的左上角,紋理的右下角映射到四邊形的右下角,紋理的左下角映射到四邊形的左下角。如果映射錯誤的話,圖像顯示時可能上下顛倒,側向一邊或者什麼都不是。

glTexCoord2f 的第一個參數是X座標。 0.0f 是紋理的左側。 0.5f 是紋理的中點, 1.0f 是紋理的右側。 glTexCoord2f 的第二個參數是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 將只顯示紋理的右半部分。
glBegin(GL_QUADS);
// 前面
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);
// 紋理和四邊形的左上
// 後面
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);
// 紋理和四邊形的左下
// 頂面
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); 
// 紋理和四邊形的右上
// 底面
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);
// 紋理和四邊形的右下
// 右面
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);
// 紋理和四邊形的左下
// 左面
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 , yrot zrot 的值。嘗試變化每次各變量的改變值來調節立方體的旋轉速度,或改變+/-號來調節立方體的旋轉方向。
xrot+=0.3f; // X 軸旋轉
yrot+=0.2f;
// Y 軸旋轉
zrot+=0.4f;
// Z 軸旋轉
return true;
// 繼續運行
}
現在您應該比較好的理解紋理映射(貼圖)了。您應該掌握了給任意四邊形表面貼上您所喜愛的圖像的技術。一旦您對2D紋理映射的理解感到自信的時候,試試給立方體的六個面貼上不同的紋理。

當您理解紋理座標的概念後,紋理映射並不難理解。!如果您有什麼意見或建議請給我EMAIL。如果您認爲有什麼不對或可以改進,請告訴我。
『譯者:NeHe的文檔似乎很簡單,似乎很羅嗦。但仔細想來這樣的高手您又見過幾個?還是那句話,我不是高手,希望您是,真誠的。』
下面是源代碼下載鏈接。祝您好運!
* DOWNLOAD Visual C++ Code For This Lesson.
* DOWNLOAD Visual C++ / OpenIL Code For This Lesson. ( Conversion by Denton Woods )
* DOWNLOAD Visual Basic Code For This Lesson. ( Conversion by Ross Dawson )
* DOWNLOAD Visual Basic Code For This Lesson. ( Conversion by Peter De Tagyos )
* DOWNLOAD GLUT Code For This Lesson. ( Conversion by Andy Restad )
* DOWNLOAD Cygwin (FREE Language) Code For This Lesson. ( Conversion by Stephan Ferraro )
* DOWNLOAD MacOS X/GLUT Code For This Lesson. ( Conversion by Raal Goff )
* DOWNLOAD Linux/GLX Code For This Lesson. ( Conversion by Mihael Vrbanec )
* DOWNLOAD Visual Fortran Code For This Lesson. ( Conversion by Jean-Philippe Perois )
* DOWNLOAD MASM Code For This Lesson. ( Conversion by Nico (Scalp) )
* DOWNLOAD Delphi Code For This Lesson. ( Conversion by Brad Choate )
* DOWNLOAD Linux Code For This Lesson. ( Conversion by Richard Campbell )
* DOWNLOAD Irix Code For This Lesson. ( Conversion by Lakmal Gunasekara )
* DOWNLOAD Solaris Code For This Lesson. ( Conversion by Lakmal Gunasekara )
* DOWNLOAD Mac OS Code For This Lesson. ( Conversion by Anthony Parker )
* DOWNLOAD Power Basic Code For This Lesson. ( Conversion by Angus Law )
* DOWNLOAD BeOS Code For This Lesson. ( Conversion by Chris Herborth )
* DOWNLOAD Java Code For This Lesson. ( Conversion by Darren Hodges )
* DOWNLOAD MingW32 & Allegro Code For This Lesson. ( Conversion by Peter Puck )
* DOWNLOAD Borland C++ Builder 4.0 Code For This Lesson. ( Conversion by Patrick Salmons )
* DOWNLOAD Python Code For This Lesson. ( Conversion by John Ferguson )
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章