NeHe Productions: OpenGL Lesson #17Date de publication : 31/03/2006 , Date de mise à jour : 31/03/2006
Par
Giuseppe D'Agata et Jeff Molofee ( NeHe ) (Autres articles)
Lesson: 17 This tutorial brought to you by NeHe & Giuseppe D'Agata...
I know everyones probably sick of fonts. The text tutorials I've done so far not only display text, they display 3D
text, texture mapped text, and can handle variables. But what happens if you're porting your project to a machine
that doesn't support Bitmap or Outline fonts?
Thanks to Giuseppe D'Agata we have yet another font tutorial.
What could possibly be left you ask!?
If you remember in the first Font tutorial I mentioned using textures
to draw letters to the screen. Usually when you
use textures to draw text to the screen you load up your favorite art
program, select a font, then type the letters or
phase you want to display. You then save the bitmap and load it into
your program as a texture. Not very efficient for a program
that require alot of text, or text that continually changes!
This program uses just ONE texture to display any of 256 different characters on the screen. Keep in mind your
average character is just 16 pixels wide and roughly 16 pixels tall. If you take your standard 256x256 texture it's
easy to see that you can fit 16 letters across, and you can have a total of 16 rows up and down. If you need a
more detailed explanation: The texture is 256 pixels wide, a character is 16 pixels wide. 256 divided by 16 is 16 :)
So... Lets create a 2D textured font demo! This program expands on the code from lesson 1. In the first section of
the program, we include the math and stdio libraries. We need the math library to move our letters around the screen
using SIN and COS, and we need the stdio library to make sure the bitmaps we want to use actually exist before we
try to make textures out of them.
We're going to add a variable called base to point us to our display lists. We'll also add texture[2]
to hold the two textures we're going to create. Texture 1 will be the font texture, and texture 2 will be a bump
texture used to create our simple 3D object.
We add the variable loop which we will use to execute loops. Finally we add cnt1 and cnt2 which
we will use to move the text around the screen and to spin our simple 3D object.
Now for the texture loading code. It's exactly the same as it was in the previous texture mapping tutorials.
The follwing code has also changed very little from the code used in previous tutorials. If you're not sure what
each of the following lines do, go back and review.
Note that TextureImage[ ] is going to hold 2 rgb image records. It's very important to double check code that
deals with loading or storing our textures. One wrong number could result in a memory leak or crash!
The next line is the most important line to watch. If you were to replace the 2 with any other number, major problems
will happen. Double check! This number should match the number you used when you set up TextureImages[ ].
The two textures we're going to load are font.bmp (our font), and bumps.bmp. The second texture can be replaced with
any texture you want. I wasn't feeling very creative, so the texture I decided to use may be a little drab.
Another important line to double check. I can't begin to tell you how many emails I've received from people asking
"why am I only seeing one texture, or why are my textures all white!?!" . Usually this line is the problem. If
you were to replace the 2 with a 1, only one texture would be created and the second texture would appear all white.
If you replaced the 2 with a 3 you're program may crash!
You should only have to call glGenTextures() once. After glGenTextures() you should generate all your textures. I've
seen people put a glGenTextures() line before each texture they create. Usually they causes the new texture to
overwrite any textures you've already created. It's a good idea to decide how many textures you need to build, call
glGenTextures() once, and then build all the textures. It's not wise to put glGenTextures() inside a loop unless
you have a reason to.
The following lines of code check to see if the bitmap data we loaded to build our textures is using up ram. If it is,
the ram is freed. Notice we check and free both rgb image records. If we used 3 different images to build our
textures, we'd check and free 3 rgb image records.
Now we're going to build our actual font. I'll go through this section of code in some detail. It's not really that
complex, but there's a bit of math to understand, and I know math isn't something everyone enjoys.
The following two variable will be used to hold the position of each letter inside the font texture. cx will
hold the position from left to right inside the texture, and cy will hold the position up and down.
Next we tell OpenGL we want to build 256 display lists. The variable base will point to the location of the
first display list. The second display list will be base+1, the third will be base+2, etc.
The second line of code below selects our font texture (texture[0]).
Now we start our loop. The loop will build all 256 characters, storing each character in it's own display lists.
The first line below may look a little puzzling. The % symbol means the remainder after loop is divided by 16.
cx will move us through the font texture from left to right. You'll notice later in the code we subtract
cy from 1 to move us from top to bottom instead of bottom to top. The % symbol is fairly hard to explain but
I will make an attempt.
All we are really concerned about is (loop%16) the /16.0f just converts the results into texture coordinates.
So if loop was equal to 16... cx would equal the remained of 16/16 which would be 0. but cy
would equal 16/16 which is 1. So we'd move down the height of one character, and we wouldn't move to the right at all.
Now if loop was equal to 17, cx would be equal to 17/16 which would be 1.0625. The remainder .0625 is
also equal to 1/16th. Meaning we'd move 1 character to the right. cy would still be equal to 1 because we are
only concerned with the number to the left of the decimal. 18/16 would gives us 2 over 16 moving us 2 characters to
the right, and still one character down. If loop was 32, cx would once again equal 0, because there is no
remained when you divide 32 by 16, but cy would equal 2. Because the number to the left of the decimal
would now be 2, moving us down 2 characters from the top of our font texture. Does that make sense?
Whew :) Ok. So now we build our 2D font by selecting an individual character from our font texture depending on the
value of cx and cy. In the line below we add loop to the value of base if we didn't, every
letter would be built in the first display list. We definitely don't want that to happen so by adding loop to base,
each character we create is stored in the next available display list.
Now that we've selected the display list we want to build, we create our character. This is done by drawing a quad,
and then texturing it with just a single character from the font texture.
cx and cy should be holding a very tiny floating point value from 0.0f to 1.0f. If both cx and
cy were equal to 0 the first line of code below would actually be: glTexCoord2f(0.0f,1-0.0f-0.0625f). Remember
that 0.0625 is exactly 1/16th of our texture, or the width / height of one character. The texture coordinate below
would be the bottom left point of our texture.
Notice we are using glVertex2i(x,y) instead of glVertex3f(x,y,z). Our font is a 2D font, so we don't need the z value.
Because we are using an Ortho screen, we don't have to translate into the screen. All you have to do to draw to an
Ortho screen is specify an x and y coordinate. Because our screen is in pixels from 0 to 639 and 0 to 479, we don't
have to use floating point or negative values either :)
The way we set up our Ortho screen, (0,0) will be at the bottom left of our screen. (640,480) will be the top right
of the screen. 0 is the left side of the screen on the x axis, 639 is the right side of the screen on the x axis. 0
is the bottom of the screen on the y axis and 479 is the top of the screen on the y axis. Basically we've gotten rid
of negative coordinates. This is also handy for people that don't care about perspective and prefer to work with
pixels rather than units :)
The next texture coordinate is now 1/16th to the right of the last texture coordinate (exactly one character wide).
So this would be the bottom right texture point.
The third texture coordinate stays at the far right of our character, but moves up 1/16th of our texture (exactly the
height of one character). This will be the top right point of an individual character.
Finally we move left to set our last texture coordinate at the top left of our character.
Finally, we translate 10 pixels to the right, placing us to the right of our texture. If we didn't translate, the
letters would all be drawn on top of eachother. Because our font is so narrow, we don't want to move 16 pixels to the
right. If we did, there would be big spaces between each letter. Moving by just 10 pixels eliminates the spaces.
The following section of code is the same code we used in our other font tutorials to free the display list before
our program quits. All 256 display lists starting at base will be deleted. (good thing to do!).
The next section of code is where all of our drawing is done. Everything is fairly new so I'll try to explain each
line in great detail. Just a small note: Alot can be added to this code, such as variable support, character sizing,
spacing, and alot of checking to restore things to how they were before we decided to print.
glPrint() takes three parameters. The first is the x position on the screen (the position from left to right).
Next is the y position on the screen (up and down... 0 at the bottom, bigger numbers at the top). Then we have
our actual string (the text we want to print), and finally a variable called set. If you have a look at
the bitmap that Giuseppe D'Agata has made, you'll notice there are two different character sets. The first character
set is normal, and the second character set is italicized. If set is 0, the first character set is selected.
If set is 1 or greater the second character set is selected.
The first thing we do is make sure that set is either 0 or 1. If set is greater than 1, we'll make it
equal to 1.
Now we select our Font texture. We do this just in case a different texture was selected before we decided to print
something to the screen.
Now we disable depth testing. The reason I do this is so that blending works nicely. If you don't disable depth
testing, the text may end up going behind something, or blending may not look right. If you have no plan to blend
the text onto the screen (so that black spaces do not show up around our letters) you can leave depth testing on.
The next few lines are VERY important! We select our Projection Matrix. Right after that, we use a command called
glPushMatrix(). glPushMatrix stores the current matrix (projection). Kind of like the memory button on a calculator.
Now that our projection matrix has been stored, we reset the matrix and set up our Ortho screen. The first and third
numbers (0) represent the bottom left of the screen. We could make the left side of the screen equal -640 if we want,
but why would we work with negatives if we don't need to. The second and fourth numbers represent the top right of the
screen. It's wise to set these values to match the resolution you are currently in. There is no depth so we set the z
values to -1 & 1.
Now we select our modelview matrix, and store it's current settings using glPushMatrix(). We then reset the modelview
matrix so we can work with it using our Ortho view.
With our perspective settings saved, and our Ortho screen set up, we can now draw our text. We start by translating
to the position on the screen that we want to draw our text at. We use glTranslated() instead of glTranslatef()
because we are working with actual pixels, so floating point values are not important. After all, you can't have
half a pixel :)
The line below will select which font set we want to use. If we want to use the second font set we add 128 to the
current base display list (128 is half of our 256 characters). By adding 128 we skip over the first 128 characters.
Now all that's left for us to do is draw the letters to the screen. We do this exactly the same as we did in all the
other font tutorials. We use glCallLists(). strlen(string) is the length of our string (how many characters we
want to draw), GL_UNSIGNED_BYTE means that each character is represented by an unsigned byte (a byte is any value from 0
to 255). Finally, string holds the actual text we want to print to the screen.
All we have to do now is restore our perspective view. We select the projection matrix and use glPopMatrix() to
recall the settings we previously stored with glPushMatrix(). It's important to restore things in the opposite order
you stored them in.
Now we select the modelview matrix, and do the same thing. We use glPopMatrix() to restore our modelview matrix to
what it was before we set up our Ortho display.
Finally, we enable depth testing. If you didn't disable depth testing in the code above, you don't need this line.
Nothing has changed in ReSizeGLScene() so we'll skip right to InitGL().
We jump to our texture building code. If texture building fails for any reason, we return FALSE. This lets our
program know that an error has occurred and the program gracefully shuts down.
If there were no errors, we jump to our font building code. Not much can go wrong when building the font so we don't
bother with error checking.
Now we do our normal GL setup. We set the background clear color to black, the clear depth to 1.0. We choose a depth
testing mode, along with a blending mode. We enable smooth shading, and finally we enable 2D texture mapping.
The section of code below will create our scene. We draw the 3D object first and the text last so that the text
appears on top of the 3D object, instead of the 3D object covering up the text. The reason I decide to add a 3D object
is to show that both perspective and ortho modes can be used at the same time.
We select our bumps.bmp texture so that we can build our simple little 3D object. We move into the screen 5 units so
that we can see the 3D object. We rotate on the z axis by 45 degrees. This will rotate our quad 45 degrees clockwise
and makes our quad look more like a diamond than a square.
After we have done the 45 degree rotation, we spin the object on both the x axis and y axis based on the variable
cnt1 times 30. This causes our object to spin around as if the diamond is spinning on a point.
We disable blending (we want the 3D object to appear solid), and set the color to bright white. We then draw a single
texture mapped quad.
Immediately after we've drawn the first quad, we rotate 90 degrees on both the x axis and y axis. We then draw another
quad. The second quad cuts through the middle of the first quad, creating a nice looking shape.
After both texture mapped quads have been drawn, we enable enable blending, and draw our text.
We use the same fancy coloring code from our other text tutorials. The color is changed gradually as the text moves
across the screen.
Then we draw our text. We still use glPrint(). The first parameter is the x position. The second parameter is the
y position. The third parameter ("NeHe") is the text to write to the screen, and the last parameter is the character
set to use (0 - normal, 1 - italic).
As you can probably guess, we swing the text around the screen using COS and SIN, along with both counters cnt1
and cnt2. If you don't understand what SIN and COS do, go back and read the previous text tutorials.
We set the color to a dark blue and write the author's name at the bottom of the screen. We then write his name to the
screen again using bright white letters. The white letters are a little to the right of the blue letters. This
creates a shadowed look. (if blending wasn't enabled the effect wouldn't work).
The last thing we do is increase both our counters at different rates. This causes the text to move, and the 3D object
to spin.
The code in KillGLWindow(), CreateGLWindow() and WndProc() has not changed so we'll skip over it.
The title of our Window has changed.
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.
I think I can officially say that my site now teaches every possible way to write text to the screen {grin}. All in
all, I think this is a fairly good tutorial. The code can be used on any computer that can run OpenGL, it's easy
to use, and writing text to the screen using this method requires very little processing power.
I'd like to thank Giuseppe D'Agata for the original version of this tutorial. I've modified it heavily, and converted
it to the new base code, but without him sending me the code I probably wouldn't have written the tutorial. His
version of the code had a few more options, such as spacing the characters, etc, but I make up for it with the
extremely cool 3D object {grin}.
I hope everyone enjoys this tutorial. If you have questions, email Giuseppe D'Agata or myself.
Giuseppe D'Agata
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.