MSDF Text renderer

Introduction

The TextRenderer class provides a powerful and efficient solution for rendering crisp, scalable text in Babylon.js scenes using MSDF (Multi-channel Signed Distance Fields). Unlike traditional bitmap fonts or SDF techniques, MSDF preserves sharp corners and detailed glyph shapes at any scale, making it ideal for modern 3D and 2D applications that demand both performance and visual fidelity.

Example:
Babylon.js Playground

Rendering text in WebGL presents a unique challenge: balancing resolution-independence with performance. Simple bitmap fonts scale poorly and blur at larger sizes, while classic single-channel SDFs tend to lose precision around complex edges. MSDF solves these issues by encoding distance information into three color channels (RGB), allowing precise reconstruction of glyph shapes using a shader, regardless of size or zoom level.

This technique offers:

  • Resolution-independent clarity — no pixelation at large scales.
  • GPU-efficient rendering — no need to regenerate or resize textures.
  • Support for complex typographic details — sharp corners, curves, and serifs remain intact.

The TextRenderer class handles MSDF rendering under the hood, managing vertex buffers, instancing, and shader logic seamlessly. It supports advanced features like billboarding, text thickness control, and instanced paragraph rendering, enabling dynamic, high-quality text rendering in immersive 3D environments.

Whether you’re building UI overlays, 3D HUDs, or in-world labels, TextRenderer offers a scalable and performant foundation for text in Babylon.js.

Installation

The MSDF Text Renderer is available as an UMD NPM package

shell

-npm install babylonjs-addons --save

and as an ES6 package:

shell

-npm install @babylonjs/addons --save

Initialization

Because we need to load the correct shaders (either webgl or webgpu), the creation of a TextRenderer is async:

javascript

const sdfFontDefinition = await (await fetch("https://assets.babylonjs.com/fonts/roboto-regular.json")).text();
const fontAsset = new ADDONS.FontAsset(sdfFontDefinition, "https://assets.babylonjs.com/fonts/roboto-regular.png");
const textRenderer = await ADDONS.TextRenderer.CreateTextRendererAsync(fontAsset, engine, 200);

The fontAsset is the definition of the font shapes and can be shared across several TextRenderers.

Generating font data

You can check the following free tools to generate font assets for your project:

Usage

Using a TextRenderer is pretty straightforward:

javascript

textRenderer.addParagraph("Hello World !");

This will add the text centered on the screen.

You can also provide options to control how your paragraph is set up:

javascript

textRenderer.addParagraph("What's up ?", {      
textAlign: "center"
});

The options are defined with that type:

javascript

type ParagraphOptions = {    
  maxWidth: number;    
  lineHeight: number;    
  letterSpacing: number;    
  tabSize: number;    
  textAlign: "left" | "right" | "center";   
  translate: Vector2 | undefined;
};

You can also add a third parameter to the call to addParagraph to control where you want your text:

javascript

textRenderer.addParagraph("What's up ?", {    
textAlign: "center"
}, BABYLON.Matrix.Translation(5, 0, 0));

The following properties are also available to control all the rendering of a TextRenderer:

  • color: Color4 used to define the color of the text
  • thicknessControl: a float indication how to change the overall default thickness (between -0.5 to 0.5 with 0 as the default value)
  • isBillboard: a boolean indicating you want the text to always face the camera
  • parent: A node entity used to attach to text to

Examples

Setting a parent:
TextRenderer

A Star Wars scroller:
Babylon.js Playground

Thickness control:
Babylon.js Playground

21 Likes

A BIG thanks for @zb_sj and @Bhushan_Wagh for their invaluable contribution!

7 Likes

It looks great. And how do I download the file without going through NPM?

It will be on cdn this Thursday:

cdn.babylonjs.com/addons/babylonjs.addons.min.js

2 Likes

Looks fantastic! :heart_eyes:

Does or can it replace TextBlock in Babylon.js GUI? Or are these entirely separate use cases?

It is a different tech mostly. gUI is a canvas 2D based tech (so cpu based). The TextRenderer is full gpu accelerated

2 Likes

Another quick demo:

Solar system | Babylon.js Playground

6 Likes

This is pretty cool! Is it somewhat similar to MeshWriter or is it a different use-case?

1 Like

It is different as it does not use meshes at all. It is probably faster by an order of magnitude also :wink:

2 Likes

Do you have to do the matrix updates every frame? Is there a way to just set the matrix references directly and have them auto update?

what do you mean by update matrix?


It looks like you have to do an render call per frame for each one and set the camera matrices.

I was hoping to just see the ability to create it and then set its matrix to some other mesh and then forget about it, looks like if I’m going to replace our current MSDF method that id have to keep track of the renders and have an update cycle on them.

Trying to figure out if this is going to be more efficient than our current solution of using plane instances per character and then parenting that. The benefit is there is no update loop to worry about.

The need for the rendering call here is because ai wanted the TextRenderer to have no scene dependency.

The render itself is as slim as possible and thus as fast as I could think of:)

The matrices expected here are the view and projection.

You can parent a textRenderer to a mesh of any node really and forget about that though

idk what https://assets.babylonjs.com/fonts/roboto-regular.png is, but it is very crispy text :hugs:

@withADoveInOneHand
It’s a Multi Channel Distance Field texture… cool name :saluting_face: Basically it is vector graphics (including font glyphs) encoded in a texture…

1 Like

@Deltakosh seems we have a kerning issue:

How it looks on https://msdf-bmfont.donmccurdy.com/ :

Can you have a look at it, please?

Nice catch buddy!

Here is the fix:

While I was at it I also added support for stroke width :slight_smile:

Now you can use these new features:

textRenderer.strokeColor = ...
textRenderer.strokeInsetWidth = 0.5;
textRenderer.strokeOutsetWidth = 0.2
2 Likes

Btw I think this is the correct version:

The S in your example seems to far apart based on the font data

1 Like

Well maybe not…Investigating

1 Like

ok no I’m dumb :slight_smile:
Your ref was correct:

I actually had 2 bugs :smiley:

2 Likes