我们已经了解足够的基础知识,现在可以创作一些好看的效果了。对于我们第一个正式的项目,我们将复刻一个开发者ilithya的作品(https://www.ilithya.rocks/),这个作品在场景中间有一个大的 3D 文本,很多几何体漂浮在文字的周围。
这个作品是学习 Three.js 早期可以实现的一个很好的例子。它简单、高效,而且特效看起来很棒。
Three.js 已经通过TextGeometry类支持 3D 文本几何图形。问题是你必须先指定一种字体,而且这个字体必须是一种叫做 typeface 的特定 json 格式。
我们不会涉及数字字体版权授权相关的问题,您使用下载字体后,使用字体时必须保证有权使用该字体,或者字体版权是供开发者免费使用的。
有很多方法可以获取 typeface 格式的字体。首先,您可以使用如下转换器转换您的字体:https://gero3.github.io/facetype.js/。您必须提供一个文件并单击转换按钮。
您还可以在node_modules
文件夹中的 Three.js 库示例中找到字体。/node_modules/three/examples/fonts/
你可以把这些字体放在/static/
文件夹中,或者你可以直接在你的 JavaScript 文件中导入它们,因为它们是 json 并且 Vite 中.json
文件就像.js
的文件一样被支持:
import typefaceFont from 'three/examples/fonts/helvetiker_regular.typeface.json'
我们打开/node_modules/three/examples/fonts/
,获取helvetiker_regular.typeface.json
文件和LICENSE
文件并将它们放入/static/fonts/
文件夹(您需要创建fonts
文件夹)来混合使用这两种技术。
现在只需在基本 URL 的末尾写入即可访问该/fonts/helvetiker_regular.typeface.json
字体。
要加载字体,我们必须使用一个名为FontLoader的新加载器类。
此类在THREE
变量中不可用。像在前面课程中我们所做的,像导入OrbitControls
那样导入它:
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js'
这个加载器像TextureLoader一样工作。在该部分之后添加以下代码textureLoader
(如果您使用的是其他字体,请不要忘记更改路径):
/**
* Fonts
*/
const fontLoader = new FontLoader()
fontLoader.load(
'/fonts/helvetiker_regular.typeface.json',
(font) =>
{
console.log('loaded')
}
)
进入你的控制台发现打印了'loaded'
。如果不是,请检查前面的步骤并在控制台中搜索潜在的错误。
我们现在可以通过使用函数内的font
变量来访问字体。与TextureLoader不同,我们必须在该函数中的成功回调编写其余代码。
正如我们之前所说,我们将使用TextGeometry来创建几何体。
就像FontLoader一样,我们需要导入它:
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js'
请注意文档页面上的示例代码;这些值比我们场景中的值大得多。
确保在成功的回调函数中编写代码:
fontLoader.load(
'/fonts/helvetiker_regular.typeface.json',
(font) =>
{
const textGeometry = new TextGeometry(
'Hello Three.js',
{
font: font,
size: 0.5,
height: 0.2,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.02,
bevelOffset: 0,
bevelSegments: 5
}
)
const textMaterial = new THREE.MeshBasicMaterial()
const text = new THREE.Mesh(textGeometry, textMaterial)
scene.add(text)
}
)
您应该得到一个需要改进的白色 3D 文本。
首先,注释掉立方体的代码。其目的是确保一切正常。
如果您想看到一些很酷的网格,请添加wireframe: true
到您的材料中。
const textMaterial = new THREE.MeshBasicMaterial({ wireframe: true })
您现在可以看到几何体是有很多三角形生成的。创建文本几何图形对计算机来说既漫长又困难。curveSegments
避免这样做太多次,并通过减少多边形和bevelSegments
属性使几何体尽可能保持低。
一旦您对几何体渲染详细程度感到满意,请删除wireframe
。
有几种方法可以使文本居中。一种方法是使用边界。边界是与几何相关联的信息,它告诉该几何占用了哪些空间。它可以是一个盒子或一个球体。
你实际上看不到这些边界,但它可以帮助 Three.js 轻松计算对象是否在屏幕上,如果不在屏幕上,则对象甚至不会被渲染。这称为视锥体剔除,但这不是本课的主题。
我们想要的是使用这个边界来了解几何体的大小并使它重新居中。默认情况下,Three.js 使用球体边界。我们想要的是一个盒子边界,更准确地说。为此,我们可以要求 Three.js 通过调用computeBoundingBox()
几何来计算此框边界:
textGeometry.computeBoundingBox()
我们可以使用boundingBox
几何属性选中此框。
console.log(textGeometry.boundingBox)
结果是一个名为Box3 的对象,它有一个min
属性和一个max
属性。该min
0并不像我们预期的那样。这是由于bevelThicknessand bevelSize
,但我们现在可以忽略它。
现在我们有了措施,我们可以移动对象。我们不移动网格,而是移动整个几何体。这样,网格仍将位于场景的中心,文本几何体也将在我们的网格内居中。
为此,我们可以在方法translate(...)
之后立即在几何体上使用该方法computeBoundingBox()
:
textGeometry.translate(
- textGeometry.boundingBox.max.x * 0.5,
- textGeometry.boundingBox.max.y * 0.5,
- textGeometry.boundingBox.max.z * 0.5
)
文本居中,但如果你想非常精确,你还应该减去 bevelSizeis 0.02
:
textGeometry.translate(
- (textGeometry.boundingBox.max.x - 0.02) * 0.5, // Subtract bevel size
- (textGeometry.boundingBox.max.y - 0.02) * 0.5, // Subtract bevel size
- (textGeometry.boundingBox.max.z - 0.03) * 0.5 // Subtract bevel thickness
)
我们在这里所做的实际上可以通过调用几何上的方法center()
更快地完成:
textGeometry.center()
容易多了,不是吗?我们手写居中做的目的是了解边界和截锥体剔除。
是时候为我们的文本添加一个很酷的材料了。我们将使用 MeshMatcapMaterial
替换 MeshBasicMaterial,因为它看起来很酷,而且性能更好。
首先,让我们选择一个 matcap
纹理。我们将使用位于/static/textures/matcaps/
文件夹中的 matcaps
,但您可以随意使用您自己的 matcaps
。
您也可以从此存储库https://github.com/nidorx/matcaps下载一个。不要花太多时间去选择它!如果不是供个人使用,请确保您有版权使用它。您不需要高分辨率的纹理,256x256
应该绰绰有余。
我们现在可以使用代码中已有的TextureLoader来加载纹理:
const matcapTexture = textureLoader.load('/textures/matcaps/1.png')
我们现在可以用漂亮的MeshMatcapMaterial替换丑陋的MeshBasicMaterial并将我们的matcapTexture
变量与matcap
属性一起使用:
const textMaterial = new THREE.MeshMatcapMaterial({ matcap: matcapTexture })
让我们添加漂浮的对象。为此,我们将在循环函数内创建一个甜甜圈。
在成功函数中,紧跟在该text
部分之后,添加循环函数:
for(let i = 0; i < 100; i++)
{
}
我们可以在 success 函数之外完成此操作,但我们需要将文本和对象一起创建,这是有充分理由的,您稍后会看到。
在此循环中,创建一个TorusGeometry(例如甜甜圈的技术名称),其材质与文本和Mesh相同:
for(let i = 0; i < 100; i++)
{
const donutGeometry = new THREE.TorusGeometry(0.3, 0.2, 20, 45)
const donutMaterial = new THREE.MeshMatcapMaterial({ matcap: matcapTexture })
const donut = new THREE.Mesh(donutGeometry, donutMaterial)
scene.add(donut)
}
你应该在同一个地方得到 100 个甜甜圈。
让我们为他们的位置添加一些随机性:
donut.position.x = (Math.random() - 0.5) * 10
donut.position.y = (Math.random() - 0.5) * 10
donut.position.z = (Math.random() - 0.5) * 10
你应该把 100 个甜甜圈分散在现场。
为旋转添加随机性。无需旋转所有 3 个轴,并且由于甜甜圈是对称的,旋转半圈就足够了:
donut.rotation.x = Math.random() * Math.PI
donut.rotation.y = Math.random() * Math.PI
甜甜圈应该向各个方向旋转。
最后,我们可以为比例添加随机性。不过要小心;我们需要对所有 3 个轴( x, y, z
)使用相同的值:
const scale = Math.random()
donut.scale.set(scale, scale, scale)
我们的代码不是最优的。正如我们在上一课中看到的,我们可以在多个网格上使用相同的材质,但我们也可以使用相同的几何体来节省性能。
将 thedonutGeometry
和 thedonutMaterial
移出循环:
const donutGeometry = new THREE.TorusGeometry(0.3, 0.2, 20, 45)
const donutMaterial = new THREE.MeshMatcapMaterial({ matcap: matcapTexture })
for(let i = 0; i < 100; i++)
{
// ...
}
你应该得到相同的结果,但我们可以走得更远。text
的材料与donut
的相同。
让我们删除donutMaterial
,重命名textMaterialbymaterial
并将其用于 thetext
和donut
:
const material = new THREE.MeshMatcapMaterial({ matcap: matcapTexture })
// ...
const text = new THREE.Mesh(textGeometry, material)
// ...
for(let i = 0; i < 100; i++)
{
const donut = new THREE.Mesh(donutGeometry, material)
// ...
}
我们可以继续优化,但是有一个关于优化的专门课程,所以先按下不表。