Troika-3d-text: library for SDF text rendering

Hello folks!

I’d like to formally introduce a library I’ve created for rendering high quality text within Three.js scenes using SDF (signed distance fields). I had found that existing SDF text libraries were missing features I needed, and I hated the idea of having to pre-generate SDF atlas textures, so I created my own solution.

PLEASE NOTE! As of v 0.29.0, this has been promoted to its own troika-three-text package, where previously you had to import a specific secondary dist file within troika-3d-text. I have edited this post to reflect its new home.

Repository: https://github.com/protectwise/troika/tree/master/packages/troika-three-text

Example: https://troika-examples.netlify.app/#text

Highlights:

Most importantly, it parses web font files (.ttf, .otf, .woff) directly using Typr.js and generates SDFs for specific glyphs on the fly as they are used. This means you can use ligatures and extended character sets without having to pre-generate large atlas textures.

Rather than using a simple custom ShaderMaterial, it “patches” any Three.js material you give it. So your text can use lighting, shadows, fog, textures, and any other feature the built-in materials provide.

Many CSS properties are supported for the text styling and layout, and I’ve strived to match CSS behavior as closely as possible. It supports clipping and I’m working on additional advanced features like truncation/ellipsis and text selection.

All font parsing, SDF generation, and text layout is performed off the main thread in a web worker to avoid frame drops. Performance has been a primary goal since the start.

Usage:

Full documentation is in the repository.

Even though it was originally created as part of our Troika scene management framework, I’ve provided it as a standalone troika-three-text package that excludes any Troika framework dependencies, which you can use in any Three.js project.

import { Text } from 'troika-three-text'

let text = new Text()
text.font = 'path/to/font.woff'
text.text = 'Hello!'
text.fontSize = 0.2
text.color = 0x9966FF

text.sync()
myScene.add(text)

It’s a simple API and easily used within other Three.js-based frameworks, for example: AFrame and react-three-fiber. It’s also being used in Mozilla’s ecsy-three framework and was featured in their Hello WebXR demo.

Feedback:

I’m still actively developing this library with fixes and features, so I welcome any suggestions or bug reports; go ahead and open an issue in the Github repo. Thanks, I hope this is useful for people!

18 Likes

Looks wonderful, great job. Very crisp and clear!

2 Likes

Very nice!

What do you think about turning this into an official example?

4 Likes

Wow, this is one of the coolest text implementations I’ve seen! You mentioned it’s not generating atlas textures, but what’s in the uniform uTroikaSDFTexture? I couldn’t figure it out at first glance.

@mrdoob I would be honored at the opportunity! How would that work with pulling in an external library like this?

@marquizzo Thank you! :smiley:

I didn’t mean to imply there are no atlas textures involved; it definitely does use atlas textures, but the point is that it generates them automatically in JS at runtime behind the scenes based on the original font file, for just the glyphs that you actually use. You don’t have to pre-generate them offline beforehand.

Check out the source for the SDF generator, if you’re curious!

1 Like

How would that work with pulling in an external library like this?

This typically means that source for for the library gets moved into three.js repo and maintained there (in part because contributors find it there and then make contributions to it).

You don’t have to pre-generate them offline beforehand.

An aside but this would be a great improvement for the existing TextMesh, as well, if it’s performant – I don’t know the history of why it wasn’t done this way in the first place, though. I haven’t used it all that much in part because of the added up front burden of generating the glyphs as json from an external website.

Also awesome work on the library. The text rendering looks phenomenal!

This is beautiful, thank you!

I would like some clarification about this. If you integrate Troika to three.js, then my own library will become kind of useless, so I don’t want to continue working on it in this case.

If this would mean moving the library source into the three.js repo, then I don’t think that’s going to be a possibility. I’d consider moving it out of the Troika monorepo to its own repo at some point, though it’s got dependencies on some other Troika utilities (for manipulating builtin material shaders and for running code in a web worker, which I also need to post about here at some point) which would also have to be split out. But IMO it really belongs as its own project rather than being absorbed into Three’s examples.

It seems like it could possibly fit into the examples like some of the other third party libraries where it’s stored as a minified JS bundle, though…?


@felixmariotto I wouldn’t worry about that; if anything this would just be the SDF text rendering library, not any of the other parts of Troika like the flexbox UI system. Your project still fulfills a huge need IMO, and in a way that’s more accessible to people than Troika’s system. So please keep it up! :smiley:

@felixmariotto your library it’s a gem!

Personally I am suggesting to add it as a screenshot searching in VR. Because right now we don’t have any visualization for screenshots and I think that Three.js as a 3d library can have 3d ui. Here we have a little contest on this topic.

1 Like

One issue I see with that is that it inevitably funnels feature requests and bug reports to the three.js repo rather than the appropriate repo (yours!)

I think it would be great if three.js could link to and advertise good “third party” three.js packages in the docs like this one, though, and help bolster the community of external package maintainers. I made an issue here to discuss that specifically.

3 Likes

Yeah, that would great. The problem is that third party plugins often start out good but are not maintained, then after a couple of years they stop working. We could add the list with a disclaimer “these are not maintained by three.js and may need to be updated before you can use them with the latest version of three.js”.

2 Likes

wow, thanks for the r3f example! this will be so useful, i was searching for something like this!

1 Like

Very cool @lojjic !

Does using esm.js allows you to avoid any conflict with ThreeJS and its unique ID system?
How does it ensure that the IDs of the items you generate do not conflict with the one in the project using it, since it (does it?) use a different base version of THREE.JS?

In the past I used a factory pattern to solve it and I’m curious about what is your solution! I looks much better the way you did it!

Cheers

I’m not totally sure about the issue you’re describing, but it sounds like maybe something with multiple versions of the three library getting loaded? I could see if a plugin library like this declared one version of three as a dependency but the consuming project used a different version, that could result in both being bundled and conflicting. For Troika I avoid that by declaring three as a “peer dependency” and give it a range of versions that I’ve tested it will work with. It’s then up to the consuming project to provide the exact dependency.

1 Like

Yes! That’s what I had in mind.

i tried making it responsive so that you can resize the browser window and it adapts, like html text. i was searching for something like this for a long time. i couldn’t believe how easy that is with troika :exploding_head:: https://codesandbox.io/s/troika-3d-text-via-react-three-fiber-eb4mx

4 Likes

@lojjic How can I apply custom shader and apply text texture? https://codesandbox.io/s/msdf-bitmap-28hy0?file=/src/app.js

You should be able to assign a ShaderMaterial just like any other. Make sure you grab 0.27.1 which has a bugfix that may have been affecting that.