webGL FrameBuffer Object
Posted 你都孤独成这样了
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了webGL FrameBuffer Object相关的知识,希望对你有一定的参考价值。
大家好,这是推送的第N篇内容。
这是一篇前端技术文章,大家如果不想看可以听听歌,乐队名称特别好玩,loveBugs.
在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>
效果图如下:
到现在位置我们就完成了动态纹理的渲染,但是现在发现纹理似乎有点奇怪。(这里的例子不是非常明显,可以看这个例子相对非常明显)。
这里需要介绍一个新的概念:
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,RenderBuffer,Texture区别