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

304 阅读4分钟

全部章节

01.shader-最简单的webgl程序

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

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

04.drawElements-绘制一个正方体

05.texture-使用材质贴图

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

将绘制的正方体作为贴图放在正方形中

本章基于上一章的内容,讲述一下frameBuffer的使用。

效果

06_result.png

代码

<canvas id="cvs" style="width: 400px; height: 400px" height="400px" width="400px"></canvas>
<script src="./06customApi.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)

  const url = '图片地址,自己整一张,懒得起web服务,所以这里我放的base64,太长去掉了'

  const OFFSCREEN_WIDTH = 256
  const OFFSCREEN_HEIGHT = 256

  const frameObj = initFramebufferObject(gl, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT)

  function drawCube(gl) {
    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.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
    ]));
    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
  }

  function drawSquare(gl) {
    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.0,      1.0, 1.0,
      -0.5, 0.5, 0.0,     0.0, 1.0,
      -0.5, -0.5, 0.0,    0.0, 0.0,
      0.5, -0.5, 0.0,     1.0, 0.0
    ]))
    const n = bindElementsBuffer(gl, new Uint8Array([
      0, 1, 2,
      0, 2, 3
    ]))

    gl.uniformMatrix4fv(gl.getUniformLocation(gl.program, 'v_ModelMatrix'), false, new Float32Array([
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
    ]));
    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
  }

  initTexture(gl, 'u_Sampler', url).then(() => {
    gl.bindFramebuffer(gl.FRAMEBUFFER, frameObj.frameBuffer); // Change the drawing destination to FBO
    gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);
    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)
    drawCube(gl);


    gl.bindFramebuffer(gl.FRAMEBUFFER, null); // Change the drawing destination to FBO
    gl.viewport(0, 0, 400, 400);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, frameObj.texture);
    drawSquare(gl);
  })

  function initFramebufferObject(
    gl,
    offscreenWidth,
    offscreenHeight
  ) {
    const frameData = {};

    const frameBuffer = gl.createFramebuffer();
    frameData.frameBuffer = frameBuffer;

    const texture = gl.createTexture(); // Create a texture object
    gl.bindTexture(gl.TEXTURE_2D, texture); // Bind the object to target
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.RGBA,
      offscreenWidth,
      offscreenHeight,
      0,
      gl.RGBA,
      gl.UNSIGNED_BYTE,
      null
    );
    gl.texParameteri(
      gl.TEXTURE_2D,
      gl.TEXTURE_MIN_FILTER,
      gl.LINEAR
    );
    frameData.texture = texture;

    const renderBuffer = gl.createRenderbuffer(); // Create a renderbuffer object
    frameData.renderBuffer = renderBuffer;

    gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuffer); // Bind the object to target
    gl.renderbufferStorage(
      gl.RENDERBUFFER,
      gl.DEPTH_COMPONENT16,
      offscreenWidth,
      offscreenHeight
    );

    gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
    gl.framebufferTexture2D(
      gl.FRAMEBUFFER,
      gl.COLOR_ATTACHMENT0,
      gl.TEXTURE_2D,
      texture,
      0
    );
    gl.framebufferRenderbuffer(
      gl.FRAMEBUFFER,
      gl.DEPTH_ATTACHMENT,
      gl.RENDERBUFFER,
      renderBuffer
    );

    frameData.status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);

    // Unbind the buffer object
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.bindTexture(gl.TEXTURE_2D, null);
    gl.bindRenderbuffer(gl.RENDERBUFFER, null);

    return frameData;
  }

</script>

解释

代码修改了很多,我们一步步看。
首先我们先了解一下什么是framebuffer,中文解释为帧缓存,简单点理解就是记录的一帧的图像信息。

着色器代码

着色器代码一点都没有变,还是原来的。

js代码

06_code.png

主要变更了四块内容,简述一下每个功能的含义

  1. drawCube绘制正方体
  2. drawSquare绘制正方形
  3. initTexture().then(...)最终显示效果的绘制流程
  4. initFramebufferObject初始化framebuffer相关内容

initFramebufferObject

我们先来看一下本章最主要的内容frameBuffer

函数内部的流程如下

  1. 创建frameBuffer,Texture,renderBuffer
  2. renderbufferStorage初始化渲染缓存的存储(深度信息DEPTH_COMPONENT16)
  3. framebufferTexture2D把贴图附加到framebuffer,framebufferRenderbuffer把渲染缓存附加到framebuffer
  4. 查看framebuffer的状态,清空绑定

流程上很清晰,简单来说就是初始化帧缓存,渲染缓存和贴图,然后将贴图和渲染缓存都绑定在帧缓存上,最后回归原来的状态。

首先先理解一下他们三者。
framebuffer相当于一个canvas的webgl上下文
renderBuffer用于存放渲染图像信息
texture用于把渲染的图像作为一个贴图保存下来。

webgl在绘制图像时,会处理三块内容颜色,深度,模板(裁切镂空),主流程存在一个类似renderbuffer的东西存放这三个的结果,然后把结果变成图像绘制在canvas上。

而framebuffer就像一个不在网页上显示的canvas,截取了一帧绘制的图像结果数据。
不过framebuffer只是一个载体,它需要能存放图像信息的空间,于是绑定了renderbuffer和texture

texture和renderbuffer的作用是处于同级关系,只绑定一个texture也能运行。
这里就涉及到上面说的,三块内容,颜色,深度,模板,texture只能接收颜色信息(webgl也支持开启depthtexture,来让texture也能存放深度信息),我们可以将renderbuffer相关的内容注释掉再运行网页,发现正方体的深度失效了。这是因为framebuffer中的深度信息丢失了

所以需要用renderbuffer去存放深度信息。

06_progress.png

opengl的规范里定义了只有按照这三种组合才可以运行

  1. texture
  2. texture + renderbuffer(深度)
  3. texture + renderbuffer(模板)

06_compose.jpg

激活了framebuffer后,draw的操作会在framebuffer上进行,最终把图像放到绑定在framebuffer上的texture,供给使用

initTexture().then(...)

理解了framebuffer的作用之后,我们先看绘制流程中的代码

gl.bindFramebuffer(gl.FRAMEBUFFER, frameObj.frameBuffer); // Change the drawing destination to FBO
gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);
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)
drawCube(gl);

gl.bindFramebuffer(gl.FRAMEBUFFER, null); // Change the drawing destination to FBO
gl.viewport(0, 0, 400, 400);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, frameObj.texture);
drawSquare(gl);

按照上面说的,先bindFramebuffer激活,然后开始清理图层,绘制正方体,然后解除framebuffer的绑定,清理图层,开始绘制正方形

这里没有把activeTexture和bindTexture的过程写在drawSquare里面,就是方便解释
正方体的图像存放在frameObj.texture中,然后将激活这个texture,在绘制正方形的时候使用的贴图是frameObj.texture,最终实现效果

drawCube和drawSquare

查看两者的代码,是非常相似的,有前五章的学习,相信大家都能够看懂代码的含义。

  1. 写入缓冲区数据并传给对应的变量
  2. 指定绘制的点的index
  3. 传入形变矩阵
  4. 绘制图形

总结

本章为webglAPI基础的最后一章,至此webgl的大部分基础API都已经讲完了,六章内容其实都是为了完成最后的这个程序。这个程序也覆盖了前五章的所有内容。

当然webgl的内容远远不止这样,webgl的API还有许多,包括之前讲的那些api有各种不一样的参数,还有高级用法的api需要大家自己去探索。

接下来会着手webgl和矩阵的系列教程,矩阵在图形学中是非常重要的,我们学了这么多api其实也是完成不了很多内容的,但是加入了矩阵,我们就可以实现很多效果。

相关代码gitee