You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在OpenGL ES 2.0片段着色器中获取先前片段颜色并实现Canvas风格混合效果?

实现WebGL中类似Canvas的混合模式(darken/lighten/burn等)

要在WebGL里实现和Canvas globalCompositeOperation 一致的混合效果,核心思路是用帧缓冲(Framebuffer)保存已绘制的内容,再在新绘制的片段着色器中采样这个内容,手动实现混合公式。下面是具体的实现步骤和代码示例:

1. 创建离屏帧缓冲保存绘制状态

WebGL默认无法在绘制当前片段时直接读取同一画布上已存在的像素,所以我们需要先把背景(以及之前绘制的粒子)渲染到一个离屏帧缓冲里,再将这个帧缓冲作为纹理传递给粒子的着色器。

function createFramebuffer(gl, width, height) {
  const framebuffer = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

  // 创建颜色附件纹理
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

  // 将纹理绑定到帧缓冲的颜色附件
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);

  // 检查帧缓冲是否完整
  if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
    console.error("Framebuffer is not complete!");
    return null;
  }

  // 解绑
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.bindTexture(gl.TEXTURE_2D, null);

  return { framebuffer, texture };
}

// 初始化时创建和画布同尺寸的帧缓冲
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
const fb = createFramebuffer(gl, canvas.width, canvas.height);

2. 先绘制背景到帧缓冲

切换到离屏帧缓冲,绘制你的背景图,完成后切回默认帧缓冲:

// 绑定帧缓冲,开始离屏渲染
gl.bindFramebuffer(gl.FRAMEBUFFER, fb.framebuffer);
// 清除帧缓冲
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);

// 这里执行你的背景绘制代码(比如绘制一张纹理图)
drawBackground(gl);

// 切回默认帧缓冲
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

3. 绘制粒子时采样帧缓冲纹理并实现混合

在粒子的片段着色器中,我们需要:

  • 接收帧缓冲纹理(已绘制的背景+之前的粒子)
  • 接收当前粒子的颜色
  • 根据选择的混合模式,计算最终输出颜色

片段着色器代码

precision mediump float;

// 已绘制的内容(帧缓冲纹理)
uniform sampler2D u_previousTexture;
// 当前粒子的颜色
uniform vec4 u_particleColor;
// 混合模式:0=darken, 1=lighten, 2=burn
uniform int u_blendMode;
// 画布尺寸(用于计算纹理坐标)
uniform vec2 u_canvasSize;

void main() {
  // 采样已绘制的像素颜色
  vec2 texCoord = gl_FragCoord.xy / u_canvasSize;
  vec4 baseColor = texture2D(u_previousTexture, texCoord);
  // 当前粒子的颜色(示例用纯色,可替换为粒子纹理采样)
  vec4 blendColor = u_particleColor;

  vec4 finalColor;
  if (u_blendMode == 0) {
    // Darken模式:取每个通道的最小值
    finalColor = vec4(min(baseColor.rgb, blendColor.rgb), base.a + blend.a - base.a * blend.a);
  } else if (u_blendMode == 1) {
    // Lighten模式:取每个通道的最大值
    finalColor = vec4(max(baseColor.rgb, blendColor.rgb), base.a + blend.a - base.a * blend.a);
  } else if (u_blendMode == 2) {
    // Burn模式:模拟加深效果,处理预乘Alpha
    vec3 baseRGB = baseColor.rgb / baseColor.a;
    vec3 blendRGB = blendColor.rgb / blendColor.a;
    vec3 resultRGB = 1.0 - (1.0 - baseRGB) / blendRGB;
    resultRGB = clamp(resultRGB, 0.0, 1.0);
    float resultAlpha = baseColor.a + blendColor.a - baseColor.a * blendColor.a;
    finalColor = vec4(resultRGB * resultAlpha, resultAlpha);
  } else {
    // 默认直接覆盖
    finalColor = blendColor;
  }

  gl_FragColor = finalColor;
}

绘制粒子的JS代码

每次绘制粒子前,需要把帧缓冲纹理传递给着色器,并且绘制完成后,要把新的结果再写回帧缓冲(这样下次绘制粒子时能包含之前的所有内容):

function drawParticles(gl, fb, blendMode) {
  // 绑定帧缓冲,粒子会叠加到之前的背景/粒子上
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb.framebuffer);

  // 设置着色器 uniforms
  const prevTexLoc = gl.getUniformLocation(program, 'u_previousTexture');
  gl.uniform1i(prevTexLoc, 0);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, fb.texture);

  const canvasSizeLoc = gl.getUniformLocation(program, 'u_canvasSize');
  gl.uniform2f(canvasSizeLoc, canvas.width, canvas.height);

  const particleColorLoc = gl.getUniformLocation(program, 'u_particleColor');
  gl.uniform4f(particleColorLoc, 1.0, 0.5, 0.0, 0.8); // 示例橙色半透明粒子

  const blendModeLoc = gl.getUniformLocation(program, 'u_blendMode');
  gl.uniform1i(blendModeLoc, blendMode);

  // 执行粒子绘制代码(比如绘制多个点/三角形)
  renderParticleBatch(gl);

  // 把帧缓冲的最终内容绘制到画布,让用户看到效果
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  drawFullscreenQuad(gl, fb.texture);
}

关键细节说明

  • 预乘Alpha处理:像burn这类混合模式需要处理透明度,建议先把颜色转到预乘Alpha空间计算,再转换回来,避免混合错误。
  • 帧缓冲尺寸同步:如果画布尺寸变化,需要重新创建帧缓冲,确保纹理尺寸和画布一致,避免采样偏移。
  • 混合模式扩展:你可以参考Canvas的globalCompositeOperation标准,扩展更多混合模式(如multiply、screen等),只需要在着色器中添加对应的计算函数即可。

内容的提问来源于stack exchange,提问作者younes rami

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 04:07:46