OpenGL学习笔记4:纹理

Posted 键盘春秋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenGL学习笔记4:纹理相关的知识,希望对你有一定的参考价值。

原始图像数据

像素包装

图像数据在内存中很少以紧密包装的形式存在。在许多硬件平台上,处于性能上的考虑,一幅图像的每一行都应该从一种特定字节对齐地址开始。绝大多数编译器会自动把变量和缓冲区放置在一个针对该架构对齐优化的地址上。
例如一个包含3个分量的rgb图像,每个分量存储在一个字节中,如果图像有199个像素,那么一行需要597个像素。如果硬件本身的体系结构是4字节排列,那么图像每一行的末尾将由额外的3个空字符进行填充达到600字节。
我们可以使用下列函数改变或者回复像素的存储方式。

void glPixelStorei(GLenum pname,Glint param);
void glPixelStoref(GLenum pname,GLfloat param);

例如,改成紧密包装(文件格式以1个字节排列,没有补齐):

glPixelStorei(GL_UNPACK_ALLGNMENT,1);

其中GL_UNPACK_ALIGNMENT指定OpenGL如何从数据缓冲区中解包图像数据。
glPixelStore参数

参数名类型初始值
GL_PACK_SWAP_BYTESGLbooleanGL_FALSE
GL_UNPACK_SWAP_BYTESGLbooleanGL_FALSE
GL_PACK_LSB_FIRSTGLbooleanGL_FALSE
GL_UNPACK_LSB_FIRSTGLbooleanGL_FALSE
GL_PACK_ROW_LENGTHGLint0
GL_UNPACK_ROW_LENGTHGLint0
GL_PACK_SKIP_ROWSGLint0
GL_UNPACK_SKIP_ROWSGLint0
GL_PACK_SKIP_PIXELSGLint0
GL_UNPACK_SKIP_PIXELSGLint0
GL_PACK_ALIGNMENTGLint4
GL_UNPACK_ALIGNMENTGLint4
GL_PACK_IMAGE_HEIGHTGLint0
GL_UNPACK_IMAGE_HEIGHTGLint0
GL_PACK_SKIP_IMAGESGLint0
GL_UNPACK_SKIP_IMAGESGLint0

像素图

每个像素需要一个以上的存储位来表示,附加位允许存储强度(亮度)。在OpenGL核心版本中,我们无法直接将一个像素图绘制到颜色缓冲区中,但是可以使用下面的函数将颜色缓冲区的内容作为像素图直接读取。

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

我们将x和y值指定为矩形左下角的窗口坐标,然后再指定矩形的width和height值(像素形式)。如果颜色缓冲区存储的数据与我们要求的不同,OpenGL将负责进行必要的转换。
第4个变量format指定pixels指向的数据元素的颜色布局,如下表:

OpenGL像素格式

常量描述
GL_RGB按照红、绿、蓝顺序排列的颜色
GL_RGBA按照红、绿、蓝、Alpha顺序排列的颜色
GL_BGR按照蓝、绿、红顺序排列的颜色
GL_BGRA按照蓝、绿、红、Alpha顺序排列的颜色
GL_RED每个像素值包含一个红色分量
GL_GREEN每个像素值包含一个绿色分量
GL_BLUE每个像素值包含一个蓝色分量
GL_RG每个像素依次包含一个红色和一个绿色分量
GL_RED_INTEGER每个像素包含一个整数形式的红色分量
GL_GREEN_INTEGER每个像素包含一个整数形式的绿色分量
GL_BLUE_INTETER每个像素包含一个整数形式的蓝色分量
GL_RG_INTEGER每个玄素依次包含一个整数形式的红色和一个整数形式的绿色分量
GL_RGB_INTEGER每个像素依次包含整数形式的红色、绿色和蓝色分量
GL_RGBA_INTEGER每个像素一次包含整数形式的红色、绿色、蓝色和Alpha分量
GL_BGR_INTEGER每个像素依次包含整数形式的蓝色、绿色和红色分量
GL_BGRA_INTEGER每个像素依次包含整数形式的蓝色、绿色、红色和Alpha分量
GL_STENCIL_INDEX每个像素只包含一个模板值
GL_DEPTH_COMPONENT每个像素只包含一个深度值
GL_DEPTH_STENCIL每个像素包含一个深度值和一个模板值

参数type解释参数*pixels指向的诗句,它告诉OpenGL使用缓冲区中的什么数据类型来存储颜色分量。如下表:
像素数据的数据类型

常量描述
GL_UNSIGNED_BYTE每种颜色分量都是一个8位无符号整数
GL_BYTE8位有符号整数
GL_UNSIGNED_SHORT16位无符号整数
GL_SHORT16位有符号整数
GL_UNSIGNED_INT32位无符号整数
GL_INT32位有符号整数
GL_FLOAT单精度浮点数
GL_HALF_FLOAT半精度浮点数
GL_UNSIGNED_BYTE_3_2_2包装的RGB值
GL_UNSIGNED_BYTE_2_3_3_REV包装的RGB值
GL_UNSIGNED_SHORT_5_6_5包装的RGB值
GL_UNSIGNED_SHORT_5_6_5_REV包装的RGB值
GL_UNSIGNED_SHORT_4_4_4_4包装的RGB值
GL_UNSIGNED_SHORT_4_4_4_4_REV包装的RGB值
GL_UNSIGNED_SHORT_5_5_5_1包装的RGB值
GL_UNSIGNED_SHORT_1_5_5_5_REV包装的RGB值
GL_UNSIGNED_INT_8_8_8_8包装的RGB值
GL_UNSIGNED_INT_8_8_8_8_REV包装的RGB值
GL_UNSIGNED_INT_10_10_10_2包装的RGB值
GL_UNSIGNED_INT_2_10_10_10_REV包装的RGB值
GL_UNSIGNED_INT_24_8包装的RGB值
GL_UNSIGNED_INT_10F_11F_REV包装的RGB值
GL_FLOAT_24_UNSIGNED_INT_24_8_REV包装的RGB值

glReadPixels从图形硬件中复制数据,通常通过总线传输到系统内存。在这种情况下,应用程序将被阻塞,直到内存传输完成。此外,如果我们制定一个与图形硬件的本地排列不同的像素不具,那么在数据进行重定格式时将产生额外的性能开销。

包装的像素格式

默认情况下,对于glReadPixels函数来说,读取操作在双缓冲区渲染环境下将在后台缓冲区进行,而在单缓冲区渲染环境下则在前台缓冲区进行。我们可以用下面的函数改变这些像素操作的源。
void glReadBuffer(GLenum mode);
模式参数可以取GL_FRONT、GL_BACK、GL_LEFT、GL_RIGHT、GL_FRONT_LEFT、GL_FRONT_RIGHT、GL_BACK_LEFT、GL_BACK_RIGHT或者甚至是GL_NONE中的任意一个。

保存像素

GLTools库中的gltWriteTGA函数从前台颜色缓冲区中读取颜色数据,并将这些数据存储到一个Targa文件格式的图像文件中。

GLint gltWriteTGA(const char *szFileName)
{
    FILE *pFile;                // 文件指针
    TGAHEADER tgaHeader;        // tga文件头
    unsigned long lImageSize;   // 图像的大小
    GLbyte    *pBits = NULL;      // 图像数据
    GLint iViewport[4];         // 视口
    GLenum lastBuffer;          // 保存当前读取缓冲区的设置

    // 取得当前视口大小
    glGetIntegerv(GL_VIEWPORT, iViewport);

    // 获得图像大小,因为tga的图像数据是紧密包装的,所以用视口的宽高乘以3个字节
    lImageSize = iViewport[2] * 3 * iViewport[3];    

    // 分配内存用于存储读取出来的图像数据
    pBits = (GLbyte *)malloc(lImageSize);
    if(pBits == NULL)
        return 0;

    // 设置为逐个像素的方式读取
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glPixelStorei(GL_PACK_ROW_LENGTH, 0);
    glPixelStorei(GL_PACK_SKIP_ROWS, 0);
    glPixelStorei(GL_PACK_SKIP_PIXELS, 0);

    // 保存当前的设置,后面再恢复它
    glGetIntegerv(GL_READ_BUFFER, (GLint *)&lastBuffer);
    glReadBuffer(GL_FRONT);
    glReadPixels(0, 0, iViewport[2], iViewport[3], GL_BGR_EXT, GL_UNSIGNED_BYTE, pBits);
    glReadBuffer(lastBuffer);

    // 初始化tag文件头的格式
    tgaHeader.identsize = 0;
    tgaHeader.colorMapType = 0;
    tgaHeader.imageType = 2;
    tgaHeader.colorMapStart = 0;
    tgaHeader.colorMapLength = 0;
    tgaHeader.colorMapBits = 0;
    tgaHeader.xstart = 0;
    tgaHeader.ystart = 0;
    tgaHeader.width = iViewport[2];
    tgaHeader.height = iViewport[3];
    tgaHeader.bits = 24;
    tgaHeader.descriptor = 0;

    // 苹果操作需要,进行大小端的转换
#ifdef __APPLE__
    LITTLE_ENDIAN_WORD(&tgaHeader.colorMapStart);
    LITTLE_ENDIAN_WORD(&tgaHeader.colorMapLength);
    LITTLE_ENDIAN_WORD(&tgaHeader.xstart);
    LITTLE_ENDIAN_WORD(&tgaHeader.ystart);
    LITTLE_ENDIAN_WORD(&tgaHeader.width);
    LITTLE_ENDIAN_WORD(&tgaHeader.height);
#endif

    // 打开文件
    pFile = fopen(szFileName, "wb");
    if(pFile == NULL)
    {
        free(pBits);    
        return 0;
    }

    // 写文件头
    fwrite(&tgaHeader, sizeof(TGAHEADER), 1, pFile);

    // 写图像数据
    fwrite(pBits, lImageSize, 1, pFile);

    // 释放临时分配的内存空间
    free(pBits);    
    fclose(pFile);

    // 成功了
    return 1;
}

读取像素

gltReadTGABits从磁盘中载入Targa文件。
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat)
{
  FILE *pFile;            // 文件指针
  TGAHEADER tgaHeader;        // TGA 文件头
  unsigned long lImageSize;        // 图像的大小,用字节表示
  short sDepth;            // 像素深度
  GLbyte    *pBits = NULL;          // 指向位的指针

  // 默认/失败值
  *iWidth = 0;
  *iHeight = 0;
  *eFormat = GL_BGR_EXT;
  *iComponents = GL_RGB;

  // 尝试打开文件
  pFile = fopen(szFileName, "rb");
  if(pFile == NULL)
    return NULL;

  // 读入文件(二进制)
  fread(&tgaHeader, 18/* sizeof(TGAHEADER)*/, 1, pFile);

  // 为大小字节存储顺序问题而进行字节交换
#ifdef __APPLE__
  LITTLE_ENDIAN_WORD(&tgaHeader.colorMapStart);
  LITTLE_ENDIAN_WORD(&tgaHeader.colorMapLength);
  LITTLE_ENDIAN_WORD(&tgaHeader.xstart);
  LITTLE_ENDIAN_WORD(&tgaHeader.ystart);
  LITTLE_ENDIAN_WORD(&tgaHeader.width);
  LITTLE_ENDIAN_WORD(&tgaHeader.height);
#endif


  // 获取纹理的宽度、高度和深度
  *iWidth = tgaHeader.width;
  *iHeight = tgaHeader.height;
  sDepth = tgaHeader.bits / 8;

  // 这里进行一些有效性检验,非常简单,我们只要懂得或者说关心8位、24位或32位
  //的targa
  if(tgaHeader.bits != 8 && tgaHeader.bits != 24 && tgaHeader.bits != 32)
    return NULL;

  // 计算图像缓冲区的大小
  lImageSize = tgaHeader.width * tgaHeader.height * sDepth;

  // 进行内存定位并进行成功检验
  pBits = (GLbyte*)malloc(lImageSize * sizeof(GLbyte));
  if(pBits == NULL)
    return NULL;

  // 读入位
  // 检查读取错误。这项操作应该发现RLE或者其他我们不想识别的奇怪格式
  if(fread(pBits, lImageSize, 1, pFile) != 1)
  {
    free(pBits);
    return NULL;
  }

  // 设置希望的OpenGL格式
  switch(sDepth)
  {
  case 3:     // 最可能的情况
    *eFormat = GL_BGR;
    *iComponents = GL_RGB;
    break;
  case 4:
    *eFormat = GL_BGRA_EXT;
    *iComponents = GL_RGBA8;
    break;
  case 1:
    *eFormat = GL_LUMINANCE;
    *iComponents = GL_LUMINANCE8;
break;
default: // RGB
// 如果是在iPhone上TGA为BGR,并且iPhone不支持没有Alpha的BGR,
//但是它支持RGB,所以只要将红色和蓝色调整一下就能满足要求。
//但是为了加快iPhone的载入速度,请保存带有Alpha的TGA。
#ifdef OPENGL_ES
    for(int i=0;i<lImageSize;i+=3){
        GLbyte temp=pBits[i];
        pBits[i]=pBits[i+2];
        pBits[i+2]=temp;
}
#endif
  };

  // 文件操作完成
  fclose(pFile);

  // 返回指向图像数据的指针
  return pBits;
}

载入纹理

有3个OpenGL函数最经常用来从存储器缓冲区中载入(比如说,从一个磁盘文件中读取)纹理数据。

void glTexImage1D(GLenum target,Glint level,Glint internalformat,GLsizei width,Glint border,GLenum format,GLenum type,void *data);
void glTexImage2D(GLenum target,Glint level,Glint internalformat,GLsizei width,GLsizei height,Glint border,GLenum format,GLenum type,void *data);
void glTexImage3D(GLenum target,Glint level,Glint internalformat,GLsizei width,GLsizei height,GLsizei depth,Glint border,GLenum format,GLenum type,void *data);

这些函数中的target变量应分别为GL_TEXTURE_1D、GL_TEXTURE_2D或GL_TEXTURE_3D。我们也可以指定代理纹理(Proxy texture),方法是指定GL_PROXY_TEXTURE_1D、GL_PROXY_TEXTURE_2D或GL_PROXY_TEXTURE_3D,并使用glGetTexPatameter函数提取代理查询的结果。
Level参数指定了这些函数所加在的mip贴图层次。
internalformat参数告诉OpenGL我们希望在每个纹理单元中存储多少颜色成分,并在可能的情况下说明这些成分的存储大小,以及是否希望对纹理进行压缩。
最常用的纹理内部格式

常量含义
GL_ALPHA按照alpha值存储纹理单元
GL_LUMINANCE按照亮度值存储纹理单元
GL_LUMINANCE_ALPHA按照亮度值和alpha值存储纹理单元
GL_RGB按照红、绿、蓝成分存储纹理单元
GL_RGBA按照红、绿、蓝和alpha成分存储纹理单元

使用颜色缓冲区

一维和二维的纹理也可以从颜色缓冲区加在数据。我们可以从颜色缓冲区读取一幅图像,并通过下面这两个函数将它化为一个新的纹理使用。

void glCopyTexImage1D(GLenum target,Glint level,GLenum internalformat,Glint x,Glint y,GLsizei width,Glint border);
void glCopyTexImage2D(GLenum target,Glint level,GLenum internalformat,Glint x,Glint y,GLsizei width,GLsizei height,Glint border);

这两个函数的操作类似于glTexImage,但在这里x和y在颜色缓冲区中指定了开始读取纹理数据的位置。源缓冲区是通过glReadBuffer函数设置的。并不存在glCopyTexImage3D。

更新纹理

替换一个纹理图像常常要比直接使用glTexImage重新加载一个新纹理快得多:

void glTexSubImage1D(GLenum target,Glint level,Glint xOffset,GLsizei width,GLenum format,GLenum type,const GLvoid *data);
void glTexSubImage2D(GLenum target,Glint level,Glint xOffset,Glint yOffset,GLsizei width,GLsizei height,GLenum format,GLenum type,const GLvoid *data);
void glTexSubImage3D(GLenum target,Glint level,Glint xOffset,Glint yOffset,Glint zOffset,GLsizei width,GLsizei height,GLsizei depth,GLenum format,GLenum type,const GLvoid *data);

下面函数允许沃恩从颜色缓冲区读取纹理,并插入或替换原来纹理的一部分。

void glCopyTexSubImage1D(GLenum target,Glint level,Glint xoffset,Glint x,Glint y,GLsizei width);
void glCopyTexSubImage2D(GLenum target,Glint level,Glint xoffset,Glint yoffset,Glint x,Glint y,GLsizei width,GLsizei height);
void glCopyTexSubImage3D(GLenum target,Glint level,Glint xoffset,Glint yoffset,Glint zoffset,Glint x,Glint y,GLsizei width,GLsizei height);

纹理对象

纹理对象允许我们一次加载一个以上的纹理状态(包括纹理图像),以及在它们之间快速切换。纹理状态是由当前绑定的纹理对象维护的,而纹理对象是由一个无符号整数标识的:
按所需加载纹理数量初始化纹理对象:

void glGenTextures(GLsizei n,GLuint *textures);

为了绑定纹理状态,我们可以调用下面这个函数:

void glBindTexture(GLenum target,GLuint texture);

为了删除纹理对象,可以调用下面这个函数:

void glDeleteTextures(GLsizei n,GLuint *textures);

我们可以使用下面这个函数对纹理对象名(或句柄)进行测试,判断它们是否有效:

GLboolean glIsTexture(GLuint texture);

如果这个证书是一个以前已经分配的纹理对象名,则返回GL_TRUE。

纹理应用

纹理坐标

典型情况下,纹理坐标是作为0.0到1.0范围内的浮点值指定的。纹理坐标命名为s、t、r和q,支持从一维到三维纹理坐标。

纹理参数

很多参数的应用都会影响渲染的规则和纹理贴图的行为,这些参数通过下列函数的变量来进行设置:

void glTexParameterf(GLenum target,GLenum pname,GLfloat param);
void glTexParamereri(GLenum target,GLenum pname,Glint param);
void glTexParameterfv(GLenum target,GLenum pname,GLfloat *params);
void glTexParameteriv(GLenum target,GLenum pname,Glint *params);

第一个参数target指定这些参数将要应用到哪个纹理模式上,它可以是GL_TEXTURE_1D、GL_TEXTURE_2D和GL_TEXTURE_3D.第二个参数pname指定了需要设置哪个纹理参数,而最后一个参数param或params用于设置特定的纹理参数的值。
基本过滤
根据一个拉伸或收缩的纹理贴图计算颜色片段的过程称为纹理过滤。使用OpenGL的纹理参数函数,可以同事设置放大和缩小过滤器。这两种过滤器的参数名分别是GL_TEXTURE_MAG_FILTER和GL_TEXTURE_MIN_FILTER。就目前来说,我们可以为他们从两种基本的纹理过滤器GL_NEAREST和GL_LINEAR中进行选择,它们分别对应于最邻近过滤和线性过滤。
我们可以使用下面这两个函数,为放大和缩小过滤器设置纹理过滤器:

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

我们可以使用下面这几行代码,简单地设置线性过滤:

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

纹理环绕
我们可以调glTexParameteri函数(并分别使用GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T或GL_TEXTURE_WRAP_R作为参数)、为每个坐标分别设置环绕模式。然后,我们可以把环绕模式设置为下面几个值之一:GL_REPEAT、GL_CLAMT、GL_CLAMP_TO_EDGE或GL_CLAMP_TO_BORDER。

综合运用

加载纹理

GLuint textureID;
glGenTextures(1,&textureID);
glBindTexture(GL_TEXTURE_2D,textureID);
loadTGATexture(“stone.tga”,GL_LINEAR,GL_LINEAR,GL_CLAMP_TO_EDGE);

其中,加载纹理的loadTGATexture定义如下:

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

    // 读取材质比特
    pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
    if (pBits == NULL)
        return false;
    //设置放大和缩小过滤
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    //设置线性过滤
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    //改成紧密包装
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    //载入纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, nWidth, nHeight, 0,
        eFormat, GL_UNSIGNED_BYTE, pBits);

    free(pBits);

    if (minFilter == GL_LINEAR_MIPMAP_LINEAR ||
        minFilter == GL_LINEAR_MIPMAP_NEAREST ||
        minFilter == GL_NEAREST_MIPMAP_LINEAR ||
        minFilter == GL_NEAREST_MIPMAP_NEAREST)
        //生成Mip层
        glGenerateMipmap(GL_TEXTURE_2D);

    return true;
}

指定纹理坐标
添加顶点:

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

为一个面计算表面法线,然后再在所有3个顶点使用它:

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

pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontLeft); 

pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vFrontRight);

实际渲染:

glBindTexture(GL_TEXTURE_2D, textureID);
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, 
                                transformPipeline.GetModelViewMatrix(),
                                transformPipeline.GetProjectionMatrix(), 
                                vLightPos, vWhite, 0);
pyramidBatch.Draw();

Mip贴图

Mip贴图纹理由一些列纹理图像组成,每个图像大小在每个轴的方向上都缩小一半。
如果想指定只加载0层至第4层,应如下操作:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAX_LEVEL,4);

Mip贴图过滤

经过Mip贴图的纹理过滤

常量描述
GL_NEAREST在Mip基层上执行最邻近过滤
GL_LINEAR在Mip基层上执行线性过滤
GL_NEAREST_MIPMAP_NEAREST选择最邻近Mip层,并执行最邻近过滤
GL_NEAREST_MIPMAP_LINEAR在Mip层之间执行线性插补,并执行最邻近过滤
GL_LINEAR_MIPMAP_NEAREST选择最邻近Mip层,并执行线性过滤
GL_LINEAR_MIPMAP_LINEAR在Mip层之间执行线性插补,并执行线性过滤,又称三线性Mip贴图

生成Mip层

void glGenerateMipmap(GLenum target);

目标参数可以是GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D、GL_TEXTURE_CUBE_MAP、GL_TEXTURE_1D_ARRAY或GL_TEXTURE_2D_ARRAY

各项异性过滤

首先,必须确认这种扩展是得到支持的:

if(gltIsExtSupported(“GL_EXT_texture_filter_anisotropic”));

然后,查询得到支持的各向异性过滤的最大数量:

GLfloat fLargest;
……
……
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT,&fLargest);

最后,设置想要应用的各向异性过滤的数量:
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAX_ANISOTROPY_EXT,fLargest);

纹理压缩

压缩纹理

纹理压缩是通过在glTexImage函数中把internalFormat参数设置为下表的任意值实现的:
通用压缩纹理格式
压缩格式 基本内部格式
GL_COMPRESSED_RGB GL_RGB
GL_COMPRESSED_RGBA GL_RGBA
GL_COMPRESSED_SRGB GL_RGB
GL_COMPRESSED_SRGB_ALPHA GL_RGBA
GL_COMPRESSED_RED GL_RED
GL_COMPRESSED_RG GL_RG(Red Green)
可以用如下方法来判断这个纹理是否被成功压缩:

Glint compFlag;
……
glGetTexLevelParameteriv(GL_TEXTURE_2D,0,GL_TEXTURE_COMPRESSED,&compFlag);

glGetTexLevelParameter函数提取的压缩纹理格式

参数返回
GL_TEXTURE_COMPRESSED如果纹理被压缩,返回1,否则返回0
GL_TEXTURE_COMPRESSED_IMAGE_SIZE压缩后的纹理的大小(以字节为单位)
GL_TEXTURE_INTERNAL_FORMAT所使用的压缩格式
GL_NUM_COMPRESSED_TEXTURE_FORMATS受支持的研所为例格式的数量
GL_COMPRESSED_TEXTURE_FORMATS一个包含了一些常量值的数组,每个常量值对应于一种受支持的压缩纹理格式
GL_TEXTURE_COMPRESSION_HINT纹理压缩提示的值(GL/NICEST/GL_FASTEST)

我们可以使用glHint指定希望OpenGL根据最快速度还是最佳质量算法来选择压缩格式:

glHint(GL_TEXTURE_COMPRESSION_HINT,GL_FASTEST);
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_NICEST);
glHint(GL_TEXTURE_COMPRESSION_HINT,GL_DONT_CARE);

GL_EXT_texture_conpression_s3tc的压缩格式

格式描述
GL_COMPRESSED_RGB_S3TC_DXT1RGB数据被压缩alpha值时钟是1.0
GL_COMPRESSED_RGBA_S3TC_DXT1RGB数据被压缩,alpha值是1.0或0.0
GL_COMPRESSED_RGBA_S3TC_DXT2RGB数据被压缩,alpha值用4位存储
GL_COMPRESSED_RGBA_S3TC_DXT3RGB数据被压缩,alpha值是一些8位置的加权平均值

加载压缩纹理

为了加载预先经过压缩的纹理数据,可以使用下列函数之一:

void glCompressedTexImage1D(GLenum target,Glint level,GLenum internalFormat,GLsizei width,Glint border,GLsizei imageSize,void *data);
void glCompressedTexImage2D(GLenum target,Glint level,GLenum internalFormat,GLsizei width,GLsizei height,Glint border,GLsizei imageSize,void *data);
void glCompressedTexImage3D(GLenum target,Glint level,GLenum internalFormat,GLsizei width,GLsizei height,GLsizei depth,Glint border,GLsizei imageSize,GLvoid *data);

转载请注明出处:http://blog.csdn.net/ylbs110/article/details/51793970

示例
为了显示纹理的使用,本示例在学习笔记3(http://blog.csdn.net/ylbs110/article/details/51760021)的示例上直接添加了纹理代码,为了让纹理显示清晰,颜色做了少许改变。
注意:UseStockShader的第一个参数要改为GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF才能成功使用纹理

#include "stdafx.h"
#include <GLTools.h>
#include <GLShaderManager.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLFrame.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>

#include <math.h>
#include <stdio.h>

#include <math.h>
#define GLUT_DISABLE_ATEXIT_HACK
#include <GLUT.H>

/*
* 当libjpeg-turbo为vs2010编译时,vs2015下静态链接libjpeg-turbo会链接出错:找不到__iob_func,
* 增加__iob_func到__acrt_iob_func的转换函数解决此问题,
* 当libjpeg-turbo用vs2015编译时,不需要此补丁文件
*/
#if _MSC_VER>=1900
#include "stdio.h" 
_ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned);
#ifdef __cplusplus 
extern "C"
#endif 
FILE* __cdecl __iob_func(unsigned i) {
    return __acrt_iob_func(i);
}
#endif /* _MSC_VER>=1900 */

#define NUM_SPHERES 50
GLFrame disk[NUM_SPHERES / 2];
GLFrame cylinder[NUM_SPHERES / 2];


GLShaderManager     shaderManager;          // Shader Manager
GLMatrixStack       modelViewMatrix;        // Modelview Matrix
GLMatrixStack       projectionMatrix;       // Projection Matrix
GLFrustum           viewFrustum;            // View Frustum
GLGeometryTransform transformPipeline;      // Geometry Transform Pipeline

GLTriangleBatch     torusBatch;
GLBatch             floorBatch;
GLTriangleBatch     sphereBatch;
GLTriangleBatch     triangleBatch;
GLTriangleBatch     cylinderBatch;
GLTriangleBatch     diskBatch;

GLFrame             cameraFrame;

GLuint              uiTextures[3];

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

    // 读取材质比特
    pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
    if (pBits == NULL)
        return false;
    //设置放大和缩小过滤
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    //设置线性过滤
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    //改成紧密包装
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    //载入纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, nWidth, nHeight, 0,
        eFormat, GL_UNSIGNED_BYTE, pBits);

    free(pBits);

    if (minFilter == GL_LINEAR_MIPMAP_LINEAR ||
        minFilter == GL_LINEAR_MIPMAP_NEAREST ||
        minFilter == GL_NEAREST_MIPMAP_LINEAR ||
        minFilter == GL_NEAREST_MIPMAP_NEAREST)
        //生成Mip层
        glGenerateMipmap(GL_TEXTURE_2D);

    return true;
}

//
// This function does any needed initialization on the rendering
// context. 
void SetupRC()
{

    // Make sure OpenGL entry points are set
    glewInit();
    // Initialze Shader Manager
    shaderManager.InitializeStockShaders();

    glEnable(GL_DEPTH_TEST);

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    // 制造花圈
    gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);

    // 制造小球
    gltMakeSphere(sphereBatch, 0.3f, 26, 13);

    //制造圆柱
    gltMakeCylinder(cylinderBatch, 0.2f, 0.2f, 0.5f, 13, 2);

    //制造圆盘
    gltMakeDisk(diskBatch, 0.2f, 0.4f, 13, 3);

    floorBatch.Begin(GL_LINES, 324);
    for (GLfloat x = -20.0; x <= 20.0f; x += 0.5) {
        floorBatch.Vertex3f(x, -0.55f, 20.0f);
        floorBatch.Vertex3f(x, -0.55f, -20.0f);

        floorBatch.Vertex3f(20.0f, -0.55f, x);
        floorBatch.Vertex3f(-20.0f, -0.55f, x);
    }
    floorBatch.End();

    // 设置3个材质物体
    glGenTextures(3, uiTextures);

    // 加载材质 Marble并绑定
    glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
    LoadTGATexture("marble.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);

    // 加载材质 Mars并绑定
    glBindTexture(GL_TEXTURE_2D, uiTextures[1]);
    LoadTGATexture("marslike.tga", GL_LINEAR_MIPMAP_LINEAR,
        GL_LINEAR, GL_CLAMP_TO_EDGE);

    // 加载材质 Moon并绑定
    glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
    LoadTGATexture("moonlike.tga", GL_LINEAR_MIPMAP_LINEAR,
        GL_LINEAR, GL_CLAMP_TO_EDGE);

    // Randomly place the spheres
    for (int i = 0; i < NUM_SPHERES; i++) {
        GLfloat x = ((GLfloat)((rand() % 400) - 200) * 0.1f);
        GLfloat z = ((GLfloat)((rand() % 400) - 200) * 0.1f);
        if (i % 2 == 0)
            disk[i / 2].SetOrigin(x, 0.0f, z);
        else
            cylinder[(i - 1) / 2].SetOrigin(x, 0.0f, z);
    }

    GLfloat vetts[3][3];
    GLfloat vNorms[3][3];
    GLfloat vTexCoords[3][2];
    GLfloat angle = 0;
    for (int i = 0; i < 3; i++) {
        angle += M3D_2PI / 6.0f;
        vetts[i][0] = float(-5 + i*0.2);
        vetts[i][1] = float(sin(float(angle)));
        vetts[i][2] = float(cos(float(angle)));

        vNorms[i][0] = float(-5 + i*0.2);
        vNorms[i][1] = float(cos(float(angle)));
        vNorms[i][2] = float(sin(float(angle)));

        vTexCoords[i][0] = float(-5 + i*0.2);
        vTexCoords[i][1] = float(sin(float(angle)));
    }
    triangleBatch.BeginMesh(3);
    triangleBatch.AddTriangle(vetts, vNorms, vTexCoords);
    triangleBatch.End();

}

///
// Screen changes size or is initialized
void ChangeSize(int nWidth, int nHeight)
{
    glViewport(0, 0, nWidth, nHeight);

    // 创建投影矩阵,并将它载入到投影矩阵堆栈中
    viewFrustum.SetPerspective(35.0f, float(nWidth) / float(nHeight), 1.0f, 100.0f);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());

    // 设置变换管线以使用两个矩阵堆栈 
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);

}
// 关闭渲染环境
void ShutdownRC(void)
{
    glDeleteTextures(3, uiTextures);
}

// Called to draw scene
void RenderScene(void)
{
    // 颜色值
    static GLfloat vFloorColor[] = { 1.0f, 0.5f, 1.0f, 1.0f };
    static GLfloat vTorusColor[] = { 0.5f, 1.0f, 1.0f, 1.0f };
    static GLfloat vSphereColor[] = { 1.0f, 1.0f, 0.5f, 1.0f };
    static GLfloat vdiskColor[] = { 1.0f, 0.5f, 0.5f, 1.0f };
    static GLfloat vcylinderColor[] = { 0.5f, 1.0f, 0.5f, 1.0f };

    // 基于时间的动画
    static CStopWatch   rotTimer;
    float yRot = rotTimer.GetElapsedSeconds() * 60.0f;

    // 清除颜色缓冲区和深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


    // 保存当前模型视图矩阵 (单位矩阵)
    modelViewMatrix.PushMatrix();

    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.PushMatrix(mCamera);

    // 将光源位置变换到视觉坐标系
    M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
    M3DVector4f vLightEyePos;
    m3dTransformVector4(vLightEyePos, vLightPos, mCamera);


    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    // 绘制背景
    shaderManager.UseStockShader(GLT_SHADER_FLAT,
        transformPipeline.GetModelViewProjectionMatrix(),
        vFloorColor);
    floorBatch.Draw();

    //绑定并使用纹理对象
    glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
    //绘制圆柱和圆盘
    for (int i = 0; i < NUM_SPHERES; i++) {
        modelViewMatrix.PushMatrix();
        if (i % 2 == 0) {
            modelViewMatrix.MultMatrix(disk[i / 2]);
            shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
                transformPipeline.GetProjectionMatrix(), vLightEyePos, vdiskColor,0);
            diskBatch.Draw();
        }
        else
        {
            modelViewMatrix.MultMatrix(cylinder[(i - 1) / 2]);
            shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
                transformPipeline.GetProjectionMatrix(), vLightEyePos, vcylinderColor, 0);
            cylinderBatch.Draw();

        }
        modelViewMatrix.PopMatrix();
    }


    // 绘制旋转花托
    modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);

    // 保存平移矩阵
    modelViewMatrix.PushMatrix();


    // 应用旋转并绘制花托
    modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
    //绑定并使用纹理对象
    glBindTexture(GL_TEXTURE_2D, uiTextures[1]);
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
        transformPipeline.GetProjectionMatrix(), vLightEyePos, vTorusColor, 0);
    torusBatch.Draw();
    modelViewMatrix.PopMatrix(); // "清除" 以前的旋转

                                 //绘制三角形
                                 //modelViewMatrix.PushMatrix();
    modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
        transformPipeline.GetProjectionMatrix(), vLightEyePos, vTorusColor, 0);
    triangleBatch.Draw();
    //modelViewMatrix.PopMatrix(); 

    //绑定并使用纹理对象
    glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
    // 应用另一个旋转,然后进行平移,然后再绘制球体
    modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
    modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
        transformPipeline.GetProjectionMatrix(), vLightEyePos, vSphereColor, 0);
    sphereBatch.Draw();

    // 还原到以前的模型视图矩阵 (单位矩阵)
    modelViewMatrix.PopMatrix();
    modelViewMatrix.PopMatrix();
    // 进行缓冲区交换
    glutSwapBuffers();

    // 通知GLUT在进行一次同样操作
    glutPostRedisplay();
}


// Respond to arrow keys by moving the camera frame of reference
void SpecialKeys(int key, int x, int y)
{
    float linear = 0.1f;
    float angular = float(m3dDegToRad(5.0f));

    if (key == GLUT_KEY_UP)
        cameraFrame.MoveForward(linear);

    if (key == GLUT_KEY_DOWN)
        cameraFrame.MoveForward(-linear);

    if (key == GLUT_KEY_LEFT)
        cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);

    if (key == GLUT_KEY_RIGHT)
        cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}

int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);

    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(800, 600);

    glutCreateWindow("OpenGL SphereWorld");

    glutSpecialFunc(SpecialKeys);
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);

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

    SetupRC();
    glutMainLoop();
    ShutdownRC();
    return 0;
}

运行结果:
除了地板所有物体都加上了材质
这里写图片描述

以上是关于OpenGL学习笔记4:纹理的主要内容,如果未能解决你的问题,请参考以下文章

片段着色器中未使用纹理数据 - OpenGL

片段着色器究竟如何用于纹理?

OpenGL、GLSL 片段着色器无法读取 Sampler2D 纹理

OpenGL - 纹理一个基本的立方体

OpenGL_Qt学习笔记之_05(纹理映射)(转)

纹理中的 OpenGL 片段着色器