THREE.js如何实现Canvas纹理在立方体表面无重复连续渲染?
问题:如何让Canvas内容在立方体面上连续渲染而非重复?
我尝试制作一个可通过Canvas元素自定义绘制内容的立方体,目前的效果是Canvas图像在每个面上重复显示。现在希望内容能连续渲染,比如当前“Hello World”文本在面边缘被截断后,能完整延伸到下一个面。请问使用简单的BoxGeometry和MeshBasicMaterial能否实现该需求?
以下是目前的实现代码:
const ratio = devicePixelRatio; drawingCanvas.width = 600 * ratio; drawingCanvas.height = 600 * ratio; drawingCanvas.style.width = `${600}px`; drawingCanvas.style.height = `${600}px`; const ctx = drawingCanvas.getContext("2d"); if (ctx) { ctx.scale(ratio, ratio); ctx.fillStyle = "white"; ctx.fillRect(0, 0, 600, 600); ctx.fillStyle = "blue"; ctx.font = "80px Arial"; ctx.fillText("Hello World", 600 / 2 - 15, 600 / 2 - 15); } const map = new THREE.CanvasTexture( drawingCanvas, THREE.UVMapping, THREE.ClampToEdgeWrapping, THREE.ClampToEdgeWrapping ); const cube = new THREE.Mesh( new THREE.BoxGeometry(200, 200, 200), new THREE.MeshBasicMaterial({ map, }) );
解答
可以实现,但需要调整UV坐标和Canvas纹理的布局,让纹理能覆盖立方体的所有面并连续显示内容,具体步骤如下:
- 调整Canvas尺寸和内容绘制逻辑
默认的Canvas尺寸对应单个面,要实现跨面连续,需要把Canvas改成立方体展开图的尺寸。比如立方体每个面尺寸为200x200,展开图的宽度为4个面(左+前+右+后)、高度为3个面(顶+前+底),以此调整Canvas大小,并把内容绘制到跨面的位置:
const faceSize = 200; const canvasWidth = faceSize * 4 * devicePixelRatio; const canvasHeight = faceSize * 3 * devicePixelRatio; drawingCanvas.width = canvasWidth; drawingCanvas.height = canvasHeight; drawingCanvas.style.width = `${faceSize * 4}px`; drawingCanvas.style.height = `${faceSize * 3}px`; const ctx = drawingCanvas.getContext("2d"); if (ctx) { ctx.scale(devicePixelRatio, devicePixelRatio); ctx.fillStyle = "white"; ctx.fillRect(0, 0, faceSize * 4, faceSize * 3); ctx.fillStyle = "blue"; ctx.font = "80px Arial"; // 让文本从前面区域延伸到右面区域 ctx.fillText("Hello World", faceSize + 100, faceSize + 100); }
- 自定义BoxGeometry的UV坐标
默认BoxGeometry每个面的UV都是[0,0]到[1,1],导致每个面单独显示完整纹理。需要手动修改每个面的UV,对应展开图的位置:
const geometry = new THREE.BoxGeometry(200, 200, 200); const uvAttribute = geometry.attributes.uv; // 按面顺序(右、左、顶、底、前、后)定义对应展开图的UV区域 const uvMaps = [ // 右面:对应展开图x范围2/4~3/4,y范围1/3~2/3 [new THREE.Vector2(2/4,1/3), new THREE.Vector2(3/4,1/3), new THREE.Vector2(3/4,2/3), new THREE.Vector2(2/4,2/3)], // 左面:x范围0/4~1/4,y范围1/3~2/3 [new THREE.Vector2(0/4,1/3), new THREE.Vector2(1/4,1/3), new THREE.Vector2(1/4,2/3), new THREE.Vector2(0/4,2/3)], // 顶面:x范围1/4~2/4,y范围0/3~1/3 [new THREE.Vector2(1/4,0/3), new THREE.Vector2(2/4,0/3), new THREE.Vector2(2/4,1/3), new THREE.Vector2(1/4,1/3)], // 底面:x范围1/4~2/4,y范围2/3~3/3 [new THREE.Vector2(1/4,2/3), new THREE.Vector2(2/4,2/3), new THREE.Vector2(2/4,3/3), new THREE.Vector2(1/4,3/3)], // 前面:x范围1/4~2/4,y范围1/3~2/3 [new THREE.Vector2(1/4,1/3), new THREE.Vector2(2/4,1/3), new THREE.Vector2(2/4,2/3), new THREE.Vector2(1/4,2/3)], // 后面:x范围3/4~4/4,y范围1/3~2/3 [new THREE.Vector2(3/4,1/3), new THREE.Vector2(4/4,1/3), new THREE.Vector2(4/4,2/3), new THREE.Vector2(3/4,2/3)], ]; // 逐个更新面的UV坐标 uvMaps.forEach((faceUVs, faceIndex) => { for (let i = 0; i < 4; i++) { const uv = faceUVs[i]; uvAttribute.setXY(faceIndex * 4 + i, uv.x, uv.y); } }); uvAttribute.needsUpdate = true;
- 调整纹理设置
保持纹理的包裹方式为ClampToEdgeWrapping,避免内容重复:
const map = new THREE.CanvasTexture(drawingCanvas); map.wrapS = THREE.ClampToEdgeWrapping; map.wrapT = THREE.ClampToEdgeWrapping; map.repeat.set(1, 1);
最后创建Mesh:
const cube = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ map }));
完成后,Canvas上的内容会按照立方体展开图的布局连续显示在各个面上,文本可以跨面完整延伸。
内容的提问来源于stack exchange,提问作者Diogo Mendonça
相关产品推荐
相关产品推荐

