我有在 three.js 中创建的 3D 模型。根据一些数据,我想创建一组由小文本标签装饰的箭头。这些标签应该是二维的。
似乎我有两种选择:要么使用单独的画布元素创建纹理,然后在 3D 模型中使用该纹理,或者在 3D 模型的画布元素顶部使用 HTML。
我想知道该怎么做。执行此操作的“正确”方法是哪种?非常欢迎任何建议和示例代码!
我有在 three.js 中创建的 3D 模型。根据一些数据,我想创建一组由小文本标签装饰的箭头。这些标签应该是二维的。
似乎我有两种选择:要么使用单独的画布元素创建纹理,然后在 3D 模型中使用该纹理,或者在 3D 模型的画布元素顶部使用 HTML。
我想知道该怎么做。执行此操作的“正确”方法是哪种?非常欢迎任何建议和示例代码!
如果您不介意文本始终位于顶部(例如,如果对象被其他东西挡住,它的文本标签仍然可见并且位于其他所有内容之上),并且文本不会受到任何影响像灯光/阴影等渲染,那么 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 点的一些指导,请使用如下代码片段:
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()。
查看 演示。
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;
如果您确实想将画布对象中的文本(或任何图像)作为 3D 场景的一部分,请查看以下示例代码: http://stemkoski.github.com/Three.js/Texture-From-Canvas。 html
更新:此方法现在已弃用并且 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;
我在 NPM 上发布了一个模块,可以为你做到这一点:https ://github.com/gamestdio/three-text2d
它允许您在不手动处理画布的情况下拥有Sprite
或呈现文本。Mesh
例子:
var Text2D = require('three-text2d').Text2D
var text = new Text2D("Hello world!", { font: '30px Arial', fillStyle: '#000000', antialias: true })
scene.add(text)
您可以创建一个 2d 画布并使用 css 将其定位到 webgl 画布的顶部。然后,您可以使用 2d 画布上下文提供的ctx.fillText(text,x,y)
或ctx.strokeText(text,x,y)
方法在其上绘制文本。通过使用这种方法,您可以绘制除文本之外的其他内容,例如箭头和米。
您可以使用@kronuus 建议的方法将点从 3d 转换为 2d 空间。
@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
(您的“箭头”)而不是将其添加到场景中。
我希望这有帮助。
来自三个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);
}
});