Three.js 之 8 炫酷的 3D Text

1,824 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

本系列为 Three.js journey 教程学习笔记。包含以下内容

未完待续

本节将学习 3D Text,并做一个炫酷的 3D Text 展示页面。我们将使用 TextGeometry 文本缓冲几何体来实现。

typeface font

Three.js 内置了 FontLoader 来加载 json 格式字体。可以使用 facetype.js 在线转换 json 字体。

Load the font

import { FontLoader } from 'three/examples/jsm/loaders/FontLoader'
...
// Load font
const fontLoader = new FontLoader()
fontLoader.load(
  '../assets/fonts/Fira Code Medium_Regular.json',
  // onLoad回调
  (font) => {
    console.log('loaded', font)
  },
)

加载成功。接下来我们需要在成功回调里继续完成代码

Create TextGeometry

// Load font
const fontLoader = new FontLoader()
fontLoader.load(
  '../assets/fonts/Fira Code Medium_Regular.json',
  // onLoad回调
  (font) => {
    console.log('loaded', font)
    const textGeometry = new TextGeometry("Joe CS's three.js world!", {
      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)
  },
)

可以看到文字并没有在中间位置,我们需要居中展示

Center the text

居中的方式是计算几何体的立方体边界,再进行位移。

使用 BoxHelper 可以观察 bounding box

const box = new THREE.BoxHelper(text, 0xffff00)
scene.add(box)

于是我们使用 computeBoundingBox 获取 box 的尺寸,再进行位移,代码如下

textGeometry.computeBoundingBox() // 计算 box 边界
if (textGeometry.boundingBox) {
  textGeometry.translate(
    -textGeometry.boundingBox.max.x * 0.5, // Subtract bevel size
    -textGeometry.boundingBox.max.y * 0.5, // Subtract bevel size
    -textGeometry.boundingBox.max.z * 0.5, // Subtract bevel thickness
  )
}

可以看到完成了居中展示

当然还可以直接使用

textGeometry.center()

完整代码如下

import * as THREE from 'three'
import './style.css'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader'
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry'
import stats from '../common/stats'
import { listenResize, dbClkfullScreen } from '../common/utils'

// Canvas
const canvas = document.querySelector('#mainCanvas') as HTMLCanvasElement

// Scene
const scene = new THREE.Scene()

// Load font
const fontLoader = new FontLoader()
fontLoader.load(
  '../assets/fonts/Fira Code Medium_Regular.json',
  // onLoad回调
  (font) => {
    console.log('loaded', font)
    const textGeometry = new TextGeometry("Joe CS's world!", {
      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()
    textMaterial.wireframe = true

    textGeometry.center() // 居中

    const text = new THREE.Mesh(textGeometry, textMaterial)
    scene.add(text)

    const box = new THREE.BoxHelper(text, 0xffff00)
    scene.add(box)
  },
)

// Size
const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
}

// Camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.set(1, 2, 3)

const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true

// Renderer
const renderer = new THREE.WebGLRenderer({
  canvas,
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

dbClkfullScreen(canvas)
listenResize(sizes, camera, renderer)

// Animations
const tick = () => {
  stats.begin()

  controls.update()
  // Render
  renderer.render(scene, camera)
  stats.end()
  requestAnimationFrame(tick)
}

tick()

Add a matcap material

可以在 github.com/nidorx/matc… 这里找到需要的纹理素材,如果商用,请确保版权。不需要特别高分辨率,256*256 足矣。

const textureLoader = new THREE.TextureLoader()
const matcapTexture = textureLoader.load('../assets/textures/matcaps/1.png')

const textMaterial = new THREE.MeshMatcapMaterial()
textMaterial.matcap = matcapTexture

Add objects

我们在添加一些几何体悬浮在周围。可以在 for 循环中创建各种几何体。

const donutGeometry = new THREE.TorusGeometry(0.3, 0.2, 20, 45)
const boxGeometry = new THREE.BoxGeometry(0.6, 0.6, 0.6)

for (let i = 0; i < 100; i += 1) {
  let mesh
  if (i % 10 <= 2) {
    mesh = new THREE.Mesh(boxGeometry, material)
  } else {
    mesh = new THREE.Mesh(donutGeometry, material)
  }
  mesh.position.set(
    (Math.random() - 0.5) * 10,
    (Math.random() - 0.5) * 10,
    (Math.random() - 0.5) * 10
  )
  mesh.setRotationFromEuler(
    new THREE.Euler(Math.PI * Math.random(), Math.PI * Math.random(), Math.PI * Math.random())
  )
  const radomeScale = Math.random() * 0.5 + 0.5
  mesh.scale.set(radomeScale, radomeScale, radomeScale)
  scene.add(mesh)
}

Animation

可以直接使用 OrbitControls 进行旋转

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

添加如下代码

const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
controls.autoRotate = true
controls.autoRotateSpeed = 0.4

别忘了在 requestAnimationFrame 中 update

// Animations
const tick = () => {
  stats.begin()

  controls.update()

  // Render
  renderer.render(scene, camera)
  stats.end()
  requestAnimationFrame(tick)
}

tick()

GUI controls

使用前面学到的 lil-gui 增加一些控制项,配置背景色

最终效果如下

在线 demo 链接

demo 源码

小结

本节我们算是做了第一个小作品。学习了 3D 文字,运用了之前学得材质、纹理、控制器等。keep going!