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

Three.js中为领带模型生成随等级变化的随机无碰撞圆形材质

解决方案:Three.js 动态生成带随机无碰撞圆形的领带材质

方案2:Canvas 动态生成纹理(最直观易实现)

这是落地成本最低的方案,直接通过Canvas绘制随机无碰撞圆形,再转为Three.js纹理,步骤清晰:

  1. 生成无碰撞圆形数据:编写函数生成指定数量的不重叠圆,核心逻辑是新圆与已有圆的距离需大于两者半径之和。
  2. Canvas绘制:创建Canvas元素,先绘制领带底色,再逐个绘制随机颜色、尺寸的圆形。
  3. 转为Three.js纹理:用THREE.CanvasTexture包装Canvas,直接赋值给材质的map属性;后续更新时重绘Canvas后设置texture.needsUpdate = true即可。

代码示例:

// 生成无碰撞随机圆数据
function generateNonOverlappingCircles(count, canvasSize = 512) {
  const circles = [];
  const maxRadius = canvasSize / 8;
  const minRadius = canvasSize / 32;

  while (circles.length < count) {
    const newCircle = {
      x: Math.random() * canvasSize,
      y: Math.random() * canvasSize,
      radius: minRadius + Math.random() * (maxRadius - minRadius),
      color: `rgb(${Math.floor(Math.random()*255)}, ${Math.floor(Math.random()*255)}, ${Math.floor(Math.random()*255)})`
    };

    // 检查碰撞
    let isOverlap = false;
    for (const circle of circles) {
      const dx = newCircle.x - circle.x;
      const dy = newCircle.y - circle.y;
      const distance = Math.sqrt(dx*dx + dy*dy);
      if (distance < newCircle.radius + circle.radius) {
        isOverlap = true;
        break;
      }
    }

    if (!isOverlap) circles.push(newCircle);
  }
  return circles;
}

// 创建Canvas纹理
function createCircleTexture(userLevel) {
  const canvas = document.createElement('canvas');
  canvas.width = canvas.height = 512; // 建议用2的幂次尺寸
  const ctx = canvas.getContext('2d');

  // 绘制领带底色
  ctx.fillStyle = '#2c3e50';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  // 绘制圆形
  const circles = generateNonOverlappingCircles(Math.min(userLevel, 100));
  circles.forEach(circle => {
    ctx.fillStyle = circle.color;
    ctx.beginPath();
    ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);
    ctx.fill();
  });

  // 转为Three.js纹理
  const texture = new THREE.CanvasTexture(canvas);
  texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;
  return texture;
}

// 应用到领带模型
const tieMaterial = new THREE.MeshBasicMaterial({
  map: createCircleTexture(userLevel),
  side: THREE.DoubleSide
});
tieMesh.material = tieMaterial;

方案1:Shader 实时绘制圆形(性能更优,适合大量圆)

当用户等级接近100时,Shader方案性能优于Canvas,利用GPU并行计算处理圆形绘制。核心是将圆数据打包为uniform传入着色器,用GLSL的数学函数替代if判断优化性能。

步骤:

  1. JS端生成归一化的无碰撞圆数据(位置、半径对应UV坐标[0,1]范围,颜色转为0-1的vec3数组)。
  2. 将数据作为uniform传入自定义ShaderMaterial。
  3. 片元着色器中遍历所有圆,用step函数判断像素是否在圆内,实现无if的颜色叠加。

代码示例:

JS 部分

// 生成归一化圆数据
function generateNormalizedCircles(count) {
  const circles = [];
  const maxRadius = 0.125;
  const minRadius = 0.03125;

  while (circles.length < count) {
    const newCircle = {
      uv: [Math.random(), Math.random()],
      radius: minRadius + Math.random() * (maxRadius - minRadius),
      color: [Math.random(), Math.random(), Math.random()]
    };

    let isOverlap = false;
    for (const circle of circles) {
      const dx = newCircle.uv[0] - circle.uv[0];
      const dy = newCircle.uv[1] - circle.uv[1];
      const distance = Math.sqrt(dx*dx + dy*dy);
      if (distance < newCircle.radius + circle.radius) {
        isOverlap = true;
        break;
      }
    }

    if (!isOverlap) circles.push(newCircle);
  }

  // 打包为uniform格式
  return {
    centers: new Float32Array(circles.flatMap(c => c.uv)),
    radii: new Float32Array(circles.map(c => c.radius)),
    colors: new Float32Array(circles.flatMap(c => c.color)),
    count: circles.length
  };
}

// 创建自定义着色器材质
const circleData = generateNormalizedCircles(userLevel);
const tieMaterial = new THREE.ShaderMaterial({
  uniforms: {
    uBgColor: { value: new THREE.Color('#2c3e50') },
    uCircleCenters: { value: circleData.centers },
    uCircleRadii: { value: circleData.radii },
    uCircleColors: { value: circleData.colors },
    uCircleCount: { value: circleData.count }
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform vec3 uBgColor;
    uniform vec2 uCircleCenters[100]; // 最大支持100个圆
    uniform float uCircleRadii[100];
    uniform vec3 uCircleColors[100];
    uniform int uCircleCount;

    varying vec2 vUv;

    void main() {
      vec3 finalColor = uBgColor;

      for (int i = 0; i < 100; i++) {
        if (i >= uCircleCount) break; // 仅早期跳出循环,性能影响极小
        vec2 delta = vUv - uCircleCenters[i];
        float distanceSq = dot(delta, delta);
        float radiusSq = uCircleRadii[i] * uCircleRadii[i];
        // 用step实现无if的颜色切换
        finalColor = mix(finalColor, uCircleColors[i], step(distanceSq, radiusSq));
      }

      gl_FragColor = vec4(finalColor, 1.0);
    }
  `,
  side: THREE.DoubleSide
});
tieMesh.material = tieMaterial;

方案3:Three.js 内置方案?

Three.js没有直接提供“生成随机无碰撞圆形纹理”的内置API,所有实现都基于Canvas或Shader的自定义逻辑。如果尝试用Three.js的2D渲染组件(如Sprite)绘制圆再烘焙纹理,反而不如直接用Canvas高效。

总结

  • 快速实现选方案2,代码简单易调试,适配大多数场景;
  • 追求高性能(尤其是100个圆的情况)选方案1,GPU并行计算效率更高;
  • 无直接内置方案,两种自定义方案可覆盖需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 04:55:24