马黑黑 发表于 2025-6-18 20:46

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">
&lt;script type="module"&gt;
        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 =&gt; {
                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;
        };
&lt;/script&gt;
        </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>

红影 发表于 2025-6-18 21:24

这个还是要写画布上绘制文本的函数啊,以为有mkText(text, 'bold 24px "微软雅黑"', 'white', 'purple', 100, 50)就直接调用,不用再写上那个函数呢。

红影 发表于 2025-6-18 21:25

依次弄上1-6的数字,去看了呈现出来的,连续的数字是在对立面上的,如1和2是对面的,3和4也是的。

马黑黑 发表于 2025-6-18 22:57

红影 发表于 2025-6-18 21:25
依次弄上1-6的数字,去看了呈现出来的,连续的数字是在对立面上的,如1和2是对面的,3和4也是的。

额,你可以用阿拉伯数字,这样可以看得更清楚一些。然后,立方体的面,它们是有顺序的,之前说过。

马黑黑 发表于 2025-6-18 23:01

红影 发表于 2025-6-18 21:24
这个还是要写画布上绘制文本的函数啊,以为有mkText(text, 'bold 24px "微软雅黑"', 'white', 'purple', 10 ...
CanvasTexture 类接收构造参数的第一个是 canvas 画布,画布上得有图案(比如文本),所以,先得在一个离线的画布上绘制,然后应用到纹理构造函数 CanvasTexture 中。

立方体有六个面,需要画六次,复用频率高,应给此功能编写一个函数。这样的函数 JS 不会内置,ThreeJS 也不会提供,需要自己弄。

杨帆 发表于 2025-6-18 23:52

谢谢马老师经典讲授,ThreeJS立方体各面终于可以加文字了{:4_190:}

梦江南 发表于 2025-6-19 08:11

看到了立方体上面的字。

马黑黑 发表于 2025-6-19 14:45

杨帆 发表于 2025-6-18 23:52
谢谢马老师经典讲授,ThreeJS立方体各面终于可以加文字了

{:4_191:}

马黑黑 发表于 2025-6-19 14:45

梦江南 发表于 2025-6-19 08:11
看到了立方体上面的字。

{:4_190:}

花飞飞 发表于 2025-6-19 18:59

马黑黑 发表于 2025-6-18 23:01
CanvasTexture 类接收构造参数的第一个是 canvas 画布,画布上得有图案(比如文本),所以,先得在一个离 ...

画布绘制后当纹理应用,这个巧思简直太棒了。。
自己编写函数实现构思,思维和技术都是无敌的存在。。
大赞。。{:4_199:}

花飞飞 发表于 2025-6-19 19:05

加了汉字的立方体更适合各种贴子使用,
设计的这个旋转刚好可以把六个面都看到,{:4_173:}不用让它乱转了

红影 发表于 2025-6-19 21:14

马黑黑 发表于 2025-6-18 22:57
额,你可以用阿拉伯数字,这样可以看得更清楚一些。然后,立方体的面,它们是有顺序的,之前说过。

是的,这些面是有顺序的,加了文字,更加感受到了。

红影 发表于 2025-6-19 21:19

马黑黑 发表于 2025-6-18 23:01
CanvasTexture 类接收构造参数的第一个是 canvas 画布,画布上得有图案(比如文本),所以,先得在一个离 ...

原来这个函数是用来绘制画布上的内容,然后给ThreeJS的纹理用的。知道了{:4_187:}

马黑黑 发表于 2025-6-20 16:44

红影 发表于 2025-6-19 21:19
原来这个函数是用来绘制画布上的内容,然后给ThreeJS的纹理用的。知道了

对,就这么简单

马黑黑 发表于 2025-6-20 16:45

红影 发表于 2025-6-19 21:14
是的,这些面是有顺序的,加了文字,更加感受到了。

更直观一点

马黑黑 发表于 2025-6-20 16:45

花飞飞 发表于 2025-6-19 19:05
加了汉字的立方体更适合各种贴子使用,
设计的这个旋转刚好可以把六个面都看到,不用让它乱转了

可以随意设置转动方向

马黑黑 发表于 2025-6-20 16:45

花飞飞 发表于 2025-6-19 18:59
画布绘制后当纹理应用,这个巧思简直太棒了。。
自己编写函数实现构思,思维和技术都是无敌的存在。。
...

谢大赞

红影 发表于 2025-6-20 20:35

马黑黑 发表于 2025-6-20 16:44
对,就这么简单

也不简单呢,是黑黑设计好的,才边简单了。

红影 发表于 2025-6-20 20:36

马黑黑 发表于 2025-6-20 16:45
更直观一点

这样的顺序其实挺出乎意料的。

马黑黑 发表于 2025-6-20 22:00

红影 发表于 2025-6-20 20:36
这样的顺序其实挺出乎意料的。

这是规定好的
页: [1] 2 3 4 5 6 7
查看完整版本: ThreeJS :给立方体各面加上文字