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

Three.js WebGL模糊着色器:如何将生成的圆用于模糊处理?

解决方案:用离屏渲染分两步处理

核心问题本质

你没法在同一个片元着色器里直接采样刚生成的像素结果——GPU是并行计算的,当前像素的输出还没写入帧缓冲,根本没法被读取。必须拆成两步走:先把圆形画到一个离屏纹理上,再用这个纹理作为输入做模糊处理。

修改后的代码实现

JS部分

// 基础场景/相机/渲染器初始化(假设你已具备)
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0.1, 10);
camera.position.z = 1;

const renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 1. 创建离屏渲染目标,专门保存圆形的渲染结果
const rt = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
  format: THREE.RGBAFormat,
  type: THREE.FloatType, // 避免透明背景下的颜色精度损失
  transparent: true
});

// 2. 圆形生成专用材质
const circleGeometry = new THREE.PlaneGeometry(1, 1);
const circleMaterial = new THREE.ShaderMaterial({
  uniforms: {
    spots: { value: 5.0 },
    offset: { value: new THREE.Vector2(0.5, 0.5) },
    radius: { value: 0.25 }
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    precision mediump float;
    varying vec2 vUv;
    uniform float spots;
    uniform vec2 offset;
    uniform float radius;

    float rnd(vec3 scale, float seed) {
      return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);
    }

    void main() {
      vec4 color1 = vec4(0.1, 0.5, 0.3, 1.0);
      vec4 color2 = vec4(0.0, 0.0, 0.0, 0.0); // 完全透明背景
      vec4 finalCol = vec4(0.0);
      vec2 shift = offset;

      for (float t = 0.0; t <= spots; t++) {
        // 这里可以加随机偏移,比如:shift += vec2(rnd(vec3(t),0.0)-0.5, rnd(vec3(t+100),0.0)-0.5)*0.2;
        float p = smoothstep(radius, radius + 0.0001, length(vUv - shift));
        vec4 col = mix(color1, color2, p);
        finalCol += col;
        shift.x += 0.05;
        shift.y -= 0.01;
      }
      gl_FragColor = finalCol;
    }
  `,
  transparent: true
});
const circlePlane = new THREE.Mesh(circleGeometry, circleMaterial);
scene.add(circlePlane);

// 3. 模糊处理专用材质
const blurGeometry = new THREE.PlaneGeometry(1, 1);
const blurMaterial = new THREE.ShaderMaterial({
  uniforms: {
    iP: { value: rt.texture }, // 把离屏纹理作为模糊输入
    dl: { value: new THREE.Vector2(0.6 / window.innerWidth, 0) }, // 偏移量要除以分辨率(UV是0-1范围)
    resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    precision mediump float;
    varying vec2 vUv;
    uniform sampler2D iP;
    uniform vec2 dl;

    float rnd(vec3 scale, float seed) {
      return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);
    }

    void main() {
      vec4 col = vec4(0.0);
      float tt = 0.0;
      float off = rnd(vec3(12.9898, 78.233, 151.7182), 0.0);

      for (float t = -30.0; t <= 30.0; t++) {
        float pc = (t + off - 0.5) / 30.0;
        float w = 1.0 - abs(pc);
        vec4 spl = texture2D(iP, vUv + dl * pc);
        spl.rgb *= spl.a; // 预乘alpha处理透明背景
        col += spl * w;
        tt += w;
      }
      col /= tt;
      col.rgb /= col.a + 0.00001; // 反预乘还原颜色
      gl_FragColor = col;
    }
  `,
  transparent: true
});
const blurPlane = new THREE.Mesh(blurGeometry, blurMaterial);
blurPlane.visible = false;
scene.add(blurPlane);

// 渲染循环:分两步渲染
function animate() {
  requestAnimationFrame(animate);

  // 第一步:把圆形渲染到离屏目标
  circlePlane.visible = true;
  blurPlane.visible = false;
  renderer.setRenderTarget(rt);
  renderer.render(scene, camera);

  // 第二步:把模糊后的结果渲染到画布
  circlePlane.visible = false;
  blurPlane.visible = true;
  renderer.setRenderTarget(null);
  renderer.render(scene, camera);
}
animate();

// 窗口 resize 时更新渲染目标和参数
window.addEventListener('resize', () => {
  renderer.setSize(window.innerWidth, window.innerHeight);
  rt.setSize(window.innerWidth, window.innerHeight);
  blurMaterial.uniforms.dl.value.set(0.6 / window.innerWidth, 0);
});

关键修改说明

  • 离屏渲染目标:用WebGLRenderTarget将圆形的渲染结果保存为可复用的纹理,解决了同一着色器无法读取自身输出的问题
  • 拆分着色器:把圆形生成和模糊处理拆成两个独立的着色器,逻辑更清晰,也符合GPU的渲染流程
  • UV偏移修正:模糊时的偏移量必须除以屏幕分辨率,因为UV坐标是0-1的相对范围,直接用像素偏移会导致模糊过度
  • Alpha通道处理:通过预乘/反预乘alpha,解决透明背景下模糊边缘发黑的问题

随机圆形补充实现

如果要给圆形加随机位置,直接在圆形着色器的循环里修改shift即可:

// 替换原循环里的shift更新逻辑
float randomX = rnd(vec3(t, 123.45, 67.89), 0.0) * 0.4 - 0.2;
float randomY = rnd(vec3(t, 987.65, 43.21), 0.0) * 0.4 - 0.2;
shift = offset + vec2(randomX, randomY);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 18:06:27