This post describes changes made to the font generation and rendering system in 1.43.
Contents
Intro
Up until now, all of our text rendering was done using plain raster fonts. While this approach could have been considered adequate years ago, in the days of 4K resolutions and high DPIs it no longer does the job fitting the requirements imposed by today's standards for modern games. The solution comes in the form of signed distance fields. Instead of storing straight raster information like we would in an image, the texture encodes distance to the nearest edge, effectively allowing us to approximate the actual shape of the glyph when rendering. The main benefit of this technique is that the glyph becomes in theory infinitely scalable, while also allowing us to apply consistent anti-aliasing regardless of size on-screen. It also allows allows us to apply a multitude of effe/cts at runtime, such as outlines or glow.
While the resulting individually generated atlases can be larger in size, we only have to generate them once to cover all the possible font sizes. For example, in cases of ATS/ETS2, we no longer need multiple texture sets for the normal, normal_o, title, big, small base fonts, since they are all based on the same source TTF file. Additionally, we can also use this to create multiple effect variations of the same font at virtually no additional cost.
Sii Definitions
The new font definition format is based on the old one. While new parameters have been added, old definitions remain mostly compatible with the new generator.
Notable changes:
- Size of glyphs in the texture itself no longer has any relationship to how big the text is on screen. They should be as big as required to encode enough detail without artifacting at all sizes we expect to see on screen. As such, the width and height parameters have been replaced with a single size parameter, while scale_width, scale_height remain and are used to add additional per-atlas scaling when rendering. Note that we now store per-atlas sizing information in all generated fonts, so as far as text sizing goes, everything should appear the way you expect it to on-screen.
- A new optional texture_size parameter can be used to directly specify the size of the generated texture to increase/decrease quality of the text. By default, this is chosen automatically by the generator based on the requested size specifications; however, if you feel like the result doesn't look good enough, you can manually use this parameter to force the generator to create a larger texture. You can also make the texture smaller to save storage, provided the impact on visual quality is negligible.
- The generator now supports OTF fonts as well.
texture_size: (256, 256) # Force the generator to create a 256x256 texture for this atlas.
Note: Tip: To reach the best size/quality ratio, you should start with a small texture size and increase it until the font reaches the desired quality.
- An optional sdf_range parameter specifies the size of the distance field, which needs to be big enough to allow us to properly render edge-based effects such as antialiasing, soft shadows, outlines etc. The generator calculates this automatically based on requirements. The only time you may need to specify it yourself is when using atlases shared accross multiple fonts - child font may use effects incompatible with the SDF range provided by the parent font, in which case the generator will tell you the size the minimum SDF range the parent font needs to use to accommodate. Note that increasing SDF range reduces quality as less data can be used to encode fine details, so you may have to specify a larger texture size to compensate.
- antialias/gradient parameters have been removed, as they are either no longer needed or weren't used at all.
An example of a font definition using a single atlas:
SiiNunit
{
configuration : .cfg
{
base: "../../base"
output: "/font/normal"
char_config[]: .cfg0
line_spacing: 2
vertical_span_bias: 1
include_escaped_charset: false
}
char_set_config : .cfg0
{
# Base atlas parameters based on the old format
font_file:"papyrus_regular.ttf"
character_set_file: "charset.txt"
size: 38 # EM size
scale_width: 1.0 # X-scaling factor (applied when rendering)
scale_height: 1.0 # Y-scaling factor (applied when rendering)
# Optional: base color
base_color: (1.0, 0.0, 0.0, 1.0) # default is (1, 1, 1, 1)
# Optional: kerning bias
kerning_bias: 2 # glyph kerning bias (px) at full scale
# Optional: thickness bias
thickness_bias: 1 # glyph thickness_bias (px) at full scale
# Optional: outline parameters
outline_width: 1 # outline width (px) at full scale
outline_color: (0.0, 0.5, 0.0, 1.0) # default is (0, 0, 0, 1)
# Optional: drop shadow parameters
shadow_angle: -45 # shadow angle in degrees
shadow_distance: 2.0 # shadow distance (px) at full scale
shadow_alpha: 0.4 # shadow alpha (0-1)
shadow_size: 2 # shadow size (px) at full scale (0 - hard shadow, >0 - softer, spread out shadow)
# Optional: glow parameters
glow_color: (0.0, 1.0, 0.0)
glow_size: 5 # glow size (px) at full scale
# Optional: bevel parameters
bevel_angle: -45 # bevel angle in degrees (should be same as shadow for consistent lighting)
bevel_alpha: 0.5 # bevel alpha (0-1)
bevel_size: 1.5 # bevel size (px) at full scale
# Optional: additional MSDFGen parameters
# Expert-only, see \etc\font\msdf-atlas-gen.exe -help for more info.
additional_msdfgen_parameters: "-errorcorrection full-auto"
}
}
Cutting corners
By default, we use three colour channels to encode distance field infomation (a method known as MSDF). This allows us to preserve hard corners, at the cost of increased storage requirements.
If you don't need sharp corners and/or need to cut filesize down, you can use the following parameter in your atlas definition:
sharp_corners: false
Note: If you're using a font that's rounded in the first place, you should also consider doing this.
This will result in a 66% decrease in memory requirements, at the cost of everything looking a bit more rounded. Note that this also allows you to use a larger texture size while still saving some memory; we currently employ this in CJK fonts where sharp corners aren't a must.
Atlas Sharing
normal.sii:
SiiNunit
{
configuration : normal.cfg # parent font configuration can't be nameless!
{
base: "../../base"
output: "/font/normal"
char_config[]: .cfg0
char_config[]: .cfg1
char_config[]: .cfg2
char_config[]: .cfg3
char_config[]: .cfg4
char_config[]: .cfg5
line_spacing: 0
default_scale: 0.83
include_escaped_charset: true
}
char_set_config : .cfg0
{
font_file:"arialbd.ttf"
character_set_file: "charset.txt"
scale_width: 0.96
size: 17
scale_height: 0.96
texture_size: (1024, 512)
sdf_range: 8
}
...
normal_o.sii:
SiiNunit
{
configuration : .cfg
{
base: "../../base"
output: "/font/normal_o"
char_config[]: .cfg0
char_config[]: .cfg1
char_config[]: .cfg2
char_config[]: .cfg3
char_config[]: .cfg4
char_config[]: .cfg5 # number of atlases needs to match!
line_spacing: 0
atlas_source: "./normal.sii"
}
char_set_config : .cfg0
{
# source ttf and charset data automatically pulled from normal.sii
size: 14
scale_width: 1.0
scale_height: 1.0
outline_width: 2
}
...