Three.js - Creating Text



Often you need to add text to your scene. In this chapter, let's see how to add 2D and 3D text to our scene.

Draw Text to Canvas and Use as a Texture

This is the easiest way to add 2D text to your scene. you can create canvas using JavaScript andd add it to the dom.

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

The code above creates a canvas element, and we set the context to 2d. The canvas.getContext() method returns an object that provides methods and properties for drawing on the canvas, which it can use to draw text, lines, boxes, circles, and more.

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

The fillText() is a method of a 2D drawing context. The fillText() method allows you to draw a text string at a coordinate with the fill (color) derived from the fillStyle you provided. You can set the font of the text using the font property.

The above code set the font to 60-pixel-tall san-serif and the fill style to green. The text 'Hello, World!' is drawn starting at the coordinates (0, 60).

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

To create a texture from a canvas element, we need to create a new instance of THREE.Texture and pass in the canvas element we made. The code above creates a texture using the canvas (in this case, our text). The needsUpdate parameter of the texture is set to true. It informs Three.js that our canvas texture has changed and needs to be updated the next time the scene is rendered.

Now, create a plane geometry and add this as a texture to the material.

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

Using Text Geometry

THREE.TextGeometry is another type of geometry that generates text as a single geometry. It takes two arguments, text - the text you want to render, and other parameters.

Parameters

  • font − This is the name of the font.

  • size − Size of the text. Default is 100.

  • height − The height property defines the depth of the text; in other words, how far the text extrudes to make it 3D. This defaults to 50.

  • curveSegments − Number of points on the curves. Default is 12.

  • bevelEnabled − A bevel provides a smooth transition from the front of the text to the side. If you set this value to true, it adds a bevel to the rendered text. By default, it is false.

  • bevelThickness − If you've set bevelEnabled to true, it defines how deep the bevel is. Default is 10.

  • bevelSize − It determines how high the bevel is. Default is equal to 8.

  • bevelOffset − How far from text outline bevel starts. Default is 0.

  • bevelSegments − The number of bevel segments. Default is 3.

You need to use THREE.FontLoader to load fonts from their typeface.json files.

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,
   })
})

Now, you should add some material to it and create a mesh.

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)

Note − There is one thing you need to take into account when working with THREE.TextGeometry and materials. It can take two materials as an array: one for the front of rendered text and another for the side of the text. If you just pass in one material, it gets applied to both the front and the side.

Example

Now, you can see the text rendered to the scene. Check out the following example.

2d-text.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>

Output

Example

Let us now take another example to see how to add 3D text in a scene.

3d-text.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>

Output

Advertisements