webgl2 方法解析: BLEND

24 阅读5分钟

WebGL 混合(Blending)功能允许你将当前渲染的片段颜色与帧缓冲区中已有的颜色进行混合,常用于实现透明效果、抗锯齿和其他合成特效。

基本混合设置

要启用WebGL混合功能:

gl.enable(gl.BLEND);

混合函数配置

WebGL使用以下两个函数控制混合:

  1. 混合方程gl.blendEquation(mode)

    • gl.FUNC_ADD (默认):源 + 目标
    • gl.FUNC_SUBTRACT:源 - 目标
    • gl.FUNC_REVERSE_SUBTRACT:目标 - 源
  2. 混合因子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_SUBTRACTresult = src - dst
  • gl.FUNC_REVERSE_SUBTRACTresult = dst - src
  • gl.MIN(WebGL2+):取 srcdst 的最小值
  • gl.MAX(WebGL2+):取 srcdst 的最大值

混合因子选项

混合因子决定了源颜色(当前渲染的颜色)和目标颜色(帧缓冲区中已有的颜色)如何组合在一起。

基本概念

混合公式一般为: 最终颜色 = 源颜色 × 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)

混合因子选择原则

  1. 透明度效果:通常使用SRC_ALPHAONE_MINUS_SRC_ALPHA
  2. 叠加效果:使用ONEONE实现发光/加亮效果
  3. 遮罩效果:使用ZEROSRC_COLOR等实现特殊遮罩
  4. 性能考虑ONEZERO是最快的因子

混合方程模式与混合因子的关系

blendFuncblendEquation 它们是 WebGL/OpenGL 混合(Blending)系统的两个互补的组成部分,必须配合使用才能实现完整的混合效果。

两者的区别与协作

功能blendFunc / blendFuncSeparateblendEquation / blendEquationSeparate
作用定义混合因子(权重)定义混合计算方式(运算操作)
控制内容源颜色(src)和目标颜色(dst)的混合权重源颜色和目标颜色的数学运算关系
典型参数gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHAgl.FUNC_ADD, gl.FUNC_SUBTRACT
默认值gl.ONE, gl.ZEROgl.FUNC_ADD

混合计算的实际公式

混合的完整计算公式是:

最终颜色 = blendEquation( srcColor * srcFactor, dstColor * dstFactor )

其中:

  • blendFunc 决定了 srcFactordstFactor
  • blendEquation 决定了如何组合这两个值

为什么必须同时使用

  1. 只设置 blendFunc 不设置 blendEquation

    • 会使用默认的 gl.FUNC_ADD
    • 只能实现简单的叠加混合,无法实现减法等效果
  2. 只设置 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(绝对值差值效果)

结论

必须同时使用 blendFuncblendEquation 才能正确控制混合效果:

  • 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>

注意事项

  1. 混合会降低性能,只在必要时启用
  2. 透明物体渲染顺序影响最终效果
  3. 混合前确保帧缓冲区的alpha通道正确
  4. 完成后可以禁用混合:gl.disable(gl.BLEND)