Lesson 14 - Vector Fonts



Lesson 14 in NeHe is about using actual vector fonts - unlike 13 where the fonts were used to generate bitmaps. Instead, the lesson is about using the wgl functions to generate actual 3D fonts with depth and lighting. The wgl functions are gone of course, but three.js does support vector fonts. It's pretty straightforward, but there are a few wrinkles that need to be covered, so let's dive in.

Creating Fonts

First, the vector font support in three.js is based on JavaScript fonts as generated from facetype.js, which is an open-source effort hosted on github. the format is not a standard, but it is pretty straightforward. It is apparently specific to three.js, which means it may or may not have continuity, but that's open-source for you! The internals of the format are not documented anywhere obvious but opening up a font in an editor shows that they are basically like SVG fonts with some metadata about each glyph and then a series of data indicating the moves, quadratic curves, etc.

Each font in a family (e.g. bold, italic, bold-italic, etc.) is a separate JS file. three.js comes with a number of "standard" fonts corresponding to Helvetica and Times-Roman (i.e. san-serif and serif). You can convert your own fonts at the facetype.js site. However, be warned that it is NOT legal to simply take any font on your computer and convert it. The major font foundries (Monotype, Adobe, etc.) have spent an immense out of money creating the fonts and they are the intellectual property of those firms. Having them installed on your computer (by the OS or an application) is done under strict licensing. The licensing is a relatively complex area (and we don't want to go there) but the net-net is that, in general, you are allowed to view them on your computer, print from them and even create PDFs with them but you cannot legally convert them to some other format, such as JavaScript.

However, there are lots of open-source fonts available. Check out Google fonts (all of which are free of licensing) or Fonts101.com. Fonts101 will even send you a daily font created by an amateur. Almost all of those are free to use as you wish. Some of them are pretty strange though...

Once you have a font that you can convert, you can navigate to facetype.js and convert it to JavaScript. Choose "JavaScript", not JSON. Most fonts get converted correctly and work fairly well, but the more complex the font, the more likely that there might be problems when it is rendered. More on this later.

Using the Fonts

Actually using the fonts is pretty easy. Let's take a look at the code.

First, one has to specify a Material for both the face and the side of the font. You can use the same material or a different one. However, three.js has some bugs, as does the font-converter software at facetype.js. The converter doesn't always keep the same winding order for nested or adjacent paths and then three.js gets confused. In addition, as of release 69 of three.js, you should not use smooth shading on the face of the font. If you do, you will get some pretty odd artifacts. Try it and see. However, the face of the font is flat anyway, so flat shading is fine. In addition, you shouldn't use Phong material on the sides. Again, you'll get odd artifacts. Finally, go easy on the beveling as large bevels tend to look odd.

In this demo, we specify two different materials, one for the face and one for the sides (bearing in mind the caveats listed above) and put them in an array we'll pass to the font engine.

var materialFront = new THREE.MeshPhongMaterial( { color: 0xff0000, shading: THREE.FlatShading } );
var materialSide = new THREE.MeshLambertMaterial( { color: 0x000088 } );
var materialArray = [ materialFront, materialSide ];

Then we instantiate a "font loader". This is a widely used pattern in three.js. There are many types of loaders, but they have the same form, they take the name of the resource being loaded and one to three functions.

  • onLoad( font ) Called when the resource has been loaded. The argument is the actual JS object that has been loaded
  • onProgress( xhr ) Called (if the loading takes long enough) with a float (0..1)indicating load progress
  • onError( xhr ) Called in case of error.

In this demo, we don't bother with progress or error handling so we define only the first function.

var loader = new THREE.FontLoader();
loader.load( fontName, function ( font ) {
    // do something with the font here...

Once the font has been loaded, we then create the TextGeometry and the Mesh. This is the code INSIDE the onLoad method function. Note that all the processing of the font MUST be inside that function because the loading process is asynchronous. If it is not inside that function your code will try to use the font before it is loaded.

textGeom = new THREE.TextGeometry(text, {
    size: size,	
    height: height,	
    curveSegments: curveSegments,
    font: font,						
    bevelThickness: bevelThickness,
    bevelSize: bevelSize,
    bevelEnabled: bevelEnabled

var textMaterial = new THREE.MeshFaceMaterial(materialArray);
var textMesh = new THREE.Mesh(textGeom, textMaterial);

A few notes. The size parameter is actually the HEIGHT of the font, i.e. the distance from the baseline to the top of the font's character. The badly misnamed parameter height is actually the THICKNESS of the extruded font. curveSegments is effectively undocumented but is, as one would expect, controlling how smooth the curve is. The bevel parameters are also effectively undocumented. Just experiment to see what you want.

Finally, get the bounding box of the font so the string can be centered, then add it to the scene.

var textWidth = textGeom.boundingBox.max.x - textGeom.boundingBox.min.x;
textMesh.position.set(-0.5 * textWidth, 0, 0);

And that's it! Click on this link to see the actual rendered demo in all 3D textual glory! Wowser!

As always, the original sources are on github here.

About Us | Contact Us | ©2017 Geo-F/X