在three.js中动态创建二维文本

Posted

技术标签:

【中文标题】在three.js中动态创建二维文本【英文标题】:Dynamically create 2D text in three.js 【发布时间】:2013-02-21 08:00:55 【问题描述】:

我有在 three.js 中创建的 3D 模型。根据一些数据,我想创建一组由小文本标签装饰的箭头。这些标签应该是二维的。

似乎我有两种选择:要么使用单独的画布元素创建纹理,然后在 3D 模型中使用该纹理,或者在 3D 模型的画布元素顶部使用 html

我想知道如何解决这个问题。执行此操作的“正确”方法是哪种?非常欢迎任何建议和示例代码!

【问题讨论】:

【参考方案1】:

如果您不介意文本始终位于顶部(例如,如果对象被其他东西挡住了,它的文本标签仍然可见并且位于其他所有内容的顶部),并且文本不会位于受到任何渲染(如灯光/阴影等)的影响,那么 HTML 是最简单的方法。

这里是一些示例代码:

var text2 = document.createElement('div');
text2.style.position = 'absolute';
//text2.style.zIndex = 1;    // if you still don't see the label, try uncommenting this
text2.style.width = 100;
text2.style.height = 100;
text2.style.backgroundColor = "blue";
text2.innerHTML = "hi there!";
text2.style.top = 200 + 'px';
text2.style.left = 200 + 'px';
document.body.appendChild(text2);

将 style.top 和 style.left 变量中的 200 替换为您希望放置文本的 y 和 x 坐标(分别)。它们将是正值,其中 (0,0) 是画布的左上角。有关如何从画布中的 3D 点投影到浏览器窗口中以像素为单位的 2D 点的一些指导,请使用如下代码 sn-p:

function toXYCoords (pos) 
        var vector = projector.projectVector(pos.clone(), camera);
        vector.x = (vector.x + 1)/2 * window.innerWidth;
        vector.y = -(vector.y - 1)/2 * window.innerHeight;
        return vector;

确保您事先调用了 camera.updateMatrixWorld()。

【讨论】:

感谢您的回答!我自己在投影坐标时遇到了一些问题,但结果是我在投影矢量之前忘了做一个 camera.updateMatrixWorld() 。结果是无法使用的值远离屏幕。 嘿,谢谢你的回答,我在三个 r79 中试过,它返回这个错误projector is not defined,有解决方法吗?谢谢 当我移动物体或旋转相机时,我必须手动更新文本位置(?)有什么方法可以让这个更新自动进行吗?例如,如果我想在场景中每个对象的顶部制作 2D 标签,同时一切都在移动。 Prokop Hapala——听起来最好的办法是让标签成为 3D 场景的一部分;请参阅下面@LeeStemkoski 的解决方案 @kronuus 嗨,你能看看我的问题吗? ***.com/questions/61631545/…【参考方案2】:

查看demo

TextGeometry 消耗大量内存,您需要加载一个字体,其中包含您将使用的每个字母的几何图形。如果场景中有超过 100 个文本网格,我的浏览器就会崩溃。

您可以在画布上绘制文本并通过Sprite 将其包含在您的场景中。它的问题是您需要计算字体大小。如果你有一个移动的相机,那么你需要定期计算字体大小。否则,如果您使用相机离文字太近,文字会变得模糊。

我编写了一个类TextSprite,它会根据到相机的距离和渲染器元素的大小自动计算最佳字体大小。诀窍是使用Object3D 类的回调.onBeforeRender,它接收相机和渲染器。

let sprite = new THREE.TextSprite(
  text: 'Hello World!',
  fontFamily: 'Arial, Helvetica, sans-serif',
  fontSize: 12,
  color: '#ffbbff',
);
scene.add(sprite);

您还可以随时更改文本和字体。

sprite.text = 'Hello Stack Overflow!';
sprite.fontFamily = 'Tahoma, Geneva, sans-serif';
sprite.fontSize = 24;

【讨论】:

嗯。我没有这样做,而是使用从相机投影的方法来查找 html 元素的位置,然后使用绝对定位进行定位。不过,我有点希望我用过这个。好工作! :D 想提一下,如果您要渲染数百个 TextSprite,这个解决方案并不令人惊奇。性能受到影响 @SeregPie 你好,请看一下我的问题***.com/questions/61631545/… 尝试了演示,但屏幕上看不到任何内容(Firefox 83、32 位)。 @steveOw 哦,谢谢。我现在修好了。有 AFrame 更新,unpkg 无法解析包。【参考方案3】:

如果您确实想将画布对象中的文本(或任何图像)作为 3D 场景的一部分,请查看以下示例代码: http://stemkoski.github.com/Three.js/Texture-From-Canvas.html

【讨论】:

我第一次解决这个问题时使用了你的例子 :) 非常感谢你提供的精彩教程!不过我想知道,为什么使用 canvas 方法会导致文本模糊(至少它在我的机器上呈现的那样)? 也许是抗锯齿的产物? 另外我相信这使用了 Three.js 中不再包含的一些功能 出现模糊文本的原因(主要)是因为three.js 将精灵渲染为正方形,即使画布可能比它的高度更宽。如果将精灵缩放到与画布相同的比例,它应该看起来更好。【参考方案4】:

更新:此方法现已弃用,占用大量 CPU,请勿使用。

我发现了另一个只有基于三个.js 的解决方案,

var textShapes = THREE.FontUtils.generateShapes( text, options );
var text = new THREE.ShapeGeometry( textShapes );
var textMesh = new THREE.Mesh( text, new THREE.MeshBasicMaterial(  color: 0xff0000  ) ) ;
scene.add(textMesh);
// Example text options : 'font' : 'helvetiker','weight' : 'normal', 'style' : 'normal','size' : 100,'curveSegments' : 300;

现在动态编辑此文本,

var textShapes = THREE.FontUtils.generateShapes( text, options );
var text = new THREE.ShapeGeometry( textShapes );
textMesh.geometry = text;
textMesh.geometry.needsUpdate = true;

【讨论】:

应该注意,虽然这是一种用于创建 2D 文本的非常昂贵的方法。如果您正在考虑显示标准 2D 文本,则在画布中使用 HTML 更为明智。 您在定义之前使用文本。请检查变量名称。 我认为现在不赞成使用 TextShape【参考方案5】:

我在 NPM 上发布了一个模块,可以为您做到这一点:https://github.com/gamestdio/three-text2d

它允许您拥有带有渲染文本的SpriteMesh,而无需手动处理画布。

示例:

var Text2D = require('three-text2d').Text2D

var text = new Text2D("Hello world!",  font: '30px Arial', fillStyle: '#000000', antialias: true )
scene.add(text) 

【讨论】:

您能举例说明如何使用您的模块吗?我刚开始使用 ThreeJS,所以我只有 ThreeJS hello-world 示例。我相信我不能在里面 require('three-text2d') ,所以我应该使用像 browserify 这样的包来用 ThreeJS 编译你的包吗?谢谢!【参考方案6】:

您可以创建一个 2d 画布并使用 css 将其定位到 webgl 画布的顶部。然后您可以使用二维画布上下文提供的ctx.fillText(text,x,y)ctx.strokeText(text,x,y) 方法在其上绘制文本。使用此方法,您可以绘制文本以外的其他内容,例如箭头和米。

您可以使用@kronuus 建议的方法将点从3d 空间转换为2d 空间。

【讨论】:

这比我的threejs画布上的HTML元素表现更好吗?【参考方案7】:

@LeeStemkoski 的回复非常有用。然而,需要对代码进行轻微更改以便文本跟随您的箭头,即以防箭头移动、旋转等。

查看下一段代码

                        var canvas1 = document.createElement('canvas');
                        var context1 = canvas1.getContext('2d');
                        context1.font = "Bold 10px Arial";
                        context1.fillStyle = "rgba(255,0,0,1)";
                        context1.fillText('Hello, world!', 0, 60);

                        // canvas contents will be used for a texture
                        var texture1 = new THREE.Texture(canvas1)
                        texture1.needsUpdate = true;

                        var material1 = new THREE.MeshBasicMaterial( map: texture1, side: THREE.DoubleSide );
                        material1.transparent = true;

                        var mesh1 = new THREE.Mesh(
                            new THREE.PlaneGeometry(50, 10),
                            material1
                          );
                        mesh1.position.set(25, 5, -5);
                        mesh1.rotation.x = -0.9;
                        shape.add(mesh1);
                        // Note that mesh1 gets added to the shape and not to the scene

                       scene.add(shape)

如您所见,诀窍是将文本 (mesh1)添加到 shape(您的“箭头”)而不是将其添加到场景中强>。

我希望这会有所帮助。

【讨论】:

【参考方案8】:

来自三个js的“几何/文字/形状”: https://threejs.org/examples/?q=text#webgl_geometry_text_shapes

我简化了代码,只需要添加一个 2D 动态文本:

var loader = new THREE.FontLoader();
loader.load( 'assets/fonts/helvetiker_regular.typeface.json', function ( font )    
    var textPositions = [[1, 1, 1],
                         [2, 2, 2]];

    var textMessages = ['Text 1', 'Text 2'];

    var textSizes = [0.1, 0.2];

    var textName = ['text1', 'text2'];

    var textsNumber = textPositions.length;     

    for (var i = 0; i < textsNumber; i++) 

“generateShapes”方法允许创建形状文本

        var textsShapes = font.generateShapes( textMessages[i], textSizes[i] );
        var textsGeometry = new THREE.ShapeBufferGeometry( textsShapes );    
        var textsMaterial = new THREE.MeshBasicMaterial(color: 0xeeeeee);

        var text = new THREE.Mesh(textsGeometry, textsMaterial);
        text.position.set(textPositions[i][0], textPositions[i][1], textPositions[i][2]);
        text.name = textName[i]; 

        scene.add(text);
    

); 

【讨论】:

以上是关于在three.js中动态创建二维文本的主要内容,如果未能解决你的问题,请参考以下文章

使用vue学习three.js之加载和使用纹理-使用CubeCamera创建反光效果,动态环境贴图实现,立方体全景贴图

three.js 粒子特效

javascript 3d网页 简单几行代码创建一个动态水潭, 湖面 示例 ( three.js r114 初探 四)

Three.js地球开发—3D经纬度等比地图,3D飞行航线最终效果

Three.js地球开发—3D经纬度等比地图,3D飞行航线最终效果

使用vue学习three.js之加载和使用纹理-使用canvas画布上的绘画作为纹理渲染到方块上,使用动态绘画纹理