当前位置: 首页 > news >正文

WebGL 纹理——在矩形表面贴上图像

目录

WebGL要进行纹理映射,需遵循以下四步: 

1. 准备好映射到几何图形上的纹理图像。

2. 为几何图形配置纹理映射方式。

3. 加载纹理图像,对其进行一些配置,以在WebGL中使用它。

4. 在片元着色器中将相应的纹素从纹理中抽取出来,并将纹素的颜色赋给片元。

纹理坐标

将纹理图像粘贴到几何图形上

示例代码

这段程序主要分五个部分。

1. 顶点着色器中接收顶点的纹理坐标,光栅化后传递给片元着色器。

2. 片元着色器根据片元的纹理坐标,从纹理图像中抽取出纹素颜色,赋给当前片元。

3. 设置顶点的纹理坐标(initVertexBuffers())。

4. 准备待加载的纹理图像,令浏览器读取它(initTextures())。

5. 监听纹理图像的加载事件,一旦加载完成,就在WebGL系统中使用纹理(loadTexture())。

设置纹理坐标(initVertexBuffers())

配置和加载纹理(initTextures()) 

 gl.createTexture()方法规范。

gl.deleteTexture()方法规范

异步加载纹理图像

为WebGL配置纹理(loadTexture()) 

图像Y轴反转

gl.pixelStorei()方法规范

 激活纹理单元(gl.activeTexture())

 gl.activeTexture()方法规范

绑定纹理对象(gl.bindTexture()) 

 gl.bindTexture()方法规范

配置纹理对象的参数(gl.texParameteri())

gl.texParameteri()方法规范

pname可以指定4个纹理参数。 

纹理参数及它们的默认值

可以赋值给gl.TEXTURE_MAG_FILTER和gl.TEXTURE_MIN_FILTER的非金字塔纹理类型常量。

可以赋值给gl.TEXTURE_WRAP_S和gl.TEXTURE_WRAP_T的常量

将纹理图像分配给纹理对象(gl.texImage2D())

gl.texImage2D()方法规范

纹素数据的格式

将纹理单元传递给片元着色器(gl.uniform1i()) 

专用于纹理的数据类型

从顶点着色器向片元着色器传输纹理坐标 

在片元着色器中获取纹理像素颜色(texture2D()) 

texture2D()方法规范

texture2D()方法的返回值

练习:修改纹理坐标从而改变纹理映射效果


如果你想创建如下图所示的一堵逼真的砖墙,问题就来了。你可能会试图创建很多个三角形,指定它们的颜色和位置来模拟墙面上的坑坑洼洼。如果你真这么做了,那就陷入了繁琐和无意义的苦海中。

你可能已经知道,在三维图形学中,有一项很重要的技术可以解决这个问题,那就是纹理映射(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_TexCoordreturn 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);   // 清楚canvasgl.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轴(垂直轴)上镜像地重复填充纹理。 

相关文章:

WebGL 纹理——在矩形表面贴上图像

目录 WebGL要进行纹理映射,需遵循以下四步: 1. 准备好映射到几何图形上的纹理图像。 2. 为几何图形配置纹理映射方式。 3. 加载纹理图像,对其进行一些配置,以在WebGL中使用它。 4. 在片元着色器中将相应的纹素从纹理中抽取出…...

javaweb-Filter-listener过滤器与监听器

这里对Javaweb三大组件 Servelet Filter Listener 的Filter Listener进行学习 即过滤器 与监听器 过滤器: 当web浏览器向服务器发送请求时就可以对请求进行拦截与增强 这里直接给出过滤器的代码 package Filter;import javax.servlet.*; import javax.servlet.annotati…...

DBeaver 常用快捷键【大全】

本文介绍DBeaver 数据库工具的常用快捷键 一、简介 DBeaver 是一款常用的数据库工具,使用快捷键可以提高操作效率。 二、常用快捷键介绍 以下是DBeaver的常用快捷键: 1.ctrlenter 执行sql 2.ctrl\ 执行sql,保留之前窗口结果 3.ctrlshift↑ 向上复制一…...

数据分析之面试题目汇总

1、解释数据清洗的过程及常见的清洗方法。 数据清洗是指在数据分析过程中对数据进行检查、处理和纠正的过程;是数据预处理的一步,用于处理数据集中的无效、错误、缺失或冗余数据 常见的清洗方法包括:处理缺失值、处理异常值、去除重复值、统一…...

【Vue-Element-Admin】级联查询

背景 有两个查询条件:模块、功能点 想实现选择模块后,点击功能点下拉框,查询出对应模块下的功能点列表 查询 listQuery: export default{return{listQuery:{//page:1,//limit:20,//如果想兼容按条件导出,可以定义查询条件age:…...

深入探讨Kubernetes(K8s)在云原生架构中的关键作用和应用

文章目录 1. 容器化的应用程序管理2. 自动化扩展和负载均衡3. 容器编排和调度4. 存储管理5. 自动化滚动更新6. 多云和混合云部署7. 监控和日志8. 安全9. 社区支持和生态系统10. 未来展望案例 🎈个人主页:程序员 小侯 🎐CSDN新晋作者 &#x1…...

redis zset score 求和

redis zset score 求和 local sum0 local zredis.call(‘ZRANGE’, KEYS[1], 0, -1, ‘WITHSCORES’) for i2, #z, 2 do sumsumz[i] end return sum 例子:lua ~$ redis-cli zadd z 1 a 2 b 3 c 4 d 5 e (integer) 5 ~$ redis-cli eval "local sum0 local zr…...

springboot属性注入增强(一)背景/需求

一 背景 springboot 在启动时候会将系统的环境变量、项目的启动时设置的属性 、application.yml文件(或application.properties文件)、PropertySource定义的配置文件中的属性加载到Environment对象中,分布式配置中心框架也会把配置加载到Env…...

《PWA实战:如何为你的网站增加离线功能和推送通知》

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…...

sqli-labs关卡之一(两种做法)

目录 一、布尔盲注(bool注入) 二、时间盲注(sleep注入) 一、布尔盲注(bool注入) 页面没有报错和回显信息,只会返回正常或者不正常的信息,这时候就可以用布尔盲注 布尔盲注原理是先将你查询结果的第一个字符转换为ascii码,再与后面的数字比较…...

Visual Studio 线性表的链式存储节点输出引发异常:读取访问权限冲突

问题: 写了一个线性表的链式存储想要输出,能够输出,但是会报错:读取访问权限冲突 分析: 当我们输出到最后倒数第二个节点时,p指向倒数第二个节点并输出; 下一轮循环:p指向倒数第二…...

[通用]计算机经典面试题基础篇Day3

[通用]计算机经典面试题基础篇Day3 1、请说明mysql的两种主要引擎 MySQL有多种存储引擎,但最常见的两种主要引擎是InnoDB和MyISAM。 2、说一下mysql这两种引擎的使用场景 MySQL的两种主要引擎,InnoDB和MyISAM,各自适用于不同的使用场景&…...

(Golang) 牛客 在线编程 Go语言入门

文章目录 前言Go的学习资料链接 AC代码01 输出打印GP1 go的第一个程序 02 变量GP2 小明信息GP3 个人信息 03 常量GP4 国家名称 04 指针GP5 值和指针 05 字符串GP6 拼接字符串GP7 字符数量GP8 回文数 06 类型转换GP9 格式化字符串GP10 字符求和 07 运算符GP11 长方形的周长GP12 …...

2.6 PE结构:导出表详细解析

导出表(Export Table)是Windows可执行文件中的一个结构,记录了可执行文件中某些函数或变量的名称和地址,这些名称和地址可以供其他程序调用或使用。当PE文件执行时Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入&…...

SpringMvc进阶

SpringMvc进阶 SpringMVC引言一、常用注解二、参数传递三、返回值 SpringMVC引言 在Web应用程序开发中,Spring MVC是一种常用的框架,它基于MVC(Model-View-Controller)模式,提供了一种结构化的方式来构建可维护和可扩…...

SpringCloud Alibaba 入门到精通 - Nacos

SpringCloud Alibaba 常用组件 一、基础结构搭建1.父工程创建2.子工程创建 二、Nacos:注册中心1.服务端搭建2.注册中心-客户端搭建3.注册中心-管理页面4.注册中心-常用配置5.注册中心-核心功能总结 三、Nacos注册中心集成Load Balancer 、OpenFeign1.Nacos客户端集成…...

new/delete, malloc/free

区别: 首先new/delete是运算符,malloc/free是库函数。malloc/free只开辟内存不初始化;new/delete及开辟内存也初始化。抛出异常的方式:new/delete开辟失败使用抛出bad_alloc;malloc/free通过返回值判断。malloc和new区…...

oracle将一个用户的表复制到另一个用户

注:scott用户和scott用户下的源表(EMP)本身就有,无需另行创建。 GRANT SELECT ON SCOTT.emp TO BI_ODSCREATE TABLE ODS_EMP AS SELECT * FROM SCOTT.emphttp://www.bxcqd.com/news/77615.html SQL语句查询要修改密码的用户…...

C#知识点、常见面试题

相关源码 https://github.com/JackYan666/CSharpCode/blob/main/CSharpCode.cs 0.简要概括 1.For循环删除集合元素可能漏删:从后面往前删除 2.Foreach不能直接修改集合元素:用递归的思想,删除完了的集合重新遍历 3.闭包问题:for循环存在闭包,可以通过使用临时变量解决…...

【STM32】锁存器

问题背景 在学习FSMC控制外部NOR存储器时,看到在NOR复用接口模式下,AD信号[15:0]是复用的。也就是说,若不使用锁存器:当NADV为低时,ADx(x0…15)上出现地址信号Ax,当NADV变高时,ADx上出现数据信号Dx。若使用…...

DGIOT-Modbus-RTU控制指令05、06的配置与下发

[小 迪 导 读]:伴随工业物联网在实际应用中普及,Modbus-RTU作为行业内的标准化通讯协议。在为物联网起到采集作用的同时,设备的控制也是一个密不可分的环节。 场景解析:在使用Modbus对设备进行采集后,可以通过自动控制…...

机器学习实战-系列教程8:SVM分类实战3非线性SVM(鸢尾花数据集/软间隔/线性SVM/非线性SVM/scikit-learn框架)项目实战、代码解读

🌈🌈🌈机器学习 实战系列 总目录 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 SVM分类实战1之简单SVM分类 SVM分类实战2线性SVM SVM分类实战3非线性SVM 4、非线性SVM 4.1 创建非线性数据 from sklearn.data…...

计算机网络-谢希任第八版学习笔记总结

一.计算机网络概述 21世纪三个特点 数字化 信息化 智能化,其中主要是围绕智能化。 网络的常见分类: 电话网络 有线电视网络 计算机网络 互联网:Internet 由数量极大的计算机网络相连接 特点: 共享性 连通性 互联网&…...

手写Spring:第5章-注入属性和依赖对象

文章目录 一、目标:注入属性和依赖对象二、设计:注入属性和依赖对象三、实现:注入属性和依赖对象3.0 引入依赖3.1 工程结构3.2 注入属性和依赖对象类图3.3 定义属性值和属性集合3.3.1 定义属性值3.3.2 定义属性集合 3.4 Bean定义补全3.5 Bean…...

初识集合框架 -Java

目录 一、集合框架的概念 二、集合框架的重要性 三、涉及的数据结构和算法 3.1 什么是数据结构 3.2 集合框架(容器)背后对应的数据结构 3.3 相关的Java知识 3.4 什么是算法 3.5 如何学好数据结构和算法 一、集合框架的概念 Java 集合框架,…...

目标检测笔记(十五): 使用YOLOX完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)

文章目录 一、目标检测介绍二、YOLOX介绍三、源码获取四、环境搭建4.1 环境检测 五、数据集准备六、模型训练七、模型验证八、模型测试 一、目标检测介绍 目标检测(Object Detection)是计算机视觉领域的一项重要技术,旨在识别图像或视频中的…...

深眸科技自研轻辙视觉引擎,以AI机器视觉赋能杆号牌识别与分拣

电线杆号牌作为电力行业标识的一种,相当于电线杆的“身份证”,担负着宣传电力知识、安全警示的作用,用于户外使用标记输电线路电压等级、线路名称、杆塔编号等,能够清晰地记录电力线路杆的信息,并为电力线路的更改以及…...

Shell命令管理进程

Shell命令管理进程 列出进程 ps命令 top命令 管理后台进程 启动后台进程 查看后台进程 jobs和ps的区别 停止进程 Linux除了是一种多用户操作系统之外,还是一种多任务系统。多任务意味着可以同时运行多个程序。Linux 提供了相关的工具来列出运行中的进程,监视…...

python创建exe文件

1、搭建环境 pip install pyinstaller 2、准备测试代码 exe_test.py import timeprint("hello") print("hello") print("hello") print("hello")time.sleep(5) 注:添加sleep以便在执行exe文件的时候能看到结果 3、生…...

【数据结构】AVL树的插入与验证

文章目录 一、基本概念1.发展背景2.性质 二、实现原理①插入操作1.平衡因子1.1平衡因子的更新1.1.1树的高度变化1.1.2树的高度不变 2. 旋转2.1左旋2.2右旋2.3右左双旋2.4 左右双旋 ②验证1.求二叉树高度2. 判断是否为AVL树 源码总结 一、基本概念 1.发展背景 普通的二叉搜索树…...

可以做思维导图的网站/企业邮箱登录入口

Android源码目录下的build/envsetup.sh文件,描述编译的命令 - m: Makes from the top of the tree. - mm: Builds all of the modules in the current directory. - mmm: Builds all of the modules in the supplied directories. 要想使用这些命令&…...

dreamweaver网站制作步骤/文山seo

扬帆起航,希望就在前方2010年11月20号,北京PMP1011班如期开班,大家对周末两天的课程反映良好,感触颇多,觉得学到了很多知识。11月20号:早上8点就有学员陆续报道,领取讲义和考勤卡,按…...

网站建设手机软件/衡阳网站建设

请注意,本篇文章属于介绍类文章,只能帮助您分析SwiftUI使用的可能性。 pdfsandwich pdfsandwich是一款 支持 OCR pdf文件的工具库 地址:http://www.tobias-elze.de/pdfsandwich/ 核心介绍 pdfsandwich生成 OCR pdf文件,即仅包含图像(不包含文本)的pdf文件将通过光学字…...

网站建设教育培训/免费网站在线观看人数在哪直播

讲解上面三条命令之前要先了解一下ELF格式文件https://blog.csdn.net/u014608280/article/details/81983055 一、readelf readelf命令可以查看ELF文件的详细信息。 选项 -a --all 显示全部信息,等价于 -h -l -S -s -r -d -V -A -I. -h --file-header 显示elf文件开始的文件…...

网站维护服务内容/汽油价格最新调整最新消息

如何构造预制体(Prefab)? 选择你需要作为预制体的物体 拖到Assets里面 如何去除物体挂载的脚本文件? 在组件栏操作 Alt鼠标左键、鼠标右键可以实现视角的转变 Box using System.Collections; using System.Collections.Generic…...

建设项目管理公司网站/广告推广的软件

声明式编程需要底层或运行时环境支持。 声明式语言的关键词确定了执行的关键控制流。 表述编程语言是说明性的东西;而不是具体的执行方案。 通常他的执行由解释器进行。 In computer science, declarative programming is a programming paradigm—a style of build…...