NeHe OpenGL第二十四课:扩展
NeHe OpenGL第二十四課:擴(kuò)展
擴(kuò)展,剪裁和TGA圖像文件的加載:
在這一課里,你將學(xué)會(huì)如何讀取你顯卡支持的OpenGL的擴(kuò)展,并在你指定的剪裁區(qū)域把它顯示出來。
?
這個(gè)教程有一些難度,但它會(huì)讓你學(xué)到很多東西。我聽到很多朋友問我擴(kuò)展方面的內(nèi)容和怎樣找到它們。這個(gè)教程將交給你這
一切。
我將教會(huì)你怎樣滾動(dòng)屏幕的一部分和怎樣繪制直線,最重要的是從這一課起,我們將不使用AUX庫,以及*.bmp文件。我將告訴你如何使用Targa(TGA)圖像文件。因?yàn)樗?jiǎn)單并且支持alpha通道,它可以使你更容易的創(chuàng)建酷的效果。
接下來我們要做的第一件事就是不包含glaux.h頭文件和glaux.lib庫。另外,在使用glaux庫時(shí),經(jīng)常會(huì)發(fā)生一些可疑的警告,現(xiàn)在我們可以測(cè)定告別它了。
?
#include?<stdarg.h>????????// 處理可變參數(shù)的函數(shù)的頭文件
#include?<string.h>????????// 處理字符串的頭文件
接下來我們添加一些變量,第一個(gè)為滾動(dòng)參數(shù)。第二給變量記錄擴(kuò)展的個(gè)數(shù),swidth和sheight記錄剪切矩形的大小。base為字體顯示列表的開始值。?
??
int??scroll;?????????// 用來滾動(dòng)屏幕
int??maxtokens;????????// 保存擴(kuò)展的個(gè)數(shù)
int??swidth;?????????// 剪裁寬度
int??sheight;?????????// 剪裁高度
GLuint??base;?????????// 字符顯示列表的開始值
現(xiàn)在我們創(chuàng)建一個(gè)數(shù)據(jù)結(jié)構(gòu)用來保存TGA文件,接著我們使用這個(gè)結(jié)構(gòu)來加載紋理。?
??
typedef?struct??????????// 創(chuàng)建加載TGA圖像文件結(jié)構(gòu)
{
?GLubyte?*p_w_picpathData;????????// 圖像數(shù)據(jù)指針
?GLuint?bpp;?????????// 每個(gè)數(shù)據(jù)所占的位數(shù)(必須為24或32)
?GLuint?width;?????????// 圖像寬度
?GLuint?height;?????????// 圖像高度
?GLuint?texID;?????????// 紋理的ID值
} TextureImage;??????????// 結(jié)構(gòu)名稱
TextureImage?textures[1];????????// 保存一個(gè)紋理
這個(gè)部分的代碼將要加載一個(gè)TGA文件并把它轉(zhuǎn)換為紋理。必須注意的是這部分代碼只能加載24/32位的不壓縮的TGA文件。
這個(gè)函數(shù)包含兩個(gè)參數(shù),一個(gè)保存載入的圖像,一個(gè)為將載入的文件名。
TGA文件包含一個(gè)12個(gè)字節(jié)的文件頭,載入圖像后,我們用type來設(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};???// 無壓縮的TGA文件頭
?GLubyte??TGAcompare[12];??????// 保存讀入的文件頭信息
?GLubyte??header[6];??????// 保存最有用的圖像信息,寬,高,位深
?GLuint??bytesPerPixel;??????// 記錄每個(gè)顏色所占用的字節(jié)數(shù)
?GLuint??p_w_picpathSize;??????// 記錄文件大小
?GLuint??temp;???????// 臨時(shí)變量
?GLuint??type=GL_RGBA;??????// 設(shè)置默認(rèn)的格式為GL_RGBA,即32位圖像
下面這個(gè)函數(shù)讀取TGA文件,并記錄文件信息。TGA文件格式如下所示:
Tga圖像格式
無顏色表 rgb 圖像?
| 偏移 | 長(zhǎng)度 | 描述 | 32位常用圖像文件各個(gè)字節(jié)的值 |
| 0 | 1 | 指出圖像信息字段的長(zhǎng)度,其取值范圍是 0 到 255 ,當(dāng)它為 0 時(shí)表示沒有圖像的信息字段。 | 0 |
| 1 | 1 | 是否使用顏色表,0 表示沒有顏色表,1 表示顏色表存在 | 0 |
| 2 | 1 | 該字段總為 2。圖像類型碼,tga一共有6種格式,2表示無顏色表 rgb 圖像 | 2 |
| 3 | 5 | 顏色表規(guī)格,總為0。 | 0 |
| 4 | 0 | ||
| 5 | 0 | ||
| 6 | 0 | ||
| 7 | 0 | ||
| 8?????? 10?????????? 圖像規(guī)格說明 開始 | |||
| 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é) | 00100000(2) |
| 18 | 可變 | 圖像數(shù)據(jù)域 這里存儲(chǔ)了(寬度)x(高度)個(gè)像素,每個(gè)像素中的 rgb 色值該色值包含整數(shù)個(gè)字節(jié) | ... |
如果一切順利,讀取文件后關(guān)閉文件。
?FILE *file = fopen(filename, "rb");??????// 打開一個(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ù)
?p_w_picpathSize?= texture->width*texture->height*bytesPerPixel;????// 計(jì)算TGA文件加載所需要的內(nèi)存大小
下面的代碼為圖像數(shù)據(jù)分配內(nèi)存并載入它?
??
?texture->p_w_picpathData=(GLubyte *)malloc(p_w_picpathSize);????// 分配內(nèi)存去保存TGA數(shù)據(jù)
?if(?texture->p_w_picpathData==NULL ||??????// 系統(tǒng)是否分配了足夠的內(nèi)存?
??fread(texture->p_w_picpathData, 1, p_w_picpathSize, file)!=p_w_picpathSize)??// 是否成功讀入內(nèi)存?
?{
??if(texture->p_w_picpathData!=NULL)?????// 是否有數(shù)據(jù)被加載
???free(texture->p_w_picpathData);?????// 如果是,則釋放載入的數(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(p_w_picpathSize); i+=bytesPerPixel)????// 循環(huán)所有的像素
?{?????????// 交換R和B的值
??temp=texture->p_w_picpathData[i];??????
??texture->p_w_picpathData[i] = texture->p_w_picpathData[i + 2];??
??texture->p_w_picpathData[i + 2] = temp;????
?}
?fclose (file);????????// 關(guān)閉文件
下面的代碼創(chuàng)建一個(gè)紋理,并設(shè)置過濾方式為線性?
??
?// 創(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è)置過濾器為線性過濾
?glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);??
??
判斷圖像的位數(shù)是否為24,如果是則設(shè)置類型為GL_RGB?
??
?if (texture[0].bpp==24)????????// 是否為24位圖像?
?{
??type=GL_RGB;????????// 如果是設(shè)置類型為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].p_w_picpathData);
?return true;?????????// 紋理綁定完成,成功返回
}
下面的代碼是從圖像創(chuàng)建字體的典型的方法,這些代碼將包含在后面的課程中,以顯示文字。
只有一個(gè)不同的地方,紋理0用來保存字符圖像。?
??
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);?????// 開始創(chuàng)建顯示列表
???glBegin(GL_QUADS);??????// 創(chuàng)建一個(gè)四邊形用來包含字符圖像
????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ù)用來刪除顯示字符的顯示列表?
??
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);????????// 開始分析參數(shù),并把結(jié)果寫入到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);????// 把字符寫入到屏幕
?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è)置窗口可見區(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ù)。
接下來清楚背景,并顯示OpenGL的銷售商,實(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)色在屏幕的下方寫上“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));????// 顯示銷售商
?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");?????// 在屏幕的底端寫上NeHe Productions字符串
??
?現(xiàn)在我們繪制顯示擴(kuò)展名的白色線框方塊,并用一個(gè)更大的白色線框方塊把所有的內(nè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ù)用來設(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),一次類推第i行文本的位置是(0,96+(cnt*32)),但我們需要考慮當(dāng)前滾動(dòng)過的位置,默認(rèn)為向上滾動(dòng),所以我們得到顯示第i行文本的位置為(0,96+(cnt*32)=scroll)。
當(dāng)然它們不會(huì)都顯示出來,記得我們使用了剪裁,只顯示(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。
我們舉例說明上面的過程,例如字符串"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ù)基本沒有變化,唯一改變的是需要?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的值
????}
原文及其個(gè)版本源代碼下載:
http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=24
轉(zhuǎn)載于:https://blog.51cto.com/yarin/381852
總結(jié)
以上是生活随笔為你收集整理的NeHe OpenGL第二十四课:扩展的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 细数英特尔收购McAfee可获得安全产品
- 下一篇: Javascript 面向对象全新理练之