目录
3. 加载纹理图像,对其进行一些配置,以在WebGL中使用它。
4. 在片元着色器中将相应的纹素从纹理中抽取出来,并将纹素的颜色赋给片元。
1. 顶点着色器中接收顶点的纹理坐标,光栅化后传递给片元着色器。
2. 片元着色器根据片元的纹理坐标,从纹理图像中抽取出纹素颜色,赋给当前片元。
3. 设置顶点的纹理坐标(initVertexBuffers())。
4. 准备待加载的纹理图像,令浏览器读取它(initTextures())。
5. 监听纹理图像的加载事件,一旦加载完成,就在WebGL系统中使用纹理(loadTexture())。
可以赋值给gl.TEXTURE_MAG_FILTER和gl.TEXTURE_MIN_FILTER的非金字塔纹理类型常量。
可以赋值给gl.TEXTURE_WRAP_S和gl.TEXTURE_WRAP_T的常量
如果你想创建如下图所示的一堵逼真的砖墙,问题就来了。你可能会试图创建很多个三角形,指定它们的颜色和位置来模拟墙面上的坑坑洼洼。如果你真这么做了,那就陷入了繁琐和无意义的苦海中。
你可能已经知道,在三维图形学中,有一项很重要的技术可以解决这个问题,那就是纹理映射(texture mapping)。纹理映射其实非常简单,就是将一张图像(就像一张贴纸)映射(贴)到一个几何图形的表面上去。将一张真实世界的图片贴到一个由两个三角形组成的矩形上,这样矩形表面看上去就是这张图片。此时,这张图片又可以称为纹理图像(texture image)或纹理(texture)。、
纹理映射的作用,就是根据纹理图像,为之前光栅化后的每个片元涂上合适的颜色。组成纹理图像的像素又被称为纹素(texels,texture elements),每一个纹素的颜色都使用RGB或RGBA格式编码,如下图所示。
WebGL要进行纹理映射,需遵循以下四步:
1. 准备好映射到几何图形上的纹理图像。
2. 为几何图形配置纹理映射方式。
3. 加载纹理图像,对其进行一些配置,以在WebGL中使用它。
4. 在片元着色器中将相应的纹素从纹理中抽取出来,并将纹素的颜色赋给片元。
为了更好地理解纹理映射的机制,让我们来写一个示例程序,该程序将一张纹理图像贴在了矩形表面。在浏览器中运行代码,结果如下图所示。
接下来,我们来仔细研究上述第1步到第4步。第1步中准备的纹理图像,可以是浏览器支持的任意格式的图像。你可以使用任何照片
第2步指定映射方式,就是确定“几何图形的某个片元”的颜色如何取决于“纹理图像中哪个(或哪几个)像素”的问题(即前者到后者的映射)。我们利用图形的顶点坐标来确定屏幕上哪部分被纹理图像覆盖,使用纹理坐标(texture coordinates)来确定纹理图像的哪部分将覆盖到几何图形上。纹理坐标是一套新的坐标系统,下面就来仔细研究一下。
纹理坐标
纹理坐标是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹素颜色。WebGL系统中的纹理坐标系统是二维的,如下图所示。为了将纹理坐标和广泛使用的x坐标和y坐标区分开来,WebGL使用s和t命名纹理坐标(st/uv坐标系统)
如上图所示,纹理图像四个角的坐标为左下角(0.0,0.0),右下角(1.0,0.0),右上角(1.0,1.0)和左上角(0.0,1.0)。纹理坐标很通用,因为坐标值与图像自身的尺寸无关,不管是128×128还是128×256的图像,其右上角的纹理坐标始终是(1.0,1.0)。
将纹理图像粘贴到几何图形上
如前所述,在WebGL中,我们通过纹理图像的纹理坐标与几何形体顶点坐标间的映射关系,来确定怎样将纹理图像贴上去,如下图所示。
在这里,我们将纹理坐标(0.0,1.0)映射到顶点坐标(-0.5,-0.5,0.0)上,将纹理坐标(1.0,1.0)映射到顶点坐标(0.5,0.5,0.0)上,等等。通过建立矩形四个顶点与纹理坐标的对应关系,就获得了如上图(右)所示的结果。
现在,你应该已经大致了解纹理映射的原理了,下面来看一下示例程序。
示例代码
示例程序如下所示。纹理映射的过程需要顶点着色器和片元着色器二者的配合:首先在顶点着色器中为每个顶点指定纹理坐标,然后在片元着色器中根据每个片元的纹理坐标从纹理图像中抽取纹素颜色。程序主要包括五个部分,已经在代码中用标记了出来。
// 顶点着色器 (第一部分) p157
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec2 a_TexCoord;\n' +
'varying vec2 v_TexCoord;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' v_TexCoord = a_TexCoord;\n' +
'}\n';
// 片元着色器 (第二部分)
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'uniform sampler2D u_Sampler;\n' +
'varying vec2 v_TexCoord;\n' +
'void main() {\n' +
' gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' +
'}\n';
function main() {
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// 设置顶点信息 (第三部分)
var n = initVertexBuffers(gl);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 配置纹理
if (!initTextures(gl, n)) {
console.log('Failed to intialize the texture.');
return;
}
}
function initVertexBuffers(gl) {
var verticesTexCoords = new Float32Array([
// 顶点坐标, 纹理坐标
-0.5, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 1.0, 1.0,
0.5, -0.5, 1.0, 0.0,
]);
var n = 4; // 顶点数
// 创建缓冲区对象
var vertexTexCoordBuffer = gl.createBuffer();
// 将顶点坐标和纹理坐标写入缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
gl.enableVertexAttribArray(a_Position); // 启用缓冲区对象的分配a_Position
// 将纹理坐标分配给a_TexCoord并开启它
var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
gl.enableVertexAttribArray(a_TexCoord); // 启用缓冲区对象的分配a_TexCoord
return n;
}
// 第四部分
function initTextures(gl, n) {
var texture = gl.createTexture(); // 创建纹理对象
// 获取 u_Sampler 的存储位置
var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
var image = new Image();
// 注册图像加载事件的响应函数
image.onload = function(){ loadTexture(gl, n, texture, u_Sampler, image); };
// 浏览器开始加载图像
image.src = '../resources/sky.jpg';
return true;
}
// 第五部分
function loadTexture(gl, n, texture, u_Sampler, image) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 对纹理图像进行y轴反转
// 开启0号纹理单元
gl.activeTexture(gl.TEXTURE0);
/*
向target绑定纹理对象:通过 gl.activeTexture 方法激活了纹理单元0。然后,纹理对象 texture 使用 gl.bindTexture 方法将其绑定到当前激活的纹理单元上 gl.TEXTURE0。
在 WebGL 中,首先需要激活一个纹理单元并将纹理对象绑定到该纹理单元上,然后才能指定纹理目标的类型。
*/
gl.bindTexture(gl.TEXTURE_2D, texture);
// 配置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 配置纹理对象
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
// 将0号纹理传递给着色器
gl.uniform1i(u_Sampler, 0);
gl.clear(gl.COLOR_BUFFER_BIT); // 清楚canvas
gl.drawArrays(gl.TRIANGLE_STRIP, 0, n); // 绘制矩形
}
这段程序主要分五个部分。
1. 顶点着色器中接收顶点的纹理坐标,光栅化后传递给片元着色器。
2. 片元着色器根据片元的纹理坐标,从纹理图像中抽取出纹素颜色,赋给当前片元。
3. 设置顶点的纹理坐标(initVertexBuffers())。
4. 准备待加载的纹理图像,令浏览器读取它(initTextures())。
5. 监听纹理图像的加载事件,一旦加载完成,就在WebGL系统中使用纹理(loadTexture())。
让我们从第3部分(使用initVertexBuffers()为每个顶点设置纹理坐标)开始。着色器(前两个部分)将在图像加载完成之后执行,所以最后再解释。
设置纹理坐标(initVertexBuffers())
将纹理坐标传入顶点着色器,与将其他顶点数据(如颜色)传入顶点着色器的方法是相同的。我们可以将纹理坐标和顶点坐标写在同一个缓冲区中:定义数组verticesTexCoords,成对记录每个顶点的顶点坐标和纹理坐标(第40行),如下所示:
可见,第1个顶点(-0.5,0.5)对应的纹理坐标是(0.0,1.0),第2个顶点(-0.5,-0.5)对应的纹理坐标是(0.0,0.0),第3个顶点(0.5,0.5)对应的纹理坐标是(1.0,1.0),第4个顶点(0.5,-0.5)对应的纹理坐标是(1.0,0.0)。
然后我们将顶点坐标和纹理坐标写入缓冲区对象,将其中的顶点坐标分配给 a_Position变量并开启之(第51到56行)。接着,获取a_TexCoord变量的存储位置,将缓冲区中的纹理坐标分配给该变量(第58到59行),并开启之(第60行)。
配置和加载纹理(initTextures())
initTextures()函数负责配置和加载纹理(第66到73行):首先调用gl.createTexture()创建纹理对象(第63行),纹理对象用来管理WebGL系统中的纹理。然后调用gl.getUniformLocation()从片元着色器中获取uniform变量u_Sampler(取样器)的存储位置,该变量用来接收纹理图像(第68行)。
gl.createTexture()方法规范。
调用该函数将在WebGL系统中创建一个纹理对象,如下图所示。gl.TEXTURE0到gl.TEXTURE7是管理纹理图像的8个纹理单元(稍后将详细解释),每一个都与gl.TEXTURE_2D相关联,而后者就是绑定纹理时的纹理目标。稍后将会详细解释这些内容。
同样,也可以使用gl.deleteTexture()来删除一个纹理对象。注意,如果试图删除一个已经被删除的纹理对象,不会报错也不会产生任何影响。
gl.deleteTexture()方法规范
接下来,请求浏览器加载纹理图像供WebGL使用,该纹理图像将会映射到矩形上。为此,我们需要使用Image对象:
这段代码创建了一个Image对象,然后为其注册了onload事件响应函数loadTexture(),图像加载完成后就会调用该函数。最后通知浏览器开始加载图像。
必须使用new操作符新建Image对象,就像你新建一个Array对象或Date对象时一样(第114行)。Image是JavaScript内置的一种对象类型,它通常被用来处理图像。
由于加载图像的过程是异步的(稍后将详细讨论),所以我们需要监听加载完成事件(即onload事件):一旦浏览器完成了对图像的加载,就将加载得到的图像交给WebGL系统。注册onload事件响应函数相当于告诉浏览器,在完成了对纹理图像的加载之后,异步调用loadTexture()函数(第71行)。
loadTexture()函数接收5个参数,最后一个参数就是刚刚加载得到的图像(即Image对象)。第1个参数gl是WebGL绘图上下文,参数n是顶点的个数,参数texture是之前创建的纹理对象(第66行),而u_Sampler是着色器中uniform变量u_Sampler的存储位置。
就像HTML中的<img>标签一样,我们为Image对象添加src属性,将该属性赋值为图像文件的路径和名称来告诉浏览器开始加载图像(第73行)。注意,出于安全性考虑,WebGL不允许使用跨域纹理图像:
在执行完第73行之后,浏览器开始异步加载图像,而程序本身则(不等待图像加载完成)继续运行到第74行的return语句并退出。然后,浏览器在某个时刻完成了对图像的加载,就会调用事件响应函数loadTexture()将加载得到的图像交给WebGL系统处理。
异步加载纹理图像
WebGL是运行在浏览器中的,我们没办法直接从磁盘上读取图像,而只能通过浏览器间接地获取图像。(一般情况下,浏览器是通过向服务器发起请求,接收服务器的响应,并从中获取图像的。)这样做的优势是,我们可以使用浏览器支持的任意格式的图像,但缺点是获取图像的过程变得更加复杂了。为了获取图像,我们需要进行两个步骤(浏览器请求,以及真正将图像加载到WebGL系统中去),而且这两个步骤是异步(在后台)运行的,不会阻断当前的程序执行。
下图显示了示例程序中的第[1]步(通知浏览器加载图像)到第[7]步(图像完成加载后,调用loadTexture())的过程。
在上图,第[1]和第[2]步是按顺序执行的,而第[2]步到第[7]步则不是。在第[2]步中,我们请求浏览器去加载一幅图像之后,JavaScript程序并没有停下来等待图像加载完成,而是继续向前执行了。(该行为的机制稍后会详细解释。)在继续执行JavaScript的同时,浏览器向Web服务器请求加载一幅图像[3]。然后,当浏览器加载图像完成[4]和[5]之后,才会再通知JavaScript程序图像已经加载完成了。这种过程就称为异步(asynchronous)。上述图像的异步加载过程与在HTML网页上显示图片的过程很类似。在HTML网页中,通过为<img>标签(如下)的src属性指定图片文件的URL来告诉浏览器从哪个指定的URL加载图像。该过程其实就是图5.23中的第[2]步。
只需考虑一下那些充满大量图片的网页的表现就可以理解,浏览器加载图像本来就是一个异步的过程。一般来说,那些网页的布局和文字会很快地显示出来,然后随着图像的加载,再逐渐显示出图像来。正是因为图像的加载和显示的过程是异步的,我们才能够在打开网页的第一时间与看到网页上的文字,并与之交互,而不必等待图片被全部加载完成。
为WebGL配置纹理(loadTexture())
loadTexture()函数的定义如下:
该函数的主要任务是配置纹理供WebGL使用。使用纹理对象的方式与使用缓冲区很类似,下面就让我们研究一下。
图像Y轴反转
在使用图像之前,你必须对它进行Y轴反转。
该方法对图像进行了Y轴反转。如下图所示,WebGL纹理坐标系统中的t轴的方向和PNG、BMP、JPG等格式图片的坐标系统的Y轴方向是相反的。因此,只有先将图像Y轴进行反转,才能够正确地将图像映射到图形上。(或者,你也可以在着色器中手动反转t轴坐标。)
gl.pixelStorei()方法规范
激活纹理单元(gl.activeTexture())
WebGL通过一种称作纹理单元(texture unit)的机制来同时使用多个纹理。每个纹理单元有一个单元编号来管理一张纹理图像。即使你的程序只需要使用一张纹理图像,也得为其指定一个纹理单元。
系统支持的纹理单元个数取决于硬件和浏览器的WebGL实现,但是在默认情况下,WebGL至少支持8个纹理单元,一些其他的系统支持的个数更多。内置的变量gl.TEXTRUE0、gl.TEXTURE1……gl.TEXTURE7各表示一个纹理单元。
在使用纹理单元之前,还需要调用gl.activeTexture()来激活它,如下图所示。
gl.activeTexture()方法规范
绑定纹理对象(gl.bindTexture())
接下来,你还需要告诉WebGL系统纹理对象使用的是哪种类型的纹理。在对纹理对象进行操作之前,我们需要绑定纹理对象,这一点与缓冲区很像:在对缓冲区对象进行操作(如写入数据)之前,也需要绑定缓冲区对象。WebGL支持两种类型的纹理,如下所示。
示例程序使用一张二维图像作为纹理,所以传入了gl.TEXTURE_2D(第86行)。
绑定纹理对象到纹理单元和纹理目标
gl.bindTexture()方法规范
注意,该方法完成了两个任务:开启纹理对象,以及将纹理对象绑定到纹理单元上。(需要先将纹理对象绑定到纹理单元上,然后才能指定纹理目标类型并绑定)在本例中,因为0号纹理单元(gl.TEXTURE0)已经被激活了,所以在执行完第86行后,WebGL系统的内部状态就如下图所示。
这样,我们就指定了纹理对象的类型(gl.TEXTURE_2D)。实际上,在WebGL中,你没法直接操作纹理对象,必须通过将纹理对象绑定到纹理单元上,然后通过操作纹理单元来操作纹理对象。
配置纹理对象的参数(gl.texParameteri())
接下来,还需要配置纹理对象的参数,以此来设置纹理图像映射到图形上的具体方式:如何根据纹理坐标获取纹素颜色、按哪种方式重复填充纹理。我们使用通用函数gl.texParameteri()来设置这些参数。
gl.texParameteri()方法规范
pname可以指定4个纹理参数。
-
放大方法(gl.TEXTURE_MAG_FILTER):这个参数表示,当纹理的绘制范围比纹理本身更大时,如何获取纹素颜色。比如说,你将16×16的纹理图像映射到32×32像素的空间里时,纹理的尺寸就变成了原始的两倍。WebGL需要填充由于放大而造成的像素间的空隙,该参数就表示填充这些空隙的具体方法。
- 缩小方法(gl.TEXTURE_MIN_FILTER):这个参数表示,当纹理的绘制范围比纹理本身更小时,如何获取纹素颜色。比如说,你将32×32的纹理图像映射到16×16像素的空间里,纹理的尺寸就只有原始的一半。为了将纹理缩小,WebGL需要剔除纹理图像中的部分像素,该参数就表示具体的剔除像素的方法。
- 水平填充方法(gl.TEXTURE_WRAP_S):这个参数表示,如何对纹理图像左侧或右侧的区域进行填充。
- 垂直填充方法(gl.TEXTURE_WRAP_T):这个参数表示,如何对纹理图像上方和下方的区域进行填充。
下表5.3显示了每种纹理参数的默认值。
纹理参数及它们的默认值
下表5.4显示了可以赋给gl.TEXTURE_MAG_FILTER和gl.TEXTURE_MIN_FILTER的常量;
可以赋值给gl.TEXTURE_MAG_FILTER和gl.TEXTURE_MIN_FILTER的非金字塔纹理类型常量。
曼哈顿距离即直角距离,棋盘距离。如(x1,y1)和(x2,y2)的曼哈顿距离为|x1-x2|+|y1-y2|
下表5.5显示了可以赋给gl.TEXTURE_WRAP_S和gl.TEXTURE_WRAP_T的常量。
可以赋值给gl.TEXTURE_WRAP_S和gl.TEXTURE_WRAP_T的常量
如表5.3所示,每个纹理参数都有一个默认值,通常你可以不调用gl.texParameteri()就使用默认值。然而,本例修改了gl.TEXTURE_MIN_FILTER参数,它的默认值是一种特殊的、被称为MIPMAP(也称金字塔)的纹理类型。MIPMAP纹理实际上是一系列纹理,或者说是原始纹理图像的一系列不同分辨率的版本。总之,我们把参数gl.TEXTURE_MIN_FILTER设置为gl.LINEAR(第88行)。
纹理对象的参数都被设置好后,WebGL系统的内部状态如下图所示。
接下来,我们将纹理图像分配给纹理对象。
将纹理图像分配给纹理对象(gl.texImage2D())
我们使用gl.texImage2D()方法将纹理图像分配给纹理对象,同时该函数还允许你告诉WebGL系统关于该图像的一些特性。
gl.texImage2D()方法规范
我们在示例程序的第90行调用了该方法。
这时,Image对象中的图像就从JavaScript传入WebGL系统中,并存储在纹理对象中,如下图所示。
将图像分配给纹理对象
快速看一下调用该方法时每个参数的取值。level参数直接用0就好了,因为我们没用到金字塔纹理。format参数表示纹理数据的格式,具体取值如下表所示,你必须根据纹理图像的格式来选择这个参数。示例程序中使用的纹理图片是JPG格式的,该格式将每个像素用RGB三个分量来表示,所以我们将参数指定为gl.RGB。对其他格式的图像,如PNG格式的图像,通常使用gl.RGBA,BMP格式的图像通常使用gl.RGB,而gl.LUMINANCE和gl.LUMINANCE_ALPHA通常用在灰度图像上等等。
纹素数据的格式
这里的流明(luminance)表示我们感知到的物体表面的亮度。通常使用物体表面红、绿、蓝颜色分量值的加权平均来计算流明。
gl.texImage2D()方法将纹理图像存储在了WebGL系统中的纹理对象中。一旦存储,你必须通过internalformat参数告诉系统纹理图像的格式类型。在WebGL中,internalformat必须和format一样
type参数指定了纹理数据类型,见下表。通常我们使用gl.UNSIGNED_BYTE数据类型。当然也可以使用其他数据类型,如gl.UNSIGNED_SHORT_5_6_5(将RGB三分量压缩入16比特中)。后面的几种数据格式通常被用来压缩数据,以减少浏览器加载图像的时间。
纹理数据的数据格式
将纹理单元传递给片元着色器(gl.uniform1i())
一旦将纹理图象传入了WebGL系统,就必须将其传入片元着色器并映射到图形的表面上去。如前所述,我们使用uniform变量来表示纹理,因为纹理图像不会随着片元变化。
必须将着色器中表示纹理对象的uniform变量声明为一种特殊的、专用于纹理对象的数据类型,如下表所示。示例程序使用二维纹理gl.TEXTURE_2D,所以该uniform变量的数据类型设为sampler2D。
专用于纹理的数据类型
在initTextures()函数(第78行)中,我们获取了uniform变量u_Sampler的存储地址(第108行),并将其作为参数传给loadTexture()函数。我们必须通过指定纹理单元编号(texture unit number)(即gl.TEXTUREn中的n)将纹理对象传给u_Sampler。本例唯一的纹理对象被绑定在了gl.TEXTURE0上,所以调用gl.uniformi()时,第2个参数为0。
在执行完第92行后,WebGL系统的内部状态如下图所示,这样片元着色器就终于能够访问纹理图像了。
从顶点着色器向片元着色器传输纹理坐标
由于我们是通过attribute变量a_TexCoord接收顶点的纹理坐标,所以将数据赋值给varying变量v_TexCoord并将纹理坐标传入片元着色器是可行的。你应该还记得,片元着色器和顶点着色器内的同名、同类型的varying变量可用来在两者之间传输数据。顶点之间片元的纹理坐标会在光栅化的过程中内插出来,所以在片元着色器中,我们使用的是内插后的纹理坐标。
这样就完成了在WebGL系统中使用纹理的所有准备工作。
剩下的工作就是,根据片元的纹理坐标,从纹理图像上抽取出纹素的颜色,然后涂到当前的片元上。
在片元着色器中获取纹理像素颜色(texture2D())
片元着色器从纹理图像上获取纹素的颜色(第19行)。
使用GLSL ES内置函数texture2D()来抽取纹素颜色。该函数很容易使用,只需要传入两个参数——纹理单元编号和纹理坐标,就可以取得纹理上的像素颜色。这个函数是内置的,留意一下其参数类型和返回值。
texture2D()方法规范
texture2D()方法的返回值
纹理放大和缩小方法的参数将决定WebGL系统将以何种方式内插出片元。我们将texture2D()函数的返回值赋给了gl_FragColor变量,然后片元着色器就将当前片元染成这个颜色。最后,纹理图像就被映射到了图形(本例中是一个矩形)上,并最终被画了出来。
这已经是进行纹理映射的最后一步了。此时,纹理已经加载好、设置好,并映射到了图形上,就等你画出来了。
如你所见,在WebGL中进行纹理映射是一个相对复杂的过程,一方面是因为你得让浏览器去加载纹理图像;另一方面是因为,即使只有一个纹理,你也得使用纹理单元。
练习:修改纹理坐标从而改变纹理映射效果
为了进一步熟悉纹理映射,让我们来修改示例程序中的纹理坐标。比如,将本例中的纹理坐标进行如下修改:
修改后的程序代码,运行的效果如下图(左)所示。右侧图显示了矩形4个顶点的纹理坐标,以及纹理图像的4个角在矩形上的位置,这幅图可以帮助你更好地理解纹理坐标系统。
由于纹理图像不足以覆盖整个矩形,所以你可以看到,在那些本该空白的区域,纹理又重复出现了。之所以会这样,是因为在示例程序中,我们将gl.TEXTURE_WRAP_S和gl.TEXTURE_WRAP_T参数都设置为了gl.REPEAT。
现在,我们来如下修改纹理参数,看看还能得到什么效果。修改后的程序名为TexturedQuad_Clamp_Mirror,图下图显示了它在浏览器中的运行效果。
可见,在s轴(水平轴)上,纹理外填充了最边缘纹素的颜色,而在t轴(垂直轴)上镜像地重复填充纹理。