OpenGL學習筆記4:紋理
原始影象資料
畫素包裝
影象資料在記憶體中很少以緊密包裝的形式存在。在許多硬體平臺上,處於效能上的考慮,一幅影象的每一行都應該從一種特定位元組對齊地址開始。絕大多數編譯器會自動把變數和緩衝區放置在一個針對該架構對齊優化的地址上。
例如一個包含3個分量的rgb影象,每個分量儲存在一個位元組中,如果影象有199個畫素,那麼一行需要597個畫素。如果硬體本身的體系結構是4位元組排列,那麼影象每一行的末尾將由額外的3個空字元進行填充達到600位元組。
我們可以使用下列函式改變或者回復畫素的儲存方式。
void glPixelStorei(GLenum pname,Glint param);
void glPixelStoref(GLenum pname,GLfloat param);
例如,改成緊密包裝(檔案格式以1個位元組排列,沒有補齊):
glPixelStorei(GL_UNPACK_ALLGNMENT,1);
其中GL_UNPACK_ALIGNMENT指定OpenGL如何從資料緩衝區中解包影象資料。
glPixelStore引數
引數名 | 型別 | 初始值 |
---|---|---|
GL_PACK_SWAP_BYTES | GLboolean | GL_FALSE |
GL_UNPACK_SWAP_BYTES | GLboolean | GL_FALSE |
GL_PACK_LSB_FIRST | GLboolean | GL_FALSE |
GL_UNPACK_LSB_FIRST | GLboolean | GL_FALSE |
GL_PACK_ROW_LENGTH | GLint | 0 |
GL_UNPACK_ROW_LENGTH | GLint | 0 |
GL_PACK_SKIP_ROWS | GLint | 0 |
GL_UNPACK_SKIP_ROWS | GLint | 0 |
GL_PACK_SKIP_PIXELS | GLint | 0 |
GL_UNPACK_SKIP_PIXELS | GLint | 0 |
GL_PACK_ALIGNMENT | GLint | 4 |
GL_UNPACK_ALIGNMENT | GLint | 4 |
GL_PACK_IMAGE_HEIGHT | GLint | 0 |
GL_UNPACK_IMAGE_HEIGHT | GLint | 0 |
GL_PACK_SKIP_IMAGES | GLint | 0 |
GL_UNPACK_SKIP_IMAGES | GLint | 0 |
畫素圖
每個畫素需要一個以上的儲存位來表示,附加位允許儲存強度(亮度)。在OpenGL核心版本中,我們無法直接將一個畫素圖繪製到顏色緩衝區中,但是可以使用下面的函式將顏色緩衝區的內容作為畫素圖直接讀取。
void glReadPixels(Glint x,Glint y,GLSizei width,GLSizei height,GLenum format,GLenum type,const void *pixels);
我們將x和y值指定為矩形左下角的視窗座標,然後再指定矩形的width和height值(畫素形式)。如果顏色緩衝區儲存的資料與我們要求的不同,OpenGL將負責進行必要的轉換。
第4個變數format指定pixels指向的資料元素的顏色佈局,如下表:
OpenGL畫素格式
常量 | 描述 |
---|---|
GL_RGB | 按照紅、綠、藍順序排列的顏色 |
GL_RGBA | 按照紅、綠、藍、Alpha順序排列的顏色 |
GL_BGR | 按照藍、綠、紅順序排列的顏色 |
GL_BGRA | 按照藍、綠、紅、Alpha順序排列的顏色 |
GL_RED | 每個畫素值包含一個紅色分量 |
GL_GREEN | 每個畫素值包含一個綠色分量 |
GL_BLUE | 每個畫素值包含一個藍色分量 |
GL_RG | 每個畫素依次包含一個紅色和一個綠色分量 |
GL_RED_INTEGER | 每個畫素包含一個整數形式的紅色分量 |
GL_GREEN_INTEGER | 每個畫素包含一個整數形式的綠色分量 |
GL_BLUE_INTETER | 每個畫素包含一個整數形式的藍色分量 |
GL_RG_INTEGER | 每個玄素依次包含一個整數形式的紅色和一個整數形式的綠色分量 |
GL_RGB_INTEGER | 每個畫素依次包含整數形式的紅色、綠色和藍色分量 |
GL_RGBA_INTEGER | 每個畫素一次包含整數形式的紅色、綠色、藍色和Alpha分量 |
GL_BGR_INTEGER | 每個畫素依次包含整數形式的藍色、綠色和紅色分量 |
GL_BGRA_INTEGER | 每個畫素依次包含整數形式的藍色、綠色、紅色和Alpha分量 |
GL_STENCIL_INDEX | 每個畫素只包含一個模板值 |
GL_DEPTH_COMPONENT | 每個畫素只包含一個深度值 |
GL_DEPTH_STENCIL | 每個畫素包含一個深度值和一個模板值 |
引數type解釋引數*pixels指向的詩句,它告訴OpenGL使用緩衝區中的什麼資料型別來儲存顏色分量。如下表:
畫素資料的資料型別
常量 | 描述 |
---|---|
GL_UNSIGNED_BYTE | 每種顏色分量都是一個8位無符號整數 |
GL_BYTE | 8位有符號整數 |
GL_UNSIGNED_SHORT | 16位無符號整數 |
GL_SHORT | 16位有符號整數 |
GL_UNSIGNED_INT | 32位無符號整數 |
GL_INT | 32位有符號整數 |
GL_FLOAT | 單精度浮點數 |
GL_HALF_FLOAT | 半精度浮點數 |
GL_UNSIGNED_BYTE_3_2_2 | 包裝的RGB值 |
GL_UNSIGNED_BYTE_2_3_3_REV | 包裝的RGB值 |
GL_UNSIGNED_SHORT_5_6_5 | 包裝的RGB值 |
GL_UNSIGNED_SHORT_5_6_5_REV | 包裝的RGB值 |
GL_UNSIGNED_SHORT_4_4_4_4 | 包裝的RGB值 |
GL_UNSIGNED_SHORT_4_4_4_4_REV | 包裝的RGB值 |
GL_UNSIGNED_SHORT_5_5_5_1 | 包裝的RGB值 |
GL_UNSIGNED_SHORT_1_5_5_5_REV | 包裝的RGB值 |
GL_UNSIGNED_INT_8_8_8_8 | 包裝的RGB值 |
GL_UNSIGNED_INT_8_8_8_8_REV | 包裝的RGB值 |
GL_UNSIGNED_INT_10_10_10_2 | 包裝的RGB值 |
GL_UNSIGNED_INT_2_10_10_10_REV | 包裝的RGB值 |
GL_UNSIGNED_INT_24_8 | 包裝的RGB值 |
GL_UNSIGNED_INT_10F_11F_REV | 包裝的RGB值 |
GL_FLOAT_24_UNSIGNED_INT_24_8_REV | 包裝的RGB值 |
glReadPixels從圖形硬體中複製資料,通常通過匯流排傳輸到系統記憶體。在這種情況下,應用程式將被阻塞,直到記憶體傳輸完成。此外,如果我們制定一個與圖形硬體的本地排列不同的畫素不具,那麼在資料進行重定格式時將產生額外的效能開銷。
包裝的畫素格式
預設情況下,對於glReadPixels函式來說,讀取操作在雙緩衝區渲染環境下將在後臺緩衝區進行,而在單緩衝區渲染環境下則在前臺緩衝區進行。我們可以用下面的函式改變這些畫素操作的源。
void glReadBuffer(GLenum mode);
模式引數可以取GL_FRONT、GL_BACK、GL_LEFT、GL_RIGHT、GL_FRONT_LEFT、GL_FRONT_RIGHT、GL_BACK_LEFT、GL_BACK_RIGHT或者甚至是GL_NONE中的任意一個。
儲存畫素
GLTools庫中的gltWriteTGA函式從前臺顏色緩衝區中讀取顏色資料,並將這些資料儲存到一個Targa檔案格式的影象檔案中。
GLint gltWriteTGA(const char *szFileName)
{
FILE *pFile; // 檔案指標
TGAHEADER tgaHeader; // tga檔案頭
unsigned long lImageSize; // 影象的大小
GLbyte *pBits = NULL; // 影象資料
GLint iViewport[4]; // 視口
GLenum lastBuffer; // 儲存當前讀取緩衝區的設定
// 取得當前視口大小
glGetIntegerv(GL_VIEWPORT, iViewport);
// 獲得影象大小,因為tga的影象資料是緊密包裝的,所以用視口的寬高乘以3個位元組
lImageSize = iViewport[2] * 3 * iViewport[3];
// 分配記憶體用於儲存讀取出來的影象資料
pBits = (GLbyte *)malloc(lImageSize);
if(pBits == NULL)
return 0;
// 設定為逐個畫素的方式讀取
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glPixelStorei(GL_PACK_SKIP_ROWS, 0);
glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
// 儲存當前的設定,後面再恢復它
glGetIntegerv(GL_READ_BUFFER, (GLint *)&lastBuffer);
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, iViewport[2], iViewport[3], GL_BGR_EXT, GL_UNSIGNED_BYTE, pBits);
glReadBuffer(lastBuffer);
// 初始化tag檔案頭的格式
tgaHeader.identsize = 0;
tgaHeader.colorMapType = 0;
tgaHeader.imageType = 2;
tgaHeader.colorMapStart = 0;
tgaHeader.colorMapLength = 0;
tgaHeader.colorMapBits = 0;
tgaHeader.xstart = 0;
tgaHeader.ystart = 0;
tgaHeader.width = iViewport[2];
tgaHeader.height = iViewport[3];
tgaHeader.bits = 24;
tgaHeader.descriptor = 0;
// 蘋果操作需要,進行大小端的轉換
#ifdef __APPLE__
LITTLE_ENDIAN_WORD(&tgaHeader.colorMapStart);
LITTLE_ENDIAN_WORD(&tgaHeader.colorMapLength);
LITTLE_ENDIAN_WORD(&tgaHeader.xstart);
LITTLE_ENDIAN_WORD(&tgaHeader.ystart);
LITTLE_ENDIAN_WORD(&tgaHeader.width);
LITTLE_ENDIAN_WORD(&tgaHeader.height);
#endif
// 開啟檔案
pFile = fopen(szFileName, "wb");
if(pFile == NULL)
{
free(pBits);
return 0;
}
// 寫檔案頭
fwrite(&tgaHeader, sizeof(TGAHEADER), 1, pFile);
// 寫影象資料
fwrite(pBits, lImageSize, 1, pFile);
// 釋放臨時分配的記憶體空間
free(pBits);
fclose(pFile);
// 成功了
return 1;
}
讀取畫素
gltReadTGABits從磁碟中載入Targa檔案。
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat)
{
FILE *pFile; // 檔案指標
TGAHEADER tgaHeader; // TGA 檔案頭
unsigned long lImageSize; // 影象的大小,用位元組表示
short sDepth; // 畫素深度
GLbyte *pBits = NULL; // 指向位的指標
// 預設/失敗值
*iWidth = 0;
*iHeight = 0;
*eFormat = GL_BGR_EXT;
*iComponents = GL_RGB;
// 嘗試開啟檔案
pFile = fopen(szFileName, "rb");
if(pFile == NULL)
return NULL;
// 讀入檔案(二進位制)
fread(&tgaHeader, 18/* sizeof(TGAHEADER)*/, 1, pFile);
// 為大小位元組儲存順序問題而進行位元組交換
#ifdef __APPLE__
LITTLE_ENDIAN_WORD(&tgaHeader.colorMapStart);
LITTLE_ENDIAN_WORD(&tgaHeader.colorMapLength);
LITTLE_ENDIAN_WORD(&tgaHeader.xstart);
LITTLE_ENDIAN_WORD(&tgaHeader.ystart);
LITTLE_ENDIAN_WORD(&tgaHeader.width);
LITTLE_ENDIAN_WORD(&tgaHeader.height);
#endif
// 獲取紋理的寬度、高度和深度
*iWidth = tgaHeader.width;
*iHeight = tgaHeader.height;
sDepth = tgaHeader.bits / 8;
// 這裡進行一些有效性檢驗,非常簡單,我們只要懂得或者說關心8位、24位或32位
//的targa
if(tgaHeader.bits != 8 && tgaHeader.bits != 24 && tgaHeader.bits != 32)
return NULL;
// 計算影象緩衝區的大小
lImageSize = tgaHeader.width * tgaHeader.height * sDepth;
// 進行記憶體定位並進行成功檢驗
pBits = (GLbyte*)malloc(lImageSize * sizeof(GLbyte));
if(pBits == NULL)
return NULL;
// 讀入位
// 檢查讀取錯誤。這項操作應該發現RLE或者其他我們不想識別的奇怪格式
if(fread(pBits, lImageSize, 1, pFile) != 1)
{
free(pBits);
return NULL;
}
// 設定希望的OpenGL格式
switch(sDepth)
{
case 3: // 最可能的情況
*eFormat = GL_BGR;
*iComponents = GL_RGB;
break;
case 4:
*eFormat = GL_BGRA_EXT;
*iComponents = GL_RGBA8;
break;
case 1:
*eFormat = GL_LUMINANCE;
*iComponents = GL_LUMINANCE8;
break;
default: // RGB
// 如果是在iPhone上TGA為BGR,並且iPhone不支援沒有Alpha的BGR,
//但是它支援RGB,所以只要將紅色和藍色調整一下就能滿足要求。
//但是為了加快iPhone的載入速度,請儲存帶有Alpha的TGA。
#ifdef OPENGL_ES
for(int i=0;i<lImageSize;i+=3){
GLbyte temp=pBits[i];
pBits[i]=pBits[i+2];
pBits[i+2]=temp;
}
#endif
};
// 檔案操作完成
fclose(pFile);
// 返回指向影象資料的指標
return pBits;
}
載入紋理
有3個OpenGL函式最經常用來從儲存器緩衝區中載入(比如說,從一個磁碟檔案中讀取)紋理資料。
void glTexImage1D(GLenum target,Glint level,Glint internalformat,GLsizei width,Glint border,GLenum format,GLenum type,void *data);
void glTexImage2D(GLenum target,Glint level,Glint internalformat,GLsizei width,GLsizei height,Glint border,GLenum format,GLenum type,void *data);
void glTexImage3D(GLenum target,Glint level,Glint internalformat,GLsizei width,GLsizei height,GLsizei depth,Glint border,GLenum format,GLenum type,void *data);
這些函式中的target變數應分別為GL_TEXTURE_1D、GL_TEXTURE_2D或GL_TEXTURE_3D。我們也可以指定代理紋理(Proxy texture),方法是指定GL_PROXY_TEXTURE_1D、GL_PROXY_TEXTURE_2D或GL_PROXY_TEXTURE_3D,並使用glGetTexPatameter函式提取代理查詢的結果。
Level引數指定了這些函式所加在的mip貼圖層次。
internalformat引數告訴OpenGL我們希望在每個紋理單元中儲存多少顏色成分,並在可能的情況下說明這些成分的儲存大小,以及是否希望對紋理進行壓縮。
最常用的紋理內部格式
常量 | 含義 |
---|---|
GL_ALPHA | 按照alpha值儲存紋理單元 |
GL_LUMINANCE | 按照亮度值儲存紋理單元 |
GL_LUMINANCE_ALPHA | 按照亮度值和alpha值儲存紋理單元 |
GL_RGB | 按照紅、綠、藍成分儲存紋理單元 |
GL_RGBA | 按照紅、綠、藍和alpha成分儲存紋理單元 |
使用顏色緩衝區
一維和二維的紋理也可以從顏色緩衝區加在資料。我們可以從顏色緩衝區讀取一幅影象,並通過下面這兩個函式將它化為一個新的紋理使用。
void glCopyTexImage1D(GLenum target,Glint level,GLenum internalformat,Glint x,Glint y,GLsizei width,Glint border);
void glCopyTexImage2D(GLenum target,Glint level,GLenum internalformat,Glint x,Glint y,GLsizei width,GLsizei height,Glint border);
這兩個函式的操作類似於glTexImage,但在這裡x和y在顏色緩衝區中指定了開始讀取紋理資料的位置。源緩衝區是通過glReadBuffer函式設定的。並不存在glCopyTexImage3D。
更新紋理
替換一個紋理影象常常要比直接使用glTexImage重新載入一個新紋理快得多:
void glTexSubImage1D(GLenum target,Glint level,Glint xOffset,GLsizei width,GLenum format,GLenum type,const GLvoid *data);
void glTexSubImage2D(GLenum target,Glint level,Glint xOffset,Glint yOffset,GLsizei width,GLsizei height,GLenum format,GLenum type,const GLvoid *data);
void glTexSubImage3D(GLenum target,Glint level,Glint xOffset,Glint yOffset,Glint zOffset,GLsizei width,GLsizei height,GLsizei depth,GLenum format,GLenum type,const GLvoid *data);
下面函式允許沃恩從顏色緩衝區讀取紋理,並插入或替換原來紋理的一部分。
void glCopyTexSubImage1D(GLenum target,Glint level,Glint xoffset,Glint x,Glint y,GLsizei width);
void glCopyTexSubImage2D(GLenum target,Glint level,Glint xoffset,Glint yoffset,Glint x,Glint y,GLsizei width,GLsizei height);
void glCopyTexSubImage3D(GLenum target,Glint level,Glint xoffset,Glint yoffset,Glint zoffset,Glint x,Glint y,GLsizei width,GLsizei height);
紋理物件
紋理物件允許我們一次載入一個以上的紋理狀態(包括紋理影象),以及在它們之間快速切換。紋理狀態是由當前繫結的紋理物件維護的,而紋理物件是由一個無符號整數標識的:
按所需載入紋理數量初始化紋理物件:
void glGenTextures(GLsizei n,GLuint *textures);
為了繫結紋理狀態,我們可以呼叫下面這個函式:
void glBindTexture(GLenum target,GLuint texture);
為了刪除紋理物件,可以呼叫下面這個函式:
void glDeleteTextures(GLsizei n,GLuint *textures);
我們可以使用下面這個函式對紋理物件名(或控制代碼)進行測試,判斷它們是否有效:
GLboolean glIsTexture(GLuint texture);
如果這個證書是一個以前已經分配的紋理物件名,則返回GL_TRUE。
紋理應用
紋理座標
典型情況下,紋理座標是作為0.0到1.0範圍內的浮點值指定的。紋理座標命名為s、t、r和q,支援從一維到三維紋理座標。
紋理引數
很多引數的應用都會影響渲染的規則和紋理貼圖的行為,這些引數通過下列函式的變數來進行設定:
void glTexParameterf(GLenum target,GLenum pname,GLfloat param);
void glTexParamereri(GLenum target,GLenum pname,Glint param);
void glTexParameterfv(GLenum target,GLenum pname,GLfloat *params);
void glTexParameteriv(GLenum target,GLenum pname,Glint *params);
第一個引數target指定這些引數將要應用到哪個紋理模式上,它可以是GL_TEXTURE_1D、GL_TEXTURE_2D和GL_TEXTURE_3D.第二個引數pname指定了需要設定哪個紋理引數,而最後一個引數param或params用於設定特定的紋理引數的值。
基本過濾
根據一個拉伸或收縮的紋理貼圖計算顏色片段的過程稱為紋理過濾。使用OpenGL的紋理引數函式,可以同事設定放大和縮小過濾器。這兩種過濾器的引數名分別是GL_TEXTURE_MAG_FILTER和GL_TEXTURE_MIN_FILTER。就目前來說,我們可以為他們從兩種基本的紋理過濾器GL_NEAREST和GL_LINEAR中進行選擇,它們分別對應於最鄰近過濾和線性過濾。
我們可以使用下面這兩個函式,為放大和縮小過濾器設定紋理過濾器:
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
我們可以使用下面這幾行程式碼,簡單地設定線性過濾:
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
紋理環繞
我們可以調glTexParameteri函式(並分別使用GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T或GL_TEXTURE_WRAP_R作為引數)、為每個座標分別設定環繞模式。然後,我們可以把環繞模式設定為下面幾個值之一:GL_REPEAT、GL_CLAMT、GL_CLAMP_TO_EDGE或GL_CLAMP_TO_BORDER。
綜合運用
載入紋理
GLuint textureID;
glGenTextures(1,&textureID);
glBindTexture(GL_TEXTURE_2D,textureID);
loadTGATexture(“stone.tga”,GL_LINEAR,GL_LINEAR,GL_CLAMP_TO_EDGE);
其中,載入紋理的loadTGATexture定義如下:
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// 讀取材質位元
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if (pBits == NULL)
return false;
//設定放大和縮小過濾
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
//設定線性過濾
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
//改成緊密包裝
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//載入紋理資料
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
free(pBits);
if (minFilter == GL_LINEAR_MIPMAP_LINEAR ||
minFilter == GL_LINEAR_MIPMAP_NEAREST ||
minFilter == GL_NEAREST_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_NEAREST)
//生成Mip層
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
指定紋理座標
新增頂點:
pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);
為一個面計算表面法線,然後再在所有3個頂點使用它:
m3dFindNormal(n, vApex, vFrontLeft, vFrontRight);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
pyramidBatch.Vertex3fv(vApex);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft);
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight);
實際渲染:
glBindTexture(GL_TEXTURE_2D, textureID);
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
vLightPos, vWhite, 0);
pyramidBatch.Draw();
Mip貼圖
Mip貼圖紋理由一些列紋理影象組成,每個影象大小在每個軸的方向上都縮小一半。
如果想指定只加載0層至第4層,應如下操作:
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAX_LEVEL,4);
Mip貼圖過濾
經過Mip貼圖的紋理過濾
常量 | 描述 |
---|---|
GL_NEAREST | 在Mip基層上執行最鄰近過濾 |
GL_LINEAR | 在Mip基層上執行線性過濾 |
GL_NEAREST_MIPMAP_NEAREST | 選擇最鄰近Mip層,並執行最鄰近過濾 |
GL_NEAREST_MIPMAP_LINEAR | 在Mip層之間執行線性插補,並執行最鄰近過濾 |
GL_LINEAR_MIPMAP_NEAREST | 選擇最鄰近Mip層,並執行線性過濾 |
GL_LINEAR_MIPMAP_LINEAR | 在Mip層之間執行線性插補,並執行線性過濾,又稱三線性Mip貼圖 |
生成Mip層
void glGenerateMipmap(GLenum target);
目標引數可以是GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D、GL_TEXTURE_CUBE_MAP、GL_TEXTURE_1D_ARRAY或GL_TEXTURE_2D_ARRAY
各項異性過濾
首先,必須確認這種擴充套件是得到支援的:
if(gltIsExtSupported(“GL_EXT_texture_filter_anisotropic”));
然後,查詢得到支援的各向異性過濾的最大數量:
GLfloat fLargest;
……
……
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT,&fLargest);
最後,設定想要應用的各向異性過濾的數量:
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAX_ANISOTROPY_EXT,fLargest);
紋理壓縮
壓縮紋理
紋理壓縮是通過在glTexImage函式中把internalFormat引數設定為下表的任意值實現的:
通用壓縮紋理格式
壓縮格式 基本內部格式
GL_COMPRESSED_RGB GL_RGB
GL_COMPRESSED_RGBA GL_RGBA
GL_COMPRESSED_SRGB GL_RGB
GL_COMPRESSED_SRGB_ALPHA GL_RGBA
GL_COMPRESSED_RED GL_RED
GL_COMPRESSED_RG GL_RG(Red Green)
可以用如下方法來判斷這個紋理是否被成功壓縮:
Glint compFlag;
……
glGetTexLevelParameteriv(GL_TEXTURE_2D,0,GL_TEXTURE_COMPRESSED,&compFlag);
glGetTexLevelParameter函式提取的壓縮紋理格式
引數 | 返回 |
---|---|
GL_TEXTURE_COMPRESSED | 如果紋理被壓縮,返回1,否則返回0 |
GL_TEXTURE_COMPRESSED_IMAGE_SIZE | 壓縮後的紋理的大小(以位元組為單位) |
GL_TEXTURE_INTERNAL_FORMAT | 所使用的壓縮格式 |
GL_NUM_COMPRESSED_TEXTURE_FORMATS | 受支援的研所為例格式的數量 |
GL_COMPRESSED_TEXTURE_FORMATS | 一個包含了一些常量值的陣列,每個常量值對應於一種受支援的壓縮紋理格式 |
GL_TEXTURE_COMPRESSION_HINT | 紋理壓縮提示的值(GL/NICEST/GL_FASTEST) |
我們可以使用glHint指定希望OpenGL根據最快速度還是最佳質量演算法來選擇壓縮格式:
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_FASTEST);
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_NICEST);
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_DONT_CARE);
GL_EXT_texture_conpression_s3tc的壓縮格式
格式 | 描述 |
---|---|
GL_COMPRESSED_RGB_S3TC_DXT1 | RGB資料被壓縮alpha值時鐘是1.0 |
GL_COMPRESSED_RGBA_S3TC_DXT1 | RGB資料被壓縮,alpha值是1.0或0.0 |
GL_COMPRESSED_RGBA_S3TC_DXT2 | RGB資料被壓縮,alpha值用4位儲存 |
GL_COMPRESSED_RGBA_S3TC_DXT3 | RGB資料被壓縮,alpha值是一些8位置的加權平均值 |
載入壓縮紋理
為了載入預先經過壓縮的紋理資料,可以使用下列函式之一:
void glCompressedTexImage1D(GLenum target,Glint level,GLenum internalFormat,GLsizei width,Glint border,GLsizei imageSize,void *data);
void glCompressedTexImage2D(GLenum target,Glint level,GLenum internalFormat,GLsizei width,GLsizei height,Glint border,GLsizei imageSize,void *data);
void glCompressedTexImage3D(GLenum target,Glint level,GLenum internalFormat,GLsizei width,GLsizei height,GLsizei depth,Glint border,GLsizei imageSize,GLvoid *data);
示例
為了顯示紋理的使用,本示例在學習筆記3(http://blog.csdn.net/ylbs110/article/details/51760021)的示例上直接添加了紋理程式碼,為了讓紋理顯示清晰,顏色做了少許改變。
注意:UseStockShader的第一個引數要改為GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF才能成功使用紋理
#include "stdafx.h"
#include <GLTools.h>
#include <GLShaderManager.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLFrame.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>
#include <math.h>
#include <stdio.h>
#include <math.h>
#define GLUT_DISABLE_ATEXIT_HACK
#include <GLUT.H>
/*
* 當libjpeg-turbo為vs2010編譯時,vs2015下靜態連結libjpeg-turbo會連結出錯:找不到__iob_func,
* 增加__iob_func到__acrt_iob_func的轉換函式解決此問題,
* 當libjpeg-turbo用vs2015編譯時,不需要此補丁檔案
*/
#if _MSC_VER>=1900
#include "stdio.h"
_ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned);
#ifdef __cplusplus
extern "C"
#endif
FILE* __cdecl __iob_func(unsigned i) {
return __acrt_iob_func(i);
}
#endif /* _MSC_VER>=1900 */
#define NUM_SPHERES 50
GLFrame disk[NUM_SPHERES / 2];
GLFrame cylinder[NUM_SPHERES / 2];
GLShaderManager shaderManager; // Shader Manager
GLMatrixStack modelViewMatrix; // Modelview Matrix
GLMatrixStack projectionMatrix; // Projection Matrix
GLFrustum viewFrustum; // View Frustum
GLGeometryTransform transformPipeline; // Geometry Transform Pipeline
GLTriangleBatch torusBatch;
GLBatch floorBatch;
GLTriangleBatch sphereBatch;
GLTriangleBatch triangleBatch;
GLTriangleBatch cylinderBatch;
GLTriangleBatch diskBatch;
GLFrame cameraFrame;
GLuint uiTextures[3];
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
// 讀取材質位元
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if (pBits == NULL)
return false;
//設定放大和縮小過濾
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
//設定線性過濾
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
//改成緊密包裝
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//載入紋理資料
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
free(pBits);
if (minFilter == GL_LINEAR_MIPMAP_LINEAR ||
minFilter == GL_LINEAR_MIPMAP_NEAREST ||
minFilter == GL_NEAREST_MIPMAP_LINEAR ||
minFilter == GL_NEAREST_MIPMAP_NEAREST)
//生成Mip層
glGenerateMipmap(GL_TEXTURE_2D);
return true;
}
//////////////////////////////////////////////////////////////////
// This function does any needed initialization on the rendering
// context.
void SetupRC()
{
// Make sure OpenGL entry points are set
glewInit();
// Initialze Shader Manager
shaderManager.InitializeStockShaders();
glEnable(GL_DEPTH_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 製造花圈
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
// 製造小球
gltMakeSphere(sphereBatch, 0.3f, 26, 13);
//製造圓柱
gltMakeCylinder(cylinderBatch, 0.2f, 0.2f, 0.5f, 13, 2);
//製造圓盤
gltMakeDisk(diskBatch, 0.2f, 0.4f, 13, 3);
floorBatch.Begin(GL_LINES, 324);
for (GLfloat x = -20.0; x <= 20.0f; x += 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
// 設定3個材質物體
glGenTextures(3, uiTextures);
// 載入材質 Marble並繫結
glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
LoadTGATexture("marble.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);
// 載入材質 Mars並繫結
glBindTexture(GL_TEXTURE_2D, uiTextures[1]);
LoadTGATexture("marslike.tga", GL_LINEAR_MIPMAP_LINEAR,
GL_LINEAR, GL_CLAMP_TO_EDGE);
// 載入材質 Moon並繫結
glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
LoadTGATexture("moonlike.tga", GL_LINEAR_MIPMAP_LINEAR,
GL_LINEAR, GL_CLAMP_TO_EDGE);
// Randomly place the spheres
for (int i = 0; i < NUM_SPHERES; i++) {
GLfloat x = ((GLfloat)((rand() % 400) - 200) * 0.1f);
GLfloat z = ((GLfloat)((rand() % 400) - 200) * 0.1f);
if (i % 2 == 0)
disk[i / 2].SetOrigin(x, 0.0f, z);
else
cylinder[(i - 1) / 2].SetOrigin(x, 0.0f, z);
}
GLfloat vetts[3][3];
GLfloat vNorms[3][3];
GLfloat vTexCoords[3][2];
GLfloat angle = 0;
for (int i = 0; i < 3; i++) {
angle += M3D_2PI / 6.0f;
vetts[i][0] = float(-5 + i*0.2);
vetts[i][1] = float(sin(float(angle)));
vetts[i][2] = float(cos(float(angle)));
vNorms[i][0] = float(-5 + i*0.2);
vNorms[i][1] = float(cos(float(angle)));
vNorms[i][2] = float(sin(float(angle)));
vTexCoords[i][0] = float(-5 + i*0.2);
vTexCoords[i][1] = float(sin(float(angle)));
}
triangleBatch.BeginMesh(3);
triangleBatch.AddTriangle(vetts, vNorms, vTexCoords);
triangleBatch.End();
}
///////////////////////////////////////////////////
// Screen changes size or is initialized
void ChangeSize(int nWidth, int nHeight)
{
glViewport(0, 0, nWidth, nHeight);
// 建立投影矩陣,並將它載入到投影矩陣堆疊中
viewFrustum.SetPerspective(35.0f, float(nWidth) / float(nHeight), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
// 設定變換管線以使用兩個矩陣堆疊
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
// 關閉渲染環境
void ShutdownRC(void)
{
glDeleteTextures(3, uiTextures);
}
// Called to draw scene
void RenderScene(void)
{
// 顏色值
static GLfloat vFloorColor[] = { 1.0f, 0.5f, 1.0f, 1.0f };
static GLfloat vTorusColor[] = { 0.5f, 1.0f, 1.0f, 1.0f };
static GLfloat vSphereColor[] = { 1.0f, 1.0f, 0.5f, 1.0f };
static GLfloat vdiskColor[] = { 1.0f, 0.5f, 0.5f, 1.0f };
static GLfloat vcylinderColor[] = { 0.5f, 1.0f, 0.5f, 1.0f };
// 基於時間的動畫
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
// 清除顏色緩衝區和深度緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 儲存當前模型檢視矩陣 (單位矩陣)
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
// 將光源位置變換到視覺座標系
M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
M3DVector4f vLightEyePos;
m3dTransformVector4(vLightEyePos, vLightPos, mCamera);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 繪製背景
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
//繫結並使用紋理物件
glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
//繪製圓柱和圓盤
for (int i = 0; i < NUM_SPHERES; i++) {
modelViewMatrix.PushMatrix();
if (i % 2 == 0) {
modelViewMatrix.MultMatrix(disk[i / 2]);
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vdiskColor,0);
diskBatch.Draw();
}
else
{
modelViewMatrix.MultMatrix(cylinder[(i - 1) / 2]);
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vcylinderColor, 0);
cylinderBatch.Draw();
}
modelViewMatrix.PopMatrix();
}
// 繪製旋轉花托
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
// 儲存平移矩陣
modelViewMatrix.PushMatrix();
// 應用旋轉並繪製花托
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
//繫結並使用紋理物件
glBindTexture(GL_TEXTURE_2D, uiTextures[1]);
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vTorusColor, 0);
torusBatch.Draw();
modelViewMatrix.PopMatrix(); // "清除" 以前的旋轉
//繪製三角形
//modelViewMatrix.PushMatrix();
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vTorusColor, 0);
triangleBatch.Draw();
//modelViewMatrix.PopMatrix();
//繫結並使用紋理物件
glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
// 應用另一個旋轉,然後進行平移,然後再繪製球體
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vSphereColor, 0);
sphereBatch.Draw();
// 還原到以前的模型檢視矩陣 (單位矩陣)
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
// 進行緩衝區交換
glutSwapBuffers();
// 通知GLUT在進行一次同樣操作
glutPostRedisplay();
}
// Respond to arrow keys by moving the camera frame of reference
void SpecialKeys(int key, int x, int y)
{
float linear = 0.1f;
float angular = float(m3dDegToRad(5.0f));
if (key == GLUT_KEY_UP)
cameraFrame.MoveForward(linear);
if (key == GLUT_KEY_DOWN)
cameraFrame.MoveForward(-linear);
if (key == GLUT_KEY_LEFT)
cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
if (key == GLUT_KEY_RIGHT)
cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800, 600);
glutCreateWindow("OpenGL SphereWorld");
glutSpecialFunc(SpecialKeys);
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
ShutdownRC();
return 0;
}
執行結果:
除了地板所有物體都加上了材質