Three.js是一个强大的JavaScript库,用于创建和显示3D计算机图形。它使得在浏览器中实现复杂的3D场景变得相对简单。本文将详细介绍如何使用Three.js加载TTF(TrueType Font)格式的字体,并实现键盘输入字符的功能。我们将通过一个具体的示例来展示这一过程,并提炼出核心代码,帮助读者更好地理解和应用。
项目概述
我们将创建一个网页应用,该应用使用Three.js加载TTF格式的字体,并允许用户通过键盘输入字符。这些字符将以3D形式显示在网页上,并且可以通过鼠标拖动来旋转视角。
项目结构
首先,我们需要一个基本的HTML结构来容纳我们的Three.js场景。以下是完整的HTML代码:
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - loader - ttf</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="info"></div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { TTFLoader } from 'three/addons/loaders/TTFLoader.js';
import { Font } from 'three/addons/loaders/FontLoader.js';
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
let container;
let camera, cameraTarget, scene, renderer;
let group, textMesh1, textMesh2, textGeo, material;
let firstLetter = true;
let text = 'three.js';
const height = 20,
size = 70,
hover = 30,
curveSegments = 4,
bevelThickness = 2,
bevelSize = 1.5;
let font = null;
const mirror = true;
let targetRotation = 0;
let targetRotationOnPointerDown = 0;
let pointerX = 0;
let pointerXOnPointerDown = 0;
let windowHalfX = window.innerWidth / 2;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
// CAMERA
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 1500);
camera.position.set(0, 400, 700);
cameraTarget = new THREE.Vector3(0, 150, 0);
// SCENE
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.Fog(0x000000, 250, 1400);
// LIGHTS
const dirLight1 = new THREE.DirectionalLight(0xffffff, 0.4);
dirLight1.position.set(0, 0, 1).normalize();
scene.add(dirLight1);
const dirLight2 = new THREE.DirectionalLight(0xffffff, 2);
dirLight2.position.set(0, hover, 10).normalize();
dirLight2.color.setHSL(Math.random(), 1, 0.5, THREE.SRGBColorSpace);
scene.add(dirLight2);
material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
group = new THREE.Group();
group.position.y = 100;
scene.add(group);
const loader = new TTFLoader();
loader.load('fonts/ttf/kenpixel.ttf', function (json) {
font = new Font(json);
createText();
});
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(10000, 10000),
new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 0.5, transparent: true })
);
plane.position.y = 100;
plane.rotation.x = - Math.PI / 2;
scene.add(plane);
// RENDERER
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
// EVENTS
container.style.touchAction = 'none';
container.addEventListener('pointerdown', onPointerDown);
document.addEventListener('keypress', onDocumentKeyPress);
document.addEventListener('keydown', onDocumentKeyDown);
window.addEventListener('resize', onWindowResize);
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentKeyDown(event) {
if (firstLetter) {
firstLetter = false;
text = '';
}
const keyCode = event.keyCode;
// backspace
if (keyCode === 8) {
event.preventDefault();
text = text.substring(0, text.length - 1);
refreshText();
return false;
}
}
function onDocumentKeyPress(event) {
const keyCode = event.which;
// backspace
if (keyCode === 8) {
event.preventDefault();
} else {
const ch = String.fromCharCode(keyCode);
text += ch;
refreshText();
}
}
function createText() {
textGeo = new TextGeometry(text, {
font: font,
size: size,
height: height,
curveSegments: curveSegments,
bevelThickness: bevelThickness,
bevelSize: bevelSize,
bevelEnabled: true
});
textGeo.computeBoundingBox();
textGeo.computeVertexNormals();
const centerOffset = - 0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
textMesh1 = new THREE.Mesh(textGeo, material);
textMesh1.position.x = centerOffset;
textMesh1.position.y = hover;
textMesh1.position.z = 0;
textMesh1.rotation.x = 0;
textMesh1.rotation.y = Math.PI * 2;
group.add(textMesh1);
if (mirror) {
textMesh2 = new THREE.Mesh(textGeo, material);
textMesh2.position.x = centerOffset;
textMesh2.position.y = - hover;
textMesh2.position.z = height;
textMesh2.rotation.x = Math.PI;
textMesh2.rotation.y = Math.PI * 2;
group.add(textMesh2);
}
}
function refreshText() {
group.remove(textMesh1);
if (mirror) group.remove(textMesh2);
if (!text) return;
createText();
}
function onPointerDown(event) {
if (event.isPrimary === false) return;
pointerXOnPointerDown = event.clientX - windowHalfX;
targetRotationOnPointerDown = targetRotation;
document.addEventListener('pointermove', onPointerMove);
document.addEventListener('pointerup', onPointerUp);
}
function onPointerMove(event) {
if (event.isPrimary === false) return;
pointerX = event.clientX - windowHalfX;
targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
}
function onPointerUp() {
if (event.isPrimary === false) return;
document.removeEventListener('pointermove', onPointerMove);
document.removeEventListener('pointerup', onPointerUp);
}
function animate() {
requestAnimationFrame(animate);
group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
camera.lookAt(cameraTarget);
renderer.render(scene, camera);
}
</script>
</body>
</html>
加载TTF字体
使用TTFLoader加载TTF字体,并将其转换为Three.js可以使用的字体格式。
const loader = new TTFLoader();
loader.load('fonts/ttf/kenpixel.ttf', function (json) {
font = new Font(json);
createText();
});
创建3D文本
使用加载的字体创建3D文本,并将其添加到场景中。
function createText() {
textGeo = new TextGeometry(text, {
font: font,
size: size,
height: height,
curveSegments: curveSegments,
bevelThickness: bevelThickness,
bevelSize: bevelSize,
bevelEnabled: true
});
textGeo.computeBoundingBox();
textGeo.computeVertexNormals();
const centerOffset = - 0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
textMesh1 = new THREE.Mesh(textGeo, material);
textMesh1.position.x = centerOffset;
textMesh1.position.y = hover;
textMesh1.position.z = 0;
textMesh1.rotation.x = 0;
textMesh1.rotation.y = Math.PI * 2;
group.add(textMesh1);
if (mirror) {
textMesh2 = new THREE.Mesh(textGeo, material);
textMesh2.position.x = centerOffset;
textMesh2.position.y = - hover;
textMesh2.position.z = height;
textMesh2.rotation.x = Math.PI;
textMesh2.rotation.y = Math.PI * 2;
group.add(textMesh2);
}
}
处理键盘输入
通过监听键盘事件,实现用户输入字符并实时更新3D文本。
function onDocumentKeyDown(event) {
if (firstLetter) {
firstLetter = false;
text = '';
}
const keyCode = event.keyCode;
// backspace
if (keyCode === 8) {
event.preventDefault();
text = text.substring(0, text.length - 1);
refreshText();
return false;
}
}
function onDocumentKeyPress(event) {
const keyCode = event.which;
// backspace
if (keyCode === 8) {
event.preventDefault();
} else {
const ch = String.fromCharCode(keyCode);
text += ch;
refreshText();
}
}
function refreshText() {
group.remove(textMesh1);
if (mirror) group.remove(textMesh2);
if (!text) return;
createText();
}
更新文本
每当文本内容发生变化时,我们需要重新创建3D文本对象,并更新场景中的显示。
function refreshText() {
group.remove(textMesh1);
if (mirror) group.remove(textMesh2);
if (!text) return;
createText();
}
处理窗口大小变化
为了确保在窗口大小变化时,Three.js场景能够正确调整,我们需要监听窗口的resize
事件,并更新相机和渲染器的参数。
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
处理鼠标拖动
为了实现通过鼠标拖动来旋转视角的功能,我们需要监听鼠标的pointerdown
、pointermove
和pointerup
事件。
function onPointerDown(event) {
if (event.isPrimary === false) return;
pointerXOnPointerDown = event.clientX - windowHalfX;
targetRotationOnPointerDown = targetRotation;
document.addEventListener('pointermove', onPointerMove);
document.addEventListener('pointerup', onPointerUp);
}
function onPointerMove(event) {
if (event.isPrimary === false) return;
pointerX = event.clientX - windowHalfX;
targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
}
function onPointerUp() {
if (event.isPrimary === false) return;
document.removeEventListener('pointermove', onPointerMove);
document.removeEventListener('pointerup', onPointerUp);
}
动画和渲染
通过requestAnimationFrame实现动画循环,并在每一帧中更新场景和渲染器。
function animate() {
requestAnimationFrame(animate);
group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
camera.lookAt(cameraTarget);
renderer.render(scene, camera);
}
总结
通过本文的介绍,我们详细讲解了如何使用Three.js加载TTF字体并实现键盘输入字符的功能。我们从初始化Three.js场景开始,逐步讲解了加载TTF字体、创建3D文本、处理键盘输入、更新文本、处理窗口大小变化和鼠标拖动等核心步骤。希望这些内容能够帮助你更好地理解和应用Three.js进行3D文本的展示和交互。