渲染世界的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 char *szTextureFiles[TEXTURE_COUNT] = { "brick.tga", "floor.tga", "ceiling.tga" };



///////////////////////////////////////////////////////////////////////////////
// 設置處理菜單。右鍵菜單設置
void ProcessMenu(int value)
{
    GLint iLoop;

    for (iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++)
    {
        glBindTexture(GL_TEXTURE_2D, textures[iLoop]);
        //把對應的貼圖進行綁定
        switch (value)
        {
            //設置使用縮小過濾器。以及對應的對應過濾模式
            //使用不同的過濾模式來對圖像進行渲染
        case 0:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            break;

        case 1:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            break;

        case 2:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
            break;

        case 3:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
            break;

        case 4:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
            break;

        case 5:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
            break;
        }
    }

    // 手動重新繪製
    glutPostRedisplay();
}


//////////////////////////////////////////////////////////////////
//初始化渲染環境
void SetupRC()
{
    GLbyte *pBytes;
    GLint iWidth, iHeight, iComponents;
    GLenum eFormat;
    GLint iLoop;

    // 背景顏色
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    shaderManager.InitializeStockShaders();

    // 生成紋理貼圖,綁定的對象個數,以及傳引用綁定到對應的對象上
    glGenTextures(TEXTURE_COUNT, textures);
    for (iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++)
    {
        // 對每一個圖像加以綁定貼圖模式
        glBindTexture(GL_TEXTURE_2D, textures[iLoop]);

        // 加載貼圖,並且得到貼圖對應的屬性。
        pBytes = gltReadTGABits(szTextureFiles[iLoop], &iWidth, &iHeight,
            &iComponents, &eFormat);//數據格式

        // 加載紋理,設置過濾器和環繞模式
        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_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes);
        glGenerateMipmap(GL_TEXTURE_2D);
        // Don't need original texture data any more
        free(pBytes);
    }

    // 創建對應的形體。
    GLfloat z;
    //使用一種貼圖進行渲染
    floorBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for (z = 60.0f; z >= 0.0f; z -= 10.0f)
    {
        //添加紋理座標,並且添加頂點位置
        floorBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        floorBatch.Vertex3f(-10.0f, -10.0f, z);

        floorBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        floorBatch.Vertex3f(10.0f, -10.0f, z);

        floorBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        floorBatch.Vertex3f(-10.0f, -10.0f, z - 10.0f);

        floorBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        floorBatch.Vertex3f(10.0f, -10.0f, z - 10.0f);
    }
    floorBatch.End();

    ceilingBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for (z = 60.0f; z >= 0.0f; z -= 10.0f)
    {
        ceilingBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        ceilingBatch.Vertex3f(-10.0f, 10.0f, z - 10.0f);

        ceilingBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        ceilingBatch.Vertex3f(10.0f, 10.0f, z - 10.0f);

        ceilingBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        ceilingBatch.Vertex3f(-10.0f, 10.0f, z);

        ceilingBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        ceilingBatch.Vertex3f(10.0f, 10.0f, z);
    }
    ceilingBatch.End();

    leftWallBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for (z = 60.0f; z >= 0.0f; z -= 10.0f)
    {
        leftWallBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        leftWallBatch.Vertex3f(-10.0f, -10.0f, z);

        leftWallBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        leftWallBatch.Vertex3f(-10.0f, 10.0f, z);

        leftWallBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        leftWallBatch.Vertex3f(-10.0f, -10.0f, z - 10.0f);

        leftWallBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        leftWallBatch.Vertex3f(-10.0f, 10.0f, z - 10.0f);
    }
    leftWallBatch.End();


    rightWallBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for (z = 60.0f; z >= 0.0f; z -= 10.0f)
    {
        rightWallBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        rightWallBatch.Vertex3f(10.0f, -10.0f, z);

        rightWallBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        rightWallBatch.Vertex3f(10.0f, 10.0f, z);

        rightWallBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        rightWallBatch.Vertex3f(10.0f, -10.0f, z - 10.0f);

        rightWallBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        rightWallBatch.Vertex3f(10.0f, 10.0f, z - 10.0f);
    }
    rightWallBatch.End();
}

///////////////////////////////////////////////////
// 渲染環境結束的時候
void ShutdownRC(void)
{
    //刪除紋理對象,釋放內存
    glDeleteTextures(TEXTURE_COUNT, textures);
}


///////////////////////////////////////////////////
// 設置對應的鍵位進行移動
void SpecialKeys(int key, int x, int y)
{
    if (key == GLUT_KEY_UP)
        viewZ += 0.5f;

    if (key == GLUT_KEY_DOWN)
        viewZ -= 0.5f;

    // 手動刷新
    glutPostRedisplay();
}

/////////////////////////////////////////////////////////////////////
// 改變對應視口大小。
void ChangeSize(int w, int h)
{
    GLfloat fAspect;

    // Prevent a divide by zero
    if (h == 0)
        h = 1;

    // Set Viewport to window dimensions
    glViewport(0, 0, w, h);

    fAspect = (GLfloat)w / (GLfloat)h;

    // 加載對應的矩陣
    viewFrustum.SetPerspective(80.0f, fAspect, 1.0, 120.0);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);

}

///////////////////////////////////////////////////////
// 渲染圖像函數
void RenderScene(void)
{
    // 清除顏色緩衝區
    glClear(GL_COLOR_BUFFER_BIT);

    //加載一個單位矩陣。
    modelViewMatrix.PushMatrix();
    //對整個視圖矩陣進行移動
    modelViewMatrix.Translate(0.0f, 0.0f, viewZ);
    //注意,先渲染一次
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0);

    //再一次綁定渲染的貼圖
    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_FLOOR]);
    floorBatch.Draw();

    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_CEILING]);
    ceilingBatch.Draw();

    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_BRICK]);
    leftWallBatch.Draw();
    rightWallBatch.Draw();

    modelViewMatrix.PopMatrix();

    // Buffer swap
    glutSwapBuffers();
}


//////////////////////////////////////////////////////
// Program entry point
int main(int argc, char *argv[])
{
    gltSetWorkingDirectory(argv[0]);

    // Standard initialization stuff
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Tunnel");
    glutReshapeFunc(ChangeSize);
    glutSpecialFunc(SpecialKeys);
    glutDisplayFunc(RenderScene);

    // Add menu entries to change filter
    glutCreateMenu(ProcessMenu);
    glutAddMenuEntry("GL_NEAREST", 0);
    glutAddMenuEntry("GL_LINEAR", 1);
    glutAddMenuEntry("GL_NEAREST_MIPMAP_NEAREST", 2);
    glutAddMenuEntry("GL_NEAREST_MIPMAP_LINEAR", 3);
    glutAddMenuEntry("GL_LINEAR_MIPMAP_NEAREST", 4);
    glutAddMenuEntry("GL_LINEAR_MIPMAP_LINEAR", 5);

    glutAttachMenu(GLUT_RIGHT_BUTTON);

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


    // Startup, loop, shutdown
    SetupRC();
    glutMainLoop();
    ShutdownRC();

    return 0;
}


我們首先爲3個紋理對象創建標識符。texture數組將會包含三個整數。他們可以用宏TEXTURE_BRICK,TEXTURE_FLOOR,TEXTURE_CEILING進行訪問。爲了增加靈活性,我們還創建了一個宏,定義了將要加載的紋理的最大數量,並且創建了一個字符串數組,包含了紋理貼圖文件的名稱。
紋理對象在SetUpRC函數進行分配。
然後是一個簡單的循環,依次綁定每個紋理對象。

注意:當示例程序設置MIP貼圖紋理過濾器的時候,被選擇爲只用於縮小過濾器。

5、各向異性過濾

各向異性過濾(Anisotropic texture filtering)並不是OPENGL核心規範的一部分,但他是一種得到廣泛支持的擴展,可以極大提高紋理過濾操作的質量。
最鄰近過濾GL_NEAREST和線性過濾GL_LINEAR, 當一個紋理貼圖被過濾的時候,OPENGL使用紋理座標來判斷一個特定的幾何片段將會落在紋理貼圖的什麼地方。然後緊鄰這個位置的紋理單元使用GL_LINEAREST或者GL_LINEAR過濾操作進行操作。
當幾何圖形進行紋理貼圖的時候,如果他的觀察方向和觀察點恰好垂直,那麼這個過程相當完美。但是從一個傾斜的角度去觀察這個幾何圖形的時候,對周圍紋理單元進行常規採樣將會導致一些紋理丟失。
我們在進行紋理過濾的時候考慮了觀察角度,那麼這種過濾方法就成爲各項異性過濾。
應用各項異性過濾需要三個步驟:

(1)首先必須確定這種擴展是得到支持的。
如果下面的函數返回真, 那麼這個擴展收到支持。

gltIsExtSupported("GL_EXT_texture_filter_anisotropic")

在確定這個擴展得到支持之後,就可以查詢得到支持的各向異性過濾的最大數量,需要調用glGetFloatv函數,並且用GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT爲參數。

glGetFloatv(GL_MAX_ANISOTROPY_EXT, &fLargest);
//fLargest爲自定義的浮點型。

各項異性過濾所應用的數量越大,沿最大變化方向(沿最強的觀察點)所採樣的紋理單元就越多。值1表示常規的紋理過濾(成爲各向同性過濾)。
最後使用glTexParameter函數以及GL_TEXTURE_MAX_ANISOTROPY_EXT常量設置想要應用的各向異性過濾數量。

注意:使用各項異性過濾還可以極大弱化GL_LINEAR_MIPMAP_NEAREST和GL_NEAREST_MIPMAP_BEAREST類型的MIP貼圖過濾器所存在的MIP貼圖過渡圖案。
在上面的Tunnel代碼中的ProcessMenu當中的switch中的選項增加下列的代碼:

case 6:
            //得到了當前支持各向異性過濾的最大數值
            glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
            //設置各向異性過濾
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
            break;
case 7:
            //還原成各向同性過濾
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);
            break;

6、紋理壓縮

使用紋理存在一個缺點,就是他們需要大量的內存來存儲和處理紋理。早起對紋理壓縮的方法可以節省磁盤空間並且減少磁盤空間並且減少網絡傳輸圖像的時間。
我們可以使用Gl_ARB_texture_compression字符串測試這個擴展是否得到支持。
OPENGL硬件對紋理壓縮的支持遠遠不止是簡單地允許加載經過壓縮的紋理,在絕大多數現實中,紋理數據甚至在圖形硬件內存中仍然保持壓縮狀態。這就允許我們在較小的內存中加載更多紋理,從而顯著地改善紋理處理性能,這是由於在紋理過濾時減少了紋理交換並且使用了更少內存的原因。

(1)壓縮紋理
爲了利用OPENGL對壓縮紋理的支持,紋理數據一開始並不需要進行壓縮。我們可以加載一幅紋理圖像時候,請求OPENGL對他進行壓縮。這是通過在glTexImage函數把internalFormat參數設置爲表中任意一個值實現的:

//通用壓縮紋理格式
GL_COMPRESSED_RGB:GL_RGB
GL_COMPRESSED_RGBA:GL_RGBA
GL_COMPRESSED_SRGB:GL_RGB
GL_COMPRESSED_SRBG_ALPHA:GL_RGBA
GL_COMPRESSED_RED:GL_RED
GL_COMPRESSED_RG:GL_RG

通過這種方式進行圖形壓縮增加了紋理加載的開銷,但是卻能夠通過更有效地使用紋理存儲空間來增加紋理性能。如果由於某些原因而無法對紋理進行壓縮,OPENGL就會使用上表所列出的基本內部格式,並且加載未經壓縮的紋理。
當我們試圖按照這種方式加載並且壓縮一個紋理時,可以使用glGetTexLevelParameteriv函數(以GL_TEXTURE_COMPRESSED爲參數)來判斷這個紋理是否成功壓縮。

GLint compFlag;

glGetTexLevelParameteriv(GL_TEXTURE_2D,0 , GL_TEXTURE_COMPRESSED, &comFlag);
//以上函數可以接收幾個新的參數名,他們都和壓縮紋理有關。
//壓縮紋理格式
//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:紋理壓縮提示的值。

OPENGL會選擇最適當的紋理壓縮格式,我們可以使用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_NUM_COMPRESSED_TEXTURE_FORMATS和GL_COMPRESSED_TEXTURE_FORMAT:爲參數查詢壓縮格式的數量以及相關值的列表。爲了檢查一組特定的壓縮紋理格式是否得到支持,需要檢查一個與這些格式有關的特定擴展。

(2)加載紋理座標

使用前面所介紹的函數,可以讓OPENGL用一中本地支持的格式對紋理進行壓縮,用glGetCompressedTexImage函數(相當於未壓縮紋理的glTexImage函數)提取經過壓縮的數據並且把它保存到磁盤中。在後續的紋理加載中,可以使用原始壓縮數據,從而極大的提高紋理的加載速度。
爲了加載預先經過壓縮的紋理數據,可以使用下列函數:

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, GLsiei height, GLint border, GLsizei imageSize, void* data);


void glCompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat,
GLsizei width, GLsiei height, GLsiei depth, GLint border, GLsizei imageSize, void* data);

紋理壓縮是一種非常流行的紋理特性,較小的紋理佔據空間更小,通過網絡傳輸的速度更快,從磁盤加載的速度更快、複製到圖形內存也更加快速。並且允許更多的紋理加載到硬件中,而且使紋理的啓用更加快速。

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