## 第五课：纹理立方体

• 什么是UV坐标
• 怎样自行加载纹理
• 怎样在OpenGL中使用纹理
• 什么是滤波？什么是mipmap？怎样使用？
• 怎样利用GLFW更加有效地加载纹理？
• 什么是alpha通道？

### 自行加载.BMP图片

GLuint loadBMP_custom(const char * imagepath);

GLuint image = loadBMP_custom("./my_texture.bmp");

// Data read from the header of the BMP fileunsigned char header[54]; // Each BMP file begins by a 54-bytes headerunsigned int dataPos;     // Position in the file where the actual data beginsunsigned int width, height;unsigned int imageSize;   // = width*height*3// Actual RGB dataunsigned char * data;

// Open the fileFILE * file = fopen(imagepath,"rb");if (!file)                                {printf("Image could not be openedn"); return 0;}

if ( fread(header, 1, 54, file)!=54 ){ // If not 54 bytes read : problem    printf("Not a correct BMP filen");    return false;}

if ( header[0]!='B' || header[1]!='M' ){    printf("Not a correct BMP filen");    return 0;}

// Read ints from the byte arraydataPos    = *(int*)&(header[0x0A]);imageSize  = *(int*)&(header[0x22]);width      = *(int*)&(header[0x12]);height     = *(int*)&(header[0x16]);

// Some BMP files are misformatted, guess missing informationif (imageSize==0)    imageSize=width*height*3; // 3 : one byte for each Red, Green and Blue componentif (dataPos==0)      dataPos=54; // The BMP header is done that way

// Create a bufferdata = new unsigned char [imageSize];// Read the actual data from the file into the bufferfread(data,1,imageSize,file);//Everything is in memory now, the file can be closedfclose(file);

// Create one OpenGL textureGLuint textureID;glGenTextures(1, &textureID);// "Bind" the newly created texture : all future texture functions will modify this textureglBindTexture(GL_TEXTURE_2D, textureID);// Give the image to OpenGLglTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

GLuint Texture = loadBMP_custom("uvtemplate.bmp");

• 优质纹理： 128128, 256256, 10241024, 2*2…
• 劣质纹理： 127128, 35, …
• 勉强可以但很怪异的纹理： 128*256

### 在OpenGL中使用纹理

#version 330 core    // Interpolated values from the vertex shadersin vec2 UV;// Ouput dataout vec3 color;// Values that stay constant for the whole mesh.uniform sampler2D myTextureSampler;void main(){    // Output color = color of the texture at the specified UV    color = texture( myTextureSampler, UV ).rgb;}

• 片断着色器需要UV坐标。看似合情合理。
• 同时也需要一个“Sampler2D”来获知要加载哪一个纹理（同一个着色器中可以访问多个纹理）
• 最后一点，用texture()访问纹理，该方法返回一个(R,G,B,A)的vec4变量。马上就会了解到分量A。

#version 330 core// Input vertex data, different for all executions of this shader.layout(location = 0) in vec3 vertexPosition_modelspace;layout(location = 1) in vec2 vertexUV;// Output data ; will be interpolated for each fragment.out vec2 UV;// Values that stay constant for the whole mesh.uniform mat4 MVP;void main(){    // Output position of the vertex, in clip space : MVP * position    gl_Position =  MVP * vec4(vertexPosition_modelspace,1);    // UV of the vertex. No special space for this one.    UV = vertexUV;}

// Two UV coordinatesfor each vertex. They were created with Blender. You'll learn shortly how to do this yourself.static const GLfloat g_uv_buffer_data[] = {    0.000059f, 1.0f-0.000004f,    0.000103f, 1.0f-0.336048f,    0.335973f, 1.0f-0.335903f,    1.000023f, 1.0f-0.000013f,    0.667979f, 1.0f-0.335851f,    0.999958f, 1.0f-0.336064f,    0.667979f, 1.0f-0.335851f,    0.336024f, 1.0f-0.671877f,    0.667969f, 1.0f-0.671889f,    1.000023f, 1.0f-0.000013f,    0.668104f, 1.0f-0.000013f,    0.667979f, 1.0f-0.335851f,    0.000059f, 1.0f-0.000004f,    0.335973f, 1.0f-0.335903f,    0.336098f, 1.0f-0.000071f,    0.667979f, 1.0f-0.335851f,    0.335973f, 1.0f-0.335903f,    0.336024f, 1.0f-0.671877f,    1.000004f, 1.0f-0.671847f,    0.999958f, 1.0f-0.336064f,    0.667979f, 1.0f-0.335851f,    0.668104f, 1.0f-0.000013f,    0.335973f, 1.0f-0.335903f,    0.667979f, 1.0f-0.335851f,    0.335973f, 1.0f-0.335903f,    0.668104f, 1.0f-0.000013f,    0.336098f, 1.0f-0.000071f,    0.000103f, 1.0f-0.336048f,    0.000004f, 1.0f-0.671870f,    0.336024f, 1.0f-0.671877f,    0.000103f, 1.0f-0.336048f,    0.336024f, 1.0f-0.671877f,    0.335973f, 1.0f-0.335903f,    0.667969f, 1.0f-0.671889f,    1.000004f, 1.0f-0.671847f,    0.667979f, 1.0f-0.335851f};

### 什么是滤波和mipmap？怎样使用？

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

#### Mipmaps

• 一开始，把图像缩小到原来的1/2，接着一次做下去，直到图像只有1×1大小（应该是图像所有纹素的平均值）
• 绘制模型时，根据纹素大小选择合适的mipmap。
• 可以选用nearest、linear、anisotropic等任意一种滤波方式来对mipmap采样。
• 要想效果更好，可以对两个mipmap采样然后混合，得出结果。

// When MAGnifying the image (no bigger mipmap available), use LINEAR filteringglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// When MINifying the image, use a LINEAR blend of two mipmaps, each filtered LINEARLY tooglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);// Generate mipmaps, by the way.glGenerateMipmap(GL_TEXTURE_2D);

### 怎样利用GLFW加载纹理？

GLuint loadTGA_glfw(const char * imagepath){    // Create one OpenGL texture    GLuint textureID;    glGenTextures(1, &textureID);    // "Bind" the newly created texture : all future texture functions will modify this texture    glBindTexture(GL_TEXTURE_2D, textureID);    // Read the file, call glTexImage2D with the right parameters    glfwLoadTexture2D(imagepath, 0);    // Nice trilinear filtering.    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);    glGenerateMipmap(GL_TEXTURE_2D);    // Return the ID of the texture we just created    return textureID;}

### 压缩纹理

#### 创建压缩纹理

• 下载The Compressonator,一款ATI工具
• 用它加载一个二次幂纹理
• 将其压缩成DXT1、DXT3或DXT5格式（这些格式之间的差别请参考Wikipedia）：

• 生成mipmap，这样就不用在运行时生成mipmap了。
• 导出为.DDS文件。

#### 使用压缩纹理

GLuint loadDDS(const char * imagepath){    unsigned char header[124];    FILE *fp;    /* try to open the file */    fp = fopen(imagepath, "rb");    if (fp == NULL)        return 0;    /* verify the type of file */    char filecode[4];    fread(filecode, 1, 4, fp);    if (strncmp(filecode, "DDS ", 4) != 0) {        fclose(fp);        return 0;    }    /* get the surface desc */    fread(&header, 124, 1, fp);     unsigned int height      = *(unsigned int*)&(header[8 ]);    unsigned int width         = *(unsigned int*)&(header[12]);    unsigned int linearSize     = *(unsigned int*)&(header[16]);    unsigned int mipMapCount = *(unsigned int*)&(header[24]);    unsigned int fourCC      = *(unsigned int*)&(header[80]);

    unsigned char * buffer;    unsigned int bufsize;    /* how big is it going to be including all mipmaps? */    bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;    buffer = (unsigned char*)malloc(bufsize * sizeof(unsigned char));    fread(buffer, 1, bufsize, fp);    /* close the file pointer */    fclose(fp);

    unsigned int components  = (fourCC == FOURCC_DXT1) ? 3 : 4;    unsigned int format;    switch(fourCC)    {    case FOURCC_DXT1:        format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;        break;    case FOURCC_DXT3:        format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;        break;    case FOURCC_DXT5:        format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;        break;    default:        free(buffer);        return 0;    }

    // Create one OpenGL texture    GLuint textureID;    glGenTextures(1, &textureID);    // "Bind" the newly created texture : all future texture functions will modify this texture    glBindTexture(GL_TEXTURE_2D, textureID);

    unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;    unsigned int offset = 0;    /* load the mipmaps */    for (unsigned int level = 0; level < mipMapCount && (width || height); ++level)    {        unsigned int size = ((width+3)/4)*((height+3)/4)*blockSize;        glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height,             0, size, buffer + offset);        offset += size;        width  /= 2;        height /= 2;    }    free(buffer);     return textureID;

#### 反转UV坐标

DXT压缩源自DirectX。和OpenGL相比，DirectX中的V纹理坐标是反过来的。所以使用压缩纹理时，得用(coord.v, 1.0-coord.v)来获取正确的纹素。这步操作何时做都可以：可以在导出脚本中做，可以在加载器中做，也可以在着色器中做……

### 练习

• 源代码中实现了DDS加载器，但没有做纹理坐标的改动（译者注：指文中讲述的反转 UV坐标）。在适当的位置添加该功能，以使正方体正确显示。
• 试试各种DDS格式。所得结果有何不同？压缩率呢？
• 试试在The Compressonator不生成mipmap。结果如何？请给出3种方案解决这一问题。