WebGL实战篇(三)——绘制图片

3,827 阅读5分钟

传送门:

  1. WebGL概述——原理篇
  2. WebGL实战篇(一)——绘制点、三角形
  3. WebGL实战篇(二)——绘制点、三角形(进阶)

前言

节上一节的内容,上一节我们给我们绘制的三角形增加了一些趣味的内容,我们绘制了一个具有渐变色的三角形。这实际上就是给三角形的顶点的赋予了除开位置信息的颜色信息。这节内容我们继续为三角形的顶点增加一些别的信息,本节中我们会给三角形增加纹理坐标信息,利用这一点让我们来一起完成一个显示图片的例子。

实战——绘制图片(贴图)

准备数据

之前我们绘制了三角形,为了更好的说明现在的这个例子,我们现在将绘制的三角形改为矩形。我们可以用两个三角形来组成一个矩形。

我们修改我们的数据如下:

// 将顶点的位置数据修改为:
const pointPos = [
    -1, 1,
    -1, -1,
    1, -1,
    1, -1,
    1, 1,
    -1, 1,
];
// 给顶点增加对应的纹理坐标信息
const texCoordPos = [
    0, 1,
    0, 0,
    1, 0,
    1, 0,
    1, 1,
    0, 1
];

纹理坐标是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹素颜色,WebGL系统中的纹理坐标是二维的。如上图所示。在上图中,纹理坐标为左下角(0,0),右下角(1,0),右上角(1,1),左上角(0,1)。纹理坐标很通用,因为坐标值与图像自身的尺寸无关,不管是128x128还是128x256的图像,其右上角的坐标值始终是(1,1)。

其实使用WebGL绘制一张图片的本质实际上就是将这张图片“粘贴”到我们绘制的几何图形上面。如何粘贴?就是利用我们给顶点提供的纹理坐标。

回想我们之前在WebGL概述——原理篇中提到的光栅化过程,经过光栅化,我们的每一个片元都会得到一个纹理坐标,根据这个坐标去纹理图片上进行查询,看看这个坐标对应的纹素的颜色值是多少,然后在把这个值赋给当前的片元就可以了。

数据传递

现在有一个问题,如何将纹理图片传递给GPU。在WebGL概述——原理篇提到,与传递顶点数据类似,传递顶点数据我们使用 WebGLBuffer 对象,那么我们可以利用 WebGLTexture这个对象来往WebGL中传递纹理。

    // 创建纹理
    let texture = gl.createTexture();
    // 绑定纹理
    gl.bindTexture(gl.TEXTURE_2D, texture);
    // 传递纹理数据
    image.onload = function () {
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
}

以上就是数据传递的方式了。现在让我们来修改一下Shader程序


const vertexShader = `
    attribute vec4 a_position;
    // 把原来的颜色信息改为纹理坐标信息
    attribute vec2 a_texCoord;
    varying vec2 v_texCoord;
    void main () {
        gl_Position = a_position;
        v_texCoord = a_texCoord;
    }  
`;

const fragmentShader = `
    precision mediump float;
    varying vec2 v_texCoord;
    // 声明一个uniform变量来保存纹理
    uniform sampler2D u_texture;
    void main () {
    	// 使用内建的texture2D函数进行采样,获取纹素颜色
        gl_FragColor = texture2D(u_texture, v_texCoord);
    }
`;

至此,我们尝试运行一下我们的程序。A~Oh~,你可能获得了一个黑屏的结果。我们打开控制台发现有这样的一个错误:


[.WebGL-0x7fb3a482c400]RENDER WARNING: texture bound to texture unit 0 is not renderable. It might be non-power-of-2 or have incompatible texture filtering (maybe)?

这个错误信息告诉我们可能是因为图片的大小不是2的整数次幂或者是不兼容的采样形式导致的无法渲染。

我们先把图片改为2的整数次幂试试看。仿佛还是不行,依然还是这个错误。

经过查阅资料发现,我们需要给 WebGLTexture 这个对象设置采样的一些参数,比如:图片比绘制的区域要小,那么图片就会被方法,遇到一些点必然需要经过插值处理,那么如何进行插值呢?这就需要我们来告诉WebGL了。

// 这告诉WebGL如果纹理需要被缩小时,采用线性插值的方式来进行采样
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 这告诉WebGL如果纹理需要被方法时,采用线性插值的方式来进行采样
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// 告诉WebGL如果纹理坐标超出了s坐标的最大/最小值,直接取边界值
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
// 告诉WebGL如果纹理坐标超出了t坐标的最大/最小值,直接取边界值
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

我们使用 texParameteri 来告诉WebGL我们应该怎样进行插值。还有一些其他的值,大家可以自行探索。

再一次进行测试,发现已经可以正常工作了。我们得到了一张图片。

但是,等等!为什么图片是反的?这是因为WebGL纹理坐标系统中的t轴(你也可以直接理解为y轴)方向和PNG,BMP,JPG等格式的图片的坐标系统的Y轴方向是相反的。因此,只有先将图片的Y轴进行反转,才能够正确的将图片映射到图形上(或者,你也可以在着色器中手动反转t坐标)。

我们可以调用WebGL提供的API

gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);

这样我们绘制出来的图片就是正常的了。

总结

OK,今天绘制纹理的教程就到此结束了。现在你已经学会了如何在WebGL中绘制一张图片,这与Canvas2D绘制图片的方式完全不同,希望你能好好体会一下其中的过程。我们再次梳理一下绘制一张图片的基本流程:

  1. 给顶点添加纹理坐标信息
  2. 创建 WebGLTexture 对象,用来传递纹理信息。注意,我们还需要额外的设置纹理采样的方式。
  3. 修改shader, 在shader中使用内建的texture2D方法进行采样

等等,别走。我现在想给你看一下这张高达图片的原图是什么样的,高达看起来很帅不是么?

看过原图过后,你是不是觉得我们之前绘制的图片看起来有点奇怪?它看起来像是……变形了?所以,这是为什么呢?这又要如何解决呢?或者说我想让图片放大、缩小、旋转又该怎么做呢?我们下一节揭晓答案。

看完了别忘了点赞哦!

点此查看完整代码