浏览器合成
从一段简单的HTML代码开始。
如下,在背景色是黑色的body上,绘制一个透明度是0.5的蓝色div。
<style>
body {
background: rgb(0, 0, 0);
}
.app {
width: 300px;
height: 300px;
background: rgba(0, 0, 255, 0.5);
}
</style>
<body>
<div class="app"></div>
</body>
通过拾色器,可以发现div和body相交的区域的色值是 rgb(0,0,128).
浏览器图层合成结果遵循如下公式:
color_{final} = color_{source} * alpha_{source} + color_{destination} * (1 - alpha_{source})
也就是,源图层的rgba分量分别 乘以 源图层的alpha分量,然后加上,目标图层的rgba分量 乘以 1减去源图层的alpha分量 之后的结果。
本示例中,div是原图层,body是目标图层,因此合成图层的rgba计算过程如下:
rgba = (0, 0, 255, 0.5) * 0.5 + (0, 0, 0, 1) * (1 - 0.5)
= (0, 0, 128 ,0.75)
WebGL与浏览器合成
WebGL绘制的内容,最终都需要绘制到屏幕上。WebGL内容可以看做源图层,浏览器视窗可以看做目标图层,浏览器是如何处理两者的合成了?
实践出真知。如下,使用webgl绘制一个填充色是rgba(0, 0, 255, 0.5)的矩形,html body的背景色仍保持rgb(0,0,0)。
const vertextSource = `
attribute vec2 a_position;
void main(void) {
gl_Position = vec4(a_position, 0, 1.0);
}
`;
const fragmentSource = `
precision mediump float;
uniform vec4 v_color;
void main() {
gl_FragColor = v_color;
}`;
// 核心js代码
// 设置顶点坐标属性
const vertexData = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
setAttribute(gl, program, vertexData, "a_position");
const v_filter = gl.getUniformLocation(program, 'v_color');
gl.uniform4f(v_color, 0, 0, 1, 0.5);
合成色值是rgb(0, 0, 255),到此,表面上看 WebGL内容与浏览器内容的合成,并没有遵循浏览器合成的规则。
const gl = canvas.getContext("webgl", {
premultipliedAlpha: true
});
浏览器默认视为,WebGL内容的rgb值已经与alpah值相乘了。因此,进行图层合成时,公式就变成如下:
color_{final} = color_{source} + color_{destination} * (1 - alpha_{source})
按照如上的公式,重新计算下合成结果,与取色结果是吻合一致的:
rgba =(0,0,255,0.5) + (0,0,0,1)*0.5
=(0,0,255,1)
通过设置premultipliedAlpha = false,使得合成结果和CSS合成保持一致。
WebGL混合
如上介绍了WebGL和浏览器之间的合成过程,那么WebGL内部的图层是如何合成的了?
接下来,我们绘制同一个位置,绘制两个同样大小的矩形,第一个矩形的填充色是rgba(0, 0, 255, 0.5),第二个矩形的填充色是rgba(0, 255, 0, 0.5)。
// 设置顶点坐标属性
const vertexData = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
setAttribute(gl, program, 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);
// 绘制第二个矩形,填充色是绿色
gl.uniform4f(v_color, 0, 1, 0, 0.5);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
结果显示,第二次绘制的内容会完全替换第一次的绘制内容,然后再与浏览器背景色进行混合。
默认每一次绘制,WebGL都会清除当前位置的颜色缓冲区。这里,我们在同一个位置绘制两个矩形,结果就是第二次绘制的颜色会完全代替第一次绘制的颜色。
我们可以通过混合两种颜色,达到两种透明物体叠加的真实效果。WebGL允许我们开启混合模式,同时指定混合函数。
gl.enable(gl.BLEND); // 使用混合函数之前,必须激活混合能力
在混合过程中,即将写入缓冲区的颜色称为 source color,已经存在于缓冲区的颜色称为 destination color。颜色混合的数学公式如下:
color_{final} = combined(color_{source} * factor_{source} \ , \ color_{destination} * factor_{destination})
想得到混合颜色,需要指定源色的乘数因子、目标色的乘数因子,以及结合方程。
首先,通过gl.blendFunc指定源色和目标色的乘数因子,
void gl.blendFunc(sfactor, dfactor);
可选的乘数因子,如下表。乘数因子可以是一个常量,也可以是颜色的某几个分量。
乘数因子(来源于MDN)
比如,gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 指定了源色的乘数因子是源色的透明分量,目标色的乘数因子是1 减去 源色的透明分量。
- 源色与乘数因子的乘积: (0, 1, 0, 0.5) * (0.5, 0.5, 0.5, 0.5);
- 目标色与乘数因子的乘积: (0, 0, 1, 0.5) * (1 - 0.5, 1- 0.5, 1 - 0.5, 1 - 0.5);
接下来,需要通过 gl.blendEquation 指定源色乘积和目标色乘积的结合方式:
void gl.blendEquation(mode);
mode可以是如下其一:
- gl.FUNC_ADD,源色乘积 与 目标色乘积相加。该值是默认值;
- gl.FUNC_SUBTRACT,源色乘积 与 目标色相减;
- gl.FUNC_REVERSE_SUBTRACT,目标色乘积与源色乘积相减;
在熟悉以上的理论知识之后,我们开启混合功能,并按照浏览器的合成公式,指定WebGL的混合函数:
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
蓝色矩形先和浏览器黑色背景混合,进入颜色缓冲区,然后绿色矩形再与颜色缓冲区的颜色混合,最终呈现如下效果。
参考
\