You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and dots ('.'), can be up to 35 characters long. Letters must be lowercase.
94 lines
4.5 KiB
94 lines
4.5 KiB
# Font character oversampling for rendering from atlas textures |
|
|
|
TL,DR: Run oversample.exe on a windows machine to see the |
|
benefits of oversampling. It will try to use arial.ttf from the |
|
Windows font directory unless you type the name of a .ttf file as |
|
a command-line argument. |
|
|
|
## Benefits of oversampling |
|
|
|
Oversampling is a mechanism for improving subpixel rendering of characters. |
|
|
|
Improving subpixel has a few benefits: |
|
|
|
* With horizontal-oversampling, text can remain sharper while still being sub-pixel positioned for better kerning |
|
* Horizontally-oversampled text significantly reduces aliasing when text animates horizontally |
|
* Vertically-oversampled text significantly reduces aliasing when text animates vertically |
|
* Text oversampled in both directions significantly reduces aliasing when text rotates |
|
|
|
## What text oversampling is |
|
|
|
A common strategy for rendering text is to cache character bitmaps |
|
and reuse them. For hinted characters, every instance of a given |
|
character is always identical, so this works fine. However, stb_truetype |
|
doesn't do hinting. |
|
|
|
For anti-aliased characters, you can actually position the characters |
|
with subpixel precision, and get different bitmaps based on that positioning |
|
if you re-render the vector data. |
|
|
|
However, if you simply cache a single version of the bitmap and |
|
draw it at different subpixel positions with a GPU, you will get |
|
either the exact same result (if you use point-sampling on the |
|
texture) or linear filtering. Linear filtering will cause a sub-pixel |
|
positioned bitmap to blur further, causing a visible de-sharpening |
|
of the character. (And, since the character wasn't hinted, it was |
|
already blurrier than a hinted one would be, and now it gets even |
|
more blurry.) |
|
|
|
You can avoid this by caching multiple variants of a character which |
|
were rendered independently from the vector data. For example, you |
|
might cache 3 versions of a char, at 0, 1/3, and 2/3rds of a pixel |
|
horizontal offset, and always require characters to fall on integer |
|
positions vertically. |
|
|
|
When creating a texture atlas for use on GPUs, which support bilinear |
|
filtering, there is a better approach than caching several independent |
|
positions, which is to allow lerping between the versions to allow |
|
finer subpixel positioning. You can achieve these by interleaving |
|
each of the cached bitmaps, but this turns out to be mathematically |
|
equivalent to a simpler operation: oversampling and prefiltering the |
|
characters. |
|
|
|
So, setting oversampling of 2x2 in stb_truetype is equivalent to caching |
|
each character in 4 different variations, 1 for each subpixel position |
|
in a 2x2 set. |
|
|
|
An advantage of this formulation is that no changes are required to |
|
the rendering code; the exact same quad-rendering code works, it just |
|
uses different texture coordinates. (Note this does potentially increase |
|
texture bandwidth for text rendering since we end up minifying the texture |
|
without using mipmapping, but you probably are not going to be fill-bound |
|
by your text rendering.) |
|
|
|
## What about gamma? |
|
|
|
Gamma-correction for fonts just doesn't work. This doesn't seem to make |
|
much sense -- it's physically correct, it simulates what we'd see if you |
|
shrunk a font down really far, right? |
|
|
|
But you can play with it in the oversample.exe app. If you turn it on, |
|
white-on-black fonts become too thick (i.e. they become too bright), and |
|
black-on-white fonts become too thin (i.e. they are insufficiently dark). There is |
|
no way to adjust the font's inherent thickness (i.e. by switching to |
|
bold) to fix this for both; making the font thicker will make white |
|
text worse, and making the font thinner will make black text worse. |
|
Obviously you could use different fonts for light and dark cases, but |
|
this doesn't seem like a very good way for fonts to work. |
|
|
|
Multiple people who have experimented with this independently (me, |
|
Fabian Giesen,and Maxim Shemanarev of Anti-Grain Geometry) have all |
|
concluded that correct gamma-correction does not produce the best |
|
results for fonts. Font rendering just generally looks better without |
|
gamma correction (or possibly with some arbitrary power stuck in |
|
there, but it's not really correcting for gamma at that point). Maybe |
|
this is in part a product of how we're used to fonts being on screens |
|
which has changed how we expect them to look (e.g. perhaps hinting |
|
oversharpens them and prevents the real-world thinning you'd see in |
|
a black-on-white text). |
|
|
|
(AGG link on text rendering, including mention of gamma: |
|
http://www.antigrain.com/research/font_rasterization/ ) |
|
|
|
Nevertheless, even if you turn on gamma-correction, you will find that |
|
oversampling still helps in many cases for small fonts.
|
|
|