WebGL 混合(Blending)功能允许你将当前渲染的片段颜色与帧缓冲区中已有的颜色进行混合,常用于实现透明效果、抗锯齿和其他合成特效。
基本混合设置
要启用WebGL混合功能:
gl.enable(gl.BLEND);
混合函数配置
WebGL使用以下两个函数控制混合:
-
混合方程:
gl.blendEquation(mode)
gl.FUNC_ADD
(默认):源 + 目标gl.FUNC_SUBTRACT
:源 - 目标gl.FUNC_REVERSE_SUBTRACT
:目标 - 源
-
混合因子:
gl.blendFunc(srcFactor, dstFactor)
- 常用组合:
// 标准透明度混合 gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // 加法混合(变亮效果) gl.blendFunc(gl.ONE, gl.ONE); // 乘法混合 gl.blendFunc(gl.DST_COLOR, gl.ZERO);
混合方程模式
(可选值)
gl.FUNC_ADD
(默认):result = src + dst
gl.FUNC_SUBTRACT
:result = src - dst
gl.FUNC_REVERSE_SUBTRACT
:result = dst - src
gl.MIN
(WebGL2+):取src
和dst
的最小值gl.MAX
(WebGL2+):取src
和dst
的最大值
混合因子选项
混合因子决定了源颜色(当前渲染的颜色)和目标颜色(帧缓冲区中已有的颜色)如何组合在一起。
基本概念
混合公式一般为: 最终颜色 = 源颜色 × srcFactor + 目标颜色 × dstFactor
其中:
- 源颜色(SrcColor) :当前正在渲染的片段颜色
- 目标颜色(DstColor) :帧缓冲区中已存在的颜色
- 混合因子(Blend Factors) :决定如何混合这两种颜色的系数
混合因子分类
WebGL提供了多种混合因子,主要分为以下几类:
1. 常量因子
gl.ZERO
:因子 = (0, 0, 0, 0)gl.ONE
:因子 = (1, 1, 1, 1)
2. 基于源颜色的因子
gl.SRC_COLOR
:因子 = (Rs, Gs, Bs, As)gl.ONE_MINUS_SRC_COLOR
:因子 = (1-Rs, 1-Gs, 1-Bs, 1-As)
3. 基于目标颜色的因子
gl.DST_COLOR
:因子 = (Rd, Gd, Bd, Ad)gl.ONE_MINUS_DST_COLOR
:因子 = (1-Rd, 1-Gd, 1-Bd, 1-Ad)
4. 基于源alpha的因子
gl.SRC_ALPHA
:因子 = (As, As, As, As)gl.ONE_MINUS_SRC_ALPHA
:因子 = (1-As, 1-As, 1-As, 1-As)
5. 基于目标alpha的因子
gl.DST_ALPHA
:因子 = (Ad, Ad, Ad, Ad)gl.ONE_MINUS_DST_ALPHA
:因子 = (1-Ad, 1-Ad, 1-Ad, 1-Ad)
常用混合模式组合
1. 标准透明度混合
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
公式: 最终颜色 = 源颜色 × 源alpha + 目标颜色 × (1 - 源alpha)
2. 加法混合(变亮效果)
gl.blendFunc(gl.ONE, gl.ONE);
公式: 最终颜色 = 源颜色 × 1 + 目标颜色 × 1
3. 乘法混合(遮蔽效果)
gl.blendFunc(gl.DST_COLOR, gl.ZERO);
公式: 最终颜色 = 源颜色 × 目标颜色 + 目标颜色 × 0
4. 预乘Alpha混合
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
公式: 最终颜色 = 源颜色 × 1 + 目标颜色 × (1 - 源alpha)
混合因子选择原则
- 透明度效果:通常使用
SRC_ALPHA
和ONE_MINUS_SRC_ALPHA
- 叠加效果:使用
ONE
和ONE
实现发光/加亮效果 - 遮罩效果:使用
ZERO
和SRC_COLOR
等实现特殊遮罩 - 性能考虑:
ONE
和ZERO
是最快的因子
混合方程模式与混合因子的关系
blendFunc
和 blendEquation
它们是 WebGL/OpenGL 混合(Blending)系统的两个互补的组成部分,必须配合使用才能实现完整的混合效果。
两者的区别与协作
功能 | blendFunc / blendFuncSeparate | blendEquation / blendEquationSeparate |
---|---|---|
作用 | 定义混合因子(权重) | 定义混合计算方式(运算操作) |
控制内容 | 源颜色(src)和目标颜色(dst)的混合权重 | 源颜色和目标颜色的数学运算关系 |
典型参数 | gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA | gl.FUNC_ADD, gl.FUNC_SUBTRACT |
默认值 | gl.ONE, gl.ZERO | gl.FUNC_ADD |
混合计算的实际公式
混合的完整计算公式是:
最终颜色 = blendEquation( srcColor * srcFactor, dstColor * dstFactor )
其中:
blendFunc
决定了srcFactor
和dstFactor
blendEquation
决定了如何组合这两个值
为什么必须同时使用
-
只设置 blendFunc 不设置 blendEquation:
- 会使用默认的
gl.FUNC_ADD
- 只能实现简单的叠加混合,无法实现减法等效果
- 会使用默认的
-
只设置 blendEquation 不设置 blendFunc:
- 会使用默认的
gl.ONE, gl.ZERO
- 新颜色会完全覆盖旧颜色(无混合效果)
- 会使用默认的
实际应用示例
标准透明度混合(最常见)
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.blendEquation(gl.FUNC_ADD); // 可省略,因为这是默认值
等效于:最终颜色 = srcColor * srcAlpha + dstColor * (1-srcAlpha)
颜色减淡效果
gl.blendFunc(gl.ONE, gl.ONE);
gl.blendEquation(gl.FUNC_ADD);
等效于:最终颜色 = srcColor + dstColor
(会使颜色变亮)
差值混合
gl.blendFunc(gl.ONE, gl.ONE);
gl.blendEquation(gl.FUNC_SUBTRACT);
等效于:最终颜色 = srcColor - dstColor
(绝对值差值效果)
结论
必须同时使用 blendFunc
和 blendEquation
才能正确控制混合效果:
blendFunc
决定"混合时各用多少"blendEquation
决定"如何组合它们"
高级混合控制
对于RGB和Alpha通道分开控制:
gl.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
gl.blendEquationSeparate(modeRGB, modeAlpha);
实际应用示例
下方是一个可直接运行的示例, 实现基本的标准透明度混合,加法混合,乘法混合:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGL2 混合效果演示</title>
<style>
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
#canvas { display: block; width: 100%; height: 100%; }
#controls {
position: absolute;
top: 10px;
left: 10px;
background: rgba(255,255,255,0.7);
padding: 10px;
border-radius: 5px;
}
button {
padding: 8px 12px;
margin: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="controls">
<button id="transparency">透明度效果</button>
<button id="additive">叠加效果</button>
<button id="mask">遮罩效果</button>
</div>
<script>
// 初始化WebGL2上下文
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const gl = canvas.getContext('webgl2');
if (!gl) {
alert('您的浏览器不支持WebGL2');
throw new Error('WebGL2 not supported');
}
// 设置视口
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
// 顶点着色器
const vsSource = `#version 300 es
in vec2 aPosition;
in vec4 aColor;
out vec4 vColor;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
vColor = aColor;
}`;
// 片段着色器
const fsSource = `#version 300 es
precision highp float;
in vec4 vColor;
out vec4 fragColor;
void main() {
fragColor = vColor;
}`;
// 编译着色器
function compileShader(gl, source, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Shader编译错误:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
// 创建着色器程序
const vertexShader = compileShader(gl, vsSource, gl.VERTEX_SHADER);
const fragmentShader = compileShader(gl, fsSource, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Program链接错误:', gl.getProgramInfoLog(program));
}
// 获取属性位置
const positionAttributeLocation = gl.getAttribLocation(program, "aPosition");
const colorAttributeLocation = gl.getAttribLocation(program, "aColor");
// 创建缓冲区
const positionBuffer = gl.createBuffer();
const colorBuffer = gl.createBuffer();
// 初始化几何数据(两个重叠的三角形)
const positions = new Float32Array([
-0.5, -0.5, // 第一个三角形
0.5, -0.5,
0.0, 0.5,
-0.3, -0.3, // 第二个三角形
0.7, -0.3,
0.2, 0.7
]);
// 颜色数据(RGBA)
const colors = new Float32Array([
1.0, 0.0, 0.0, 0.7, // 红色,70%透明度
0.0, 1.0, 0.0, 0.7, // 绿色,70%透明度
0.0, 0.0, 1.0, 0.7, // 蓝色,70%透明度
1.0, 1.0, 0.0, 0.7, // 黄色,70%透明度
0.0, 1.0, 1.0, 0.7, // 青色,70%透明度
1.0, 0.0, 1.0, 0.7 // 品红,70%透明度
]);
// 绑定位置数据
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
// 绑定颜色数据
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
// 启用混合
gl.enable(gl.BLEND);
// 默认使用透明度混合
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// 渲染函数
function render() {
// 清除画布
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 使用程序
gl.useProgram(program);
// 设置位置属性
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// 设置颜色属性
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.enableVertexAttribArray(colorAttributeLocation);
gl.vertexAttribPointer(colorAttributeLocation, 4, gl.FLOAT, false, 0, 0);
// 绘制两个三角形
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
// 按钮事件处理
document.getElementById('transparency').addEventListener('click', () => {
// 透明度混合
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
render();
});
document.getElementById('additive').addEventListener('click', () => {
// 叠加混合(加法混合)
gl.blendFunc(gl.ONE, gl.ONE);
render();
});
document.getElementById('mask').addEventListener('click', () => {
// 遮罩效果(乘法混合)
gl.blendFunc(gl.DST_COLOR, gl.ZERO);
render();
});
// 初始渲染
render();
// 窗口大小调整
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
render();
});
</script>
</body>
</html>
注意事项
- 混合会降低性能,只在必要时启用
- 透明物体渲染顺序影响最终效果
- 混合前确保帧缓冲区的alpha通道正确
- 完成后可以禁用混合:
gl.disable(gl.BLEND)