這個(gè)教程有一些難度,但它會(huì)讓你學(xué)到很多東西。我聽(tīng)到很多朋友問(wèn)我擴(kuò)展方面的內(nèi)容和怎樣找到它們。這個(gè)教程將交給你這
一切。
我將教會(huì)你怎樣滾動(dòng)屏幕的一部分和怎樣繪制直線(xiàn),最重要的是從這一課起,我們將不使用AUX庫(kù),以及*.bmp文件。我將告訴你如何使用Targa(TGA)圖像文件。因?yàn)樗?jiǎn)單并且支持alpha通道,它可以使你更容易的創(chuàng)建酷的效果。
接下來(lái)我們要做的第一件事就是不包含glaux.h頭文件和glaux.lib庫(kù)。另外,在使用glaux庫(kù)時(shí),經(jīng)常會(huì)發(fā)生一些可疑的警告,現(xiàn)在我們可以測(cè)定告別它了。
#include <stdarg.h> // 處理可變參數(shù)的函數(shù)的頭文件
#include <string.h> // 處理字符串的頭文件
接下來(lái)我們添加一些變量,第一個(gè)為滾動(dòng)參數(shù)。第二給變量記錄擴(kuò)展的個(gè)數(shù),swidth和sheight記錄剪切矩形的大小。base為字體顯示列表的開(kāi)始值。
int scroll; // 用來(lái)滾動(dòng)屏幕
int maxtokens; // 保存擴(kuò)展的個(gè)數(shù)
int swidth; // 剪裁寬度
int sheight; // 剪裁高度
GLuint base; // 字符顯示列表的開(kāi)始值
現(xiàn)在我們創(chuàng)建一個(gè)數(shù)據(jù)結(jié)構(gòu)用來(lái)保存TGA文件,接著我們使用這個(gè)結(jié)構(gòu)來(lái)加載紋理。
typedef struct // 創(chuàng)建加載TGA圖像文件結(jié)構(gòu)
{
GLubyte *imageData; // 圖像數(shù)據(jù)指針
GLuint bpp; // 每個(gè)數(shù)據(jù)所占的位數(shù)(必須為24或32)
GLuint width; // 圖像寬度
GLuint height; // 圖像高度
GLuint texID; // 紋理的ID值
} TextureImage; // 結(jié)構(gòu)名稱(chēng)
TextureImage textures[1]; // 保存一個(gè)紋理
這個(gè)部分的代碼將要加載一個(gè)TGA文件并把它轉(zhuǎn)換為紋理。必須注意的是這部分代碼只能加載24/32位的不壓縮的TGA文件。
這個(gè)函數(shù)包含兩個(gè)參數(shù),一個(gè)保存載入的圖像,一個(gè)為將載入的文件名。
TGA文件包含一個(gè)12個(gè)字節(jié)的文件頭,載入圖像后,我們用type來(lái)設(shè)置圖像中像素格式在OpenGL中的對(duì)應(yīng)。如果是24位的圖像我們使用GL_RGB,如果是32位的圖像我們使用GL_RGBA。
bool LoadTGA(TextureImage *texture, char *filename) // 把TGA文件加載入內(nèi)存
{
GLubyte TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0}; // 無(wú)壓縮的TGA文件頭
GLubyte TGAcompare[12]; // 保存讀入的文件頭信息
GLubyte header[6]; // 保存最有用的圖像信息,寬,高,位深
GLuint bytesPerPixel; // 記錄每個(gè)顏色所占用的字節(jié)數(shù)
GLuint imageSize; // 記錄文件大小
GLuint temp; // 臨時(shí)變量
GLuint type=GL_RGBA; // 設(shè)置默認(rèn)的格式為GL_RGBA,即32位圖像
下面這個(gè)函數(shù)讀取TGA文件,并記錄文件信息。TGA文件格式如下所示:
Tga圖像格式
無(wú)顏色表 rgb 圖像
偏移 長(zhǎng)度 描述 32位常用圖像文件各個(gè)字節(jié)的值
0 1 指出圖像信息字段的長(zhǎng)度,其取值范圍是 0 到 255 ,當(dāng)它為 0 時(shí)表示沒(méi)有圖像的信息字段。 0
1 1 是否使用顏色表,0 表示沒(méi)有顏色表,1 表示顏色表存在 0
2 1 該字段總為 2。圖像類(lèi)型碼,tga一共有6種格式,2表示無(wú)顏色表 rgb 圖像 2
3 5 顏色表規(guī)格,總為0。 0
4 0
5 0
6 0
7 0
8 10 圖像規(guī)格說(shuō)明 開(kāi)始
8 2 圖像 x 坐標(biāo)起始位置,一般為0 0
9
10 2 圖像 y 坐標(biāo)起始位置,一般為0 0
11
12 2 圖像寬度,以像素為單位 256
13
14 2 圖像高度,以像素為單位 256
15
16 1 圖像每像素存儲(chǔ)占用位(bit)數(shù) 32
17 1 圖像描述符字節(jié)
bits 3-0 - 每像素對(duì)應(yīng)的屬性位的位數(shù),對(duì)于 TGA 24,該值為 0
bit 4 - 保留,必須為 0
bit 5 - 屏幕起始位置標(biāo)志,0 = 原點(diǎn)在左下角,1 = 原點(diǎn)在左上角
一般這個(gè)字節(jié)設(shè)為0x00即可
00100000(2)
18 可變 圖像數(shù)據(jù)域
這里存儲(chǔ)了(寬度)x(高度)個(gè)像素,每個(gè)像素中的 rgb 色值該色值包含整數(shù)個(gè)字節(jié) ...
如果一切順利,讀取文件后關(guān)閉文件。
FILE *file = fopen(filename, "rb"); // 打開(kāi)一個(gè)TGA文件
if( file==NULL || // 文件存在么?
fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) || // 是否包含12個(gè)字節(jié)的文件頭?
memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0 || // 是否是我們需要的格式?
fread(header,1,sizeof(header),file)!=sizeof(header)) // 如果是讀取下面六個(gè)圖像信息
{
if (file == NULL) // 文件不存在返回錯(cuò)誤
return false;
else
{
fclose(file); // 關(guān)閉文件返回錯(cuò)誤
return false;
}
}
下面的代碼記錄文件的寬度和高度,并判斷文件是否為24位/32位TGA文件。
texture->width = header[1] * 256 + header[0]; // 記錄文件高度
texture->height = header[3] * 256 + header[2]; // 記錄文件寬度
if( texture->width <=0 || // 寬度是否小于0
texture->height <=0 || // 高度是否小于0
(header[4]!=24 && header[4]!=32)) // TGA文件是24/32位?
{
fclose(file); // 如果失敗關(guān)閉文件,返回錯(cuò)誤
return false;
}
下面的代碼記錄文件的位深和加載它需要的內(nèi)存大小
texture->bpp = header[4]; // 記錄文件的位深
bytesPerPixel = texture->bpp/8; // 記錄每個(gè)象素所占的字節(jié)數(shù)
imageSize = texture->width*texture->height*bytesPerPixel; // 計(jì)算TGA文件加載所需要的內(nèi)存大小
下面的代碼為圖像數(shù)據(jù)分配內(nèi)存并載入它
texture->imageData=(GLubyte *)malloc(imageSize); // 分配內(nèi)存去保存TGA數(shù)據(jù)
if( texture->imageData==NULL || // 系統(tǒng)是否分配了足夠的內(nèi)存?
fread(texture->imageData, 1, imageSize, file)!=imageSize) // 是否成功讀入內(nèi)存?
{
if(texture->imageData!=NULL) // 是否有數(shù)據(jù)被加載
free(texture->imageData); // 如果是,則釋放載入的數(shù)據(jù)
fclose(file); // 關(guān)閉文件
return false; // 返回錯(cuò)誤
}
TGA文件中,顏色的存儲(chǔ)順序?yàn)锽GR,而OpenGL中顏色的順序?yàn)镽GB,所以我們需要交換每個(gè)象素中R和B的值。如果一切順利,TGA文件中的圖像數(shù)據(jù)將按照OpenGL的要求存儲(chǔ)在內(nèi)存中了。
for(GLuint i=0; i<int(imageSize); i+=bytesPerPixel) // 循環(huán)所有的像素
{ // 交換R和B的值
temp=texture->imageData[i];
texture->imageData[i] = texture->imageData[i + 2];
texture->imageData[i + 2] = temp;
}
fclose (file); // 關(guān)閉文件
下面的代碼創(chuàng)建一個(gè)紋理,并設(shè)置過(guò)濾方式為線(xiàn)性
// 創(chuàng)建紋理
glGenTextures(1, &texture[0].texID); // 創(chuàng)建紋理,并記錄紋理ID
glBindTexture(GL_TEXTURE_2D, texture[0].texID); // 綁定紋理
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 設(shè)置過(guò)濾器為線(xiàn)性過(guò)濾
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
判斷圖像的位數(shù)是否為24,如果是則設(shè)置類(lèi)型為GL_RGB
if (texture[0].bpp==24) // 是否為24位圖像?
{
type=GL_RGB; // 如果是設(shè)置類(lèi)型為GL_RGB
}
下面的代碼在OpenGL中創(chuàng)建一個(gè)紋理
glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData);
return true; // 紋理綁定完成,成功返回
}
下面的代碼是從圖像創(chuàng)建字體的典型的方法,這些代碼將包含在后面的課程中,以顯示文字。
只有一個(gè)不同的地方,紋理0用來(lái)保存字符圖像。
GLvoid BuildFont(GLvoid) // 創(chuàng)建字體顯示列表
{
base=glGenLists(256); // 創(chuàng)建256個(gè)顯示列表
glBindTexture(GL_TEXTURE_2D, textures[0].texID); // 綁定紋理
for (int loop1=0; loop1<256; loop1++) // 循環(huán)創(chuàng)建256個(gè)顯示列表
{
float cx=float(loop1%16)/16.0f; // 當(dāng)前字符的X位置
float cy=float(loop1/16)/16.0f; // 當(dāng)前字符的Y位置
glNewList(base+loop1,GL_COMPILE); // 開(kāi)始創(chuàng)建顯示列表
glBegin(GL_QUADS); // 創(chuàng)建一個(gè)四邊形用來(lái)包含字符圖像
glTexCoord2f(cx,1.0f-cy-0.0625f); // 左下方紋理坐標(biāo)
glVertex2d(0,16); // 左下方坐標(biāo)
glTexCoord2f(cx+0.0625f,1.0f-cy-0.0625f); // 右下方紋理坐標(biāo)
glVertex2i(16,16); // 右下方坐標(biāo)
glTexCoord2f(cx+0.0625f,1.0f-cy-0.001f); // 右上方紋理坐標(biāo)
glVertex2i(16,0); // 右上方坐標(biāo)
glTexCoord2f(cx,1.0f-cy-0.001f); // 左上方紋理坐標(biāo)
glVertex2i(0,0); // 左上方坐標(biāo)
glEnd(); // 四邊形創(chuàng)建完畢
glTranslated(14,0,0); // 向右移動(dòng)14個(gè)單位
glEndList(); // 結(jié)束創(chuàng)建顯示列表
}
}
下面的函數(shù)用來(lái)刪除顯示字符的顯示列表
GLvoid KillFont(GLvoid)
{
glDeleteLists(base,256); // 從內(nèi)存中刪除256個(gè)顯示列表
}
glPrint函數(shù)只有一點(diǎn)變化,我們?cè)赮軸方向把字符拉長(zhǎng)一倍
GLvoid glPrint(GLint x, GLint y, int set, const char *fmt, ...)
{
char text[1024]; // 保存我們的字符
va_list ap; // 指向第一個(gè)參數(shù)
if (fmt == NULL) // 如果要顯示的字符為空則返回
return;
va_start(ap, fmt); // 開(kāi)始分析參數(shù),并把結(jié)果寫(xiě)入到text中
vsprintf(text, fmt, ap);
va_end(ap);
if (set>1) // 如果字符集大于1則使用第二個(gè)字符集
{
set=1;
}
glEnable(GL_TEXTURE_2D); // 使用紋理映射
glLoadIdentity(); // 重置視口矩陣
glTranslated(x,y,0); // 平移到(x,y,0)處
glListBase(base-32+(128*set)); // 選擇字符集
glScalef(1.0f,2.0f,1.0f); // 沿Y軸放大一倍
glCallLists(strlen(text),GL_UNSIGNED_BYTE, text); // 把字符寫(xiě)入到屏幕
glDisable(GL_TEXTURE_2D); // 禁止紋理映射
}
窗口改變大小的函數(shù)使用正投影,把視口范圍設(shè)置為(0,0)-(640,480)
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
{
swidth=width; // 設(shè)置剪切矩形為窗口大小
sheight=height;
if (height==0) // 防止高度為0時(shí),被0除
{
height=1;
}
glViewport(0,0,width,height); // 設(shè)置窗口可見(jiàn)區(qū)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0f,640,480,0.0f,-1.0f,1.0f); // 設(shè)置視口大小為640x480
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
初始化操作非常簡(jiǎn)單,我們載入字體紋理,并創(chuàng)建字符顯示列表,如果順利,則成功返回。
int InitGL(GLvoid)
{
if (!LoadTGA(&textures[0],"Data/Font.TGA")) // 載入字體紋理
{
return false; // 載入失敗則返回
}
BuildFont(); // 創(chuàng)建字體
glShadeModel(GL_SMOOTH); // 使用平滑著色
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // 設(shè)置黑色背景
glClearDepth(1.0f); // 設(shè)置深度緩存中的值為1
glBindTexture(GL_TEXTURE_2D, textures[0].texID); // 綁定字體紋理
return TRUE; // 成功返回
}
繪制代碼幾乎是全新的:),token為一個(gè)指向字符串的指針,它將保存OpenGL擴(kuò)展的全部字符串,cnt紀(jì)錄擴(kuò)展的個(gè)數(shù)。
接下來(lái)清楚背景,并顯示OpenGL的銷(xiāo)售商,實(shí)現(xiàn)它的公司和當(dāng)前的版本。
int DrawGLScene(GLvoid)
{
char *token; // 保存擴(kuò)展字符串
int cnt=0; // 紀(jì)錄擴(kuò)展字符串的個(gè)數(shù)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清楚背景和深度緩存
glColor3f(1.0f,0.5f,0.5f); // 設(shè)置為紅色
glPrint(50,16,1,"Renderer");
glPrint(80,48,1,"Vendor");
glPrint(66,80,1,"Version");
下面的代碼顯示OpenGL實(shí)現(xiàn)方面的相關(guān)信息,完成之后我們用藍(lán)色在屏幕的下方寫(xiě)上“NeHe Productions”,當(dāng)然你可以使用任何你想使用的字符,比如"DancingWind Translate"。
glColor3f(1.0f,0.7f,0.4f); // 設(shè)置為橘黃色
glPrint(200,16,1,(char *)glGetString(GL_RENDERER)); // 顯示OpenGL的實(shí)現(xiàn)組織
glPrint(200,48,1,(char *)glGetString(GL_VENDOR)); // 顯示銷(xiāo)售商
glPrint(200,80,1,(char *)glGetString(GL_VERSION)); // 顯示當(dāng)前版本
glColor3f(0.5f,0.5f,1.0f); // 設(shè)置為藍(lán)色
glPrint(192,432,1,"NeHe Productions"); // 在屏幕的底端寫(xiě)上NeHe Productions字符串
現(xiàn)在我們繪制顯示擴(kuò)展名的白色線(xiàn)框方塊,并用一個(gè)更大的白色線(xiàn)框方塊把所有的內(nèi)容包圍起來(lái)。
glLoadIdentity(); // 重置模型變換矩陣
glColor3f(1.0f,1.0f,1.0f); // 設(shè)置為白色
glBegin(GL_LINE_STRIP);
glVertex2d(639,417);
glVertex2d( 0,417);
glVertex2d( 0,480);
glVertex2d(639,480);
glVertex2d(639,128);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2d( 0,128);
glVertex2d(639,128);
glVertex2d(639, 1);
glVertex2d( 0, 1);
glVertex2d( 0,417);
glEnd();
glScissor函數(shù)用來(lái)設(shè)置剪裁區(qū)域,如果啟用了GL_SCISSOR_TEST,繪制的內(nèi)容只能在剪裁區(qū)域中顯示。
下面的代碼設(shè)置窗口的中部為剪裁區(qū)域,并獲得擴(kuò)展名字符串。
glScissor(1 ,int(0.135416f*sheight),swidth-2,int(0.597916f*sheight)); // 定義剪裁區(qū)域
glEnable(GL_SCISSOR_TEST); // 使用剪裁測(cè)試
char* text=(char*)malloc(strlen((char *)glGetString(GL_EXTENSIONS))+1); // 為保存OpenGL擴(kuò)展的字符串分配內(nèi)存空間
strcpy (text,(char *)glGetString(GL_EXTENSIONS)); // 返回OpenGL擴(kuò)展字符串
下面我們創(chuàng)建一個(gè)循環(huán),循環(huán)顯示每個(gè)擴(kuò)展名,并紀(jì)錄擴(kuò)展名的個(gè)數(shù)
token=strtok(text," "); // 按空格分割text字符串,并把分割后的字符串保存在token中
while(token!=NULL) // 如果token不為NULL
{
cnt++; // 增加計(jì)數(shù)器
if (cnt>maxtokens) // 紀(jì)錄最大的擴(kuò)展名數(shù)量
{
maxtokens=cnt;
}
現(xiàn)我們已經(jīng)獲得第一個(gè)擴(kuò)展名,下一步我們把它顯示在屏幕上。
我們已經(jīng)顯示了三行文本,它們?cè)赮軸上占用了3*32=96個(gè)像素的寬度,所以我們顯示的第一個(gè)行文本的位置是(0,96),一次類(lèi)推第i行文本的位置是(0,96+(cnt*32)),但我們需要考慮當(dāng)前滾動(dòng)過(guò)的位置,默認(rèn)為向上滾動(dòng),所以我們得到顯示第i行文本的位置為(0,96+(cnt*32)=scroll)。
當(dāng)然它們不會(huì)都顯示出來(lái),記得我們使用了剪裁,只顯示(0,96)-(0,96+32*9)之間的文本,其它的都被剪裁了。
更具我們上面的講解,顯示的第一個(gè)行如下:
1 GL_ARB_multitexture
glColor3f(0.5f,1.0f,0.5f); // 設(shè)置顏色為綠色
glPrint(0,96+(cnt*32)-scroll,0,"%i",cnt); // 繪制第幾個(gè)擴(kuò)展名
glColor3f(1.0f,1.0f,0.5f); // 設(shè)置顏色為黃色
glPrint(50,96+(cnt*32)-scroll,0,token); // 輸出第i個(gè)擴(kuò)展名
當(dāng)我們顯示完所有的擴(kuò)展名,我們需要檢查一下是否已經(jīng)分析完了所有的字符串。我們使用strtok(NULL," ")函數(shù)代替strtok(text," ")函數(shù),把第一個(gè)參數(shù)設(shè)置為NULL會(huì)檢查當(dāng)前指針位置到字符串末尾是否包含" "字符,如果包含返回其位置,否則返回NULL。
我們舉例說(shuō)明上面的過(guò)程,例如字符串"GL_ARB_multitexture GL_EXT_abgr GL_EXT_bgra",它是以空格分割字符串的,第一次調(diào)用strtok("text"," ")返回text的首位置,并在空格" "的位置加入一個(gè)NULL。以后每次調(diào)用,刪除NULL,返回空格位置的下一個(gè)位置,接著搜索下一個(gè)空格的位置,并在空格的位置加入一個(gè)NULL。直道返回NULL。
返回NULL時(shí)循環(huán)停止,表示已經(jīng)顯示完所有的擴(kuò)展名。
token=strtok(NULL," "); // 查找下一個(gè)擴(kuò)展名
}
下面的代碼讓OpenGL返回到默認(rèn)的渲染狀態(tài),并釋放分配的內(nèi)存資源
glDisable(GL_SCISSOR_TEST); // 禁用剪裁測(cè)試
free (text); // 釋放分配的內(nèi)存
下面的代碼讓OpenGL完成所有的任務(wù),并返回TRUE
glFlush(); // 執(zhí)行所有的渲染命令
return TRUE; // 成功返回
}
KillGLWindow函數(shù)基本沒(méi)有變化,唯一改變的是需要?jiǎng)h除我們創(chuàng)建的字體
KillFont(); // 刪除字體
CreateGLWindow(), 和 WndProc() 函數(shù)保持不變
在WinMain()函數(shù)中我們需要加入新的按鍵控制
下面的代碼檢查向上的箭頭是否被按下,如果scroll大于0,我們把它減少2
if (keys[VK_UP] && (scroll>0)) // 向上的箭頭是否被按下?
{
scroll-=2; // 如果是,減少scroll的值
}
如果向下的箭頭被按住,并且scroll小于32*(maxtoken-9),則增加scroll的值,32是每一個(gè)字符的高度,9是可以顯示的行數(shù)。
if (keys[VK_DOWN] && (scroll<32*(maxtokens-9))) // 向下的箭頭是否被按住
{
scroll+=2; // 如果是,增加scroll的值
}
我希望你覺(jué)得這個(gè)教程有趣,學(xué)完了這個(gè)教程你應(yīng)該知道如何獲得你的顯卡的發(fā)售商的名稱(chēng),實(shí)現(xiàn)OpenGL的組織和你的顯卡所使用的OpenGL的版本。進(jìn)一步,你應(yīng)該知道你的顯卡支持的擴(kuò)展的名稱(chēng),并熟練的使用剪切矩形和加載TGA圖像。
如果你發(fā)現(xiàn)任何問(wèn)題,請(qǐng)讓我知道。我想做最好的教程,你的反饋對(duì)我很重要