【图片完整效果代码位于文章末】
往期文章:
前言
在三维场景中,我们经常需要添加文字对场景物体等进行标注说明,本文将介绍如何添加三维场景的文字,并实现文字始终面向屏幕的效果。
1.准备工作
1.1引入必要的库
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
1.2准备字体文件
由于threejs添加文字必须要一个字体文件且没有默认提供字体文件,所以在使用时,需要准备一个字体文件。示例中的文件下载地址:点击下载字体,如果链接失效可以私信。
大家也可以使用facetype.js进行自定义文字转换,支持.ttf格式的字体文件,链接如下:
http://gero3.github.io/facetype.js/
本文使用的是华为开源免费字体HONOR_Sans_CN_Regular,具体字体资源可以私信获取。
2.创建文字
首先创建字体加载器loader,并进行字体资源加载。回调函数中设定字体的样式,下面的函数创建了一个如图所示的白色半透明3d文字。
const loader = new FontLoader() // 创建字体加载器
function addText() {
loader.load(
// font资源URL
'./font/HONOR_Sans_CN_Regular.json',
// onLoad回调
function (font) {
const geometry = new TextGeometry('测试文字', {
font: font,
size: 0.3, // 字体大小
height: 0.1, // 挤出文本的厚度
})
geometry.center() // 居中文本
const materials = new THREE.MeshBasicMaterial({
color: 0xffffffff,
transparent: true,
opacity: 0.5,
})
const textMesh = new THREE.Mesh(geometry, materials)
textMesh.position.set(0,0,0)
scene.add(textMesh)
}
)
}
字体其他可选的属性:
curveSegments:控制文本曲线的平滑度。默认值为 12,增加此值可以使文本更加平滑,但也会增加计算成本。
bevelEnabled:一个布尔值,用于指定是否启用文字的斜角。默认为 false。
bevelThickness:如果启用了斜角,此属性控制斜角的厚度。默认值为 6。
bevelSize:如果启用了斜角,此属性控制斜角的尺寸。默认值为 4。
bevelSegments:如果启用了斜角,此属性控制斜角的分段数。默认值为 3。
需要特别注意的是,代码中用到了一个geometry.center()方法,它的作用是把文字居中,如果没有这个,文字加载时会以左下角为中心。大家可以根据自己的需求进行设置。
3.让文字作为标注始终面向屏幕
在往期文章中,我们经常用到两个方块,下面的代码我们也将用两个方块进行文字标注的演示
1.添加两个方块
<template>
</template>
<script setup>
import * as THREE from 'three'
import { onMounted, ref } from 'vue'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader'
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry'
const msg = ref('')
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
const renderer = new THREE.WebGLRenderer({ antialias: true })
const controls = new OrbitControls(camera, renderer.domElement)
const loader = new FontLoader() // 创建字体加载器
onMounted(() => {
init()
})
function init() {
camera.position.set(0, 0, 5)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material1 = new THREE.MeshBasicMaterial({
color: 0xff00a2d7,
transparent: true,
opacity: 0.5,
})
const material2 = new THREE.MeshBasicMaterial({
color: 0xffd3e3fd,
transparent: true,
opacity: 0.5,
})
const cube1 = new THREE.Mesh(geometry, material1)
const cube2 = new THREE.Mesh(geometry, material2)
scene.add(cube1, cube2)
cube1.position.set(0, 0, 0)
cube1.name = '方块1'
cube2.position.set(2, 0, 0)
cube2.name = '方块2'
cube1.position.x = -2
controls.update()
function animate() {
requestAnimationFrame(animate)
controls.update()
cube1.rotation.y += 0.01
cube2.rotation.y -= 0.01
renderer.render(scene, camera)
}
animate()
addEachText()
}
</script>
2.为场景中的每个方块添加文字标注
我们为每个方块添加了名字,可以根据名字进行筛选。大家也可以根据其他的信息,例如给物体添加userData自定义信息等方法进行筛选。
// 为每个物体添加文字标注
function addEachText() {
// 遍历场景中的所有物体
scene.children.forEach((child) => {
if (child.name.includes('方块')) {
addText(child)
}
})
}
// 为指定物体添加文字标注
function addText(obj) {
loader.load(
// font资源URL
'./font/HONOR_Sans_CN_Regular.json',
// onLoad回调
function (font) {
const geometry = new TextGeometry(obj.name, {
font: font,
size: 0.3, // 字体大小
height: 0.1, // 挤出文本的厚度
})
geometry.center() // 居中文本
const materials = new THREE.MeshBasicMaterial({
color: 0xffffffff,
transparent: true,
opacity: 0.5,
})
const textMesh = new THREE.Mesh(geometry, materials)
textMesh.position.copy(obj.position)
textMesh.position.y = 1.2
scene.add(textMesh)
}
)
}
3.让文字始终面向屏幕
我们需要创建一个循环渲染的函数。使用lookAt()让文字一直面向相机的位置。
function animate() {
requestAnimationFrame(animate)
textMesh.lookAt(camera.position)
}
animate()
4.完整效果代码如下所示
<template>
<div
style="
font-size: 24px;
color: #ffffff;
text-align: center;
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
"
>
{{ msg }}
</div>
</template>
<script setup>
import * as THREE from 'three'
import { onMounted, ref } from 'vue'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader'
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry'
const msg = ref('')
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
const renderer = new THREE.WebGLRenderer({ antialias: true })
const controls = new OrbitControls(camera, renderer.domElement)
const loader = new FontLoader() // 创建字体加载器
onMounted(() => {
init()
})
function init() {
camera.position.set(0, 0, 5)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material1 = new THREE.MeshBasicMaterial({
color: 0xff00a2d7,
transparent: true,
opacity: 0.5,
})
const material2 = new THREE.MeshBasicMaterial({
color: 0xffd3e3fd,
transparent: true,
opacity: 0.5,
})
const cube1 = new THREE.Mesh(geometry, material1)
const cube2 = new THREE.Mesh(geometry, material2)
scene.add(cube1, cube2)
cube1.position.set(0, 0, 0)
cube1.name = '方块1'
cube2.position.set(2, 0, 0)
cube2.name = '方块2'
cube1.position.x = -2
controls.update()
function animate() {
requestAnimationFrame(animate)
controls.update()
cube1.rotation.y += 0.01
cube2.rotation.y -= 0.01
renderer.render(scene, camera)
}
animate()
addEachText()
}
// 为每个物体添加文字标注
function addEachText() {
// 遍历场景中的所有物体
scene.children.forEach((child) => {
if (child.name.includes('方块')) {
addText(child)
}
})
console.log('添加文字', scene)
}
// 为指定物体添加文字标注
function addText(obj) {
loader.load(
'./font/HONOR_Sans_CN_Regular.json',
function (font) {
const geometry = new TextGeometry(obj.name, {
font: font,
size: 0.3, // 字体大小
height: 0.1, // 挤出文本的厚度
})
geometry.center() // 居中文本
const materials = new THREE.MeshBasicMaterial({
color: 0xffffffff,
transparent: true,
opacity: 0.5,
})
const textMesh = new THREE.Mesh(geometry, materials)
textMesh.position.copy(obj.position)
textMesh.position.y = 1.2
scene.add(textMesh)
// 可选:在渲染循环中保持文字面向摄像机
function animate() {
requestAnimationFrame(animate)
textMesh.lookAt(camera.position)
}
animate()
}
)
}
</script>
文章如有技术相关错误请各位批评指正