Three.js - 创建文本


通常您需要向场景添加文本。在本章中,我们将了解如何将 2D 和 3D 文本添加到场景中。

将文本绘制到画布并用作纹理

这是向场景添加 2D 文本的最简单方法。您可以使用 JavaScript 创建画布并将其添加到 dom。

const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')

上面的代码创建了一个画布元素,并将上下文设置为 2d。canvas.getContext() 方法返回一个对象,该对象提供在画布上绘图的方法和属性,可用于绘制文本、线条、方框、圆形等。

context.fillStyle = 'green'
context.font = '60px sans-serif
context.fillText('Hello World!', 0, 60)

fillText() 是 2D 绘图上下文的方法。fillText() 方法允许您使用从您提供的 fillStyle 派生的填充(颜色)在坐标处绘制文本字符串。您可以使用 font 属性设置文本的字体。

上面的代码将字体设置为 60 像素高的无衬线字体,并将填充样式设置为绿色。文本“你好,世界!” 从坐标 (0, 60) 开始绘制。

// canvas contents are used for a texture
const texture = new THREE.Texture(canvas)
texture.needsUpdate = true

要从画布元素创建纹理,我们需要创建 THREE.Texture 的新实例并传入我们创建的画布元素。上面的代码使用画布(在本例中是我们的文本)创建纹理。纹理的needsUpdate参数设置为true。它通知 Three.js 我们的画布纹理已更改,需要在下次渲染场景时更新。

现在,创建一个平面几何体并将其作为纹理添加到材质中。

var material = new THREE.MeshBasicMaterial({
   map: texture,
   side: THREE.DoubleSide,
})
material.transparent = true
var mesh = new THREE.Mesh(new THREE.PlaneGeometry(50, 10), material)

使用文本几何形状

THREE.TextGeometry 是另一种类型的几何图形,它将文本生成为单个几何图形。它需要两个参数,文本 - 要呈现的文本,以及其他参数。

参数

  • font - 这是字体的名称。

  • size - 文本的大小。默认值为 100。

  • height - height 属性定义文本的深度;换句话说,文本拉伸多远才能使其成为 3D。默认为 50。

  • curveSegments - 曲线上的点数。默认值为 12。

  • bevelEnabled - 斜角提供从文本正面到侧面的平滑过渡。如果将此值设置为 true,则会向渲染的文本添加斜角。默认情况下,它是错误的。

  • bevelThickness - 如果您将 bevelEnabled 设置为 true,则它定义斜角的深度。默认值为 10。

  • bevelSize - 它决定斜角有多高。默认等于 8。

  • bevelOffset - 距文本轮廓斜角开始的距离。默认值为 0。

  • bevelSegments - 斜角段的数量。默认值为 3。

您需要使用 THREE.FontLoader 从 typeface.json 文件加载字体。

const loader = new THREE.FontLoader()
loader.load('fonts/helvetiker_regular.typeface.json', function (font) {
   const geometry = new THREE.TextGeometry('Hello Three.js!', {
      font: font,
      size: 3,
      height: 0.2,
      curveSegments: 12,
      bevelEnabled: false,
      bevelThickness: 0.5,
      bevelSize: 0.3,
      bevelOffset: 0,
      bevelSegments: 5,
   })
})

现在,您应该向其添加一些材质并创建一个网格。

const material = new THREE.MeshFaceMaterial([
   new THREE.MeshPhongMaterial({
      color: 0xff22cc,
      flatShading: true,
   }), // front
   new THREE.MeshPhongMaterial({
      color: 0xffcc22
   }), // side
])
const mesh = new THREE.Mesh(geometry, material)
mesh.name = 'text'
scene.add(mesh)

注意- 使用 THREE.TextGeometry 和材质时需要考虑一件事。它可以将两种材质作为一个数组:一种用于渲染文本的正面,另一种用于文本的侧面。如果您只传递一种材料,它将同时应用于正面和侧面。

例子

现在,您可以看到渲染到场景的文本。查看以下示例。

二维文本.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - 2d text</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
               Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Adding 2d text to Three.js scene
         // Writing on canvas and then adding the canvas as a texture to material
         // GUI
         const gui = new dat.GUI()

         // sizes
         let width = window.innerWidth
         let height = window.innerHeight
         const size = 256
         const container = document.querySelector('#threejs-container')
         const canvas = document.createElement('canvas'),
         ctx = canvas.getContext('2d')
         function changeCanvas() {
            ctx.font = '20pt Arial'
            ctx.fillStyle = 'white'
            ctx.fillRect(0, 0, canvas.width, canvas.height)
            ctx.fillStyle = 'black'
            ctx.textAlign = 'center'
            ctx.textBaseline = 'middle'
            ctx.fillText('Tutorialspoint!', canvas.width / 2, canvas.height / 2)
         }
         // scene
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // lights
         const ambientLight = new THREE.AmbientLight(0xffffff, 1)
         scene.add(ambientLight)
         const pointLight = new THREE.PointLight(0xffffff, 0.5)
         pointLight.position.x = 20
         pointLight.position.y = 30
         pointLight.position.z = 40
         scene.add(pointLight)
         // camera
         const camera = new THREE.PerspectiveCamera(70, width / height, 1, 1000)
         camera.position.z = 500
         scene.add(camera)
         // renderer
         const renderer = new THREE.WebGL1Renderer({ antialias: true })
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         // cube
         const texture = new THREE.Texture(canvas)
         const material = new THREE.MeshStandardMaterial({ map: texture })
         const geometry = new THREE.BoxGeometry(200, 200, 200)
         const mesh = new THREE.Mesh(geometry, material)
         scene.add(mesh)
         canvas.width = canvas.height = size
         // responsiveness
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // animation
         function animate() {
            requestAnimationFrame(animate)
            changeCanvas()
            texture.needsUpdate = true
            mesh.rotation.y += 0.01
            renderer.render(scene, camera)
         }
         animate()
      </script>
   </body>
</html>

输出

例子

现在让我们再举一个例子来看看如何在场景中添加 3D 文本。

3d-文本.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - 3d text</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
               Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   <head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Creating 3d text using Text Geometry in Three.js
         // GUI
         const gui = new dat.GUI()
         // sizes
         let width = window.innerWidth
         let height = window.innerHeight
         // scene
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // lights
         const ambientLight = new THREE.AmbientLight(0xffffff, 1)
         scene.add(ambientLight)
         const pointLight = new THREE.PointLight(0xffffff, 0.5)
         pointLight.position.x = 20
         pointLight.position.y = 30
         pointLight.position.z = 40
         scene.add(pointLight)
         // camera
         const camera = new THREE.PerspectiveCamera(30, width / height, 0.1, 1000)
         camera.position.set(0, 0, 50)
         const camFolder = gui.addFolder('Camera')
         camFolder.add(camera.position, 'z').min(10).max(500).step(10)
         camFolder.open()
         function createMaterial() {}
         const loader = new THREE.FontLoader()
         // promisify font loading
         function loadFont(url) {
            return new Promise((resolve, reject) => {
               loader.load(url, resolve, undefined, reject)
            })
         }
         async function doit() {
            const font = await loadFont('https://threejs.org/examples/fonts/helvetiker_regular.typeface.json')
            let text = 'Hello World !'
            const geometry = new THREE.TextGeometry(text, {
               font: font,
               size: 3,
               height: 0.2,
               curveSegments: 12,
               bevelEnabled: true,
               bevelOffset: 0,
               bevelThickness: 0.5,
               bevelSize: 0.3,
               bevelSegments: 5
            })
            const material = [
               new THREE.MeshPhongMaterial({
                  color: 0xff22cc,
               flatShading: true
               }), // front
               new THREE.MeshPhongMaterial({
               color: 0xffcc22
               }) // side
            ]
            const mesh = new THREE.Mesh(geometry, material)
            geometry.computeBoundingBox()
            geometry.computeVertexNormals()
            geometry.boundingBox.getCenter(mesh.position).multiplyScalar(-1)
            mesh.position.x = -geometry.boundingBox.max.x / 2
            const parent = new THREE.Object3D()
            parent.add(mesh)
            scene.add(parent)
            const opts = geometry.parameters.options
            console.log(opts)
            const geoProps = {
               font: opts.font,
               size: opts.size,
               height: opts.height,
               curveSegments: opts.curveSegments,
               bevelEnabled: opts.bevelEnabled,
               bevelOffset: opts.bevelOffset,
               bevelThickness: opts.bevelThickness,
               bevelSize: opts.bevelSize,
               bevelSegments: opts.bevelSegments
            }
            console.log(geoProps)
            // GUI for exporimenting cube properties
            const props = gui.addFolder('Properties')
            props
               .add(geoProps, 'size', 1, 30)
               .step(1)
               .onChange(redraw)
               .onFinishChange(() => console.dir(mesh.geometry))
            props.add(geoProps, 'height', 0, 30).step(0.1).onChange(redraw)
            props.add(geoProps, 'curveSegments', 1, 30).step(1).onChange(redraw)
            props.add(geoProps, 'bevelEnabled').onChange(redraw)
            props.add(geoProps, 'bevelOffset', 0, 1).onChange(redraw)
            props.add(geoProps, 'bevelThickness', 0, 3).onChange(redraw)
            props.add(geoProps, 'bevelSize', 0, 3).onChange(redraw)
            props.add(geoProps, 'bevelSegments', 1, 8).step(1).onChange(redraw)
            props.open()
            function redraw() {
               camera.position.set(0, 0, 80)
               let newGeometry = new THREE.TextGeometry(text, {
                  font: geoProps.font,
                  size: geoProps.size,
                  height: geoProps.height,
                  curveSegments: geoProps.curveSegments,
                  bevelEnabled: geoProps.bevelEnabled,
                  bevelOffset: geoProps.bevelOffset,
                  bevelThickness: geoProps.bevelThickness,
                  bevelSize: geoProps.bevelSize,
                  bevelSegments: geoProps.bevelSegments
               })
               mesh.geometry.dispose()
               mesh.geometry = newGeometry
               mesh.geometry.parameters.options.depth = 0.2
               console.log(mesh.geometry.parameters.options)
            }
         }
         doit()
         // responsiveness
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // renderer
         const renderer = new THREE.WebGL1Renderer({ antialias: true })
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // animation
         function animate() {
            requestAnimationFrame(animate)
            renderer.render(scene, camera)
         }
         // rendering the scene
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

输出