Text rendering - Minification?

Started by
11 comments, last by L. Spiro 10 years, 3 months ago

Ok, so I've implemented my own BitMap font system. Using the textures and description file produced by Angel Code's BMFont (http://www.angelcode.com/products/bmfont/) I can display properly formated text (kerning included!) in my project. Since I'm also using a custom spritebatch class, the text rendering is rather fast too.

There is just one problem: It looks like utter shite.

You see, I use a font with character size at 50 pixels. Then in my project I use various sizes (all smaller than the original font) which i achieve by scaling. Then I also support various resolutions (yet more scalling for my font). The final result is barely legible (moth-eaten text without mipmaps, smudged-out text with mipmaps).

Now before someone suggests SDF (Signed Distance Field) text, I must point out that, although being able to render almost any size of text, at any angle, is a remarkable thing, that my needs consist entirely of displaying scaled-down text, without any rotation. And, I've seen mention that SDFs are not that great with rendering minified text.

Another thing I've noticed is the positioning of the individual glyphs on screen. Integer positions are supposed to give a much cleaner result, but how do i manage to do this when I also have kerning involved?

So, what is the best way to render minified text?

Advertisement

Trilinear filtering might help with the mipmapping a little perhaps.

However, probably the best/easiest fix is to generate some more fonts which you can switch to when you're rendering. So instead of a one-size-fits-all 50 pixel source, have a 15 pixel, a 30 pixel and a 50 pixel version. You might even find that your chosen typeface looks poor at 15 pixel, so you might need to use a different font for very small characters, one which has been optimised specifically for small sizes.

Trilinear filtering might help with the mipmapping a little perhaps.

However, probably the best/easiest fix is to generate some more fonts which you can switch to when you're rendering. So instead of a one-size-fits-all 50 pixel source, have a 15 pixel, a 30 pixel and a 50 pixel version. You might even find that your chosen typeface looks poor at 15 pixel, so you might need to use a different font for very small characters, one which has been optimised specifically for small sizes.

Hm, I wonder, maybe there is a way to use smaller fonts as mipmaps? I mean, I generate one large 64pix font texture, then use a 32pix, 16pix, 8pix etc font generated by BMFont, and put these in the original 64pix texture as its mipmaps.

Or do character details (width, xadvance etc...) do not change linearly with scalling?

Edit: Gah..., after a bunch of experimenting, I can't get BMFont to give a consistent layout of the glyphs. It keeps rearranging them when I specify different texture sizes. I think I'm doing everything correctly (64pix font in a 512x512 texture with 8 spacing on either side of each character, then a 32pix font in 256x256 texture with 4 spacing, but BMFont keeps changing each characters possition.!.....)

I implemented BMFont rendering in an OpenTK project, and remember having to tweak the output settings a bit to get the text to look good. I don't have the config file I ended up using handy, but I remember it taking some experimentation. The end result is pleasing to me, with both min- and mag- (I just let OpenGL create the mipmaps). Of course, I suppose it depends on how much scaling you are doing.

Maybe you can post some screen shots of your results, maybe someone might see something there?

EDIT: ah, found the config I'm using:


# AngelCode Bitmap Font Generator configuration file
fileVersion=1

# font settings
fontName=Open Sans
fontFile=
charSet=0
fontSize=24
aa=1
scaleH=100
useSmoothing=1
isBold=0
isItalic=0
useUnicode=1
disableBoxChars=1
outputInvalidCharGlyph=0
dontIncludeKerningPairs=0
useHinting=1
renderFromOutline=1
useClearType=0

# character alignment
paddingDown=0
paddingUp=0
paddingRight=0
paddingLeft=0
spacingHoriz=1
spacingVert=1
useFixedHeight=0
forceZero=0

# output file
outWidth=256
outHeight=256
outBitDepth=32
fontDescFormat=1
fourChnlPacked=0
textureFormat=png
textureCompression=0
alphaChnl=0
redChnl=4
greenChnl=4
blueChnl=4
invA=0
invR=0
invG=0
invB=0

# outline
outlineThickness=0

# selected chars
chars=32-126

# imported icon images

Your spacing is too small (1 pixel). If you want mipmaps the spacing should be at least 8. (Personally I think mips 0, 1, 2, are enough, anything smaller is completely useless, so a spacing of 4 should be good enough).

I don't have the complete code base in front of me, but I believe these are the settings that worked fine for my rendering implementation (likely I was using texture clamping). That said, the exact settings depend on the rendering methods used.

How are you generating the mipmaps?

If you are letting Direct3D or OpenGL do it for you it is expected to look like crap; they default to a box filter.

If you have to do it through image filtering, the best you can use is a Kaiser filter.

But for best results, the tool should be able to redraw all the font images at half resolutions and create a mipmap chain for you. If this is not possible, use a Kaiser filter for sharper scaled-down text.

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Hi Spiro.

I generate the font by converting the png file I get from BMFont to dds,

using Paint.Net, A8R8G8B8, with supersampling for the mipmaps.

I am pretty sure this method creates the best mipmaps possible, but please correct me if I'm wrong.

(Actualy, I first edit the BMFont png file to premultiplied alpha, using Pixelformer,

as I do all my drawing with a blendstate that expects premultiplied textures.)

In my shader, I use trilinear filtering, and a mipmap bias of -0.65f.

This does help a bit, but not by much.

Here is the result:

example.png

I have come to the conclusion that there are two problems. First, the mipmaps are awful:

font.png

Mipmap 1 is visibly worse than what BMFont would create at half of the originals pixel width.

Also the subsequent mipmaps are worse than useless.

Then there is the fact that screen positioning plays a very big role in how the final text will appear. And it is not a simple matter of setting the glyph positions to integer values. For example, the left square bracket '[' looks good when you force its left coordinates to integers, while the right square bracked ']' needs its right coordinate to be an integer. Nevermind how all this would ruin the effect of kerning.

To get proper mipmap usage you need padding between the glyphs that match the number of mipmaps. For example if you have 2 mip-levels (original image + one scaled to half size), the glyphs must be aligned on 2-pixel boundaries and must have two pixels padding between them (to avoid texture filtering to cause bleeding of adjacent glyphs in the texture).

If you need another mip-level, that requires 4-pixel alignment, and the next 8 pixels.

If your original glyphs are 32x32 and you allow two extra mip-levels that will be 16x16 and 8x8 and smaller glyphs than that probably aren't readable anyway so I wouldn't allow smaller mipmaps but let them pixelize if they get smaller (so just create the texture with only 3 mip-levels).

^ Yeah. In the font i posted, I use a spacing of 4 pixels (on either side), and in my project, I specify that only the first 2 mipmaps are used (D3DX10_IMAGE_LOAD_INFO MipFilter = 3)

This topic is closed to new replies.

Advertisement