objarni's picture

Texture font experiment

I've thought about trying out "texture font rendering" for some weeks.

So I google "texture font" and grabbed the first available font. It was a 512x512 pixel PNG in RGBA format (32-bit) featuring a black/white simple font. It'll do.

First thing I did was load the texture into the GLControl tutorial program, to just be able to render a.s.a.p.

Next step: render the whole 512x512 texture as a single quad all over the viewport, to see how it looks when resizing the window.

Images

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
objarni's picture

OK finished the WriteText method. I just called WriteCharacter for each character, while translating 16 pixels right efter each call. Note that the characters are 32 pixels; when translating that much it just looked silly (too much space inbetween characters).

Next step: take a break.

objarni's picture

Update: I experimented a little with scaling and rotation. Scaling down to 50% of original size didn't look good.

That might be improved if I start using mipmaps, but I don't remember the APIs for those now (it's Glu something, right?).

However 75% look OK:

Fiddler, about your question, there are some practical advantages to textured fonts:

  • You know they will look 100% the same on all OpenTK platforms
  • There will be no question of the font being available or not (of course you could just ship the .ttf file with the app' but anyway)
  • They give graphical designers 100% artistic freedom regarding colors (eg. a font made of true color trees and bushes is conceivable)

Of course the downside is you loose unicode, slicker glyph-layouting (including non-fixed-width characters) and a a wider range of acceptably-looking sizes for the font.

So it is by no means as general as TextPrinter. But for a simple game it is quite alright anyway.

More to do: wrap this "texture font thing" into some usable class. Maybe add it to the TexLib, a new class "TextureFont" which takes a path in the constructor, and has a "WriteString" method as only public method.

the Fiddler's picture

Indeed, you could achieve more fancy effects using premade bitmaps. To generate mipmaps:

  • bind the texture and call GL.GenerateMipmap (needs GL3-capable drivers, but not a GL3 context or GL3 hardware)
  • bind the texture and call GL.TexParameter with the GenerateMipmaps token (if I remember the token correctly) (suitable for pre-GL3 drivers, deprecated in GL3)
  • scale the bitmap through GDI+ and upload each texture level (supported everywhere, but slow)
  • use some glu function (don't do that, seriously. Glu is marked as deprecated starting with OpenTK 0.9.9)
objarni's picture

Thanks. If I understand your points correctly - they are mutually exclusive? Alternatives to do the same thing?

If that is the case I think I will go the GDI+ road for now. The most portable way would be to compute it "by hand" - it's not that hard, just average the colors from parent bitmap's "four pixel neighbourhood". Maybe there are more fancy filters to do this, but that will be quite alright. Maybe the project for some other TexLib journey ;)

objarni's picture

I should add that I had to "remove borders" in the WriteCharacter method. I saw some artefacts close to edges, I thought it must be "leftovers" from neighbouring characters in the texture font. So I used only a 26x26 square of each character instead of the full 32x32 square. That removed the artefacts. And then I had to change the character-spacing to 18 instead of 16 since each character got relatively bigger (due to the 26x26 instead of 32x32 cutout).

In code:

    private void WriteCharacter(char ch)
    {
      byte ascii;
      unchecked { ascii = (byte)ch; }
      int row = ascii / 16;
      int col = ascii % 16;
      GL.BindTexture(TextureTarget.Texture2D, texture);
      int skip = 3; // this is the border "skip" size, to avoid near-border-artefacts
      int size = 32, sizefloor = size - skip;
      float dim = (float)(size * 16);
      float left = (col * size + skip) / dim;
      float top = (row * size + skip) / dim;
      float right = (col * size + sizefloor) / dim;
      float bottom = (row * size + sizefloor) / dim;
      GL.Begin(BeginMode.Quads);
        GL.TexCoord2(left, top); GL.Vertex2(0, sizefloor);
        GL.TexCoord2(right, top); GL.Vertex2(sizefloor, sizefloor);
        GL.TexCoord2(right, bottom); GL.Vertex2(sizefloor, 0);
        GL.TexCoord2(left, bottom); GL.Vertex2(0, 0);
      GL.End();
    }
Inertia's picture

Why do you precalculate sizefloor but do not use the reciprocal of dim and replace 4 divisions with multiplications?

objarni's picture

.. because I'm no fan of premature optimization, and frankly I'm not finished with the code. It's kind of rough :)

Inertia's picture

It's not just about speed, when you change dim to be a reciprocal and for some reason size==0, the division by zero exception will be thrown exactly in the line where it needs to be addressed, and not 1 line later for float left = etc....
You will have to consider the possibility that size is not always 32 when dealing with kerning, in most cases a fixed-width font won't be sufficient. Just trying to help, no intent to change the subject to have our 10th "optimization is the root of all evil" flamewar. :P

objarni's picture

Just trying to help, no intent to change the subject to have our 10th "optimization is the root of all evil" flamewar. :P

OK thanks! And yes, we always seem get into a debate about these kind of issues. Let's just agree that we disagree on this subject!

Edit: actually you got me thinking "why am I using integers at all?". When generalizing to any bitmap size, 128x128, 256x256, 512x512 and so on (might even be 256x512!) the computations are better suited for floating point / uniform 0.0 .. 1.0 texture coordinates.

I think I will have a class with this kind of API:

int texName = // create & load texture object
TextureFont texFont = new TextureFont(texName);
texFont.HorizontalStep = 0.75; // the step to take for each character; 0.75 might be good default
texFont.HorizontalBorder = 0.1; // horizontal "skip border" around each character;
texFont.VerticalBorder = 0.1; // likewize for vertical border
texFont.WriteString("1234"); // will cover modelspace rectangle [0,0]x[3,1]

The height of the output will always be 1.0 in modelspace, and HorizontalStep*numberOfCharacter wide, so the user doesn't have to think of the exact bitmap size of the texture font. Just rescale to whatever works.

The "border skip area" I'm talking about - to remove the artefacts seen without such a border - do they have a common nomenclature in the font rendering community? Something like "crest"?

.. Getting coding hungry .. :)

objarni's picture

OK I've found some resources detailing the nomenclature used in the "font community".

The "HorizontalStep" I mention is called "Advance width".
The area covering the character/glyph is called "Bounding Box" (duh!).

So updating my class interface:

int texName = // create & load texture object
TextureFont texFont = new TextureFont(texName);
texFont.AdvanceWidth = 0.75; // the step to take for each character; 0.75 might be good default
texFont.BoundingBoxWidth = 0.8; // to avoid artefacts when rotating/scaling
texFont.BoundingBoxHeight = 0.8; // - " -
texFont.WriteString("1234"); // will cover modelspace rectangle [-1.5, -0.5]x[1.5, 0.5]

I'm also thinking it might be simpler to render the text centered around model space coordinate (0,0). Then it would be really simple to put text where you want it on screen, centered, which I think is the most common scenario (eg. text above or beneath player avatar or scores).