• JavaScript WebGL 使用图片


    目录

    引子

    JavaScript WebGL 设置颜色效果始终有限,这时候就会想到使用图片,这就涉及到 WebGL 中的纹理使用,比预想中要麻烦的多。

    使用图片

    纹理(texture)可以用来添加模拟物体的细节,在 3D 游戏中各种模拟的物体都使用了纹理。在绘制矩形的基础上主要有以下几个方面的变化:

    • 数据
    • 顶点着色器
    • 片元着色器
    • 缓冲纹理坐标数据
    • 加载并创建纹理
    • 绘制

    数据

    先准备一张图片,然后为了把纹理映射到对应的矩形上,需要指定矩形每个顶点各自对应纹理的那个位置。

    纹理二维坐标在 x 和 y 轴上,范围为 0 到 1 之间。纹理坐标起于(0, 0),对应图片的左下角,终于(1, 1),对应图片的右上角。所以对应的纹理坐标为:

      const texCoords = [
        1.0,
        1.0, // 右上角
        0.0,
        1.0, // 左上角
        0.0,
        0.0, // 左下角
        1.0,
        0.0, // 右下角
      ];
    

    顶点着色器

    纹理坐标需要进行缓冲并进行传递,在顶点着色器中增加了变量 aVertexTextureCoord ,其值会传递到片元着色器。

      const source = `
        attribute vec3 aVertexPos;
        attribute vec2 aVertexTextureCoord;
    
        varying highp vec2 vTextureCoord;
        void main(void){
          gl_Position = vec4(aVertexPos, 1);
          vTextureCoord = aVertexTextureCoord;
        }
      `;
    

    片元着色器

    在片元着色器中接受纹理坐标,定义纹理采样器 uSampler ,注意这个是全局变量,在任何阶段都可以访问,目前还没有值。内置的方法 texture2D 获得最终的颜色。

      const fragmentSource = `
        varying highp vec2 vTextureCoord;
        uniform sampler2D uSampler;
        void main(void){
          gl_FragColor = texture2D(uSampler, vTextureCoord);
        }
      `;
    

    缓冲纹理坐标数据

    纹理坐标数据同样需要进入缓冲。

    /**
     * 缓冲纹理坐标数据并激活
     * @param {*} gl WebGL 上下文
     * @param {*} shaderProgram 着色器程序
     * @param {*} data 纹理坐标数据
     */
    function setTextureBuffers(gl, shaderProgram, data) {
      // 创建空白的缓冲对象
      const buffer = gl.createBuffer();
      // 绑定目标
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      // WebGL 不支持直接使用 JavaScript 原始数组类型,需要转换
      const dataFormat = new Float32Array(data);
      // 初始化数据存储
      gl.bufferData(gl.ARRAY_BUFFER, dataFormat, gl.STATIC_DRAW);
    
      // 获取对应数据索引
      const texCoord = gl.getAttribLocation(
        shaderProgram,
        "aVertexTextureCoord"
      );
      // 解析顶点数据
      gl.vertexAttribPointer(texCoord, 2, gl.FLOAT, false, 0, 0);
      // 启用顶点属性,顶点属性默认是禁用的。
      gl.enableVertexAttribArray(texCoord);
    }
    

    加载并创建纹理

    要保证图片加载完成后才能使用。得到图片数据后需要创建纹理对象。

    function loadImage(gl) {
      var img = new Image();
      img.onload = (e) => {
        createTexture(gl, e.target);
      };
      img.src = "./1.jpg";
    }
    
    function createTexture(gl, source) {
      const texture = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, texture);
      // 反转图片 Y 轴方向
      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
      // 纹理坐标水平填充 s
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      // 纹理坐标垂直填充 t
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      // 纹理放大处理
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
      // 纹理缩小处理
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
      // 图片数据赋给纹理对象
      gl.texImage2D(
        gl.TEXTURE_2D,
        0,
        gl.RGBA,
        gl.RGBA,
        gl.UNSIGNED_BYTE,
        source
      );
      // 激活纹理
      gl.activeTexture(gl.TEXTURE0);
    }
    

    createTexture 创建纹理对象,接着使用 bindTexture 并绑定到对应的目标,这里是二维的图片,第一个参数值取 gl.TEXTURE_2D 表示二维纹理,第二个参数是纹理对象,当为 null 时表示取消绑定。绑定之后才能对纹理进行进一步操作。

    pixelStorei 方法对图像进行反转 Y 方向坐标,这是因为图片的坐标系统和纹理参考的坐标系不一样。

    texParameteri 方法设置纹理的各种参数,这里需要特地说明一下,如果希望使用各种尺寸的图片,需要对水平和垂直填充进行上面的设置,否则只能显示特定尺寸的图片。

    texImage2D 方法把纹理源赋值给纹理对象,这里就是把图像的像素数据传给纹理对象,这样才能在绘制纹理的时候看到图像。

    activeTexture 方法激活指定的纹理,纹理单元的范围是 0 到 gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1 ,这里只有一个,取值为 gl.TEXTUREI0 。默认的第一个纹理单元总是激活的,所以这行代码可以去掉。

    绘制

    在片元着色器中声明的全局变量,绘制时使用 uniform1i 方法指定对应的值,第二个参数表示纹理单元,这里 0 就是第一个纹理单元。

    /**
     * 绘制
     * @param {*} gl WebGL 上下文
     * @param {*} shaderProgram 着色器程序
     */
    function draw(gl, shaderProgram) {
      // 获取纹理采样器
      const samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
      // 指定全局变量关联的纹理单元
      gl.uniform1i(samplerUniform, 0);
      gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
    }
    

    效果

    这是示例, 效果如下:

    96-result

    如果对比原图的话,可以发现这张图片变形了,并没有自适应。

    参考资料

  • 相关阅读:
    关于Android的布局
    一个新的开端
    Flux的基础概念和实战入门
    在Redux中使用插件createAction之后
    学习
    Object.assign() 对象的扩展
    Redux 中的CombineReducer的函数详解
    React组件的防呆机制(propTypes)
    css的新特性 calc () 使用
    shim和polyfill有什么区别
  • 原文地址:https://www.cnblogs.com/thyshare/p/15761197.html
Copyright © 2020-2023  润新知