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