• JavaScript WebGL 帧缓冲区对象


    目录

    引子

    在看 How I built a wind map with WebGL 的时候,里面用到了 framebuffer ,就去查了下资料单独尝试了一下。

    帧缓冲区对象

    WebGL 有一个能力是将渲染结果作为纹理使用,使用到的就是帧缓冲区对象(framebuffer object)。

    在默认情况下,WebGL 最终绘图结果存储在颜色缓冲区,帧缓冲区对象可以用来代替颜色缓冲区,如下图所示,绘制在帧缓冲区中的对象并不会直接显示在 Canvas 上,因此这种技术也被称为离屏绘制(offscreen drawing)。

    99-1

    示例

    为了验证上面的功能,这个示例会在帧缓冲区里面绘制一张图片,然后将其作为纹理再次绘制显示出来。

    基于使用图片示例的逻辑,主要有下面几个方面的变化:

    • 数据
    • 帧缓冲区对象
    • 绘制

    数据

    在帧缓冲区里面绘制跟正常的绘制一样,只是不显示,所以也要有对应的绘制区域大小、顶点坐标和纹理坐标。

      offscreenWidth: 200, // 离屏绘制的宽度
      offscreenHeight: 150, // 离屏绘制的高度
      // 部分代码省略
      // 针对帧缓冲区绘制的顶点和纹理坐标
      this.offScreenBuffer = this.initBuffersForFramebuffer(gl);
      // 部分代码省略
      initBuffersForFramebuffer: function (gl) {
        const vertices = new Float32Array([
          0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
        ]); // 矩形
        const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
        const texCoords = new Float32Array([
          1.0,
          1.0, // 右上角
          0.0,
          1.0, // 左上角
          0.0,
          0.0, // 左下角
          1.0,
          0.0, // 右下角
        ]);
    
        const obj = {};
        obj.verticesBuffer = this.createBuffer(gl, gl.ARRAY_BUFFER, vertices);
        obj.indexBuffer = this.createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, indices);
        obj.texCoordsBuffer = this.createBuffer(gl, gl.ARRAY_BUFFER, texCoords);
    
        return obj;
      },
      createBuffer: function (gl, type, data) {
        const buffer = gl.createBuffer();
        gl.bindBuffer(type, buffer);
        gl.bufferData(type, data, gl.STATIC_DRAW);
        gl.bindBuffer(type, null);
        return buffer;
      }
      // 部分代码省略
    

    顶点着色器和片元着色器都可以新定义,这里为了方便公用了一套。

    帧缓冲区对象

    想要在帧缓冲区绘制,需要创建对应的帧缓冲区对象。

      // 帧缓冲区对象
      this.framebufferObj = this.createFramebufferObject(gl);
      // 部分代码省略
      createFramebufferObject: function (gl) {
        let framebuffer = gl.createFramebuffer();
    
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(
          gl.TEXTURE_2D,
          0,
          gl.RGBA,
          this.offscreenWidth,
          this.offscreenHeight,
          0,
          gl.RGBA,
          gl.UNSIGNED_BYTE,
          null
        );
        // 反转图片 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);
        framebuffer.texture = texture; // 保存纹理对象
    
        // 关联缓冲区对象
        gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
        gl.framebufferTexture2D(
          gl.FRAMEBUFFER,
          gl.COLOR_ATTACHMENT0,
          gl.TEXTURE_2D,
          texture,
          0
        );
    
        // 检查配置是否正确
        var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
        if (gl.FRAMEBUFFER_COMPLETE !== e) {
          console.log("Frame buffer object is incomplete: " + e.toString());
          return;
        }
    
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.bindTexture(gl.TEXTURE_2D, null);
    
        return framebuffer;
      }
      // 部分代码省略
    
    • createFramebuffer 函数创建帧缓冲区对象,删除对象的函数是 deleteFramebuffer
    • 创建好后,需要将帧缓冲区的颜色关联对象指定一个纹理对象,示例创建的纹理对象有几个特点:1 纹理的宽高跟绘制区域宽高一致;2 使用 texImage2D 时最后一个参数为 null ,也就是预留了一个空白的存储纹理对象的区域;3 创建好的纹理对象放到了帧缓冲区对象上,就是这行代码 framebuffer.texture = texture
    • bindFramebuffer 函数将帧缓冲区绑定到目标上,然后使用 framebufferTexture2D 将前面创建的纹理对象绑定到帧缓冲区的颜色关联对象 gl.COLOR_ATTACHMENT0 上。
    • checkFramebufferStatus 检查帧缓冲区对象配置是否正确。

    绘制

    绘制时候主要的区别是有切换的过程:

    // 部分代码省略
      draw: function () {
        const gl = this.gl;
        const frameBuffer = this.framebufferObj;
        this.canvasObj.clear();
        const program = this.shaderProgram;
        gl.useProgram(program.program);
    
        // 这个就让绘制的目标变成了帧缓冲区
        gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
        gl.viewport(0, 0, this.offscreenWidth, this.offscreenHeight);
        this.drawOffFrame(program, this.imgTexture);
    
        // 解除帧缓冲区绑定,绘制的目标变成了颜色缓冲区
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        this.drawScreen(program, frameBuffer.texture);
      },
      // 部分代码省略
    
    • 先使用 bindFramebuffer 让绘制的目标变成帧缓冲区,需要指定对应的视口。
    • 帧缓冲区绘制完成后解除绑定,恢复到正常默认的颜色缓冲区,同样需要指定对应的视口,还要比较特别的是使用了缓冲区对象的纹理,这个表明就是从帧缓冲区得到的绘制结果。

    观察及思考

    网上找的相关示例感觉比较复杂,尝试简化的过程中有下面的一些观察和思考。

    framebuffer.texture 是本来就有的属性还是人为添加的 ?

    在创建帧缓冲区对象的时候有这个逻辑: framebuffer.texture = texture ,那么帧缓冲区对象本身就有 texture 属性吗?

    打印日志发现刚创建的时候并没有这个属性,所以推测应该是人为的添加。

    framebuffer.texture 什么时候有的内容 ?

    初始化帧缓冲区对象的时候,存储的纹理是空白的,但从最终结果来看,在帧缓冲区绘制之后,纹理就有内容了,那么 framebuffer.texture 属性是什么时候有了内容?

    在绘制逻辑中,跟纹理相关语句有:

      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_2D, texture);
      gl.uniform1i(program.uSampler, 0);
      gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
    

    推测是 gl.drawElements 方法绘制结果存储在帧缓冲区的颜色关联对象,帧缓冲区的颜色关联对象又在初始化时关联了创建的空白纹理 对象,framebuffer.texture 指向的也是同一个空白纹理对象,所以最终就有了内容。

    最终的显示为什么没有铺满整个画布?

    最终绘制可显示的内容时,可以发现顶点对应整个画布,纹理坐标对应的整个完整的纹理,但为什么没有铺满整个画布?

    最终绘制可显示内容时使用的纹理来自帧缓冲区的绘制结果,而帧缓冲区的顶点对应的是整个缓冲区域的一半,如果把整个帧缓冲区绘制结果当做一个纹理,按照最终绘制可视区比例缩放,那么最后的绘制没有铺满就是预期正确的结果。

    这个是铺满画布的示例,只需将缓冲区顶点调整为对应整个缓冲区大小。

    参考资料

  • 相关阅读:
    C#图片处理示例(裁剪,缩放,清晰度,水印)
    lucene4.5近实时搜索
    mongo 多条件 查询
    Lucene:QueryParser
    Lucene的中文分词器IKAnalyzer
    Lucene为不同字段指定不同分词器(转)
    Thrift初用小结
    lucene4.0与之前版本的一些改变
    lucene 资料
    Mongodb快速入门之使用Java操作Mongodb
  • 原文地址:https://www.cnblogs.com/thyshare/p/15859444.html
Copyright © 2020-2023  润新知