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);

轉載請註明出處:http://blog.csdn.net/ylbs110/article/details/51793970

示例
爲了顯示紋理的使用,本示例在學習筆記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;
}

運行結果:
除了地板所有物體都加上了材質
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章