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

Three.js中UV岛约束的动态纹理实现及Canvas与GLSL性能对比

问题解答

一、限制Canvas绘制区域到UV岛范围内

要让Canvas上的圆形仅出现在UV岛内部,核心是基于模型的UV数据筛选有效绘制区域,有两种实用思路:

  1. 预生成UV遮罩

    • 创建与目标纹理尺寸一致的Canvas,遍历模型所有UV坐标,将对应Canvas像素标记为不透明(白色),其余区域设为透明(黑色),得到一张UV遮罩图
    • 绘制圆形前,先随机生成像素坐标,检查遮罩图对应位置的Alpha值,仅在不透明区域绘制圆形
  2. 直接从有效UV点采样

    • 提取模型的所有UV坐标存入数组,生成圆形时直接从该数组中随机选取UV点,再转换为Canvas像素坐标(UV.x * Canvas.width, UV.y * Canvas.height),确保位置天然在UV岛内

核心代码示例:

// 提取模型的有效UV点集
const uvArray = geometry.attributes.uv.array;
const validUVs = [];
for (let i = 0; i < uvArray.length; i += 2) {
  validUVs.push({ x: uvArray[i], y: uvArray[i+1] });
}

// 在UV岛内绘制指定数量的圆形
function drawCircles(ctx, count, canvasSize) {
  for (let i = 0; i < count; i++) {
    const randomUV = validUVs[Math.floor(Math.random() * validUVs.length)];
    const x = randomUV.x * canvasSize;
    const y = randomUV.y * canvasSize;
    const radius = Math.random() * 10 + 5;

    ctx.beginPath();
    ctx.arc(x, y, radius, 0, Math.PI * 2);
    ctx.fillStyle = '#ff4444';
    ctx.fill();
  }
}

二、Canvas方案 vs GLSL着色器方案性能对比

  • Canvas方案
    • 优势:逻辑直观,JS调试成本低,对2D图形组合、图片混合等复杂操作控制灵活
    • 劣势:大量圆形(上千级)时,JS循环绘制会占用CPU;纹理更新需重新上传GPU,实时交互场景下易卡顿;纹理分辨率固定,缩放后易模糊
  • GLSL着色器方案
    • 优势:完全在GPU执行,性能远超Canvas,尤其适合大量圆形或实时动态更新场景;基于UV坐标计算,无分辨率限制,缩放不失真
    • 劣势:GLSL语法有学习成本,复杂随机逻辑、碰撞检测需用GPU友好的算法实现

总结:圆形数量少(几百以内)、更新频率低时用Canvas足够;上千级圆形或实时动态需求,优先选GLSL。

三、GLSL实现可控制数量的圆形绘制

核心是通过uniform变量传递圆形参数,在片元着色器中基于UV坐标判断像素是否在圆形范围内:

  1. 着色器代码
varying vec2 vUv;

uniform int circleCount;
uniform vec2 circlePositions[1000]; // 预设最大圆形数量
uniform float circleRadii[1000];
uniform vec3 circleColors[1000];

void main() {
  vec3 finalColor = vec3(1.0); // 默认背景色
  for (int i = 0; i < 1000; i++) {
    if (i >= circleCount) break;
    float dist = length(vUv - circlePositions[i]);
    if (dist < circleRadii[i]) {
      finalColor = circleColors[i];
      break; // 圆形重叠时取最后一个的颜色,可按需调整
    }
  }
  gl_FragColor = vec4(finalColor, 1.0);
}
  1. 前端传递参数
const circleCount = 500;
const positions = [];
const radii = [];
const colors = [];

// 基于有效UV点生成圆形数据
for (let i = 0; i < circleCount; i++) {
  const uv = validUVs[Math.floor(Math.random() * validUVs.length)];
  positions.push(uv.x, uv.y);
  radii.push(Math.random() * 0.02 + 0.01); // UV空间下的半径
  colors.push(Math.random(), Math.random(), Math.random());
}

// 传递给着色器
material.uniforms.circleCount.value = circleCount;
material.uniforms.circlePositions.value = positions;
material.uniforms.circleRadii.value = radii;
material.uniforms.circleColors.value = colors;

如果需要支持上万级圆形,可将参数打包成Float32纹理(用纹理存储圆形的位置、半径、颜色),着色器中通过采样纹理读取数据,突破uniform数组的长度限制。


内容的提问来源于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 10:25:24