webGL FrameBuffer Object

Posted 你都孤独成这样了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了webGL FrameBuffer Object相关的知识,希望对你有一定的参考价值。

    

大家好,这是推送的第N篇内容。

当前浏览器不支持播放音乐或语音,请在微信或其他浏览器中播放 webGL FrameBuffer Object

这是一篇前端技术文章,大家如果不想看可以听听歌,乐队名称特别好玩,loveBugs.

webGL FrameBuffer Object

在webgl中,一系列的数据(顶点、纹理、and so on)经过渲染管线的一系列处理之后,最终绘制成一个个像素点输出到屏幕(canvas)中。对于webgl来说,渲染管线的最终输出目的地叫做帧缓存。

The WebGLFramebuffer interface is part of the WebGL API and represents a collection of buffers that serve as a rendering destination.

上面这句话是MDN中对于WebGLFramebuffer的释义,WebGLFramebuffer实际上是一系列buffer数据的集合。

Texture

默认情况下,webgl绘制的最终目的地就是屏幕(canvas),但是我们可以将渲染管线的输出重定向的指定的对象中去——也就是我们本文的主角FrameBuffer Object(FBO).

通常情况下我们使用FBO的目的是做offscreen rendering,简单的来说就是去做动态纹理。所以这里就是将webgl输出到texture中去。

这里的操作是很简单的,具体的代码如下:

// 创建帧缓冲,并设置为当前工作的帧缓冲
fb = webgl.createFramebuffer();
webgl.bindFramebuffer(webgl.FRAMEBUFFER, fb);
// 创建贴图对象
texture1 = webgl.createTexture();

webgl.bindTexture(webgl.TEXTURE_2D, texture1);
//把贴图对象也绑定到帧缓冲中
webgl.framebufferTexture2D(
    webgl.FRAMEBUFFER, webgl.COLOR_ATTACHMENT0,
    webgl.TEXTURE_2D, texture1, 0
);
//把贴图对象的图片数据指针注销(交给帧缓冲管理)
webgl.texImage2D(
    webgl.TEXTURE_2D, 0, webgl.RGB, 256, 256, 0,
    webgl.RGB, webgl.UNSIGNED_BYTE, null
);
//设置贴图对象的属性
webgl.texParameteri(
    webgl.TEXTURE_2D, webgl.TEXTURE_MIN_FILTER, webgl.LINEAR
);
webgl.bindTexture(webgl.TEXTURE_2D, null);

webgl.activeTexture(webgl.TEXTURE0);

FBO和纹理创建设置完毕就可以开始进行绘制了:

setInterval(function () {
    //坐标计算
    var s = Math.sin(a), c = Math.cos(a);
    a += 0.02;
    webgl.uniformMatrix4fv(tra, false, [
        c * c, -s, s * c, 0, s * c, c, s * s, 0, -s, 0, c, 0, 0, 0, -3, 1

    ]);
    //绘制到帧缓冲
    webgl.uniform1i(tp, false);
    webgl.bindFramebuffer(webgl.FRAMEBUFFER, fb);
    webgl.bindTexture(webgl.TEXTURE_2D, texture);
    webgl.viewport(0, 0, 256, 256);
    webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);
    webgl.drawElements(webgl.TRIANGLES, 36, webgl.UNSIGNED_SHORT, 0);
    //绘制到屏幕
    webgl.uniform1i(tp, true);
    webgl.bindFramebuffer(webgl.FRAMEBUFFER, null);
    webgl.bindTexture(webgl.TEXTURE_2D, texture1);
    webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);
    webgl.drawElements(webgl.TRIANGLES, 36, webgl.UNSIGNED_SHORT, 0);
}, 16);

完整代码如下,有兴趣可以复制出来自己跑一下:

<html>
<head>
</head>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
<script id="vs_s" type="text/plain">
//顶点着色器
attribute vec3 po;
attribute vec3 co;
attribute vec3 no;
attribute vec2 mp;
uniform mat4 tra;
uniform mat4 pro;
uniform vec3 li_co;
uniform vec3 li_di;
varying vec3 co_v;
varying vec2 mp_v;
varying vec3 li;
void main(){
  gl_Position=pro*tra*vec4(po,1.0);
  co_v=co;
  mp_v=mp;
  //计算漫射
  vec3 no_di=(tra*vec4(no,0.0)).xyz;
  li=max(-dot(li_di,no_di),0.0)*li_co;
}


</script>
<script id="fs_s" type="text/plain">
//片段着色器
precision lowp float;
varying vec3 co_v;
varying vec2 mp_v;
varying vec3 li;
uniform bool tp;
uniform vec3 li_en;
uniform sampler2D tex;
void main(){
  //如果tp为真则用贴图绘制,否则用颜色绘制
  if(tp)
    gl_FragColor=texture2D(tex,mp_v)*vec4(li,1.0)+vec4(li_en,1.0);
  else
    gl_FragColor=vec4(co_v*li+li_en,1.0);
}


</script>
<script>
    /**************准备**************/
    var webgl, program, vs, fs, dat, tmp, i, j;
    webgl = canvas.getContext("experimental-webgl");
    program = webgl.createProgram();
    vs = webgl.createShader(webgl.VERTEX_SHADER);
    fs = webgl.createShader(webgl.FRAGMENT_SHADER);
    webgl.shaderSource(vs, vs_s.textContent);
    webgl.shaderSource(fs, fs_s.textContent);
    webgl.compileShader(vs);
    webgl.compileShader(fs);
    webgl.attachShader(program, vs);
    webgl.attachShader(program, fs);
    webgl.linkProgram(program);
    webgl.useProgram(program);

    /**************顶点属性相关**************/
    var po, co, no, mo, mp;
    po = webgl.getAttribLocation(program, "po");
    co = webgl.getAttribLocation(program, "co");
    no = webgl.getAttribLocation(program, "no");
    mp = webgl.getAttribLocation(program, "mp");
    webgl.enableVertexAttribArray(po);
    webgl.enableVertexAttribArray(co);
    webgl.enableVertexAttribArray(no);
    webgl.enableVertexAttribArray(mp);
    //顶点位置数据
    webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer());
    webgl.bufferData(
        webgl.ARRAY_BUFFER, new Float32Array([
            -1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1,
            -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1,
            -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1,
            1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1,
            -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1,
            -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1
        ]), webgl.STATIC_DRAW
    );
    webgl.vertexAttribPointer(po, 3, webgl.FLOAT, false, 0, 0);
    //顶点颜色数据
    tmp = [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], [1, 0, 1], [0, 1, 1]];
    for (dat = [], i = 0; i < tmp.length; i++)
        for (j = 0; j < 4; j++) dat.push.apply(dat, tmp[i]);
    webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer());
    webgl.bufferData(
        webgl.ARRAY_BUFFER, new Float32Array(dat), webgl.STATIC_DRAW
    );
    webgl.vertexAttribPointer(co, 3, webgl.FLOAT, false, 0, 0);
    //贴图坐标数据
    tmp = [0, 0, 1, 0, 0, 1, 1, 1];
    for (dat = [], i = 0; i < 6; i++) dat.push.apply(dat, tmp);
    webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer());
    webgl.bufferData(
        webgl.ARRAY_BUFFER, new Float32Array(dat), webgl.STATIC_DRAW
    );
    webgl.vertexAttribPointer(mp, 2, webgl.FLOAT, false, 0, 0);
    //法向量数据
    tmp = [[0, 0, -1], [0, 0, 1], [-1, 0, 0], [1, 0, 0], [0, -1, 0], [0, 1, 0]];
    for (dat = [], i = 0; i < tmp.length; i++)
        for (j = 0; j < 4; j++) dat.push.apply(dat, tmp[i]);
    webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer());
    webgl.bufferData(
        webgl.ARRAY_BUFFER, new Float32Array(dat), webgl.STATIC_DRAW
    );
    webgl.vertexAttribPointer(no, 3, webgl.FLOAT, false, 0, 0);

    /**************全局属性相关**************/
    var tra, pro, li_co, li_di, li_en, tp, tex;
    tra = webgl.getUniformLocation(program, "tra");
    pro = webgl.getUniformLocation(program, "pro");
    li_co = webgl.getUniformLocation(program, "li_co");
    li_di = webgl.getUniformLocation(program, "li_di");
    li_en = webgl.getUniformLocation(program, "li_en");
    tp = webgl.getUniformLocation(program, "tp");
    tex = webgl.getUniformLocation(program, "tex");
    webgl.uniformMatrix4fv(pro, false, [
        1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, -1, 0, 0, -2, 0
    ]); //投射矩阵
    webgl.uniform3fv(li_co, [0.8, 0.8, 0.8]); //平行光颜色
    webgl.uniform3fv(li_di, [0, 0, -1]); //平行光方向
    webgl.uniform3fv(li_en, [0.2, 0.2, 0.2]); //环境光颜色
    webgl.uniform1i(tex, 0); //贴图序号

    /**************缓冲相关**************/
    var fb, rb, texture, texture1;
    //创建帧缓冲,并设置为当前工作的帧缓冲
    fb = webgl.createFramebuffer();
    webgl.bindFramebuffer(webgl.FRAMEBUFFER, fb);
    // 创建贴图对象
    texture1 = webgl.createTexture();

    webgl.bindTexture(webgl.TEXTURE_2D, texture1);
    //把贴图对象也绑定到帧缓冲中
    webgl.framebufferTexture2D(
        webgl.FRAMEBUFFER, webgl.COLOR_ATTACHMENT0,
        webgl.TEXTURE_2D, texture1, 0
    );
    //把贴图对象的图片数据指针注销(交给帧缓冲管理)
    webgl.texImage2D(
        webgl.TEXTURE_2D, 0, webgl.RGB, 256, 256, 0,
        webgl.RGB, webgl.UNSIGNED_BYTE, null
    );
    //设置贴图对象的属性
    webgl.texParameteri(
        webgl.TEXTURE_2D, webgl.TEXTURE_MIN_FILTER, webgl.LINEAR
    );
    webgl.bindTexture(webgl.TEXTURE_2D, null);

    webgl.activeTexture(webgl.TEXTURE0);

    // 由于着色器使用的是同一个,目标绘制纹理和当前纹理不能相同,所以要创建一个新的纹理
    texture = webgl.createTexture();

    webgl.bindTexture(webgl.TEXTURE_2D, texture);

    webgl.texImage2D(
        webgl.TEXTURE_2D, 0, webgl.RGB, 256, 256, 0,
        webgl.RGB, webgl.UNSIGNED_BYTE, null
    );
    webgl.texParameteri(
        webgl.TEXTURE_2D, webgl.TEXTURE_MIN_FILTER, webgl.LINEAR
    );


    /**************绘制相关**************/
    //构造索引
    for (dat = [], i = 0; i < 24; i += 4) dat.push(i, i + 1, i + 2, i + 3, i + 2, i + 1);
    webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, webgl.createBuffer());
    webgl.bufferData(
        webgl.ELEMENT_ARRAY_BUFFER,
        new Uint16Array(dat),
        webgl.STATIC_DRAW
    );
    //设置渲染参数
    webgl.enable(webgl.DEPTH_TEST);
    webgl.bindFramebuffer(webgl.FRAMEBUFFER, null);
    //计时器绘制
    var a = 0;
    setInterval(function () {
        //坐标计算
        var s = Math.sin(a), c = Math.cos(a);
        a += 0.02;
        webgl.uniformMatrix4fv(tra, false, [
            c * c, -s, s * c, 0, s * c, c, s * s, 0, -s, 0, c, 0, 0, 0, -3, 1

        ]);
        //绘制到帧缓冲
        webgl.uniform1i(tp, false);
        webgl.bindFramebuffer(webgl.FRAMEBUFFER, fb);
        webgl.bindTexture(webgl.TEXTURE_2D, texture);
        webgl.viewport(0, 0, 256, 256);
        webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);
        webgl.drawElements(webgl.TRIANGLES, 36, webgl.UNSIGNED_SHORT, 0);
        //绘制到屏幕
        webgl.uniform1i(tp, true);
        webgl.bindFramebuffer(webgl.FRAMEBUFFER, null);
        webgl.bindTexture(webgl.TEXTURE_2D, texture1);
        webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);
        webgl.drawElements(webgl.TRIANGLES, 36, webgl.UNSIGNED_SHORT, 0);
    }, 16);
</script>
</body>
</html>

效果图如下: 

webGL FrameBuffer Object


到现在位置我们就完成了动态纹理的渲染,但是现在发现纹理似乎有点奇怪。(这里的例子不是非常明显,可以看这个例子相对非常明显)。 


这里需要介绍一个新的概念:

Renderbuffer Object

Renderbuffer Object

In addition, renderbuffer object is newly introduced for offscreen rendering. It allows to render a scene directly to a renderbuffer object, instead of rendering to a texture object. Renderbuffer is simply a data storage object containing a single image of a renderable internal format. It is used to store OpenGL logical buffers that do not have corresponding texture format, such as stencil or depth buffer.

可以用一张图来描述下FrameBuffer Object、Renderbuffer Object、texture之间的关系。


途中的连线可以看做是bind操作。FBO的本质是一系列附件的组合,其中颜色信息和texture绑定,而深度附件则依赖Renderbuffer Object中的深度缓冲。

所以需要在FBO中加入 RenderBuffer Object.具体代码如下:

fb = webgl.createFramebuffer();
webgl.bindFramebuffer(webgl.FRAMEBUFFER, fb);
//创建渲染缓冲,并设置渲染缓冲为当前操作渲染缓冲
rb = webgl.createRenderbuffer();
webgl.bindRenderbuffer(webgl.RENDERBUFFER, rb);
//设置渲染缓冲区的存储尺寸
webgl.renderbufferStorage(
    webgl.RENDERBUFFER, webgl.DEPTH_COMPONENT16, 256, 256
);
//把渲染缓冲绑定到当前工作的帧缓冲上
webgl.framebufferRenderbuffer(
    webgl.FRAMEBUFFER, webgl.DEPTH_ATTACHMENT, webgl.RENDERBUFFER, rb
);
···

这样最终产出的动态纹理就会带有深度信息。

今天之所以写这篇内容的目的不是单纯的想做一个动态纹理。FBO在另外的一个方向也有非常大的用处,那就是GPU计算。我们都知道GPU的算力非常强,现在很多人用GPU来进行挖矿。目前在web中调用GPU的方式似乎只有webgl一个方法(这里不敢绝对)。所以可以通过framebuffer来操作GPU进行大量的计算操作(机器学习等),然后通过gl.readPixels()方法做数据的输出。

以上为我关于FBO的理解,有任何问题可以和我联系交流。

参考文档:

  • https://webglfundamentals.org/webgl/lessons/webgl-render-to-texture.html

  • https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bindRenderbuffer

  • https://www.khronos.org/opengl/wiki/Renderbuffer_Object

  • https://stackoverflow.com/questions/2213030/whats-the-concept-of-and-differences-between-framebuffer-and-renderbuffer-in-op

  • http://blog.csdn.net/dreamcs/article/details/7691690

  • https://www.web-tinker.com/article/20169.html


以上是关于webGL FrameBuffer Object的主要内容,如果未能解决你的问题,请参考以下文章

WebGL渲染错误:GL_INVALID_FRAMEBUFFER_OPERATION: Draw framebuffer is incomplete

WebGL渲染错误:GL_INVALID_FRAMEBUFFER_OPERATION: Draw framebuffer is incomplete

Webgl Framebuffer 低图片 - 质量

WebGL— FrameBuffer,RenderBuffer,Texture区别

WebGL— FrameBuffer,RenderBuffer,Texture区别

OpenGL ARB_framebuffer_object 扩展不可用