天天看點

OpenGL::紋理的初步應用

說到紋理,通俗的講就是我們不用再自己去畫圖啦,用一些裝有資訊格式的檔案去實作畫圖,這将為我們帶來一個更加清新的展示效果。在紋理中涉及到許多概念也是需要我們去了解的,了解的部分我無法為你們完成,概念的部分我盡量寫出自己的了解。希望可以幫到看本部落格的同學。

本文例子檢視位址,本例中展示了讀取tag檔案并加載紋理生成圖像,mac使用者可以把例子下下來看看效果。

紋理坐标自描述

典型情況下,紋理坐标是作為0.0到1.0範圍内的浮點值指定的,坐标命名為s,t,r,q,分别對應頂點坐标的x,y,z,w;自己了解下就是紋理會被先加載到一個各邊都視為機關1的坐标系下,然後再将這樣的坐标系映射到真實的螢幕坐标系裡,因為每個坐标系上的全長都是機關一,那麼根據在第邊上的比例即可計算紋理的真實像素。而且若三個坐标系的機關一不相同的情況下,得到的結果也不會是一個正方體,是以真實的紋理計算過程會進行拉伸或收縮。

讀取像素

Targa圖像格式是一種友善且容易使用的圖檔格式,先貼上一個函數,該函數詳細介紹了tga檔案的加載過程:

/*自定義tga圖檔頭資訊結構*/
#pragma pack(1)//結構體位元組對齊
typedef struct
{
    GLbyte  identsize;              // Size of ID field that follows header (0)
    GLbyte  colorMapType;           // 0 = None, 1 = paletted
    GLbyte  imageType;              // 0 = none, 1 = indexed, 2 = rgb, 3 = grey, +8=rle
    unsigned short  colorMapStart;          // First colour map entry
    unsigned short  colorMapLength;         // Number of colors
    unsigned char   colorMapBits;   // bits per palette entry
    unsigned short  xstart;                 // image x origin
    unsigned short  ystart;                 // image y origin
    unsigned short  width;                  // width in pixels
    unsigned short  height;                 // height in pixels
    GLbyte  bits;                   // bits per pixel (8 16, 24, 32)
    GLbyte  descriptor;             // image descriptor
} TGAHEADER;
#pragma pack(8)


/*tga圖檔讀取*/
//進行記憶體定位并載入targa位,傳回指向新的緩沖區指針,紋理高寬,以及OpenGL資料格式
//注:隻支援targa,隻能是8位、24位或32位色,沒有調色闆和RLE編碼(這部分沒看懂,應該是跟圖像格式有關的)
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat)
{
    FILE *pFile;            // File pointer
    TGAHEADER tgaHeader;        // TGA file header
    unsigned long lImageSize;       // Size in bytes of image
    short sDepth;           // Pixel depth;
    GLbyte  *pBits = NULL;          // Pointer to bits

    //預設或失敗值
    *iWidth = ;
    *iHeight = ;
    *eFormat = GL_RGB;
    *iComponents = GL_RGB;

    //嘗試打開檔案
    pFile = fopen(szFileName, "rb");
    if(pFile == NULL)
        return NULL;

    // 讀入檔案頭(二進制)
    fread(&tgaHeader, /* sizeof(TGAHEADER)*/, , 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位、24位或32位
    if(tgaHeader.bits !=  && tgaHeader.bits !=  && tgaHeader.bits != )
        return NULL;

    // 計算圖像緩沖區大小
    lImageSize = tgaHeader.width * tgaHeader.height * sDepth;

    // 記憶體定位和成功檢驗
    pBits = (GLbyte*)malloc(lImageSize * sizeof(GLbyte));
    if(pBits == NULL)
        return NULL;

    // 讀入位
    // 檢查讀取錯誤,這項操作應該發現RLE或者其他我們不想識别的格式
    // RLE:一種壓縮過的位圖檔案格式,RLE壓縮方案是一種極其成熟的壓縮方案,
    // 特點是無損失壓縮,既節省了磁盤空間又不損失任何圖像資料;
    if(fread(pBits, lImageSize, , pFile) != )
    {
        free(pBits);
        return NULL;
    }

    // 設定希望的OpenGL格式
    switch(sDepth)
    {
#ifndef OPENGL_ES
        case :     // Most likely case
            *eFormat = GL_BGR;
            *iComponents = GL_RGB;
            break;
#endif
        case :
            *eFormat = GL_BGRA;
            *iComponents = GL_RGBA;
            break;
        case :
            *eFormat = GL_LUMINANCE;
            *iComponents = GL_LUMINANCE;
            break;
        default:        // RGB
            //如果是在iPhone上,TGA為BGR,并且iPhone不支援沒有alpha的BGR
            //iPhone支援RGB,是以隻要将紅色和藍色調整一下就能符合要求
            //但是為了加快iPhone的載入速度,請儲存帶有alpha的TGA
#ifdef OPENGL_ES
            for(int i = ; i < lImageSize; i+=)
            {
                GLbyte temp = pBits[i];
                pBits[i] = pBits[i+];
                pBits[i+] = temp;
            }
#endif
            break;
    }

    // 檔案結束
    fclose(pFile);

    // 傳回指向圖像的指針
    return pBits;
}
           

函數

gltReadTGABits

是自定義讀取函數,大緻的過程就是打開一個tga檔案,然後以二進制的形式讀取出來,進而對外部指針width等做修改,是以該函數傳回了像素的寬高等的一些資訊。

載入紋理

接下來載入緩沖區内的紋理:

bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
    GLbyte *pBits;
    int nWidth, nHeight, nComponents;
    GLenum eFormat;

    // Read the texture bits
    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, );
    glTexImage2D(GL_TEXTURE_2D, , nComponents, nWidth, nHeight, ,
                 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)
        glGenerateMipmap(GL_TEXTURE_2D);

    return true;
}
           

glTexParameteri

OpenGL在拉伸和收縮時對紋理貼圖計算顔色片段的過程稱為紋理過濾;紋理坐标總是根據紋理圖像的紋理單元進行求值和繪圖;使用上面這個函數設定放大和縮小的過濾模式:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
           

GL_LINEAR

,是線性過濾,把紋理坐标周圍的的紋理單元的權重平均值應用到紋理坐标上,這可以需要一些額外的開銷;

GL_NEAREST

是最鄰近過濾,把最鄰近的紋理單元應用到紋理坐标中,它我們能選擇的最簡單、最快速的過濾方法;

前面說到紋理坐标坐落在一個0.0到1.0的範圍内,當超過了範圍,OpenGL使用這個函數處理紋理環繞模式;

GL_REPEAT

模式下OpenGL在紋理坐标值超過1.0的方向上進行重複;

GL_CLAMP

所需的紋理單元取自紋理邊界或

TEXTURE_BORDER_COLOR

(glTexParameterfv函數設定的值);

GL_CLAMP_TO_EDGE

強制對範圍外的紋理坐标沿着合法的紋理坐标的最後一行或一列進行渲染;

GL_CLAMP_TO_BORDER

範圍之外的紋理坐标使用邊界紋理單元;

注:在

GL_NEAREST

模式下過濾模式并不起作用,因為紋理坐标總是對齊到紋理貼圖中的一些特定的紋理單元;

glPixelStorei

GL_UNPACK_ALIGNMENT

指定OpenGL如何從資料緩沖區中解包圖像資料;關于這個函數我們暫時隻說明這一點;這似乎跟OpenGL對像素的記憶體配置設定有關;

glTexImage2D

函數原型:

glTexImage2D(GLenum target, GLint level, GLint internalformat, 
                GLsizei width, GLsizei height, GLsizei depth, GLint border,
                GLeunm format, GLeunm type,void* data);
           

函數比較長,參數講解:

target變量分别是

GL_TEXTURE_1D

,

GL_TEXTURE_2D

,

GL_TEXTURE_3D

,這裡我們選擇

GL_TEXTURE_2D

;該函數之後會有詳細說明;

level指定了加載的mip貼圖層次(你說你不知道mip貼圖是什麼,下次我們會講,這裡你了解成你們家鋪地的瓷磚就好了);

我們必須指定紋理資料的internalformat,這個資訊告訴我們希望在每個紋理中存儲多少顔色成分,并在可能的情況下說明這些成分的存儲大小,以及是否希望對紋理進行壓縮;竟然一個參數有這麼多作用,具體參數清單為:

常量 含義
GL_ALPHA 按照alpha值存儲紋理單元
GL_LUMINANCE(亮度) 按照亮度值存儲紋理單元
GL_LUMINANCE_ALPHA 按照亮度值和alpha值存儲紋理單元
GL_RGB 按照紅、綠、藍成分存儲紋理單元
GL_RGBA 按照紅、綠、藍和alpha成分存儲紋理單元

width,height,depth指定了被加載紋理的寬、高和深,這些值必須是2的整數次方,這一點非常重要;紋理貼圖并不要求是立方體,但是一個紋理在加載時如果使用了非2的整數次幂值,将會導緻紋理貼圖被禁用,意思就是你什麼也顯示不出來;

border允許我們為紋理貼圖指定一個邊界寬度;

format,type,data詳見

glReadPixels()

函數中對應的解釋;

glReadPixels

glReadPixels(GLint x,GLint y,GLSizei width,GLSizei height,
GLenum format,GLeunm type,const void* pixels);
           

OpenGL提供了簡潔的函數來操作像素:

glReadPixels:讀取一些像素。目前可以簡單了解為“把已經繪制好的像素(它可能已經被儲存到顯示卡的顯存中)讀取到記憶體”。

glDrawPixels:繪制一些像素。目前可以簡單了解為“把記憶體中一些資料作為像素資料,進行繪制”。

glCopyPixels:複制一些像素。目前可以簡單了解為“把已經繪制好的像素從一個位置複制到另一個位置”。雖然從功能上看,好象等價于先讀取像素再繪制像素,但實際上它不需要把已經繪制的像素(它可能已經被儲存到顯示卡的顯存中)轉換為記憶體資料,然後再由記憶體資料進行重新的繪制,是以要比先讀取後繪制快很多。

這三個函數可以完成簡單的像素讀取、繪制和複制任務,但實際上也可以完成更複雜的任務。

該函數總共有七個參數。前四個參數可以得到一個矩形,該矩形所包括的像素都會被讀取出來;(第一、二個參數表示了矩形的左下角橫、縱坐标,坐标以視窗最左下角為零,最右上角為最大值;第三、四個參數表示了矩形的寬度和高度)

第五個參數表示讀取的内容,例如:GL_RGB就會依次讀取像素的紅、綠、藍三種資料,GL_RGBA則會依次讀取像素的紅、綠、藍、alpha四種資料,GL_RED則隻讀取像素的紅色資料(類似的還有GL_GREEN,GL_BLUE,以及GL_ALPHA)。如果采用的不是RGBA顔色模式,而是采用顔色索引模式,則也可以使用GL_COLOR_INDEX來讀取像素的顔色索引。目前僅需要知道這些,但實際上還可以讀取其它内容,例如深度緩沖區的深度資料等;

OpenGL::紋理的初步應用

第六個參數表示讀取的内容儲存到記憶體時所使用的格式,例如:GL_UNSIGNED_BYTE會把各種資料儲存為GLubyte,GL_FLOAT會把各種資料儲存為GLfloat等。

OpenGL::紋理的初步應用

第七個參數表示一個指針,像素資料被讀取後,将被儲存到這個指針所表示的位址。注意,需要保證該位址有足夠的可以使用的空間,以容納讀取的像素資料。例如一幅大小為256*256的圖象,如果讀取其RGB資料,且每一資料被儲存為GLubyte,總大小就是:256*256*3 = 196608位元組,即192千位元組。如果是讀取RGBA資料,則總大小就是256*256*4 = 262144位元組,即256千位元組。

注意:glReadPixels實際上是從緩沖區中讀取資料,如果使用了雙緩沖區,則預設是從正在顯示的緩沖(即前緩沖)中讀取,而繪制工作是預設繪制到後緩沖區的。是以,如果需要讀取已經繪制好的像素,往往需要先交換前後緩沖。

本篇就先到這裡吧,本人要下班了。

繼續閱讀