在前三章里,我们学会了用 WebGL 绘制一个三角形、变着花样替换顶点数据等。但目前为止所有绘制的内容都是基于基础图元的绘制,如果想让绘制的内容看起来更真实,就需要有更多的顶点或者足够多的颜色。比如:一个画框里的画,可以是蒙娜丽莎,可以是向日葵,此时,我们可以增加一张纹理来为物体添加更多的细节。
纹理的应用就涉及到一项很重要的技术:纹理映射。所谓纹理映射,就是将一张图片映射到一个几何图形的表面上去,比如纹理映射到矩形物体上,这个矩形看上去就像是一张图片,这张图片又可以称为纹理图像或纹理。
纹理映射
纹理映射的作用是根据纹理图像,为光栅化后的每个片元涂上适当的颜色,组成纹理图像的像素又称之为纹素(Texel),每一个纹素的颜色都可以使用 RGB 或者 RGBA 格式编码。
纹理坐标
为了能把一张纹理映射到物体上,我们需要指定物体的每个顶点各自对应纹理的哪个部分。纹理使用上更多采用的是 2D 纹理,纹理坐标在 x 和 y 轴上,范围在 0-1 之间。2D 的纹理坐标通常又称之为 uv 坐标,u 对应水平方向,也就是 x 轴,v 对应垂直方向,也就是 y 轴。如果是 3D 纹理,第三个则是 w,对应 z 轴。纹理坐标始于(0,0)点,也就是纹理左下角,终于(1,1),也就是纹理的右上角。使用纹理坐标来获取纹理颜色的方式称之为采样。每个顶点会关联着一个纹理坐标,用来表明该从纹理的哪部分采样。
纹理坐标看起来像是这样的:
const uvs = [
0, 0, // 左下角
0, 1, // 左上角
1, 0, // 右下角
1, 1 // 右上角
];
映射原理主要是将纹理图像的顶点映射到 WebGL 坐标系统的四个顶点。
纹理环绕方式
纹理坐标的范围通常是从 (0, 0) 到 (1, 1),如果超出这个范围该怎么办呢?OpenGL 默认行为是重复这个纹理图像,但是也提供了一些其它选择:
// 可以通过 gl.texParameter[fi] 对坐标不同轴向设置(2D 纹理 st 对应 uv,3D 纹理 str 对应 uvw )
// void gl.texParameterf(target, pname, param);
// 参数请参考:https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/texParameter
// 由于应用条件较多,可以直接上链接了解一下,然后对应理解教程里涉及的部分即可
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
当纹理坐标超出默认范围时,每个选项都有不同的视觉效果输出。
纹理过滤
纹理坐标不依赖分辨率,可以是任意浮点值,所以 OpenGL 知道如何将纹素映射到纹理坐标。但是,如果此时有一个小的纹理需要映射到一个很大的物体上,就可能导致多个像素都映射到同一个纹素上,相反,单个像素可能会被映射到多个纹素。纹理过滤就是为了解决不一致时纹理的采样计算问题,其中最重要的就是如下两种:
NEAREST 临近过滤(下图左):选择中心点最接近纹理坐标的那个像素,也是最简单的纹理过滤方式,效率最高。
LINEAR 线性过滤(下图右):选择中心点周围最近的 4 个纹素加权计算出来,一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。
从图中可以看出,采用临近过滤的图片有更明显的锯齿感(比如眼眶那个地方),而右边图片则更加平滑。我这里选用的图片尺寸较大,尺寸小的会更加明显。线性过滤可以产生更加真实的输出,但是如果想开发像素风格的游戏,就可以用临近过滤选项。
当对图像进行放大和缩小的时候,我们可以选择不同的过滤选项。比如:在缩小的时候采用临近过滤,获取最高效率;