渲染世界的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);

纹理压缩是一种非常流行的纹理特性,较小的纹理占据空间更小,通过网络传输的速度更快,从磁盘加载的速度更快、复制到图形内存也更加快速。并且允许更多的纹理加载到硬件中,而且使纹理的启用更加快速。

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