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

Three.js中Multiply等三种混合模式失效显示白方块问题排查

问题描述

使用Three.js实现粒子效果时,切换至MultiplyBlending、SubtractiveBlending、NoBlending三种混合模式时,粒子显示为白方块;但使用AdditiveBlending时显示正常。相关代码如下:

片元着色器

uniform sampler2D diffuseTexture;

varying vec4 vColor;
varying vec2 vAngle;

void main() {
  vec2 coords = (gl_PointCoord - 0.5) * mat2(vAngle.x, vAngle.y, -vAngle.y, vAngle.x) + 0.5;
  gl_FragColor = texture2D(diffuseTexture, coords) * vColor;
}

顶点着色器

uniform float pointMultiplier;

attribute float size;
attribute float angle;

varying vec4 vColor;
varying vec2 vAngle;

void main() {
  vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);

  gl_Position = projectionMatrix * mvPosition;
  gl_PointSize = size * pointMultiplier / gl_Position.w;

  vAngle = vec2(cos(angle), sin(angle));
  vColor = color;
}

TypeScript实现代码

this.material = new Three.ShaderMaterial({
  uniforms: {
    diffuseTexture: {
      value: new Three.TextureLoader().load(basicParticle)
    },
    pointMultiplier: {
      value: window.innerHeight / (2.0 * Math.tan(0.5 * 60.0 * Math.PI / 180.0))
    }
  },
  vertexShader,
  fragmentShader,
  blending: Three.MultiplyBlending,
  depthTest: true,
  depthWrite: false,
  transparent: true,
  vertexColors: true
});

this.geometry = new Three.BufferGeometry();
new Three.Points(this.geometry, this.material);
问题原因与修复方案

核心原因

  1. 缺失关键顶点属性数据:顶点着色器依赖size、angle、color三个attribute,但提供的代码中仅创建了空的BufferGeometry,未填充这些属性的具体数据。GPU读取未初始化的垃圾值后,不同混合模式对异常数值的容错性不同,导致非Additive模式下渲染异常。
  2. 混合模式与透明设置不兼容:
    • MultiplyBlending/SubtractiveBlending下,transparent: true启用的alpha混合逻辑与默认混合因子不匹配,若纹理或顶点颜色异常,会直接输出白色。
    • NoBlending模式下开启transparent: true属于冗余设置,会触发不必要的alpha处理,引发渲染错误。
  3. 未处理纹理透明区域:片元着色器未对纹理的透明区域做丢弃处理,透明区域的异常值参与混合后可能导致白色方块。

修复步骤

1. 补全BufferGeometry的顶点属性

必须为position、size、angle、color添加有效数据,示例代码:

const particleCount = 100; // 粒子数量
// 创建数据数组
const positions = new Float32Array(particleCount * 3);
const sizes = new Float32Array(particleCount);
const angles = new Float32Array(particleCount);
const colors = new Float32Array(particleCount * 3);

// 填充粒子数据
for (let i = 0; i < particleCount; i++) {
  // 随机位置
  positions[i * 3] = Math.random() * 10 - 5;
  positions[i * 3 + 1] = Math.random() * 10 - 5;
  positions[i * 3 + 2] = Math.random() * 10 - 5;
  // 随机大小
  sizes[i] = Math.random() * 20 + 10;
  // 随机旋转角度
  angles[i] = Math.random() * Math.PI * 2;
  // 随机颜色
  colors[i * 3] = Math.random();
  colors[i * 3 + 1] = Math.random();
  colors[i * 3 + 2] = Math.random();
}

// 将数据添加到BufferGeometry
this.geometry.setAttribute('position', new Three.BufferAttribute(positions, 3));
this.geometry.setAttribute('size', new Three.BufferAttribute(sizes, 1));
this.geometry.setAttribute('angle', new Three.BufferAttribute(angles, 1));
this.geometry.setAttribute('color', new Three.BufferAttribute(colors, 3));

2. 调整混合模式对应的材质配置

  • MultiplyBlending/SubtractiveBlending:
    保持transparent: true,可显式指定匹配的混合因子,并启用纹理预乘alpha:

    const texture = new Three.TextureLoader().load(basicParticle);
    texture.premultiplyAlpha = true; // 开启预乘alpha,适配混合逻辑
    
    this.material = new Three.ShaderMaterial({
      uniforms: {
        diffuseTexture: { value: texture },
        pointMultiplier: { value: window.innerHeight / (2.0 * Math.tan(0.5 * 60.0 * Math.PI / 180.0)) }
      },
      vertexShader,
      fragmentShader,
      blending: Three.MultiplyBlending, // 或SubtractiveBlending
      blendSrc: Three.SrcAlpha,
      blendDst: Three.OneMinusSrcAlpha,
      depthTest: true,
      depthWrite: false,
      transparent: true,
      vertexColors: true
    });
    
  • NoBlending:
    关闭transparent,开启深度写入:

    this.material = new Three.ShaderMaterial({
      uniforms: {
        diffuseTexture: { value: new Three.TextureLoader().load(basicParticle) },
        pointMultiplier: { value: window.innerHeight / (2.0 * Math.tan(0.5 * 60.0 * Math.PI / 180.0)) }
      },
      vertexShader,
      fragmentShader,
      blending: Three.NoBlending,
      depthTest: true,
      depthWrite: true,
      transparent: false,
      vertexColors: true
    });
    

3. 优化片元着色器的透明处理

添加alpha测试,丢弃完全透明的片元,避免异常值参与渲染:

uniform sampler2D diffuseTexture;

varying vec4 vColor;
varying vec2 vAngle;

void main() {
  vec2 coords = (gl_PointCoord - 0.5) * mat2(vAngle.x, vAngle.y, -vAngle.y, vAngle.x) + 0.5;
  vec4 texColor = texture2D(diffuseTexture, coords);
  
  // 丢弃透明区域,阈值可根据纹理调整
  if (texColor.a < 0.1) discard;
  
  gl_FragColor = texColor * vColor;
}

内容的提问来源于stack exchange,提问作者Get Off My Lawn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 07:40:37