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:

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.

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?
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 :)
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
SpriteFontclass 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
SpriteFontof 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.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).
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
DrawPrimitivecall.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