共计 4179 个字符,预计需要花费 11 分钟才能阅读完成。
引子
在看 How I built a wind map with WebGL 的时候,外面用到了 framebuffer,就去查了下材料独自尝试了一下。
- Origin
- My GitHub
帧缓冲区对象
WebGL 有一个能力是将渲染后果作为纹理应用,应用到的就是 帧缓冲区对象(framebuffer object)。
在默认状况下,WebGL 最终绘图后果存储在色彩缓冲区,帧缓冲区对象能够用来代替色彩缓冲区,如下图所示,绘制在帧缓冲区中的对象并不会间接显示在 Canvas 上,因而这种技术也被称为 离屏绘制(offscreen drawing)。
示例
为了验证下面的性能,这个示例会在帧缓冲区外面绘制一张图片,而后将其作为纹理再次绘制显示进去。
基于应用图片示例的逻辑,次要有上面几个方面的变动:
- 数据
- 帧缓冲区对象
- 绘制
数据
在帧缓冲区外面绘制跟失常的绘制一样,只是不显示,所以也要有对应的绘制区域大小、顶点坐标和纹理坐标。
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
指向的也是同一个空白纹理对象,所以最终就有了内容。
最终的显示为什么没有铺满整个画布?
最终绘制可显示的内容时,能够发现顶点对应整个画布,纹理坐标对应的整个残缺的纹理,但为什么没有铺满整个画布?
最终绘制可显示内容时应用的纹理来自帧缓冲区的绘制后果,而帧缓冲区的顶点对应的是整个缓冲区域的一半,如果把整个帧缓冲区绘制后果当做一个纹理,依照最终绘制可视区比例缩放,那么最初的绘制没有铺满就是预期正确的后果。
这个是铺满画布的示例,只需将缓冲区顶点调整为对应整个缓冲区大小。
参考资料
- WebGL 编程指南帧缓冲示例
- WebGL Framebuffers
- WebGL display framebuffer?