当在Web上进行任何3D操作时,开发人员脑海中出现的第一个库是Three.js。Three.js提供高级抽象,以便在浏览器中使用WebGL绘制3D图形。我最近一直在使用它进行3D文本渲染,并意识到使用自定义字体是一件痛苦的事情,特别是当你想要一种简单的方法来渲染来自CDN的自定义字体时,比如Google Fonts,Fontsource。网上关于这个主题的文档很少,我希望这篇文章有助于填补这一空白。
3D文本渲染的元素
让我们来看看在Three.js中渲染文本时涉及的不同API。
TextGeometry
Three.js公开了一个 TextGeometry
类,专门用于为文本创建底层几何图形。把"几何体"看作是一组顶点、边、面--基本上就是物体的结构。 TextGeometry
有各种选项来自定义字体,大小,厚度,斜角等。
js
const loader = new FontLoader(); loader.load('fonts/helvetiker_regular.typeface.json',
function (font) {
const geometry = new TextGeometry('Hello three.js!',
{ font: font, size: 80, height: 5, // other options... });
});
FontLoader
注意到上面代码片段中的 FontLoader
类了吗?它负责创建一个 FontLoader
实例,该实例处理加载自定义字体的问题,这些自定义字体可由 TextGeometry
创建该字体中的文本。
FontLoader
实例的 load()
方法接受一个指向字体文件的文件路径/ URL和一个回调函数,该函数将使用加载的字体数据进行调用。这个字体数据可以被传递给 TextGeometry
的构造函数,用于创建该字体的文本几何体。
Mesh
和 Material
文本几何体不能按原样渲染,必须首先将其转换为 Mesh
对象。 Mesh
保存该对象的几何体定义和应用于该几何体的材质。在下面的示例中,我将 MeshStandardMaterial
分配给Text对象。您可以阅读更多关于可以在材料中设置的选项的信息,请参阅与之链接(MeshStandardMaterial -- three.js docs (threejs.org))的文档。
- index.js
js
import * as THREE from 'three';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
import init from './setup';
const scene = init();
const loader = new FontLoader();
// Loading the JSON font file from CDN. Can be a file path too.
loader.load('https://unpkg.com/three@0.77.0/examples/fonts/helvetiker_regular.typeface.json', (font) => {
// Create the text geometry
const textGeometry = new TextGeometry('Hello world', {
font: font,
size: 18,
height: 5,
curveSegments: 32,
bevelEnabled: true,
bevelThickness: 0.5,
bevelSize: 0.5,
bevelSegments: 8,
});
// Create a standard material with red color and 50% gloss
const material = new THREE.MeshStandardMaterial({
color: 'hotpink',
roughness: 0.5
});
// Geometries are attached to meshes so that they get rendered
const textMesh = new THREE.Mesh(textGeometry, material);
// Update positioning of the text
textMesh.position.set(-50, 0, 0);
scene.add(textMesh);
});
- setupjs
js
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
export default function init() {
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 70);
const renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.55);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 0.5);
pointLight.position.set(10, 10, 20);
scene.add(pointLight);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 50;
controls.maxDistance = 500;
controls.maxPolarAngle = Math.PI / 2;
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
return scene;
}
在codesandbox 查看最终的效果
似乎很简单,直到你注意到字体文件路径中的一些奇怪的东西- 'fonts/helvetiker_regular.typeface.json'
。看到.json扩展名了吗?很奇怪吧?我们大多数人都熟悉标准字体文件格式,如TTF,WOFF,WOFF 2等,但 FontLoader
只理解.json格式的字体文件!我的下一个问题是-我如何将我现有的字体文件转换为上面提到的标准字体文件格式之一,以JSON格式支持的 FontLoader
?
Facetype.js
FontLoader
的文档谈到了一个名为[facetype.js](Facetype.js (gero3.github.io))的工具,该工具可用于将字体文件转换为JSON格式。该工具要求您选择字体文件,并将其转换为JSON或JS文件。然后,JSON文件可以加载到Three.js中。
该项目的[GitHub repo](gero3/facetype.js: typeface.js generator (github.com))表明它没有发布为可以在其他项目中使用的库。如果我们想从Google Fonts动态加载字体,这给我们留下了两个选择:
-
从Google Fonts下载所有字体源文件(TTF格式),使用facetype.js工具将其转换为静态JSON格式,然后将其包含在项目的源代码中并加载到Three.js中。这是不可扩展的,因为Google Fonts库正在不断扩展,每种字体都有不同的字体粗细(正常,半粗体,粗体等)和字体样式(正常,斜体)。将它们转换为JSON格式并从我们的服务器提供服务是对资源的浪费💸
-
使用facetype.js源代码中的JavaScript文件。JS文件可以包含在项目的源代码中,Google Fonts CDN和API可以用于获取所需的TTF字体文件。一旦文件被获取,就可以调用相关的函数(从facetype.js中包含的JS文件)来将TTF数据转换为JSON格式。最后,这个JSON数据可以加载到Three.js中。这种方法比以前的方法好得多,但缺点是它包含外部JS脚本而没有包管理器。如果脚本发生变化,我们将不会获得任何未来的更新(除非这些变化被手动包含在我们的项目中),并且将其与ES6导入和TypeScript一起使用可能会很棘手。
three.js示例文件来拯救!
在花了一些时间试图弄清楚这一切之后,我几乎放弃了,接受了上面提到的第二种方法。我想要某种抽象,可以直接在Three.js中加载标准格式的字体文件,而不必担心将其转换为JSON格式的中间步骤。
Three.js的一个被低估的特性是库中的官方示例数量。您可以在https://threejs.org/examples/
上体验所有这些示例。在深入研究了Three.js示例之后,一个特殊的示例引起了我的注意。这个例子的标题是"TTFLoader using opentype",这让我很兴奋。
这个例子的源代码中有一行我想强调一下:
js
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';
Three.js已经有了一个 TTFLoader
类,可以用来加载TTF字体文件,并将它们用作 TextGeometry
的字体!我不知道!由于某些原因,文档中没有提到它,但它似乎得到了官方支持。
最终代码
在我们的工具箱中有了 TTFLoader
,在一个普通的Three.js项目中的实现,看起来比我一开始想象的要简单得多。
- index.js
js
import * as THREE from 'three';
import { TTFLoader } from 'three/examples/jsm/loaders/TTFLoader.js'
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';
import { Font } from 'three/examples/jsm/loaders/FontLoader.js';
import init from './setup';
const scene = init();
const loader = new TTFLoader();
// Loading the TTF font file from Fontsource CDN. Can also be the link to font file from Google Fonts
loader.load('https://api.fontsource.org/v1/fonts/lora/latin-600-italic.ttf', (fontData) => {
// Convert the parsed fontData to the format Three.js understands
const font = new Font(fontData);
// Create the text geometry
const textGeometry = new TextGeometry('Hello world', {
font: font,
size: 18,
height: 5,
curveSegments: 32,
bevelEnabled: true,
bevelThickness: 0.5,
bevelSize: 0.5,
bevelSegments: 8,
});
// Create a standard material with red color and 50% gloss
const material = new THREE.MeshStandardMaterial({
color: 'hotpink',
roughness: 0.5
});
// Geometries are attached to meshes so that they get rendered
const textMesh = new THREE.Mesh(textGeometry, material);
// Update positioning of the text
textMesh.position.set(-50, 0, 0);
scene.add(textMesh);
});
在React中使用
要在React中使用Three.js,我建议安装 @react-three/fiber
和 @react-three/drei
包。
他们让在React中使用Three.js变得轻而易举。drei包导出了一个 Text3D
类,它是 TextGeometry
的包装器,用于渲染3D文本。它必须使用React的 <Suspense />
组件包装,因为在加载字体时 Text3D
组件会挂起。默认情况下, Text3D
组件不支持TTF字体文件。它公开了一个 font
prop,该prop可以设置JSON文件路径。
为了添加对TTF文件的支持,让我们在drei的 Text3D
组件添加一个包装器组件。这个新组件接受一个 url
prop,它是我们想要使用的TTF字体文件的URL。在使用 TTFLoader
获取和加载字体时,让我们挂起组件。我将使用 suspend-react
包。如果任何依赖项发生变化,它会自动挂起 async
函数,并返回promise的解析值。
js
import { Suspense } from 'react';
import { Canvas } from '@react-three/fiber';
import { Text3D as Text3DBase, OrbitControls, Center } from '@react-three/drei';
import { suspend } from 'suspend-react';
import { TTFLoader } from 'three/examples/jsm/loaders/TTFLoader.js';
/**
* This wrapper component builds on top of "Text3D" component exported by @react-three/drei
* to support TTF file URLs.
*/
function Text3D({ url, ...props }) {
// Suspend while loading and parsing the TTF file.
const font = suspend(() => {
const loader = new TTFLoader();
return new Promise((resolve) => {
loader.load(url, resolve)
});
}, [url]);
return (
// Center component centers the text
<Center>
{/* Pass the loaded font to the component for rendering */}
<Text3DBase font={font} {...props} />
</Center>
);
}
export default function App() {
return (
<div className="w-screen h-screen">
<Canvas>
<Suspense>
<Text3D
url="https://api.fontsource.org/v1/fonts/lora/latin-600-italic.ttf"
height={0.25}
curveSegments={32}
bevelEnabled
bevelSegments={12}
>
Hello world
{/* Assign a material to the text */}
<meshStandardMaterial color="hotpink" roughness={0.5} />
</Text3D>
</Suspense>
<pointLight position={[0, 2, 2]} />
<ambientLight intensity={0.2} />
<OrbitControls makeDefault />
</Canvas>
</div>
);
}
结论
在本文中,我介绍了如何在Three.js中使用自定义字体进行3D文本渲染。它开始以JSON格式加载字体,使用流行的CDN字体,如Fontsource和Google Fonts。