UPBGE: Properly rendered fonts

I’m sure it’s something you’ve seen many times in BGE games. Text that looks like this:

Or maybe even like this:

The first is pixelated, and the second has aliasing artefacts. Here’s a comparison of three types of text:

The bottom one is properly anti-aliased.

Why is this an issue?
Blender’s font system works by turning the font (a vector graphic) into a tile sheet (pixels). It does so at a certain resolution. Then it samples the texture sheet based on the nearest pixel. This is unlike regular textures, which get mipmapped and linearly interpolated. After fiddling around with the source, it became apparent that this is because the same font rendering process is used for blender’s main interface. Applying mipmapps and linear filtering resulted in blurry text for the whole of blender!

The workaround
The font-tilesheet itself is properly anti-aliased, just the sampling of the image when it’s rendered is poor. As a result, if we match the tile-sheet resolution with the resolution displayed in-game, then we can achieve perfectly anti-aliased text. Just to complicate things, the number you put into a font’s “resolution” using pythons API is not the pixel size of the tilesheet - or the number of pixels per character. Coming from the source code we have:

#define BGE_FONT_RES 100
... snip ...
// HARDCODED MULTIPLICATION FACTOR - this will affect the render resolution directly
const float RES = BGE_FONT_RES * m_resolution;
const float size = fabs(m_fsize * NodeGetWorldScaling()[0] * RES);

For those that don’t understand C, this translates as:
A font objects resolution is measured in units of 100px/bu

[SUB]Side note: this is why animating the scale of a font object results in very poor performance.[/SUB]

As a result, once we have computed the ideal size of our image, we need to scale it by this factor. In practice, this looks like the function:

import bge

def fix_text(obj):
    '''Compute a font object's ideal resolution assuming an orthographic camera'''
    # Defined in blender source: an object 1 unit high with a resultion of 1 will have 100px
    default_px_per_bu = 100  

    window_width = bge.render.getWindowWidth()

    # Measure the size of the font object (height)
    text = obj.text
    obj.text = '|'
    obj_height = obj.dimensions[1]
    obj.text = text
    if not obj.scene.active_camera.perspective:
        view_width = obj.scene.active_camera.ortho_scale
        raise Exception("Only for orthographic cameras at the moment")

    pixel_ratio = window_width / view_width # pixels / bu
    obj_pixels = pixel_ratio * obj_height

    obj.resolution = obj_pixels / obj_height / default_px_per_bu

Currently I only have support in that function for orthographic cameras, because on a perspective camera, the font will change size as the camera moves. So, the above code is only useful for creating GUI’s and menu’s

Sample blend file:
Fonts.blend (463 KB)

Fantastic! Thanks so much :slight_smile:

I pulled this script off of Stack Exchange a while back:

import bge

scene = bge.logic.getCurrentScene()
for obj in scene.objects:
    if obj.name == "Text": # text object we want to fix
        obj.resolution = 8.0 # resolution is normaly 1.0 / 72 dpi

Yup, a fixed (high) resolution is often suitable, but will lead to aliasing artifacts