Динамически создавать 2D текст в three.js

22

У меня есть 3D-модель, которую я создал в three.js. Основываясь на некоторых данных, я хочу создать набор стрелок, которые украшены небольшой текстовой меткой. Эти метки должны быть в 2D.

Похоже, у меня есть две альтернативы: либо использовать отдельный элемент canvas для создания текстуры, которая в свою очередь используется в 3D-модели, либо использовать HTML поверх элемента холста 3D-модели.

Мне интересно, как это сделать. Каков "правильный" способ сделать это? Любые предложения и примерный код очень приветствуются!

Теги:
three.js

7 ответов

33
Лучший ответ

Если вы не возражаете, что текст всегда будет сверху (например, если объект заблокирован чем-то другим, его текстовая метка все еще будет видна и поверх всего остального) и что текст не будет под воздействием какого-либо рендеринга, такого как lights/shadow и т.д., тогда HTML - это самый простой способ пойти imho.

Вот пример кода:

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);

Замените 200 в переменных style.top и style.left для координат y и x (соответственно), которые вы хотите поместить в текст. Они будут положительными, где (0,0) - верхний левый холст. Для некоторых рекомендаций о том, как проектировать из трехмерной точки вашего холста в 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().

  • 0
    Спасибо за ответ! У меня были некоторые проблемы с проецированием координат самостоятельно, но оказалось, что я забыл сделать camera.updateMatrixWorld () перед проекцией вектора. Результатом стали непригодные значения за пределами экрана.
  • 0
    Эй, спасибо за этот ответ, я попробовал его в Three r79, и он возвращает эту ошибку projector is not defined , есть ли обходной путь это? Спасибо
Показать ещё 2 комментария
17

Если вы действительно хотите включить текст (или любое изображение) из объекта canvas как часть вашей 3D-сцены, проверьте код образца по адресу: http://stemkoski.github.com/Three.js/Texture-From-Canvas.html

  • 0
    Я использовал ваш пример, чтобы понять это в первый раз :) Большое спасибо за отличные уроки! Однако мне интересно, почему при использовании метода canvas получается размытый текст (по крайней мере, он отображается так на моем компьютере)?
  • 0
    Возможно, это артефакт сглаживания?
Показать ещё 2 комментария
8

Обновить. Этот метод устарел и сильно загружен процессором. Не используйте его.

Я узнал еще одно решение на основе трёх .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;
  • 0
    Можете ли вы отредактировать это в рабочем примере?
  • 3
    Следует отметить, что это очень дорогой метод для создания 2D-текста. Использование HTML на холсте более разумно, если вы думаете об отображении стандартного 2D-текста.
Показать ещё 3 комментария
6

Я опубликовал модуль 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) 
  • 4
    Не могли бы вы привести примеры использования вашего модуля? Я только начинаю использовать ThreeJS, поэтому у меня есть только пример с ThreeJS hello-world. Я считаю, что не могу требовать ('three-text2d') внутри него, поэтому я должен использовать такой пакет, как browserify, для компиляции вашего пакета с ThreeJS? Спасибо!
5

Вы можете создать холст 2d и использовать css, чтобы поместить его в верхнюю часть холста webgl. Затем вы можете нарисовать текст с помощью методов ctx.fillText(text,x,y) или ctx.strokeText(text,x,y), предоставленных контекстом canvas 2d. Используя этот метод, вы можете рисовать другие вещи помимо текста, например стрелки и метры.

Вы можете использовать метод, предложенный @kronuus для преобразования точки из 3d в 2d.

  • 0
    Является ли это более эффективным, чем элементы HTML, расположенные поверх моего холста Threejs?
4

Просмотрите демо.

TextGeometry потребляет много памяти, и вам нужно загрузить шрифт, который содержит геометрию для каждой буквы, которую вы будете использовать. Если у меня более 100 текстовых сеток в сцене, мой браузер отключается.

Вы можете нарисовать текст на холсте и включить его в свою сцену с помощью Sprite. Проблема заключается в том, что вам нужно рассчитать размер шрифта. Если у вас есть движущаяся камера, вам нужно периодически рассчитать размер шрифта. В противном случае текст будет размытым, если вы приблизитесь к тексту с камерой.

Я написал класс TextSprite, который автоматически вычисляет наилучший размер шрифта в зависимости от расстояния до камеры и размера элемент рендеринга. Хитрость заключается в использовании обратного вызова .onBeforeRender класса Object3D, который принимает камеру и средство визуализации.

let sprite = new THREE.TextSprite({
    textSize: 10,
    texture: {
        text: 'Hello World!',
        fontFamily: 'Arial, Helvetica, sans-serif',
    },
    material: {color: 0xffbbff},
});
scene.add(sprite);

Вы также можете изменить текст, размер текста и шрифт на лету.

sprite.textSize = 25;
sprite.material.map.text = 'Hello Stack Overflow!';
sprite.material.map.fontFamily = 'Tahoma, Geneva, sans-serif';
  • 1
    да. Вместо этого я использовал метод проецирования с камеры, чтобы найти положение HTML-элемента, который я затем позиционировал с абсолютным позиционированием. Хотелось бы, чтобы я этим воспользовался. Хорошая работа! : D
  • 0
    Как упомянуть здесь сверху, слева, справа, снизу
Показать ещё 2 комментария
1

Ответ @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 (ваша "стрелка" ) вместо добавления его в сцену.

Надеюсь, это поможет.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню