24

I'm trying to figure out a simple to draw some text in OpenGL. My research has shown its a fairly complex task. It involves creating (or generating at runtime) a font atlas texture, and then creating a quad for each letter with just the right placement and texture coordinates.

I've heard some good things about freetype, but have found very little about how to get it running on the iPhone, and it appears to be pretty complex.

So is there any Objective-C wrapped OpenGL text library that works with the iPhone SDK that people have been using? Or am I just over thinking this and there is an easier approach that I am missing?

genpfault
  • 51,148
  • 11
  • 85
  • 139
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337

8 Answers8

22

One way to do this is by setting up a UILabel and then rendering its layer into a texture. An indirect route to this would be to first set up the UILabel with text, font, etc. and then use the following code

UIGraphicsBeginImageContext(yourLabel.frame.size);
[yourLabel.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *layerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

to capture the UILabel to a UIImage. You could then use the initWithImage: constructor of the Texture2D class in the CrashLanding project to transform this to a texture.

It looks like initWithImage: in that example re-renders the UIImage to a bitmap context and uses that to create the texture. With a little work, you could extract the relevant code from that method and alter the above code to render your layer directly into the texture.

This way, you get the nice Unicode and font support of UILabel when creating the text for your texture.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • I'm trying to avoid generating a different texture on the fly for every rendered string. My app would have many many different strings being displayed and it seems this would be a huge strain on the CPU and memory. – Alex Wayne Feb 04 '09 at 19:35
  • 2
    This is effectively what the iPhone does for every piece of text that you see. All UILabels are CALayer-backed, and CALayers are just wrapped OpenGL textures. I don't think this is as much of a resource hog as you believe. – Brad Larson Feb 04 '09 at 20:59
  • The crash landing demo application no longer seems to be available for download from the iphone developer site, is there a suitable alternative? – rck Mar 01 '09 at 06:53
  • 2
    GLSprite uses a similar technique, so I believe that you may be able to use that in its place. – Brad Larson Mar 01 '09 at 20:35
  • This method isn't so useful since you must call UIGraphicsBeginImageContext inside drawRect method, and this requires creation of a UIKit view solely for rendering the UILabel. Better to do manually using UIGraphicsPushContext. – KomodoDave Jan 16 '12 at 20:31
  • @BradLarson this may well be what iPhone does for text. However, it is slow (sometimes: very slow) - even on iPhone 4S/5 class hardware it only needs a handful of dynamic text to make noticeable impact. The main problem appears to be that text tends to be anti-aliased and have an alpha background, which hurts PowerVR chips. I suspect it's also made worse that Apple's logic for when to re-build the texture is less than perfect (I've noticed a few places in CA where they seem too pessimistic about the underlying textures). – Adam Sep 01 '13 at 09:38
8

Your description of using freetype to create a texture atlas along with all the glyph information is exactly what I'm doing in my current iPhone project.

I do all the parsing of the font in a pre-process step (All in C# on Windows as it happens). The atlas itself gets generated and contains only the characters needed - to cut down on space, I also sometimes end up generating more than a single atlas to keep textures sizes pow2 and of a sensible resolution.

You can easily pull out the glyph information from freetype. Pass that along to your app along with the textures at runtime and then it's fairly trivial to just alter the UV coords of the texture as required.

For what it's worth, this is how I define a glyph character inside my little engine:

struct FontGlyph
{

u32 char_code;

u32 m_x;                // base x position on tpage

u32 m_y;            // base y position on tpage

u32 m_width;            // glyph width

u32 m_height;           // glyph height

i32 m_horiBearingX;     // Distance to X shift

i32 m_horiBearingY;     // Distance to Y shift

u32 m_horiAdvance;      // Total Horizontal advance this character requires

u32 m__tpage_index;     // Which tpage does this glyph use?

};

I then have a 'SpriteString' object, which behaves pretty much like any other string, apart from I can draw it using sprites....

I agree this all might seem like a big job for a fairly trivial problem, but I've found it's given me plenty of flexibility and really didn't take that long to implement when I got down to it.

Feel free to reply or comment if you need any other details. (And good luck :)


Reply to your comment as I ran out of room in the comment section...

Check out the freetype documentation, it basically gives you the glyph data without messing about, it just pulls it right out of the font. As for the textures themselves, you get freetype to render each glyph, and then manage that to build the texture. This I do as a pre-process entirely separate from my main app.

Here's an excerpt of my tool code that does this: http://pastebin.com/md1c6354

Please note though, this was only ever intended for my eyes and not for public consumption, so excuse the fact it's messy as hell :)

Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
Ali Parr
  • 4,737
  • 3
  • 31
  • 35
6

I found a simple solution to this. Here's a quick function I wrote for it. (You will need the Texture2D class.)

- (void) drawText:(NSString*)theString AtX:(float)X Y:(float)Y {
// Use black
glColor4f(0, 0, 0, 1.0);

// Set up texture
Texture2D* statusTexture = [[Texture2D alloc] initWithString:theString dimensions:CGSizeMake(150, 150) alignment:UITextAlignmentLeft fontName:@"Helvetica" fontSize:11];

// Bind texture
glBindTexture(GL_TEXTURE_2D, [statusTexture name]);

// Enable modes needed for drawing
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

// Draw
[statusTexture drawInRect:CGRectMake(X,Y-1,1,1)];   

// Disable modes so they don't interfere with other parts of the program
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);    
}

I believe the input coordinates need to be in your world coordinates, but I'm not sure if that's true in general or if it just works that way for me. You might need to play with the offsets to get it right.

3

Use the "musings" font library:

http://www.themusingsofalostprogrammer.com/2010/01/how-to-do-font-rendering-in-opengl-on.html

Font font("Verdena");
font.print("Hello World!", x, y, z);

Easy :)

It comes with unicode support, loads glyphs "on-demand", and even has a way to output variables

font.print(x, y, z) << "fps: " << fps;
Dustin
  • 1,030
  • 11
  • 15
  • 2
    This library loads each character as a separate texture, which will be very slow if you're doing any decent-sized amount of drawing. It's *much* better to use a texture atlas, as suggested by some of the other answers. – Jesse Beder Aug 06 '10 at 06:09
  • 1
    Sad that this is in OpenGL ES 1 (not 2), and the download link doesn't work anymore.. – Xys Aug 07 '17 at 10:33
3

Thanks for that method. It saved me some time.

There were a couple things I had to change though. The glBlendFunc call needed to be:

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_ALPHA)

And secondly, the text appeared to be drawing into a single point. The CGRectMake call is creating a 1x1 rectangle, unless I'm reading that incorrectly.

2

I've created a Font class similar to the "musings" font library, minus the memory leaks and minus the texture creation for every character, every frame. It still uses a different glTexture for each character though. Note: Texture2D has been changed to comment out its texture deletion.

init in constructor: _font = new Font("Helvetica", 40);

use in redraw() method: _font->draw("Hello, World!", 50, 100, ALIGN_LEFT);

GitHub link:

https://github.com/chandl34/public/tree/master/personal/c%2B%2B/Font

Pieter van den Ham
  • 4,381
  • 3
  • 26
  • 41
1

Yes, there are 2 that I know of. The trick is, you have to render to texture using Quartz (CGContext* functions), then render that texture for the user to see.

If you're concerned about performance, you can also use Quartz to generate your texture atlas.

It's fairly easy to whip up a texture generation tool (in xCode) once you know the basics of rendering a font.

All you have to do is have a valid CGContext. You can find some really great sample code in this texture loader.

Basically the code goes:

// Create the cgcontext as shown by links above

// Now to render text into the cg context,
// the drop the raw data behind the cgcontext
// to opengl.  2 ways to do this:
// 1)  CG* way: (advantage: easy to use color)
CGContextSelectFont(cgContext, "Arial", 24, kCGEncodingMacRoman);

// set color to yellow text
CGContextSetRGBFillColor(cgContext, 1, 1, 0, 1) ; // Be sure to use FILL not stroke!

// draw it in there
CGContextShowTextAtPoint(cgContext, 20, 20, "HI THERE", strlen("HI THERE") ) ;

/*
// WAY #2:  this way it's always black and white
// DRAW SOME TEXT IN IT
// that works ok but let's try cg to control color
UIGraphicsPushContext(cgContext);
UIFont *helv = [UIFont fontWithName:@"Helvetica" size:[UIFont systemFontSize]];
NSString* msg = @"Hi hello" ;  
[msg drawInRect:CGRectMake(0,0,pow2Width,pow2Height) withFont:helv] ;
    UIGraphicsPopContext();
*/

// put imData into OpenGL's memory
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pow2Width, pow2Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imData);
CHECK_GL ;

It comes out looking pretty good and antialiased,

tex atlas

A list of ios fonts is available here, and with prerenders here

bobobobo
  • 64,917
  • 62
  • 258
  • 363
1

Check the crash landing demo. The Texture2D class that is provided allows for creation of textures with text and a font. (Hopefully there will be a better answer I would love a simpler way of drawing to an opengles view)

Nick Van Brunt
  • 15,244
  • 11
  • 66
  • 92
  • I think that is not quite what I'm looking for. I believe that creates texture for a whole string on the fly, which is a CPU intensive operation. Looking for more of a way to construct words out of individual quads so that I can render arbitrary text at runtime. This should be much faster. – Alex Wayne Feb 04 '09 at 17:17
  • Why do you think your proposed way would be quicker? – Jay Jan 09 '10 at 04:55
  • You can't draw to a view without a texture -- it just wouldn't run fast enough – bobobobo Sep 23 '12 at 01:42