NeHe Productions: OpenGL Lesson #13Date de publication : 31/03/2006 , Date de mise à jour : 31/03/2006
Par
Jeff Molofee ( NeHe ) (Autres articles)
Lesson: 13 Welcome to yet another Tutorial. This time on I'll be teaching you how to use Bitmap Fonts.
You may be saying to yourself "what's so hard about putting text onto the screen". If you've
ever tried it, it's not that easy!
Sure you can load up an art program, write text onto an image, load the image into your OpenGL
program, turn on blending then map the text onto the screen. But this is time consuming, the
final result usually looks blurry or blocky depending on the type of filtering you use, and
unless your image has an alpha channel your text will end up transparent (blended with the
objects on the screen) once it's mapped to the screen.
If you've ever used Wordpad, Microsoft Word or some other Word Processor, you may have noticed
all the different types of Font's available. This tutorial will teach you how to use the
exact same fonts in your own OpenGL programs. As a matter of fact... Any font you install on
your computer can be used in your demos.
Not only do Bitmap Fonts looks 100 times better than graphical fonts (textures). You can change the text
on the fly. No need to make textures for each word or letter you want to write to the screen. Just
position the text, and use my handy new gl command to display the text on the screen.
I tried to make the command as simple as possible. All you do is type glPrint("Hello"). It's
that easy. Anyways. You can tell by the long intro that I'm pretty happy with this tutorial. It
took me roughly 1 1/2 hours to create the program. Why so long? Because there is literally
no information available on using Bitmap Fonts, unless of course you enjoy MFC code. In order
to keep the code simple I decided it would be nice if I wrote it all in simple to understand
C code :)
A small note, this code is Windows specific. It uses the wgl functions of Windows to build the
font. Apparently Apple has agl support that should do the same thing, and X has glx.
Unfortunately I can't guarantee this code is portable. If anyone has platform independant code
to draw fonts to the screen, send it my way and I'll write another font tutorial.
We start off with the typical code from lesson 1. We'll be adding the stdio.h header file for standard
input/output operations; the stdarg.h header file to parse the text and convert variables to text,
and finally the math.h header file so we can move the text around the screen using SIN and COS.
We're going to add 3 new variables as well. base will hold the number of the first display list we create.
Each character requires it's own display list. The character 'A' is 65 in the display list, 'B' is 66, 'C' is 67,
etc. So 'A' would be stored in display list base+65.
Next we add two counters (cnt1 & cnt2). These counters will count up at different rates, and
are used to move the text around the screen using SIN and COS. This creates a semi-random looking movement on
the screen. We'll also use the counters to control the color of the letters (more on this later).
The following section of code builds the actual font. This was the most difficult part of the
code to write. 'HFONT font' in simple english tells Windows we are going to be manipulating
a Windows font. oldfont is used for good house keeping.
Next we define base. We do this by creating a group of 96 display lists using glGenLists(96).
After the display lists are created, the variable base will hold the number of the first list.
Now for the fun stuff. We're going to create our font. We start off by specifying the size
of the font. You'll notice it's a negative number. By putting a minus, we're telling windows
to find us a font based on the CHARACTER height. If we use a positive number we match the font
based on the CELL height.
Then we specify the cell width. You'll notice I have it set to 0. By setting values to 0,
windows will use the default value. You can play around with this value if you want. Make
the font wide, etc.
Angle of Escapement will rotate the font. Unfortunately this isn't a very useful feature.
Unless your at 0, 90, 180, and 270 degrees, the font usually gets cropped to fit inside it's
invisible square border. Orientation Angle quoted from MSDN help Specifies the angle, in tenths
of degrees, between each character's base line and the x-axis of the device. Unfortunately I have
no idea what that means :(
Font weight is a great parameter. You can put a number from 0 - 1000 or you can use one of
the predefined values. FW_DONTCARE is 0, FW_NORMAL is 400, FW_BOLD is 700 and FW_BLACK is 900.
There are alot more predefined values, but those 4 give some good variety. The higher the
value, the thicker the font (more bold).
Italic, Underline and Strikeout can be either TRUE or FALSE. Basically if underline is TRUE,
the font will be underlined. If it's FALSE it wont be. Pretty simple :)
Character set Identifier describes the type of Character set you wish to use. There are too
many types to explain. CHINESEBIG5_CHARSET, GREEK_CHARSET, RUSSIAN_CHARSET, DEFAULT_CHARSET,
etc. ANSI is the one I use, although DEFAULT would probably work just as well.
If you're interested in using a font such as Webdings or Wingdings, you need to use
SYMBOL_CHARSET instead of ANSI_CHARSET.
Output Precision is very important. It tells Windows what type of character set to use if
there is more than one type available. OUT_TT_PRECIS tells Windows that if there is more than
one type of font to choose from with the same name, select the TRUETYPE version of the font.
Truetype fonts always look better, especially when you make them large. You can also use
OUT_TT_ONLY_PRECIS, which ALWAYS trys to use a TRUETYPE Font.
Clipping Precision is the type of clipping to do on the font if it goes outside the clipping
region. Not much to say about this, just leave it set to default.
Output Quality is very important. You can have PROOF, DRAFT, NONANTIALIASED, DEFAULT or
ANTIALIASED. We all know that ANTIALIASED fonts look good :) Antialiasing a font is the
same effect you get when you turn on font smoothing in Windows. It makes everything look less
jagged.
Next we have the Family and Pitch settings. For pitch you can have DEFAULT_PITCH,
FIXED_PITCH and VARIABLE_PITCH, and for family you can have FF_DECORATIVE, FF_MODERN,
FF_ROMAN, FF_SCRIPT, FF_SWISS, FF_DONTCARE. Play around with them to find out what they do.
I just set them both to default.
Finally... We have the actual name of the font. Boot up Microsoft Word or some other text
editor. Click on the font drop down menu, and find a font you like. To use the font,
replace 'Courier New' with the name of the font you'd rather use.
oldFont stores the previously used font. SelectObject will return the font (or pen, or filler, or whatever GDI objects)
that was previously set when it switches to the new font. The way that GDI works is such that the use of the return
value of SelectObject is not very apparent. At first glance it looks as if the code is selecting the new font and
returning a pointer and storing it within oldFont.
The following code is pretty simple. It deletes the 96 display lists from memory starting at
the first list specified by 'base'. I'm not sure if windows would do this for you, but it's
better to be safe than sorry :)
Now for my handy dandy GL text routine. You call this section of code with the command
glPrint("message goes here"). The text is stored in the char string *fmt.
The first line below creates storage space for a 256 character string. text is the string we will end up
printing to the screen. The second line below creates a pointer that points to the list of arguments we
pass along with the string. If we send any variables along with the text, this will point to them.
The next two lines of code check to see if there's anything to display? If there's no text, fmt
will equal nothing (NULL), and nothing will be drawn to the screen.
The following three lines of code convert any symbols in the text to the actual numbers the symbols represent.
The final text and any converted symbols are then stored in the character string called "text". I'll explain
symbols in more detail down below.
We then push the GL_LIST_BIT, this prevents glListBase from affecting any other display lists we may be using
in our program.
The command glListBase(base-32) is a little hard to explain. Say we draw the letter 'A', it's
represented by the number 65. Without glListBase(base-32) OpenGL wouldn't know where to find this letter.
It would look for it at display list 65, but if base was equal to 1000, 'A' would actually
be stored at display list 1065. So by setting a base starting point, OpenGL knows where to
get the proper display list from. The reason we subtract 32 is because we never made the first
32 display lists. We skipped them. So we have to let OpenGL know this by subtracting 32
from the base value. I hope that makes sense.
Now that OpenGL knows where the Letters are located, we can tell it to write the text to the
screen. glCallLists is a very interesting command. It's capable of putting more than one
display list on the screen at a time.
The line below does the following. First it tells OpenGL we're going to be displaying lists to the screen.
strlen(text) finds out how many letters we're going to send to the screen. Next it needs to know what the
largest list number were sending to it is going to be. We're not sending any more than 255 characters.
The lists parameter is treated as an array of unsigned bytes, each in the range 0 through 255. Finally we
tell it what to display by passing text (pointer to our string).
In case you're wondering why the letters don't pile on top of eachother. Each display list
for each character knows where the right side of the letter is. After the letter is drawn,
OpenGL translates to the right side of the drawn letter. The next letter or object drawn will
be drawn starting at the last location GL translated to, which is to the right of the last
letter.
Finally we pop the GL_LIST_BIT setting GL back to how it was before we set our base setting
using glListBase(base-32).
The only thing different in the Init code is the line BuildFont(). This jumps to the code above that builds the font
so OpenGL can use it later on.
Now for the drawing code. We start off by clearing the screen and the depth buffer. We call
glLoadIdentity() to reset everything. Then we translate one unit into the screen. If we don't
translate, the text wont show up. Bitmap fonts work better when you use an ortho projection
rather than a perspective projection, but ortho looks bad, so to make it work in projection,
translate.
You'll notice that if you translate even deeper into the screen the size of the font does not
shrink like you'd expect it to. What actually happens when you translate deeper is that you
have more control over where the text is on the screen. If you translate 1 unit into the screen,
you can place the text anywhere from -0.5 to +0.5 on the X axis. If you translate 10 units into the
screen, you place the text from -5 to +5. It just gives you more control instead of using
decimal places to position the text at exact locations. Nothing will change the size of the
text. Not even glScalef(x,y,z). If you want the font bigger or smaller, make it bigger or
smaller when you create it!
Now we use some fancy math to make the colors pulse. Don't worry if you don't understand
what I'm doing. I like to take advantage of as many variables and stupid tricks as I can to
achieve results :)
In this case I'm using the two counters we made to move the text around the screen to change
the red, green and blue colors. Red will go from -1.0 to 1.0 using COS and counter 1. Green
will also go from -1.0 to 1.0 using SIN and counter 2. Blue will go from 0.5 to 1.5 using
COS and counter 1 and 2. That way blue will never be 0, and the text should never completely
fade out. Stupid, but it works :)
Now for a new command. glRasterPos2f(x,y) will position the Bitmapped Font on the screen. The
center of the screen is still 0,0. Notice there's no Z position. Bitmap Fonts only use the
X axis (left/right) and Y axis (up/down). Because we translate one unit into the screen, the far left is -0.5,
and the far right is +0.5. You'll notice that I move 0.45 pixels to the left on the X axis. This
moves the text into the center of the screen. Otherwise it would be more to the right of the
screen because it would be drawn from the center to the right.
The fancy(?) math does pretty much the same thing as the color setting math does. It moves
the text on the x axis from -0.50 to -0.40 (remember, we subtract 0.45 right off the start).
This keeps the text on the screen at all times. It swings left and right using COS and counter
1. It moves from -0.35 to +0.35 on the Y axis using SIN and counter 2.
Now for my favorite part... Writing the actual text to the screen. I tried to make it super
easy, and very user friendly. You'll notice it looks alot like an OpenGL call, combined with
the good old fashioned Print statement :) All you do to write the text to the screen is
glPrint("{any text you want}"). It's that easy. The text will be drawn onto the screen at
the exact spot you positioned it.
Shawn T. sent me modified code that allows glPrint to pass variables to the screen. This means that you
can increase a counter and display the results on the screen! It works like this... In the line below you
see our normal text. Then there's a space, a dash, a space, then a "symbol" (%7.2f). Now you may look at
%7.2f and say what the heck does that mean. It's very simple. % is like a marker saying don't print 7.2f
to the screen, because it represents a variable. The 7 means a maximum of 7 digits will be displayed to the
left of the decimal place. Then the decimal place, and right after the decimal place is a 2. The 2 means
that only two digits will be displayed to the right of the decimal place. Finally, the f. The f means that
the number we want to display is a floating point number. We want to display the value of cnt1 on the
screen. As an example, if cnt1 was equal to 300.12345f the number we would end up seeing on the screen
would be 300.12. The 3, 4, and 5 after the decimal place would be cut off because we only want 2 digits
to appear after the decimal place.
I know if you're an experienced C programmer, this is absolute basic stuff, but there may be people out there that
have never used printf. If you're interested in learning more about symbols, buy a book, or read through the MSDN.
The last thing to do is increase both the counters by different amounts so the colors pulse and
the text moves.
The last thing to do is add KillFont() to the end of KillGLWindow() just like I'm showing below. It's important to
add this line. It cleans things up before we exit our program.
That's it... Everything you need to know in order to use Bitmap Fonts in your own OpenGL
projects. I've searched the net looking for a tutorial similar to this one, and have found
nothing. Perhaps my site is the first to cover this topic in easy to understand C code?
Anyways. Enjoy the tutorial, and happy coding!
Jeff Molofee ( NeHe )
|
Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Nehe Gamedev.net. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.