本文主旨是探究使用framebuffer特性,对blend功能有何影响。
WebGL Blend
回顾前文,可以通过gl.blend开启WebGL混合功能。
本文再通过绘制一个半透明的蓝色矩形,进一步开启gl.blend之后,浏览器是如何一步步合成最终内容的。
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
const vertexData = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
setAttribute(program, gl, vertexData, "a_position");
const v_color = gl.getUniformLocation(program, 'v_color');
gl.uniform4f(v_color, 0,0,1,0.5);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
可以发现,最终显示的颜色值是(0,0,31)。整个混合过程,涉及三个图层(自下而上):浏览器页面(黑色)、canvas(黑色)、矩形(具有透明度的蓝色)。
混合过程的顺序是,webgl内容和canvas混合,然后再与浏览器内容混合。
1、蓝色矩形和canvas进行混合,计算过程遵循gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA):
(0,0,1,0.5) * 0.5 + (0,0,0,0) * 0.5 = (0,0,0.5,0.25)
2、webgl混合内容与浏览器内容混合,这个计算过程由浏览器决定,不可改变。计算公式如gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)相同。
(0,0,0.5,0.25) * 0.25 + (0,0,0,0) * 0.75 = (0,0,0.125,0.0625) = (0,0,32,0.0625)
FrameBuffer Texture
FrameBuffer的作用是存储WebGL绘制的内容,Canvas WebGL上下文默认会创建一个FrameBuffer对象,应用可以使用createFramebuffer创建新的FrameBuffer,然后使用bindFramebuffer替换默认的FrameBuffer。
// 创建和绑定framebuffer
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
FrameBuffer,本质上是一个附件集合,附件的类型可以是WebGLTexture 或 WebGLRenderbuffer 。WebGL提供方法framebufferTexture2D用来绑定texture附件,framebufferRenderbuffer用来绑定renderBuffer附件。
// 绑定texture附件。
const framebufferTexture = gl.createTexture();
gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, framebufferTexture , 0);
内容绘制到自定义frameBuffer之后,我们可以像普通texture一样使用framebuffer texture。把framebuffer texture绘制到默认的framebuffer即可。
gl.bindFramebuffer(gl.FRAMEBUFFER, null); // 切换回默认的framebuffer
gl.bindTexture(gl.TEXTURE_2D, framebufferTexture);// 绘制framebuffer纹理
const uTexture = gl.getUniformLocation(program, 'u_texture');
gl.uniform1i(uTexture, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
总结一下framebuffer texture使用过程如下图
FrameBuffer Blend
回到文章开始,使用framebuffer绘制一个半透明的蓝色矩形,看看有什么变化。
// 绑定自定义framebuffer
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
const framebufferTexture = createTextureImage(gl, null, gl.canvas.width, gl.canvas.height);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, framebufferTexture, 0);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// 开启blend
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// 绘制矩形
const vertexData = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
setAttribute(program, gl, vertexData, "a_position");
const v_color = gl.getUniformLocation(program, 'v_color');
gl.uniform4f(v_color, ...color);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// 切换回默认framebuffer
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// 绘制framebuffer纹理
gl.bindTexture(gl.TEXTURE_2D, framebufferTexture);
const uTexture = gl.getUniformLocation(program, 'u_texture');
gl.uniform1i(uTexture, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
最终绘制的内容如下图,颜色值变成了(0,0,2)。
framebuffer texture作为一个新的图层,使得整个混合过程最终涉及四个图层(自下而上):浏览器页面(黑色)、canvas(黑色)、framebuffer texture(黑色)、矩形(具有透明度的蓝色)。
混合过程的顺序是,webgl内容和framebuffer texture混合,然后再与canvas混合,最后再与浏览器内容混合。
1、蓝色矩形和framebuffer texture进行混合,计算公式遵循gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA):
(0,0,1,0.5) * 0.5 + (0,0,0,0) * 0.5 = (0,0,0.5,0.25)
2、framebuffer texture混合内容与canvas进行混合。默认,计算公式和第一步一致,开发者可以选择设置不同的计算公式。我们这里并未做额外设置,所以和第一步的计算过程一样,同样遵循gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA):
(0,0,0.5,0.25) * 0.25 + (0,0,0,0) * 0.75 = (0,0,0.125,0.0625)
3、canvas混合内容与浏览器内容进行混合,计算公式由浏览器决定,不可改变。公式如gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)相同。
(0,0,0.125,0.0625) * 0.0625 + (0,0,0,0) * 0.9375 = (0,0,0.0078125,0.004) = (0,0,2)
因此,为了避免framebuffer texture带来的额外混合计算,导致绘制内容失真。应该在framebuffer texture绘制到canvs时,关闭blend功能。
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.disable(gl.BLEND);
完整代码见:framebuffer texture blend。