05.texture-使用材质贴图

lxf2023-05-05 06:06:02

全部章节

01.shader-最简单的webgl程序

02.buffer-在一个着色器程序中绘制多个点

03.drawArrays-绘制其他平面图形

04.drawElements-绘制一个正方体

05.texture-使用材质贴图

06.frameBuffer-将webgl绘制的正方体作为材质

在正方体上使用贴图

上章我们绘制了一个彩色的正方体,本章我们学习一下贴图,然后基于上章的正方体把颜色修改成贴图

效果

05.texture-使用材质贴图

代码

<canvas id="cvs" style="width: 400px; height: 400px" height="400px" width="400px"></canvas>
<script src="./05customApi.js"></script>

<script>
  const VS = `
    attribute vec4 a_Position;
    uniform mat4 v_ModelMatrix;
    attribute vec2 a_TexCoord;
    varying vec2 v_TexCoord;
    void main() {
      gl_Position = v_ModelMatrix * a_Position;
      v_TexCoord = a_TexCoord;
    }
  `;

  const FS = `
    #ifdef GL_ES
    precision mediump float;
    #endif
    uniform sampler2D u_Sampler;
    varying vec2 v_TexCoord;
    void main() {
      gl_FragColor = texture2D(u_Sampler, v_TexCoord);
    }
  `;
</script>

<script>
  const cvs = document.getElementById('cvs')
  const gl = cvs.getContext('webgl')
  initShader(gl, VS, FS)
  bindBuffer(gl, [
    {
      name: 'a_Position',
      size: 3,
      type: gl.FLOAT,
      stride: 5,
      offset: 0
    },
    {
      name: 'a_TexCoord',
      size: 2,
      type: gl.FLOAT,
      stride: 5,
      offset: 3
    },
  ], new Float32Array([
    0.5, 0.5, 0.5,       1, 1,
    -0.5, 0.5, 0.5,      0, 1, 
    -0.5, -0.5, 0.5,     0, 0,
    0.5, -0.5, 0.5,      1, 0, // v0-v1-v2-v3 front
    0.5, 0.5, 0.5,       0, 1,
    0.5, -0.5, 0.5,      0, 0,
    0.5, -0.5, -0.5,     1, 0,
    0.5, 0.5, -0.5,      1, 1, // v0-v3-v4-v5 right
    0.5, 0.5, 0.5,       1, 0,
    0.5, 0.5, -0.5,      1, 1,
    -0.5, 0.5, -0.5,     0, 1,
    -0.5, 0.5, 0.5,      0, 0, // v0-v5-v6-v1 up
    -0.5, 0.5, 0.5,      1, 1,
    -0.5, 0.5, -0.5,     0, 1,
    -0.5, -0.5, -0.5,    0, 0,
    -0.5, -0.5, 0.5,     1, 0, // v1-v6-v7-v2 left
    -0.5, -0.5, -0.5,    0, 0,
    0.5, -0.5, -0.5,     1, 0,
    0.5, -0.5, 0.5,      1, 1,
    -0.5, -0.5, 0.5,     0, 1, // v7-v4-v3-v2 down
    0.5, -0.5, -0.5,     0, 0,
    -0.5, -0.5, -0.5,    1, 0,
    -0.5, 0.5, -0.5,     1, 1,
    0.5, 0.5, -0.5,      0, 1, // v4-v7-v6-v5 back
  ]))
  const n = bindElementsBuffer(gl, new Uint8Array([
    0, 1, 2, 0, 2, 3,    // front
    4, 5, 6, 4, 6, 7,    // right
    8, 9, 10, 8, 10, 11,    // up
    12, 13, 14, 12, 14, 15,    // left
    16, 17, 18, 16, 18, 19,    // down
    20, 21, 22, 20, 22, 23     // back
  ]))
  gl.clearColor(1.0, 1.0, 1.0, 1.0);
  gl.enable(gl.DEPTH_TEST);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT )

  gl.uniformMatrix4fv(gl.getUniformLocation(gl.program, 'v_ModelMatrix'), false, new Float32Array([
    0.7071067690849304,  0.5,  -0.5,  0,
    -0.5,  0.8535534143447876,  0.1464466154575348,  0,
    0.5,  0.1464466154575348,  0.8535534143447876,  0,
    0,  0,  0,  1
  ]));

  const url = '图片地址,自己整一张,懒得起web服务,所以这里我放的base64,太长去掉了'
  initTexture(gl, 'u_Sampler', url).then(() => {
    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
  })

  function initTexture(gl, uniform, url) {
    return new Promise(resolve => {
      const image = new Image();
      image.onload = () => {
        loadTexture(gl, gl.getUniformLocation(gl.program, uniform), image)
        resolve();
      };
      image.src = url
    })
  }

  function loadTexture (gl, uniform, image) {
    const texture = gl.createTexture();
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    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);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    gl.uniform1i(uniform, 0);
  }

</script>

解释

首先我们需要了解在webgl中,贴图是怎么映射到片元着色器上的。
这就不得不提到一个概念,UV坐标

UV坐标

UV就是一个二维的坐标系,横轴为U,竖轴为V,他们的区间都是[0,1]
这个坐标系可以关联贴图和模型,当我们将某个顶点的UV值设置成1,1,那么它就对应在了贴图的右上角 我们用图来直观的解释一下。

05.texture-使用材质贴图

当我们把front面的四个顶点的UV坐标定义在四个角,那么使用的贴图会整个铺入front面,未被定义的像素区域,会被自动赋值两点之间的贴图像素

我们可以把right面(因为front在这个旋转角度看不到)的0.5,-0.5,0.5的UV坐标从0,0改成0,0.5,把0.5,-0.5,-0.5的UV坐标从1,0改成1,0.5,会发现绿色和粉色就没有的。

着色器代码

const VS = `
  attribute vec4 a_Position;
  uniform mat4 v_ModelMatrix;
  attribute vec2 a_TexCoord;
  varying vec2 v_TexCoord;
  void main() {
    gl_Position = v_ModelMatrix * a_Position;
    v_TexCoord = a_TexCoord;
  }
`;

const FS = `
  #ifdef GL_ES
  precision mediump float;
  #endif
  uniform sampler2D u_Sampler;
  varying vec2 v_TexCoord;
  void main() {
    gl_FragColor = texture2D(u_Sampler, v_TexCoord);
  }
`;

相比较上章的着色器程序,本例中移除了color的获取和传递,而改成texCoord的传递,texCoord代表了UV坐标,所以它是vec2类型。
最终在片元着色器中使用内置函数texture2D来完成贴图的赋值。

attribute,uniform,varying

第二章的时候有提到过attribute和varying这两个限定词,本章在单独解释一下这三个限定词的区别。

  1. attribute 顶点着色器中的全局变量,储存逐顶点信息
  2. uniform 两个着色器中的全局变量,非逐顶点信息,逐片元
  3. varying 顶点着色器向片元着色器传递数据的全局变量

我对他们的理解是这样的

attribute定义的变量,是根据顶点数量来的,我需要绘制多少个顶点,attribute的变量就会接收多少个数据
比如我绘制两个POINTS,那么我定义的attribute a_Position就会获取到两次数据

uniform定义的变量,是整个着色器程序共用的,比如说形变矩阵,它虽然作用于每个顶点,但是它的值不会因为是不同顶点而不一样,是恒定的。

varying定义的变量,是用于在两个着色器直接传递数据的,只需要定义相同名称的变量就可以完成数据的传递。常用于attribute变量的传递,我的理解是因为在片元着色器中没有顶点的概念,所以不能定义attribute的变量。

js代码

除了UV传值和贴图相关的地方,其他的基本和上章相同。
我们直接来看webgl关于贴图相关的api

initTexture(gl, 'u_Sampler', url).then(() => {
    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
  })

function initTexture(gl, uniform, url) {
  return new Promise(resolve => {
    const image = new Image();
    image.onload = () => {
      loadTexture(gl, gl.getUniformLocation(gl.program, uniform), image)
      resolve();
    };
    image.src = url
  })
}

function loadTexture (gl, uniform, image) {
  const texture = gl.createTexture();
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, texture);
  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);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  gl.uniform1i(uniform, 0);
}

initTexture

我们先看initTexture函数,定义了一个Promise,然后创建了一个HTMLImageElement加载图片,加载完了以后执行loadTexture,执行完了以后resolve。

这个函数相对比较简单,只有两个点,一个是异步,另一个是image标签。
异步是因为,图片加载时需要时间的,如果没有加载完就传入到贴图中,是不存在贴图的,而且没有加载完贴图就运行drawElements绘制出来的正方体是黑色的。
image标签是因为,可以提供给webgl当做贴图的一种数据源是image标签。

loadTexture

直接看一下流程

  1. 创建一个贴图对象
  2. 激活一个贴图单元
  3. 绑定贴图对象
  4. 配置贴图的属性
  5. 指定贴图的图像
  6. 将贴图单元传递给webgl中的uniform变量

流程和创建buffer差不多,都是创建然后绑定,再给处于绑定状态的贴图,设置属性,传值。
有一个区别是激活贴图单元这个过程,即gl.activeTexture
贴图是需要一个贴图单元是暂存图像信息的,而不同浏览器提供的贴图单元数量是不同的,可以通过gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS查看。

gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)翻转Y轴坐标,正常UV的Y轴是向下的,而webgl中的y轴是向上的,所以惯性思维会用上方的点赋值(x,1)的UV,比如0,1,0点和0,0,0点,常理来说肯定把1,1赋值给0,1,0,把1,0赋值给0,0,0。
但是由于UV的Y轴是向下的,所以这样赋值会使得图像显示是反的。

gl.texParameteri是设置贴图的一些参数配置,如本例中的填充方式等,gl.pixelStorei其实也是类似的方法,设置的是图像里一些参数。

然后使用gl.texImage2D指定图像,最后将激活的贴图单元的所在index传给着色器程序

总结

本章简单讲述了一下纹理的使用过程,其中只简单涉及到的每个api的少部分内容。

贴图配置的api(pexelStorei,texParameteri)可选参数,多贴图的使用(activeTexture(gl.TEXTURE0), gl.TEXTURE1 ...)这些需要大家自己去在demo中修改尝试,查看变化

相关代码gitee