1. 程式人生 > >渲染世界的OpenGL 基礎紋理

渲染世界的OpenGL 基礎紋理

基礎紋理的大體步驟:
載入紋理影象
設定紋理貼圖引數
管理多重紋理
生成Mip貼圖
使用各向異性過濾
載入壓縮紋理

紋理貼圖(Texture mapping):紋理只是一種能夠應用到場景當中三角形上的影象資料,他通過經過過濾的紋理單元填充到實心區域。

1.原始影象資料
點陣圖:經常用在飽含灰度或全綵色的影象中。

(1)畫素包裝

影象資料在記憶體中很少以緊密包裝的形式存在。處於效能的考慮,一幅影象的每一行都應該從一種特定的位元組對齊地址開始。絕大多數編譯器會自動把變數和緩衝區放置在一個針對該架構對齊優化的地址上。(OPENGL採用4位元組對齊方式)
windows中的”.BMP”檔案格式使用4位元組排列,targa(.TGA)檔案格式為1個位元組排列的。
(記憶體分配意圖對於OPENGL非常重要,我們向OPENGL當中提交影象資料或從OPENGL獲得影象資料的時候,OPENGL需要知道我們想要在記憶體中進行怎麼樣的包裝或解包裝操作)

void glPixelStorei(GLenum pname,GLint param);
void glPixelStoref(GLenum pname,GLfloat param);
//改變或者恢復畫素的儲存方式。
//pname引數見glPixelStore列舉值

畫素圖
畫素圖:每個畫素都需要一個以上的儲存位表示。每個畫素的附加為允許儲存強度值,或者顏色分量值。
可以使用下面的函式將顏色緩衝區的內容作為畫素圖直接讀取。

void glReadPixels(GLint x,GLint y,GLSizei width,GLSizei height,
                   GLenum format, GLenum type, const
void* pixels); //x、y指定為矩形左下角的視窗座標,然後在指定矩形的長寬,如果顏色緩衝區儲存的資料與我們要求的不同,OpenGL將會進行必要的轉換。format變數指定piexel指向的資料元素的顏色佈局。而type解釋引數pixel指向的資料。告訴緩衝區中的什麼資料型別來儲存顏色分量。 //上面的兩種列舉量,具體見 opengl畫素格式、畫素資料型別的列舉值表。

注意 :glReadPixels從圖形硬體中複製資料,通常通過匯流排傳輸到系統記憶體。在這種情況下,應用程式將被阻塞。知道記憶體傳輸完成。如果我們指定一個和圖形硬體本地排列不同的畫素佈局,那麼在資料進行重定格式時候回產生額外開銷。

包裝的畫素格式
允許圖形資料以更多的壓縮形式進行儲存,以便和更廣泛的顏色圖形硬體相匹配。
包裝的畫素格式將顏色資料壓縮到了儘可能少的儲存位中,每個顏色通道的位數顯示在常量中。例如格式為第一個分量提供三位儲存空間。第二個分量也提供三位儲存空間,而為第三個分量提供了兩位分量空間。其制定分量的排列順序仍然是根據format確定的。這些分量從最高位(MSB)到最小位(LSB)進行排序。
這些格式和資料型別引數也在大量其他影象和紋理相關函式當中使用,後面我們會再次提到這個表格。預設狀態下,對於glReadPixels函式來說,讀取操作在雙緩衝區渲染環境下將在後臺緩衝區進行。而在單緩衝渲染環境下則在前臺緩衝區進行。我們可以用下面的函式改變這些畫素操作的源。

void glReadBuffer(GLenum mode);

引數模式可以取:GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT, GL_FRONT_RIGHT, GL_BACK_LEFT, GL_BACK_RIGHT, GL_NONE。

儲存畫素
GLTools庫當中的glWriteTGA函式從前臺顏色緩衝區中讀取顏色資料,並且將這些資料儲存到一個targa檔案格式的影象檔案中。能夠將當前的OpenGL渲染儲存到一個標準影象檔案格式中,這可能會非常有用。
包括tga檔案的檔案頭以及相關的函式程式碼如下。

#pragma comment(lib,"GLTools.lib")

#include <GLTools.h>    // OpenGL toolkit
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLGeometryTransform.h>
#include <windows.h>
#include <math.h>
#include <stdlib.h>

#include <GL/glut.h>

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;


GLint gltWriteTGA(const char* szFileName)
{
    //1.首先宣告一堆具體的資料
    FILE *pFile;
    TGAHEADER tgaHeader;
    unsigned long lImageSize;
    GLbyte *pBits = NULL;  //指向位的指標
    GLint iViewport[4];  //以畫素表示的視口
    GLint lastBuffer;  //讀取緩衝區設定
    //獲取視口大小,注意這種方法

    //2.對當前資料進行初始化
    glGetIntegerv(GL_VIEWPORT, iViewport);
    //影象應該多大
    lImageSize = iViewport[2] * 3 * iViewport[3];
    //動態分配塊
    pBits = (GLbyte *)malloc(lImageSize);
    if (pBits==NULL)
    {
        return 0;
    }


    //3.讀取畫素
    //從顏色緩衝區讀取位
    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, &lastBuffer);
    glReadBuffer(GL_FRONT);
    glReadPixels(0,0,iViewport[2], iViewport[3],GL_RGB, GL_UNSIGNED_BYTE, pBits);
    glReadBuffer(lastBuffer);


    //4.對於tga檔案頭進行初始化
    //初始化TGA頭
    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;


    //5.把tga檔案寫入硬碟
    //嘗試開啟檔案
    pFile = fopen(szFileName, "wb");
    if (pFile==NULL)
    {
        free(pBits);//作為C++程式設計師所必備的釋放記憶體
        return 0;
    }

    //寫入檔案頭
    fwrite(&tgaHeader, sizeof(TGAHEADER), 1, pFile);

    //寫入影象資料
    fwrite(pBits, lImageSize, 1, pFile);

    //釋放臨時緩衝區並且關閉檔案
    free(pBits);
    fclose(pFile);

    return 1;
}


讀取畫素
Targa影象格式是一種容易使用的影象格式,並且既支援簡單顏色影象,也支援帶有Alpha值的影象。在磁碟中載入TGA檔案函式:

GLbyte *gltReadTGABits(const char* szFileName, GLint *iWidth, GLint* iHeight,
GLint *iComponents, GLenum *eFormat );

第一個引數是將要載入的Targa檔案的檔名,如果有必要的話則會附加路徑。Targa影象格式是得到廣泛支援格式,與JPEG檔案不同,JPEG檔案通常以未經壓縮的格式儲存影象。gltReadTGABits函式用來開啟檔案,然後讀入檔案頭並且進行語法分析,以確定檔案的寬度、高度和資料格式。分量的數量可以是一個、3個、4個。亮度、RGB、RGBA格式的影象。最終引數是一個新定位到直接從檔案中讀取的影象資料的指標。如果沒有函式呼叫成功,那麼他就會返回一個新定位到直接從檔案中讀取的影象資料的指標。如果沒有找到檔案,或者出現其他的錯誤,函式則會返回NULL。

GLbyte* gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat)
{
    FILE *pFile; //檔案指標
    TGAHEADER tgaHeader;
    unsigned long lImageSize;
    short sDepth;
    GLbyte *pBits = NULL;
    //預設值
    *iWidth = 0;
    *iHeight = 0;
    *eFormat = GL_RGB;
    *iComponents = GL_RGB;

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

    //讀入檔案頭(二進位制檔案)
    fread(&tgaHeader, sizeof(TGAHEADER), 1, pFile);

    //獲取紋理
    *iWidth = tgaHeader.width;
    *iHeight = tgaHeader.height;
    sDepth = tgaHeader.bits / 8;

    //這裡進行一些有效性檢驗。
    //我們僅僅關心8、24、32
    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)
    {
#ifdef OPENGL_ES
    case 3:
        *eFormat = GL_RGB;
        *iComponents = GL_RGB;
        break;
#endif // OPENGL_ES

#ifdef WIN32
    case 3:
        *eFormat = GL_RGB;
        *iComponents = GL_RGB;
        break;
#endif // WIN32
        case 4:
            *eFormat = GL_RGBA;
            *iComponents = GL_RGBA;
            break;

        case 1:
            *eFormat = GL_LUMINANCE;
            *iComponents = GL_LUMINANCE;
            break;
    default:
#ifdef OPENGL_ES
        for (int i=0;i<lImageSize;i++)
        {
            GLbyte temp = pBits[i];
            pBits[i] = pBits[i + 2];
            pBits[i + 2] = temp;
        }

#endif // OPENGL_ES

        break;
    }

    fclose(pFile);

    return pBits;

}

2.載入紋理
在集合圖形中應用紋理貼時,第一個必要步驟就是將紋理載入記憶體。一旦被載入,這些紋理就會成為當前紋理狀態的一部分。有三個OPENGL函式最經常用來從儲存器緩衝區中載入紋理資料。

void glTexImage1D(GLenum target,GLint level, GLint internalformat,
GLsizei width, GLint border, 
GLernum format, GLenum type, void* data);

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

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

由同一個函式glTexImage函式派生而來。target變數應該分別為:GL_TEXTURE 1D、 GL _TEXTURE _2D、GL _TEXTURE 3D。我們也可以指定代理紋理。方法是指定GL _ PROXY _ TEXTURE 1D、GL PROXY _ TEXTURE 2D、GL PROXY _ TEXTURE _3D。並且使用glGetTexParameter函式提取代理查詢的結果。
Level引數制定了這些函式所載入的mip貼圖層次。非mip貼圖的紋理,我們可以把這個引數設定為0。
internalformat:告訴OPENGL我們希望在每個紋理單元中儲存了多少顏色成分,並且在可能的情況下說明這些成分的儲存大小。以及是否希望對紋理進行壓縮。

//常用的紋理內部格式
GL_ALPHA:按照alpha值儲存紋理單元。
GL_LUMINANCE:按照亮度值儲存紋理單元。
GL_LUMINANCE_ALPHA:按照亮度值和alpha值儲存紋理單元。
GL_RGB:按照紅綠藍成分儲存紋理單元。
GL_RGBA:按照紅綠藍和alpha成分儲存紋理單元。

width、height、depth引數指定了被載入紋理的寬度、高度以及深度。注意,這些值必須是2的整數次方。非常重要。
border:允許我們為紋理貼圖指定一個邊界寬度。紋理邊界允許我們通過對邊界處的紋理單元進行額外的設定,來對他的寬度、高度、深度進行擴充套件。
最後三個引數:format、type、data和用於把影象資料放入顏色緩衝區中的glDrawPixels函式的對應引數相同。

使用顏色緩衝區
一維和二維紋理也可以從顏色緩衝區載入資料。我們可以從顏色緩衝區讀取一幅影象,並且通過以下函式將它作為一個新的紋理使用:

void glCopyTexImage1D(GLenum target, GLint level, GLenum interalformat,
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。 無法從2D顏色緩衝區獲得體積資料。

更新紋理
在一些時間敏感的場合,重複加新紋理,可能會成為效能瓶頸。如果我們不在需要某個已載入的紋理,他可以全部替換掉,也可以被替換一部分。替換一個紋理影象常常要比直接使用glTexImage重新載入一個新紋理快得多。用於完成這個任務的glTexSubImage,劇透三個變形:

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

絕大部分引數都和之前的glTexImage函式相同。三個Offset引數指定了在原來的紋理貼圖當中開始替換紋理資料的偏移量。width、height、depth引數指定了插入到原來紋理當中的新紋理的寬度深度和高度。
最後一組函式允許我們從顏色緩衝區讀取紋理,並且插入替換原來紋理的一部分。下面這些函式都是glCopyTexSubImage函式的變形。

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,
GLsizei width,GLsizei height);

注意,可以在一個三維紋理當中使用顏色緩衝區的資料來設定他的一個紋理平面單元。

紋理物件
紋理影象本身就是所謂紋理狀態的一部分。紋理狀態包含了紋理影象本身和一組紋理引數,這些引數控制過濾和紋理座標行為,使用glTexParameter函式設定這些紋理狀態引數的相關內容隨後進行討論。

在紋理之間進行切換或者重新載入不同的紋理是一種開銷很大的操作,紋理物件允許我們載入一個以上的紋理狀態,以及在他們之間盡情快速切換。紋理狀態是由當前繫結的紋理物件維護的,而紋理物件是由一個無符號整數標識的。

void glGenTexture(GLsizei n,GLuint *texture);
//這個函式當中,我們可以指定紋理物件的數量和一個指標,這個指標指向了一個無符號整型陣列。可以把他們看成是不同的可用紋理狀態控制代碼。為了繫結一種紋理狀態,可以呼叫下面這個函式:
void glBindTexture(GLenum target, GLuint texture);
//target引數必須是GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D,而texture引數則是需要繫結的特定紋理物件。此後,所有紋理載入和紋理引數設定隻影響當前繫結的紋理物件。為了刪除當前紋理物件。
void glDeleteTextures(GLsizei n, GLuint* texture);
//這個函式的引數和glGenTextures函式具有相同的意義,我們不需要同時產生和刪除所有的紋理物件。多次呼叫glGenTextures帶來的開銷很小。但是呼叫glDeleteTextures可能會帶來一些延遲。只有在銷燬大量紋理記憶體時才會發生。
GLboolean glIsTexture(GLuint texture);
//使用上面的函式對紋理物件進行測試,以判斷是否有效。

3.紋理應用
載入紋理只是在幾何圖形上應用紋理的第一步。最低限度我們必須同時同時提供紋理座標, 並且設定紋理座標環繞模式和紋理過濾。最後選擇對紋理進行MIP貼圖來提高紋理貼圖效能和視覺質量。

(1)紋理座標
我們是通過為每一個頂點制定一個紋理座標而直接在集合圖形上進行紋理貼圖的。紋理座標要麼指定為著色器一個屬性,要麼通過演算法計算出來。紋理貼圖中的紋理單元是作為一個更加抽象的(GLfloat)紋理座標,而不是作為記憶體位置進行定址的。
通常情況下,紋理座標是作為0-1範圍的浮點數指定的。紋理座標命名為s(x)、t(y)、r(z)和q(w),支援從一維到三維的紋理座標。並且可以選擇一種對座標進行縮放的方法。注意,q是作為放縮因子存在。
一個紋理座標會在每個頂點上應用一個紋理,OPENGL根據需要對紋理進行放大或縮小,將紋理貼圖到幾何圖形上。

(2)紋理引數
這些紋理引數是通過glTexParameter函式的變數來進行設定的。

void glTexParameterf(GLenum target, GLenum pname, GLfloat param);
void glTexParameteri(GLenum target, GLenum pname, GLint param);
void glTexParameterfv(GLenum target, GLenum pname, GLfloat *param);
void glTexParameteriv(GLenum target, GLenum pname, GLint *param);

第一個引數target指定這些引數將要應用到哪個紋理上,他可以是GL_TEXTURE 1D, GL TEXTURE _ 2D或者 GL _ TEXTURE _ 3D。第二個引數pname指定了需要設定哪個引數。最後一個引數用於設定特定的紋理引數的值。

基本過濾

紋理影象中的紋理單元和螢幕中的畫素幾乎從來都不會形成1對1的對應關係。一般,當紋理應用在幾何體表面的時候,紋理影象不是被拉伸,就是收縮。
根據一個拉伸或者收縮的紋理貼圖計算顏色片段的過程稱之為紋理過濾。使用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);

紋理環繞
正常情況下,在0-1範圍內確定紋理座標,使他和紋理貼圖中的紋理單元形成對映關係。如果紋理座標落在這個範圍之外,OPENGL則根據當前紋理環繞模式處理這個問題。
glTexParameteri函式為每個座標分別設定環繞模式。(分別使用GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T 、GL_TEXTURE_WRAP_R作為引數),環繞模式的列舉值為:GL_REPEAT、GL_CLAMP、GL_CLAMP_TO_EDGE 或者GL_CLAMP_TO_BORDER。

GL_REPEAT環繞模式:OPENGL在紋理座標超過1的方向上對紋理進行重複,這種紋理重複對每個整型紋理座標都適用。如果我們需要把一個小型的平鋪式紋理應用到大型幾何圖形的表面。這種模式就會非常有用。設計良好的無縫紋理可以導致大型紋理看上去是無縫的。付出的代價是需要小的多的紋理影象。
其他模式不進行重複,進行擷取。

紋理環繞模式對於紋理貼圖邊緣如何進行紋理過濾有著非常大的影響。在GL_NEAREST過濾模式中,環繞模式並不起作用,紋理座標總是對其到紋理貼圖中一些特定的紋理單元。但是,GL_LINEAR過濾則是需要取紋理座標周圍的畫素的平均值,對於那些位於紋理貼圖邊緣的紋理邊緣的紋理單元,這樣就會出現問題。

擷取型紋理環繞模式採用一些其他的選項處理紋理邊緣。
GL_CLAMP,所需的紋理單元取自紋理邊界或者TEXTURE_BORDER_COLOR。
GL _ CLAMP TO EDGE環繞模式強制對範圍之外的紋理座標沿著合法的紋理單元的最後一行或者最後一列進行取樣。
最後GL _ CLAMP _ TO _BORDER。環繞模式在紋理座標在0-1的範圍之外時候,只是用邊界紋理單元。邊界紋理單元是作為圍繞基本影象的額外行和列。並且和基本紋理影象一起載入。
擷取模式的一個典型應用就是在必須堆一塊大型區域進行紋理處理的時候,此時如果使用單個紋理,他將會由於過於龐大而無法裝入記憶體。可能會載入到單個紋理影象中。如果不適用像GL_CLAMP_TO_EDGE這樣的環繞模式會導致瓷磚之間存在明顯的縫隙痕跡。
渲染示例:

#pragma comment(lib,"GLTools.lib")

#include <GLTools.h>    // OpenGL toolkit
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLGeometryTransform.h>

#include <math.h>

#include <GL/glut.h>


/////////////////////////////////////////////////////////////////////////////////
// An assortment of needed classes
GLShaderManager     shaderManager;
GLMatrixStack       modelViewMatrix;
GLMatrixStack       projectionMatrix;
GLFrame             cameraFrame;
GLFrame             objectFrame;
GLFrustum           viewFrustum;

GLBatch             pyramidBatch;

GLuint              textureID;

GLGeometryTransform transformPipeline;
M3DMatrix44f        shadowMatrix;

//建立一個金字塔
void MakePyramid(GLBatch& pyramidBatch)
{
    //最後一個引數為1,那麼將會應用一個紋理如果我們使用預設引數的
    //C++特性,如果保持這個引數關閉狀態,自動設定為0。
    pyramidBatch.Begin(GL_TRIANGLES, 18, 1);

    // Normal3f方法向批次中添加了一個表面法線。
    //MultiTexCoord2f添加了一個紋理座標
    //最後Vertex3f添加了頂點的位置。
    //注意,如果為任何頂點指定了法線和紋理座標,
    //那麼就必須為每個頂點都進行同樣的指定。


    pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    //void GLBatch::MultiTexCoord2f(GLuint texture, GLclamp s, GLclamp t);
    pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);

    pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3f(1.0f, -1.0f, -1.0f);

    pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
    pyramidBatch.Vertex3f(1.0f, -1.0f, 1.0f);

    pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
    pyramidBatch.Vertex3f(-1.0f, -1.0f, 1.0f);

    pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);

    pyramidBatch.Normal3f(0.0f, -1.0f, 0.0f);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
    pyramidBatch.Vertex3f(1.0f, -1.0f, 1.0f);

    //設定各個方向的向量,在下面根據這些向量進行計算
    M3DVector3f vApex = { 0.0f, 1.0f, 0.0f };
    M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f };
    M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f };
    M3DVector3f vBackLeft = { -1.0f, -1.0f, -1.0f };
    M3DVector3f vBackRight = { 1.0f, -1.0f, -1.0f };
    M3DVector3f n;

    // 金字塔的前面
    //為金字塔的表面的一個面計算表面法線。注意下面的第一個函式
    //根據三個向量計算得到對應的法線向量。表面法線代表表面面向
    //的方向。
    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);        //  前右

    //下面的也一樣。
    m3dFindNormal(n, vApex, vBackLeft, vFrontLeft);
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);      // Apex

    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);      // Back left corner

    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontLeft);     // Front left corner

    m3dFindNormal(n, vApex, vFrontRight, vBackRight);
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);              // Apex

    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontRight);        // Front right corner

    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackRight);         // Back right cornder


    m3dFindNormal(n, vApex, vBackRight, vBackLeft);
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);      // Apex

    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackRight);     // Back right cornder

    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);      // Back left corner

    pyramidBatch.End();
}

// 載入TGA影象的函式如下
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
    GLbyte *pBits;
    int nWidth, nHeight, nComponents;
    GLenum eFormat;

    // 讀取tga檔案。
    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, nComponents, 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)
        glGenerateMipmap(GL_TEXTURE_2D);

    return true;
}


///////////////////////////////////////////////////////////////////////////////
// 首先需要對渲染環境進行初始化
//任務:生成紋理、繫結紋理、載入紋理

void SetupRC()
{
    // 設定背景顏色
    glClearColor(0.7f, 0.7f, 0.7f, 1.0f);

    shaderManager.InitializeStockShaders();

    glEnable(GL_DEPTH_TEST);

    //真正的第一步工作,載入紋理stone.tga
    glGenTextures(1, &textureID);
    //注意:制定一個紋理物件,並且將其放置到這個變數中
    glBindTexture(GL_TEXTURE_2D, textureID);
    //呼叫bind函式對其進行繫結
    LoadTGATexture("stone.tga", GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
    //載入紋理影象和設定紋理狀態是由LoadTGATexture完成的
    //bool LoadTGATexture(const char* szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode);
    //這個函式接受,影象的檔名字,需要縮小和放大過濾器,以及紋理座標的環繞方式,
    //他將會完整的對紋理狀態進行設定。

    //這個函式手動建立一個由獨立三角形構建的金字塔,並且將其放入到
    //容器類當中。
    MakePyramid(pyramidBatch);

    cameraFrame.MoveForward(-7.0f);
}

///////////////////////////////////////////////////////////////////////////////
// Cleanup... such as deleting texture objects
void ShutdownRC(void)
{
    glDeleteTextures(1, &textureID);
}

///////////////////////////////////////////////////////////////////////////////
// 真正渲染函式當中對金字塔的渲染
void RenderScene(void)
{
    static GLfloat vLightPos[] = { 1.0f, 1.0f, 0.0f };
    static GLfloat vWhite[] = { 1.0f, 1.0f, 1.0f, 1.0f };

    // Clear the window with current clearing color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    //儲存單位矩陣
    //並且把對應的攝像機矩陣也存入矩陣堆疊中
    modelViewMatrix.PushMatrix();
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.MultMatrix(mCamera);

    //一個Frame物件幀
    M3DMatrix44f mObjectFrame;
    objectFrame.GetMatrix(mObjectFrame);
    modelViewMatrix.MultMatrix(mObjectFrame);

    //必須再一次繫結紋理,然後開啟渲染器進行渲染
    glBindTexture(GL_TEXTURE_2D, textureID);
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,
        transformPipeline.GetModelViewMatrix(),
        transformPipeline.GetProjectionMatrix(),
        vLightPos, vWhite, 0);
    //注意其使用vWhite對幾何圖形進行著色。使用它乘以紋理顏色。
    pyramidBatch.Draw();


    modelViewMatrix.PopMatrix();

    // Flush drawing commands
    glutSwapBuffers();
}


// Respond to arrow keys by moving the camera frame of reference
void SpecialKeys(int key, int x, int y)
{
    if (key == GLUT_KEY_UP)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);

    if (key == GLUT_KEY_DOWN)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);

    if (key == GLUT_KEY_LEFT)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);

    if (key == GLUT_KEY_RIGHT)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);

    glutPostRedisplay();
}




///////////////////////////////////////////////////////////////////////////////
// Window has changed size, or has just been created. In either case, we need
// to use the window dimensions to set the viewport and the projection matrix.
void ChangeSize(int w, int h)
{
    glViewport(0, 0, w, h);
    //設定透視投影
    //並且載入到對應的矩陣中去。
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}








///////////////////////////////////////////////////////////////////////////////
// Main entry point for GLUT based programs
int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);

    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Pyramid");
    glutReshapeFunc(ChangeSize);
    glutSpecialFunc(SpecialKeys);
    glutDisplayFunc(RenderScene);

    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }


    SetupRC();

    glutMainLoop();

    ShutdownRC();

    return 0;
}

4.MIP貼圖
MIP貼圖是一種功能強大的紋理技巧,不僅僅能夠提高渲染效能,還可以改善場景的渲染質量。
使用標準紋理貼圖處理的兩個問題:
~閃爍(鋸齒假影):當螢幕上被渲染的物體的表面和他應用的紋理影象相比顯得非常小的時候,就會出現這種效果。當紋理影象的取樣區域的移動幅度與他在螢幕上大小不成比例的時候,就會出現這種情況。
~效能問題:必須載入大量的紋理記憶體並且對他們進行過濾處理,但是螢幕上實際顯示的只是很少的一部分。紋理越大,這個問題造成的效能影響越大。
解決手段:MIP(一個小地方有許多東西)我們不是把單個影象載入到紋理狀態中,而是把一系列從最大到最小的紋理影象載入到單個“Mip貼圖”紋理狀態中。
然後在OPENG中使用一組新的過濾模式,為一個特定的集合圖形選擇具有最佳過濾效果的紋理。在付出一些額外記憶體的代價之後,不僅可以消除閃爍現象,同時可以大大降低紋理貼圖所需要的記憶體。
Mip貼圖由一系列紋理影象組成,每個影象大小在每個軸的方向上都會縮小一半。或者原來影象畫素總數的1/4。
MIP貼圖不一定是正方形的,但每個影象的接下來的減半處理就只發生在其他維度上了。使用一組正方形的MIP貼圖所要求的記憶體比不適用MIP貼圖高出1/3。
MIP貼圖是通過glTexImage函式載入的。現在輪到level引數發揮作用了。他指定了影象資料用於哪個MIP層。第一層是0,接著是1,2等等。如果mip貼圖沒有使用,那麼就只有第0層被載入。在預設狀況下,為了使用mip貼圖未被使用。那麼可以使用GL_TEXTURE_BASE_LEVEL和GL_TEXTURE_MAX_LEVEL紋理引數特別設定需要使用的基層和最大層。我們還可以使用GL_TEXTURE_MIN_LOD和GL_TEXTURE_MAX_LOD引數限制已經載入的MIP圖層使用範圍。

(1)MIP貼圖過濾
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貼圖。

僅僅使用glTexImage函式載入MIP層不能啟用mip貼圖功能。如果過濾紋理設定為GL_LINEAR或者GL_NEAREST。那麼就只有紋理貼圖基層會被使用。其他所有載入的mip層都將被忽略。
我們必須制定其中一個mip貼圖過濾器,這樣才能使用所有已載入的mip層。這個常量具有GL_FILTER_MIPMAP_SELECTOR的形式,其中FILTER指定了被選擇的MIP層將要使用的紋理過濾器,SELECTOR則指定了如何選擇MIP層。
如果選擇了GL_LINEAR,他就會在兩個最鄰近的MIP層之間執行線性插值,其結果又由被選擇的紋理過濾器進行過濾。如果選擇了其中一種MIP貼圖過濾模式,但是不載入MIP層,那麼這將導致無效的紋理狀態。
應該選擇哪種過濾器取決於具體的應用以及希望實現的效能要求。GL_NEAREST_MIPMAP_NEAREST具有非常好的效能,並且閃爍現象非常弱,但是最鄰近過濾在視覺效果上常常難以令人滿意。GL_NEAREST_MIPMAP_LINEAR常常用於對遊戲加速,因為它使用了更高質量的線性過濾器。但是他需要在不同大小的可用MIP層之間進行快速選擇(最鄰近過濾)。
使用最鄰近模式作為MIP貼圖選擇器,可能會導致令人失望的視覺效果。通過一個傾斜的觀察角度,常常可以看到物體表面從一個MIP層到另外一個MIP層的轉變。我們可以看到一條扭曲的線段,或者從一個細節層次到另外一個細節層次之間的急劇的轉變。GL_LINEAR_MIPMAP_NEAREST和GL_LINEAR_MIPMAP_LINEAR過濾器在MIP層之間執行一些額外的插值,從而消除他們之間過渡的痕跡。但是它需要相當可觀的額外處理開銷。
GL_LINEAR_MIPMAP_LINEAR 過濾器通常又稱為三線性mip貼圖,是黃金準則,具有最高精度。

(2)生成MIP層
和僅僅載入的基本紋理影象相比,MIP貼圖所需要的紋理記憶體大概多出大約1/3。要求所有更小的基本紋理影象可以進行載入。

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。

為了得到提高視覺質量,我們應該載入自己預先生成的MIP貼圖。

(3)活動的MIP貼圖

Tunnel程式當中,啟動時候同時載入三個紋理,然後在其中對其進行隨意切換,從而達到對隧道的渲染效果。具體程式以及詳細分析如下:

#pragma comment(lib,"GLTools.lib")
#include <GLTools.h>
#include <GLShaderManager.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLFrame.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#include <GL/glut.h>


GLShaderManager     shaderManager;          // 著色器管理器
GLMatrixStack       modelViewMatrix;        // 模型檢視矩陣
GLMatrixStack       projectionMatrix;       // 投影矩陣
GLFrustum           viewFrustum;            // 投影矩陣
GLGeometryTransform transformPipeline;      // 幾何變換管線

GLBatch             floorBatch;                
GLBatch             ceilingBatch;
GLBatch             leftWallBatch;
GLBatch             rightWallBatch;

GLfloat             viewZ = -65.0f;

// 紋理物件,巨集定義
#define TEXTURE_BRICK   0
#define TEXTURE_FLOOR   1
#define TEXTURE_CEILING 2
#define TEXTURE_COUNT   3
//設定當前所需要的貼圖的陣列
GLuint  textures[TEXTURE_COUNT];
//設定當前所需要貼圖的名字的陣列
const