纹理(TEXTURE)


何为纹理(Texture)?贴到几何图形上之图片也,使之具有如实物一般之外观,犹如纹理。

我们绘制出来的几何图形,无论是长方形、圆形、多边形、球体等等,都是纯几何图形。以球体为例,倘若我们在这球体上贴上一张足球图片,那么它看起来就是一个足球;如果贴上一张世界地图,那么它看起来就是一个地球仪;以此类推。

图片的实质是一组颜色值的集合,给几何图形贴图,实际上就是把这些颜色值应用到该几何图形的过程,称为纹理映射(Texture Mapping)。

纹理是一张 2D 图片,而要被「贴图」的几何图形,与纹理在大小、形状上各异,譬如,我们如何把一张长方形的世界地图贴到一个球体上?这些问题都是「纹理映射」需要解决的。

纹理的分类


纹理分为两种:

(1) 2D纹理(2D TEXTURE)

2D纹理其实就是一张图片,是一个二维的图片数据组成的数组。

其坐标系是:

注意,这个坐标的 Y 轴跟图片的像素排列是相反的。

(2) 立方体纹理(CUBE MAP TEXTURE)

立方体纹理由六张正方形的纹理组成,分别表示从 +X、-X、+Y、-Y、+Z、-Z 六个方向拍的照片。经常被用于「环境映射」中,譬如游戏中行驶的赛车,其表面可以反射周围环境,就像镜子一样。

纹理映射(TEXTURE MAPPING)


参考资料:Texture Mapping on WiKi

前面已经说到,把纹理图的颜色值「贴」到几何图形上的过程,叫纹理映射,江湖人称「贴图」。

「贴图」中用到了三种技术:

    - Texture Filtering:这个术语有许多翻译,「texture」译成「纹理」、「材质」;「filtering」译成「过滤」、「滤波」,因此组合后,有叫「纹理过滤」、「材质过滤」、「纹理滤波」等等,因此均不取,直接取原文。filtering 主要用于处理「纹理」与「几何图形」大小不一致带来的问题,分为缩小和放大两种,每种又有多种方案。 - Mipmapping:这个技术用于加快渲染速度和减少图像锯齿。它通过预先为纹理产生多个级别的小图来实现。

 

  • Texture Coordinate Wrapping:纹理的坐标超出 (0, 1) 范围时所用,决定该如何在这种情况下贴图。

TEXTURE FILTERING


参考资料:Texture filtering on Wiki

我们把纹理上的像素称为「texel」,以区别于屏幕上的「pixel」。实际情况是,它们通常不是一一对应的。根据纹理和几何图形的大小关系,可以分为 Magnification 和 Minification 两种。

  • Magnification (放大)当「纹理」比待贴图的「几何图形」小时,需要放大。譬如一张 2x2 的纹理映射到一个 4x4 的矩形,矩形的每个「pixel」对应0.5个「texel」。
  • Minification (缩小)当「纹理」比待贴图的「几何图形」大时,需要缩小。譬如一张 4x4 的纹理映射到一个 2x2 的矩形,矩形的每个「pixel」对应2个「texel」。

注:以下简称为 MAG_FILTER 和 MIN_FILTER。

采样方案

当某个「pixel」需要「渲染」(render)时,并不会直接取该「pixel」坐标(采用 normalized device coordinates)上的「texel」。因为纹理和几何图形的大小通常不一样,因此需要「采样」(sample)。

  • NEAREST:就近取样。该方案更像是「四舍五入」,直接取最接近「pixel」坐标的那个「texel」。因此速度很快,但缺点是会造成「锯齿」。
  • LINEAR:平均取样。采用经过「加权平均」(weighted average)计算的结果。取邻近的4个「texel」,根据其距离远近加权,算出其平均值,以该值渲染。速度较慢,但可抗锯齿。又被称为「bilinear」采样。

更多

无论是 MAG_FILTER 还是 MIN_FILTER,都有上述的两种采样方案。然而除此之外,MIN_FILTER 还有其他的方案,那就是「Mipmapping」。

MIPMAPPING


参考资料:Mipmapping on Wiki

在 MIN_FILTER 下采用 NEAREST 采样时,可能会产生严重的走样。譬如,我们有一张 1024x1024 的纹理要映射到一个 10x10 的几何图形上,那么每个「pixel」之间跨越了102.4个「texel」,其取样精度可谓非常离谱,最终的显示结果必将与我们期待的差很远。

因此,一项被称为「Mipmapping」的技术在 1983 年被 Lance Williams 提出来了(在其论文「Pyramidal parametrics」中)。

「Mipmapping」的工作原理是这样的:假设给定一张纹理图,依次根据一定的「filtering」算法生成一张长宽均只有原图一半大小的图,直到 1x1 为止。

譬如我们有一张 256x256 的纹理图,将会产生 128x128、64x64、32x32、16x16、8x8、4x4、2x2、1x1 总共9张图(包括原图)。

当我们在进行采样的时候,将会参考几何图形的大小,选择合适的一张 Mipmap 来进行。

以下是一张 Mipmap 示意图:

图片出处

如何生成 Mipmapping 呢?通常采用一个叫做「box filtering」的技术。然而 OpenGL 业已提供了一个接口供我们使用:

glGenerateMipmap — generate a complete set of mipmaps for a texture object

void glGenerateMipmap(GLenum target);

值得注意的是,OpenGL ES 2.0 并没有指定实现该接口所采用的算法,虽然通常都是采用「box filtering」技术。如果你想需要特定的方法,仍然需要自己实现。然而使用该接口有一大优点就是,它是在 GPU 上计算的,因此不会给 CPU 造成额外的压力。

由于有了「Mipmapping」,MIN_FILTER 又多了四种参数,四种参数的格式是:

[NEAREST/LINEAR]_MIPMAP_[NEAREST/LINEAR]

其中,第一个 NEAREST/LINEAR 是和前面一样的,第二个是修饰 MIPMAP 的。
  • NEAREST_MIPMAP_NEAREST取最接近的「一张 MIPMAP」,对此 MIPMAP 应用就近采样技术。
  • NEAREST_MIPMAP_LINEAR取最接近的「两张 MIPMAP」,各对其应用就近采样,并将取两个样本之均值。
  • LINEAR_MIPMAP_NEAREST取最接近的「一张 MIPMAP」,并对其采用「bilinear」采样技术。
  • LINEAR_MIPMAP_LINEAR取最接近的「两张 MIMAP」,各对其采用「bilinear」采样,然后再取两个样本之均值。这个方法又被称为「trilinear」,是最耗性能,但质量最高的方法。

TEXTURE COORDINATE WRAPPING


当我们进行纹理映射的时候,会给顶点指定纹理坐标(texture coordinate)。指定的值超过 [0.0, 1.0] 范围时,会使用一种称为「Texture wrap modes」的技术。

TEXTURE_WRAP_S 表示横坐标轴,TEXTURE_WRAP_T 表示纵坐标轴。

关于这三种模式,可以举个实例加以说明。我们把一张纹理映射到一个矩形中,并指定四个纹理坐标为 (-1, 2)、(-1, -1)、(2, -1)、(2, 2),如下图所示,被绿色框住的是 [0, 1] 的纹理坐标范围内,此范围外的区域均超出了纹理坐标系,因此其渲染采样根据三种模式而不同,其效果正如图所示:

分别为 GL_REPEAT、GL_CLAMP_TO_EDGE、GL_MIRRORED_REPEAT。

  • GL_REPEAT:重复。
  • GL_CLAMP_TO_EDGE:以边缘的像素填充。
  • GL_MIRRORED_REPEAT:镜像重复。

注:经实验证明,OpenGL ES 2.0 中,GL_REPEAT 和 GL_MIRRORED_REPEAT 要求纹理的长宽必须是 POT (Power Of Two)。

OPENGL ES 中的实现


通过 glTexParameter 接口可以设置纹理映射时要使用的参数。

Name

glTexParameter — set texture parameters

C Specification

void glTexParameterf(GLenum target,
                    GLenum pname,
                    GLfloat param);

void glTexParameteri(GLenum target,
                    GLenum pname,
                    GLint param);

其中,target 可选参数为两种纹理分类:

  • GL_TEXTURE_2D
  • GL_TEXTURE_CUBE_MAP

pname 与 param 的参数组合为:

    - GL\_TEXTURE\_MAG\_FILTER - GL\_NEAREST - GL\_LINEAR

 

    - GL*TEXTURE*MIN\_FILTER - GL\_NEAREST - GL\_LINEAR - GL\_NEAREST\_MIPMAP\_NEAREST - GL\_NEAREST\_MIPMAP\_LINEAR - GL\_LINEAR\_MIPMAP\_NEAREST - GL\_LINEAR\_MIPMAP\_LINEAR

 

    - GL*TEXTURE*WRAP\_S - GL\_CLAMP\_TO\_EDGE - GL\_MIRRORED\_REPEAT - GL\_REPEAT

 

    - GL*TEXTURE*WRAP\_T - 与 GL\_TEXTURE\_WRAP\_S 同。

 

创建和加载纹理


void glGenTextures(GLsizei n, GLuint *textures)

创建纹理。
n 为需要创建的纹理个数,textures 是一个数组,包含 n 个返回的 texture 对象的ID。

void glActiveTexture(GLenum texture)

选择要激活的纹理单元。
当前硬件所支持的纹理单元个数为 GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS。譬如其值为 32,那么该参数的值就是从 GL_TEXTURE0、GL_TEXTURE1、......、GL_TEXTURE31。

void glBindTexture(GLenum target, GLuint texture)

绑定纹理。
target 为纹理的两个种类之一:GL_TEXTURE_2D 或 GL_TEXTURE_CUBE_MAP。
texture 为纹理的ID。
将 texture 指定的纹理对象绑定到当前激活的纹理单元。

void glTexImage2D(GLenum target, GLint level,
                GLenum internalFormat, GLsizei width,
                GLsizei height, GLint border,
                GLenum format, GLenum type,
                const void* pixels)

指定一张图片作为纹理。
pixels 包含图片数据。                    

void glTexSubImage2D(GLenum target, GLint level, 
                    GLint xoffset, GLint yoffset,
                   GLsizei width, GLsizei height,
                   GLenum format, GLenum type,
                   const void* pixels)

可以局部替换纹理。

在 FRAGMENT SHADER 中使用纹理


texture2D 是一个内建函数,至于究竟有多少个内建函数,可以参考官方的 Reference Card

更多相关主题


  • 纹理支持压缩
  • 立方体纹理
  • 3D纹理(OpenGL扩展)

小结


纹理(Texture)

二维图像数据。通常是一张图片。OpenGL ES 中分为「2D纹理」(2D Texture)和「立方体纹理」(Cube Map Texture)两种。

纹理映射(Texture Mapping)

将纹理的颜色值应用到多边形(即几何图形)的过程。

Texture Filtering

纹理映射过程中采用的技术。有 MAG_FILTER 和 MIN_FILTER。其中,MIN_FILTER 中还有 Mipmapping 的技术。

Mipmapping

通过预先产生多个级别的小图来提高渲染速度和抗锯齿的技术。

Texture Coordinate Wrapping

当顶点的纹理坐标超出 [0.0, 1.0] 范围时的情形,有三种模式:重复、边缘填充、镜像重复。