ThreeJS :给立方体各面加上文字
本帖最后由 马黑黑 于 2025-6-18 20:48 编辑 <br /><br /><style>.artBox { font-size: 18px; }
.artBox > p { margin: 10px 0; line-height: 30px; }
.artBox mark { padding: 4px 6px; background: lightblue; }
#prevBox { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: #eee; display: none; padding: 0; overflow: hidden; z-index: 1000; margin: 0; }
#prevBox::after { position: absolute; content: '关闭预览'; bottom: 10px; left: calc(50% - 40px); padding: 0 4px; width: 80px; height: 30px; line-height: 30px; text-align: center; border: 1px solid #efe; border-radius: 6px; background: #eee; font-size: 14px; box-shadow: 2px 2px 6px rgba(0,0,0,.25); cursor: pointer; }
iframe { position: relative; width: 100%; height: 100%; border: none; outline: none; box-sizing: border-box; margin: 0; }
</style>
<div id="prevBox"></div>
<div class="artBox">
<p>ThreeJS 对文本的处理相对薄弱,它提供有一个扩展几何体 TextGeometry,需要加载字体,且对非英文字体一点都不友好。故此,若需要给立方体每一个面都添加文本,需要另辟蹊径,其中,通过离线 canvas 绘制文本然后作为纹理贴图贴到立方体的每一个面,是个很常见的实现方法。</p>
<p>ThreeJS 本身就是通过 canvas 标签在2d平面上绘制3d效果,它对 canvas 的支持度非常强,核心库内置有canvas纹理,一个 <mark>CanvasTexture</mark> 类,构造器复杂无比(<a href="http://www.yanhuangxueyuan.com/threejs/docs/index.html?q=texture#api/zh/textures/CanvasTexture" target="_blank">CanvasTexture</a>),不过没关系,通常用到的参数主要只是一个 canvas 标签,像这样:</p>
<div class="hEdiv"><pre class="hEpre">const texture = new THREE.CanvasTexture(canvasElement);</pre></div>
<p><mark>canvasElement</mark> 即为canvas画布元素,其上应该是已经绘制好了所需文本。为此,需要熟悉 canvas 绘制文本功能,可以参阅 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Drawing_text" target="_blank">canvas绘制文本</a> 加以学习掌握,主要学习其上介绍的 fillText 方法,因为 ThreeJS 貌似对 strokeText 支持不好。</p>
<p>为方便复用,我编写了一个在 canvas 画布上绘制文本的函数:</p>
<div class="hEdiv"><pre class="hEpre">
function mkText(txt, font, color, bg, w, h) {
const canv = document.createElement('canvas');
canv.width = w;
canv.height = h;
const ctx = canv.getContext('2d');
ctx.fillStyle = bg;
ctx.fillRect(0, 0, w, h); // 绘制矩形(面的背景色)
ctx.font = font;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = color;
ctx.fillText(txt, w/2, h/2); // 绘制文本
return canv;
};
</pre></div>
<p>参数解释:</p>
<blockquote>
1. txt : 输出的文本,文本型<br>
2. font : 字体设置,字符型,例如,<mark>'bold 20px "宋体"'</mark><br>
3. color : 文本颜色,字符型<br>
4. bg : 背景色,字符型<br>
5. w : 宽度,数值型<br>
6. h : 高度,数值型
</blockquote>
<p>可以根据需要修改绘制文本的函数。这是离线创建的 canvas 标签,它没有添加到文档中,不会显示出来。函数返回(return)所创建的 canvas 画布 canv,所以,下一步,以其返回值创建 ThreeJS 纹理:</p>
<div class="hEdiv"><pre class="hEpre">
const texture = new THREE.CanvasTexture(
mkText(
'文字', // 1. 输出的文本
'bold 24px "微软雅黑"', // 2. 字体设置
'white', // 3. 前景色
'purple', // 4. 背景色
100, // 5. 宽度
50 // 6. 高度
)
);
// 代码可以写在一行
// const texture = mkText('文字', '24px "微软雅黑"', 'white', 'purple', 100, 50)
</pre></div>
<p>最后创建立由方体几何体 BoxGeometry 和六个面的基础材质 MeshBasicMaterial 构建而成的 Mesh 网格,再初始化 Mesh 的 Pose,加上旋转动画,就大功告成。下面是完整代码,可在线运行:</p>
<div class="hEdiv"><pre class="hEpre" id="pre1">
<script type="module">
import { THREE, scene, camera, renderer, clock, basic3, click3 } from 'https://638183.freep.cn/638183/3dev/3/3basic.js';
basic3();
const texts = ['壹', '贰', '叁', '肆', '伍', '陆'];
let faces = [];
texts.forEach(text => {
const texture = new THREE.CanvasTexture(mkText(text, 'bold 24px "微软雅黑"', 'white', 'purple', 100, 50));
faces.push(new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff, map: texture}));
});
const mesh = new THREE.Mesh(new THREE.BoxGeometry(), faces);
mesh.rotateY(Math.PI / 4);
scene.add(mesh);
animate();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mesh.rotation.x -= delta / 2;
renderer.render(scene, camera);
};
function mkText(txt, font, color, bg, w, h) {
const canv = document.createElement('canvas');
canv.width = w;
canv.height = h;
const ctx = canv.getContext('2d');
ctx.fillStyle = bg;
ctx.fillRect(0, 0, w, h);
ctx.font = font;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = color;
ctx.fillText(txt, w/2, h/2);
return canv;
};
</script>
</pre></div>
<blockquote><button id="btnPrev1">运行代码</button></blockquote>
</div>
<script type="module">
import hlight from 'https://638183.freep.cn/638183/web/helight/helight1.js';
const pres = document.querySelectorAll('.hEpre');
const divs = document.querySelectorAll('.hEdiv');
divs.forEach( (div, key) => hlight.hl(div, pres));
const preView = (htmlCode, targetBox) => {
if (targetBox.innerHTML) return;
const iframe = document.createElement('iframe');
htmlCode = htmlCode + '<style>body {margin: 0; }</style>';
iframe.srcdoc = htmlCode;
targetBox.appendChild(iframe);
targetBox.style.display = 'block';
targetBox.onclick = () => {
targetBox.innerHTML = '';
targetBox.style.display = 'none';
}
};
const value1 = pre1.textContent;
btnPrev1.onclick = () => preView(value1, prevBox);
</script> 这个还是要写画布上绘制文本的函数啊,以为有mkText(text, 'bold 24px "微软雅黑"', 'white', 'purple', 100, 50)就直接调用,不用再写上那个函数呢。 依次弄上1-6的数字,去看了呈现出来的,连续的数字是在对立面上的,如1和2是对面的,3和4也是的。 红影 发表于 2025-6-18 21:25
依次弄上1-6的数字,去看了呈现出来的,连续的数字是在对立面上的,如1和2是对面的,3和4也是的。
额,你可以用阿拉伯数字,这样可以看得更清楚一些。然后,立方体的面,它们是有顺序的,之前说过。 红影 发表于 2025-6-18 21:24
这个还是要写画布上绘制文本的函数啊,以为有mkText(text, 'bold 24px "微软雅黑"', 'white', 'purple', 10 ...
CanvasTexture 类接收构造参数的第一个是 canvas 画布,画布上得有图案(比如文本),所以,先得在一个离线的画布上绘制,然后应用到纹理构造函数 CanvasTexture 中。
立方体有六个面,需要画六次,复用频率高,应给此功能编写一个函数。这样的函数 JS 不会内置,ThreeJS 也不会提供,需要自己弄。 谢谢马老师经典讲授,ThreeJS立方体各面终于可以加文字了{:4_190:} 看到了立方体上面的字。 杨帆 发表于 2025-6-18 23:52
谢谢马老师经典讲授,ThreeJS立方体各面终于可以加文字了
{:4_191:} 梦江南 发表于 2025-6-19 08:11
看到了立方体上面的字。
{:4_190:} 马黑黑 发表于 2025-6-18 23:01
CanvasTexture 类接收构造参数的第一个是 canvas 画布,画布上得有图案(比如文本),所以,先得在一个离 ...
画布绘制后当纹理应用,这个巧思简直太棒了。。
自己编写函数实现构思,思维和技术都是无敌的存在。。
大赞。。{:4_199:} 加了汉字的立方体更适合各种贴子使用,
设计的这个旋转刚好可以把六个面都看到,{:4_173:}不用让它乱转了 马黑黑 发表于 2025-6-18 22:57
额,你可以用阿拉伯数字,这样可以看得更清楚一些。然后,立方体的面,它们是有顺序的,之前说过。
是的,这些面是有顺序的,加了文字,更加感受到了。 马黑黑 发表于 2025-6-18 23:01
CanvasTexture 类接收构造参数的第一个是 canvas 画布,画布上得有图案(比如文本),所以,先得在一个离 ...
原来这个函数是用来绘制画布上的内容,然后给ThreeJS的纹理用的。知道了{:4_187:} 红影 发表于 2025-6-19 21:19
原来这个函数是用来绘制画布上的内容,然后给ThreeJS的纹理用的。知道了
对,就这么简单 红影 发表于 2025-6-19 21:14
是的,这些面是有顺序的,加了文字,更加感受到了。
更直观一点 花飞飞 发表于 2025-6-19 19:05
加了汉字的立方体更适合各种贴子使用,
设计的这个旋转刚好可以把六个面都看到,不用让它乱转了
可以随意设置转动方向 花飞飞 发表于 2025-6-19 18:59
画布绘制后当纹理应用,这个巧思简直太棒了。。
自己编写函数实现构思,思维和技术都是无敌的存在。。
...
谢大赞 马黑黑 发表于 2025-6-20 16:44
对,就这么简单
也不简单呢,是黑黑设计好的,才边简单了。 马黑黑 发表于 2025-6-20 16:45
更直观一点
这样的顺序其实挺出乎意料的。 红影 发表于 2025-6-20 20:36
这样的顺序其实挺出乎意料的。
这是规定好的