Three.js中UV岛约束的动态纹理实现及Canvas与GLSL性能对比
问题解答
一、限制Canvas绘制区域到UV岛范围内
要让Canvas上的圆形仅出现在UV岛内部,核心是基于模型的UV数据筛选有效绘制区域,有两种实用思路:
预生成UV遮罩
- 创建与目标纹理尺寸一致的Canvas,遍历模型所有UV坐标,将对应Canvas像素标记为不透明(白色),其余区域设为透明(黑色),得到一张UV遮罩图
- 绘制圆形前,先随机生成像素坐标,检查遮罩图对应位置的Alpha值,仅在不透明区域绘制圆形
直接从有效UV点采样
- 提取模型的所有UV坐标存入数组,生成圆形时直接从该数组中随机选取UV点,再转换为Canvas像素坐标(
UV.x * Canvas.width, UV.y * Canvas.height),确保位置天然在UV岛内
- 提取模型的所有UV坐标存入数组,生成圆形时直接从该数组中随机选取UV点,再转换为Canvas像素坐标(
核心代码示例:
// 提取模型的有效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坐标判断像素是否在圆形范围内:
- 着色器代码
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); }
- 前端传递参数
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
相关产品推荐
相关产品推荐

