Vector-based Font Rendering

in

I've been fiddling around with font rendering again this weekend. For my upcoming game, I wanted a cool intro effect like in some movies, where the displayed text is very very slowly expanding. But I couldn't quite replicate that effect with the XNA SpriteFont class. Even with antialiasing turned on, the borders of the text were flickering. Using very large font sizes resulted in hopelessly oversized .xnb files with only a slight improvement for the unsteady borders.

So my new solution was to write a component that renders vector-based text instead of bitmaps. Extracting the vector data from a font was a bit harder than I had expected because fonts consist mostly of complicated second and third order bezier curves. I managed to break these down into plain straight line segments and am now in the possession of a content pipeline importer that converts .ttf fonts into arrays of line segments. Behold:

New Nuclex.Fonts vector font rendering capability

In the upper left is my replacement sprite font importer which can take the place of Microsoft's own importer by the flip of a combobox. It produces identical .xnb files that can be used with the SpriteFont class, so you can toggle forth and back at will. The upper line is from a font imported with my own importer (using FreeType) and the lower line is from the same font imported with the XNA SpriteFont importer (using Windows Vista .ttf rendering).

The big outlined vector text highlights the separate outlines the characters consist of in the upper line and shows the normal rendered result below. Both outputs have been generated by writing a series of line strips into a vertex buffer and rendering it.

Now filling the characters requires the outlines to be tessellated into a triangle mesh, which is another unexpected complexity I hadn't thought about when I set out to create a vector font rendering component. But I already took the hurdle by cheating with OpenGL's gluTess...() routines. Of course, these will only be used in the content pipeline assembly, the final game .exe has zero references to unmanaged code and/or OpenGL.

Update: I completed the GLU tessellator interfacing code and am now proud to announce the near completion of the Nuclex.Fonts VectorFont importer. Once I'm done with the runtime side of things, you can have smooth-zooming text without any blurry pixels or loss of quality, rotating 3D text or just a means of converting strings into vertices for whatever purpose you want.

New Nuclex.Fonts vector font tessellated into triangles using the GLU tessellator

Mart's picture

Performance?

Congrats, it looks really cool! I was just wondering what the cost is if you compare it with spritefont rendering. Can you tell something about that?

Cygon's picture

Not Certain Yet

Thanks! I haven't done a complete test with filled fonts yet, but (depending on the font used) that "Hello World" string takes less than 1K triangles, so I suspect performance won't be an issue.

It's intended for things like fancy credits and titles, of course. Rendering an FPS counter or text on a HUD won't make much sense when you can do the same with the SpriteFont class :)

Cygon's picture

Performance Results

I can't help but smirk at the results of my small performance test. Because I haven't yet checked out PIX, I decided to just draw so many strings that the graphics card falls behind and the frame rate drops below 1 fps (using 4 strings of 12 letters in 25.000 batches).

The string "Hello World" imported from Arial.ttf consists of 288 triangles in my vector font renderer. The same string using the SpriteFont class takes 24 triangles. Both tests were done on a release build without a debugger attached.

VectorFont: 2.36 seconds per frame (12203390 tri/s)
SpriteFont: 3.36 seconds per frame (714286 tri/s)

The SpriteFont of course looks way better at small font sizes, so I'd still prefer sprite fonts for normal GUI/HUD elements and only use vector fonts for the cinematics.

MK42's picture

Random Thoughts

Something which might be worth checking out for HUD/UI "sprite" rendering:

http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf

This method seems to provide an excellent "texture memory vs. quality" tradeoff and appears reasonably simple to implement. I think it could be used for font rendering as well (although I haven't had a chance to try it myself). The distance field generator could even use the font geometry directly rather than going to a hi-res texture as explained in the PDF.

Geometry fonts are cool when used for dynamic "3D" GUI systems. Best example is Doom3 (plus derivative titles), but you will absolutely have to implement anti-aliasing which will increase triangle count even more (1 quad per outline edge). I also believe that something like a string/word cache indexbuffer is something worth investigating for reducing the number of DrawCalls. You could even have an adaptive dictionary similar to what's used for compression algorithms (Lempel-Ziv schemes come to mind).

Cygon's picture

Doom3

Does Doom3 really use vector-based fonts?

I had the impression that the interactive computers in the game were just simple textures (there were typical filtering artifacts and everything). At least using the textures as render targets and doing the usual quad-based HUD rendering would be the straightforward way to do it. The seamless use of the crosshair as a mouse cursor on the screens is certainly cool, though :)

That paper looks very interesting. I'll have to try this, thanks!

About the AA thing, I don't see why I would need additional quads for that. If you look closely, you'll notice that the sample images are already done using 4xFSAA ;). A string caching system has also been in place from the start. Any string can be concatenated into a single optimized vertex buffer / index buffer pair and rendered in a single DrawPrimitive call.

MK42's picture

Whoops

You are right. Doom3 was using textures for the fonts. A colleague of mine claimed it wasn't because he still saw the font when in wireframe. Since the text in the GUIs looked so damn good and since the guy usually knows his stuff I didn't question/verify it :( Anyways, sorry for the confusion. However, I'm not entirely sure if they used rendertargets for the monitors. I guess we shall see when Doom 3 becomes open source ...

Regarding AA, take a look at the Crysis HUD (lower right corner in the screenshot):

http://www.pcgames.de/screenshots/original/2007/08/crysis1.jpg

That's using the "manual" AA. The lines and the icons are all vector art. The font uses a texture cache. Sometimes you can't rely on FSAA to be there for these elements and that HUD starts looking ugly fairly quickly without anti-aliasing (yes, it's a really bad HUD design from a technical perspective).

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Lines and paragraphs break automatically.
  • Allowed HTML tags: <br> <a> <em> <strong> <u> <i> <b> <cite> <blockcode> <code> <ul> <ol> <li> <dl> <dt> <dd> <p> <pre> <span>
  • You can highlight code with any of the following tags: <blockcode>

More information about formatting options