歡迎來到NeHe教程第38課。離上節課的寫作已經有些時日了,加上寫了一整天的code,也許筆頭已經開始生銹了 :)
現在你已經學會了如何做方格貼圖,如何讀入bitmap及各種光柵圖像...那么如何做三角形貼圖,又如何在.exe文件中體現你的紋理呢?
我每每被問及這兩個問題,可是一旦你看到他們是多么簡單,你就會大罵自己居然沒有想到過 :)
我不會事無巨細地解釋每一個細節,只需給你一些抓圖,就明白了。我將基于最新的code,請在主頁"NeHeGL I Basecode"下或者這張網頁最下面下載。
首先,我們把圖像加載入資源文件。我向大家已經知道怎么做了,只是,你忽略了幾步,于是值得到一些無用的資源文件。里面有bitmap文件,卻無法使用。
還記得吧?我們使用Visual C++ 6.0 做的。如果你使用其它工具,這頁教材關于資源的部分(尤其是那些圖)完全不適用。
* 暫時你只能用24bit BMP 圖像。如果讀8bit BMP文件要寫很多額外的code。我很希望聽到你們誰有更小的/更好的loader。我這里的讀入8bit 和 24bit BMP 的code實在臃腫。用LoadImage就可以。
打開文件,點擊“插入”菜單,選“資源”
然后選擇你要插入的資源類型BITMAP文件,單擊"插入"
然后是文件窗口,進入DATA目錄,選中三個圖形文件(用Ctrl啦)然后點“讀入”。注意文件類型是否正確。
接下來會彈出三次警告(一個文件一次),說讀入正確,但該文件不能被瀏覽或編輯,因為它有多于256種顏色。沒什么的!
一旦所有圖形都調入,將會出現一個列表。每個圖分配有一個ID,每個ID都是IDB_BITMAP打頭的,然后數字1-3。你要是懶得改,就不用管它了。不過我們還都比較勤快!
右健單擊每個ID,選"屬性",然后重命名,使之與文件名匹配。就像我圖片上那樣。
接下來,選“文件--〉全部保存”。你剛剛創建一個新的資源文件,所以Windows會問你取什么名字。你隨便拉,也可以叫"lesson38.rc" , 然后保存。
到此為止,你有了一個資源文件,里面全是保存在硬盤上的Bitmap 圖形文件,要使用這些文件,你還需要完成一系列步驟。
接下來該把資源文件加到你自己的項目里面了。選“項目--〉添加到項目--〉文件”
選擇resorce.h文件和資源文件Lesson38.rc(用Ctrl)
最后確認資源文件Lesson38.rc放入RESOURCE FILES文件夾。就像上面圖片里那樣,點擊并拖入RESOURCE FILES文件夾就好了。
移動之后選“文件--〉全部保存”,然后文件部分就好了。好多的圖阿:)
然后我們開始code的部分。下面一段最重要的一行是#include "resource.h".沒有這行,編譯的時候就會有無數未定義變量的錯誤。resource.h文件定義了資源文件里的對象。所以要從IDB_BUTTERFLY1里面讀取數據的話,最好include這個頭文件 !
#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <gl\glaux.h>
#include "NeHeGL.h"
#include "resource.h" // 資源文件的頭文件
#pragma comment( lib, "opengl32.lib" )
#pragma comment( lib, "glu32.lib" )
#pragma comment( lib, "glaux.lib" )
GL_Window* g_window;
Keys* g_keys;
下面一段第一行分配三個紋理所需空間,接下來的結構體用于保存關于約50個在屏幕上運動的物體的信息。
tex將跟蹤每個物體所用紋理,x是物體的x坐標,y是y坐標,z,z坐標,yi是一個隨機數用來控制物體下落速度,
spinz用來控制沿z軸的旋轉,spinzi是另一個隨機樹,記錄旋轉速度。flap用來控制物體的翅膀(一會在解釋這個)隨機數fi控制翅膀拍打的速度。
// 定義三個保存紋理變量的ID
GLuint texture[3]; // 保存三個紋理
struct object // 定義一個物體
{
int tex; // 紋理值
float x; // 位置
float y;
float z;
float yi; // 速度
float spinz; // 沿Z軸旋轉的角度和速度
float spinzi;
float flap; // 是否翻轉三角形
float fi;
};
object obj[50]; // 創建50個物體
下面一段代碼是物體obj[loop]的初始化,loop從0到49(表示50個物體中的一個)。首先是隨機紋理從0到2表示
一個隨機著色的蝴蝶。x坐標隨機的取-17.0f到+17.0f之間的值,y取18.0f,也就是從屏幕的上面一點點開始,
這樣一開始時看不到物體的。z也是-10.0f到-40.f之間的隨機數,spinzi取-1.0f到1.0f之間。flap取翅膀中心
位置,為0.0f。最后拍打速度fi和下落速度yi也是隨機的。
void SetObject(int loop) // 循環設置50個物體
{
obj[loop].tex=rand()%3; // 紋理
obj[loop].x=rand()%34-17.0f; // 位置
obj[loop].y=18.0f;
obj[loop].z=-((rand()%30000/1000.0f)+10.0f);
obj[loop].spinzi=(rand()%10000)/5000.0f-1.0f; // 旋轉
obj[loop].flap=0.0f;
obj[loop].fi=0.05f+(rand()%100)/1000.0f;
obj[loop].yi=0.001f+(rand()%1000)/10000.0f;
}
這回該到了最有意思的地方了。從資源文件中讀入bitmap,轉為紋理。hBMP是指向這個bitmap文件的指針,
它將告訴我們的程序從哪里讀取數據。BMP是一個bitmap結構體,我們把從資源文件中讀取的數據保存在里面。
第三行是告訴我們的程序我們將使用哪些ID:IDB_BUTTERFLY1,IDB_BUTTERFLY2,IDB_BUTTERFLY3。要用更多
的圖像的話,只需增加資源文件中的圖像,并在Texture[]中增加新的ID。
void LoadGLTextures() // 資源文件中讀入bitmap,轉為紋理
{
HBITMAP hBMP; // 位圖句柄
BITMAP BMP; // 位圖結構
// 紋理句柄
byte Texture[]={ IDB_BUTTERFLY1, IDB_BUTTERFLY2, IDB_BUTTERFLY3 };
下面一行使用sizeof(Texture)來計算要創建多少個紋理。我們有3個ID,也就是3個紋理。
glGenTextures(sizeof(Texture), &texture[0]); // 創建三個紋理
for (int loop=0; loop<sizeof(Texture); loop++) // 循環載入所有的位圖
{
LoadImage需要如下參數:GetModuleHandle(NULL)-指向實例的句柄,MAKEINTRESOURCE(Texture[loop])-把Texture[loop]從整型轉為一個資
源值,也就是要讀的圖形文件。IMAGE_BITMAP-告訴我們要讀的是一個bitmap文件。
接下來兩個參數(0,0)是讀入圖像的高度和寬度像素數,使用默認大小就設為0。
最后一個參數(LR_CREATEDIBSECTION)返回DIB section bitmap??這是一個沒有保存顏色信息的bitmap。也正是我們需要的。
hBMP 指向從LoadImage()讀入的bitmap數據。
hBMP=(HBITMAP)LoadImage(GetModuleHandle(NULL),MAKEINTRESOURCE(Texture[loop]), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
檢查指針hBMP是否有效,即指向有用數據。如果沒有指向任何數據,也可以彈出錯誤提示。
如果數據存在,用GetObject()從hBMP取得數據(sizeof(BMP))并存儲在BMP中。
glPixelStorei告訴OpenGL這些數據是以word alignments存儲的,也就是每像素4字節。
綁定紋理,設置濾波方式為GL_LINEAR_MIPMAP_LINEAR(又好又光滑),然后生成紋理。
注意到我們使用BMP.bmWidth和BMP.bmHeight獲取圖像的高度和寬度。并用GL_BGR_EXT交換紅藍,實際使用的資源數據是從BMP.bmBits中取得的
。
最后刪除bitmap對象,釋放所有與之相聯系的系統資源空間。
if (hBMP) // 位圖是否存在
{ // 存在
GetObject(hBMP,sizeof(BMP), &BMP); // 獲得位圖
glPixelStorei(GL_UNPACK_ALIGNMENT,4); // 以四字節方式對其內存
glBindTexture(GL_TEXTURE_2D, texture[loop]); // 綁定位圖
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 設置紋理過濾器
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
// 創建紋理
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, BMP.bmWidth, BMP.bmHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits);
DeleteObject(hBMP); // 刪除位圖對象
}
}
}
init code沒有什么新鮮的,只是增加了LoadGLTextures()調用上面的code。清屏的顏色是黑色,不進行深度檢測,這樣比較快。啟用紋理映
射和混色效果。
BOOL Initialize (GL_Window* window, Keys* keys) // 初始化
{
g_window = window;
g_keys = keys;
LoadGLTextures(); //載入紋理
glClearColor (0.0f, 0.0f, 0.0f, 0.5f); // 設置背景
glClearDepth (1.0f);
glDepthFunc (GL_LEQUAL);
glDisable(GL_DEPTH_TEST); // 啟用深度測試
glShadeModel (GL_SMOOTH);
glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glEnable(GL_TEXTURE_2D); // 啟用2D紋理
glBlendFunc(GL_ONE,GL_SRC_ALPHA); // 使用混合
glEnable(GL_BLEND);
初始化所有的物體
for (int loop=0; loop<50; loop++)
{
SetObject(loop);
}
return TRUE; // 成功返回
}
void Deinitialize (void)
{
}
void Update (DWORD milliseconds) // 更新,執行動畫
{
if (g_keys->keyDown [VK_ESCAPE] == TRUE) // 按ESC退出
{
TerminateApplication (g_window);
}
if (g_keys->keyDown [VK_F1] == TRUE) // 按F1切換顯示模式
{
ToggleFullscreen (g_window);
}
}
接下來看看繪制代碼。在這部分我將講解如何用盡可能簡單的方式將一個圖像映到兩個三角形上。有些人認為有理由相信,一個圖像到三角形
上的單一映射是不可能的。
實際上,你可以輕而易舉地將圖像映到任何形狀的區域內。使得圖像與邊界匹配或者完全不考慮形式。根本沒關系的。(譯者:我想作者的意
思是,從長方形到三角形的解析影射是不存在的,但不考慮那么多的話,任意形狀之間的連續影射總是可以存在的。他說的使紋理與邊界匹配
,大概是指某一種參數化的方法,簡單地說使得扭曲最小。)
首先清屏,循環潤色50個蝴蝶對象。
void Draw (void) // 繪制場景
{
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
for (int loop=0; loop<50; loop++)
{
調用glLoadIdentify()重置投影矩陣,然后選擇對象的紋理。用glTranslatef()為蝴蝶定位,然后沿x軸旋轉45度。使之向觀眾微略傾斜,這
樣比較有立體感。最后沿z軸旋轉,蝴蝶就旋轉下落了。
glLoadIdentity (); // 重置矩陣
glBindTexture(GL_TEXTURE_2D, texture[obj[loop].tex]); // 綁定紋理
glTranslatef(obj[loop].x,obj[loop].y,obj[loop].z); // 繪制物體
glRotatef(45.0f,1.0f,0.0f,0.0f);
glRotatef((obj[loop].spinz),0.0f,0.0f,1.0f);
其實到三角形上的映射和到方形上并沒有很大區別。只是你只有三個定點,要小心一點。
下面的code中,我們將會值第一個三角形。從一個設想的方形的右上角開始,到左上角,再到左下角。潤色的結果像下面這樣:
注意半個蝴蝶出現了。另外半個出現在第二個三角形里。同樣地將三個紋理坐標與頂點坐標非別對應,這給出充分的信息定義一個三角形上的映射。
glBegin(GL_TRIANGLES);
glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f,1.0f); glVertex3f(-1.0f, 1.0f, obj[loop].flap);
glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
下面的code潤色另一半。同上,只是我們的三角變成了從右上到左下,再到右下。
第一個三角形的第二點和第二個三角形的第三點(也就是翅膀的尖端)在z方向往復運動(即z=-1.0f和1.0f之間),兩個三角形沿著蝴蝶的身
體折疊起來,產生拍打的效果,簡易可行。
glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f,0.0f); glVertex3f( 1.0f,-1.0f, obj[loop].flap);
glEnd();
下面一段通過從obj[loop].y中遞減obj[loop].yi使蝴蝶自上而下運動。spinz值遞增spinzi(可正可負)flap遞增fi.fi的正負取決于翅膀向
上還是向下運動。
//移動,選擇圖像
obj[loop].y-=obj[loop].yi;
obj[loop].spinz+=obj[loop].spinzi;
obj[loop].flap+=obj[loop].fi;
當蝴蝶向下運行時,需要檢查是否越出屏幕,如果是,就調用SetObject(loop)來給蝴蝶賦新的紋理,新下落速度等。
if (obj[loop].y<-18.0f) //判斷是否超出了屏幕,如果是重置它
{
SetObject(loop);
}
翅膀拍打的時候,還要檢查flap是否小于-1.0f或大于1.0f,如果是,令fi=-fi,以改變運動方向。Sleep(15)是用來減緩運行速度,每幀15毫
秒。在我朋友的機器上,這讓蝴蝶瘋狂的飛舞。不過我懶得改了:)
if ((obj[loop].flap>1.0f) || (obj[loop].flap<-1.0f))
{
obj[loop].fi=-obj[loop].fi;
}
}
Sleep(15);
glFlush ();
}
希望你在這一課學的開心。也希望通過這一課,從資源文件里讀取紋理,和三角形映射的過程變得比較容易理解。我花五分鐘又沖讀了一遍,
感覺還好。如果你還有什么問題,盡管問。我希望我的講義盡可能好,因此期待您的任何回應。