This tutorial is a sequel to the last tutorial. In tutorial 13 I taught you how to use Bitmap Fonts. In this tutorial I'll teach you how to use Outline Fonts.

The way we create Outline fonts is fairly similar to the way we made Bitmap fonts in lesson 13. However... Outline fonts are about 100 times more cool! You can size Outline fonts. Outline font's can move around the screen in 3D, and outline fonts can have thickness! No more flat 2D fonts. With Outline fonts, you can turn any font that's installed on your computer into a 3D font for OpenGL, complete with proper normals so the characters light up really nice when light shines on them.

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 a 3rd font tutorial.

We start off with the basic GL code from lesson 1. We'll be adding the Math.h header file so we can move the text around the screen in 3D using SIN and COS.
#include <windows.h>		// Header File For Windows
#include <math.h>		// Header File For Windows Math Library
#include <gl\gl.h>		// Header File For The OpenGL32 Library
#include <gl\glu.h>		// Header File For The GLu32 Library
#include <gl\glaux.h>		// Header File For The GLaux Library

static	HGLRC hRC;		// Permanent Rendering Context
static	HDC hDC;		// Private GDI Device Context

We're going to add 2 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 a variable called rot. Rot will be used to spin the text around on the screen using both SIN and COS. It will also be used to pulse the colors.
GLuint	base;			// Base Display List For The Font Set
GLfloat	rot;			// Used To Rotate The Text

BOOL	keys[256];		// Array Used For The Keyboard Routine

The following section of code builds the actual font similar to the way we made our Bitmap font. Just like in lesson 13, this section of code was the hardest part for me to figure out.

GLYPHMETRICSFLOAT gmf[256] will hold our 256 floating point display list characters. 'HFONT font' in simple english tells Windows we are going to be manipulating a Windows font.

Next we define 'base'. We do this by creating a group of 256 display lists using glGenLists(256). After the display lists are created, the variable 'base' will hold the number of the first list.
GLvoid BuildFont(GLvoid)					// Build Our Bitmap Font
{
	GLYPHMETRICSFLOAT	gmf[256];			// Address Buffer For Font Storage
	HFONT	font;						// Windows Font ID

	base = glGenLists(256);					// Storage For 256 Characters

More fun stuff. We're going to create our Outline 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.
	font = CreateFont(	-12,				// Height Of Font

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.
				0,				// Width Of Font

Angle of Escapement will rotate the font. 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 :(
				0,				// Angle Of Escapement
				0,				// Orientation Angle

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).
				FW_BOLD,			// Font Weight

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 :)
				FALSE,				// Italic
				FALSE,				// Underline
				FALSE,				// Strikeout

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.
				ANSI_CHARSET,			// Character Set Identifier

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.
				OUT_TT_PRECIS,			// Output Precision

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.
				CLIP_DEFAULT_PRECIS,		// Clipping Precision

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.
				ANTIALIASED_QUALITY,		// Output Quality

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.
				FF_DONTCARE|DEFAULT_PITCH,	// Family And Pitch

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 'Comic Sans MS' with the name of the font you'd rather use.
				"Comic Sans MS");		// Font Name

Now we select the font by relating it to our DC.
	SelectObject(hDC, font);				// Selects The Font We Created

Now for the new code. We build our Outline font using a new command wglUseFontOutlines. We select our DC, the starting character, the number of characters to create and the 'base' display list value. All very similar to the way we built our Bitmap font.
	wglUseFontOutlines(	hDC,				// Select The Current DC
				0,				// Starting Character
				255,				// Number Of Display Lists To Build
				base,				// Starting Display Lists

That's not all however. We then set the deviation level. The closer to 0.0f, the smooth the font will look. After we set the deviation, we get to set the font thickness. This describes how thick the font is on the Z axis. 0.0f will produce a flat 2D looking font and 1.0f will produce a font with some depth.

The parameter WGL_FONT_POLYGONS tells OpenGL to create a solid font using polygons. If we use WGL_FONT_LINES instead, the font will be wireframe (made of lines).

The last parameter gmf points to the address buffer for the display list data.
				0.0f,				// Deviation From The True Outlines
				0.2f,				// Font Thickness In The Z Direction
				WGL_FONT_POLYGONS,		// Use Polygons, Not Lines
				gmf);				// Address Of Buffer To Recieve Data
}

The following code is pretty simple. It deletes the 256 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 :)
GLvoid KillFont(GLvoid)						// Delete The Font
{
 	glDeleteLists(base, 256);				// Delete All 256 Characters
}

Now for my handy dandy GL text routine. You call this section of code with the command glPrint("message goes here"). Exactly the same way you drew Bitmap fonts to the screen in lesson 13. The text is stored in the character string *text.
GLvoid glPrint(char *text)					// Custom GL "Print" Routine
{

These two lines of code check to see if there's anything to display? If there's no text, text will equal nothing (NULL), and nothing will be drawn to the screen.
	if (text == NULL)					// If There's No Text
		return;						// Do Nothing

If there is text, we 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) tells OpenGL where to find the proper display list for each character.
	glPushAttrib(GL_LIST_BIT);				// Pushes The Display List Bits
	glListBase(base);					// Sets The Base Character to 0

Now that OpenGL knows where the characters are located, we can tell it to write the text to the screen. glCallLists writes the entire string of text to the screen at once by making multiple display list calls.

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 still not sending any more than 255 characters. So we can use an UNSIGNED_BYTE. (a byte represents a number from 0 - 255 which is exactly what we need). Finally we tell it what to display by passing the string 'text'.

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 character is. After the letter is drawn to the screen, 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).
	glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);	// Draws The Display List Text
	glPopAttrib();						// Pops The Display List Bits
}

There are a few new lines at the end of the InitGL code. The line BuildFont() from lesson 13 is still there, along with code to do quick and dirty lighting. Light0 is predefined on most video cards and will light up the scene nicely with no effort on my part on lighting has been enabled :)

I've also added the command glEnable(GL_Color_Material). Because the characters are 3D objects you need to enable Material Coloring, otherwise changing the color with glColor3f(r,g,b) will not change the color of the text. If you're drawing shapes of your own to the screen while you write text enable material coloring before you write the text, and disable it after you've drawn the text, otherwise all the object on your screen will be colored.
GLvoid InitGL(GLsizei Width, GLsizei Height)			// Will Be Called Right After The GL Window Is Created
{
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);			// Clear The Background Color To Black
	glClearDepth(1.0);					// Enables Clearing Of The Depth Buffer
	glDepthFunc(GL_LESS);					// The Type Of Depth Test To Do
	glEnable(GL_DEPTH_TEST);				// Enables Depth Testing
	glShadeModel(GL_SMOOTH);				// Enables Smooth Color Shading

	glMatrixMode(GL_PROJECTION);				// Select The Projection Matrix
	glLoadIdentity();					// Reset The Projection Matrix

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);

	glMatrixMode(GL_MODELVIEW);				// Select The Modelview Matrix

	BuildFont();						// Build The Font
	glEnable(GL_LIGHT0);					// Enable Default Light (Quick And Dirty)
	glEnable(GL_LIGHTING);					// Enable Lighting
	glEnable(GL_COLOR_MATERIAL);				// Enable Coloring Of Material
}

Resizing code is exactly the same as the code in Lesson 1.
GLvoid ReSizeGLScene(GLsizei Width, GLsizei Height)		// Handles Window Resizing
{
	if (Height==0)						// Is Window Too Small (Divide By Zero Error)
		Height=1;					// If So Make It One Pixel Tall

	// Reset The Current Viewport And Perspective Transformation
	glViewport(0, 0, Width, Height);

	glMatrixMode(GL_PROJECTION);				// Select The Projection Matrix
	glLoadIdentity();					// Reset The Projection Matrix

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
	glMatrixMode(GL_MODELVIEW);				// Select The Modelview Matrix
}


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 ten units into the screen. Outline fonts look great in perspective mode. The further into the screen you translate, the smaller the font becomes. The closer you translate, the larger the font becomes.

Outline fonts can also be manipulated by using the glScalef(x,y,z) command. If you want the font 2 times taller, use glScalef(1.0f,2.0f,1.0f). the 2.0f is on the y axis, which tells OpenGL to draw the list twice as tall. If the 2.0f was on the x axis, the character would be twice as wide.
GLvoid DrawGLScene(GLvoid)					// Handles Rendering
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear The Screen And The Depth Buffer
	glLoadIdentity();					// Reset The View
	glTranslatef(0.0f,0.0f,-10.0f);				// Move Ten Units Into The Screen

After we've translated into the screen, we want the text to spin. The next 3 lines rotate the screen on all three axes. I multiply rot by different numbers to make each rotation happen at a different speed.
	glRotatef(rot,1.0f,0.0f,0.0f);				// Rotate On The X Axis
	glRotatef(rot*1.2f,0.0f,1.0f,0.0f);			// Rotate On The Y Axis
	glRotatef(rot*1.4f,0.0f,0.0f,1.0f);			// Rotate On The Z Axis

Now that the screen has been spun, we translate a little bit to the left, so that the text draws from the left of center to the right of center. If we didn't translate, the text would start at the center and move right. Then if we were to spin it, it wouldn't spin in the middle.
	glTranslatef(-3.5f,0.0f,0.0f);				// Move To The Left Before Drawing

Now for the crazy color cycling. As usual, I make use of the only variable that counts up (rot). The colors pulse up and down using COS and SIN. I divide the value of rot by different numbers so that each color isn't increasing at the same speed. The final results are nice.
	// Pulsing Colors Based On The Rotation
	glColor3f(1.0f*float(cos(rot/20.0f)),1.0f*float(sin(rot/25.0f)),1.0f-0.5f*float(cos(rot/17.0f)));

My favorite part... Writing the text to the screen. I've used the same command we used to write Bitmap fonts to the screen. All you do to write the text to the screen is glPrint("{any text you want}"). It's that easy.
 	glPrint("OpenGL With NeHe");				// Print GL Text To The Screen

The last thing to do is increase the rotation variable so the colors pulse and the text spins.
	rot+=0.1f;						// Increase The Rotation Variable
}

Just like in lesson 13, I've added a line of code to the WM_DESTROY / WM_CLOSE section.
LRESULT CALLBACK WndProc(	HWND	hWnd,
				UINT	message,
				WPARAM	wParam,
				LPARAM	lParam)
{
	RECT	Screen;					// Used Later On To Get The Size Of The Window
	GLuint	PixelFormat;				// Pixel Format Storage
	static	PIXELFORMATDESCRIPTOR pfd=		// Pixel Format Descriptor
	{
		sizeof(PIXELFORMATDESCRIPTOR),		// Size Of This Pixel Format Descriptor
		1,					// Version Number (?)
		PFD_DRAW_TO_WINDOW |			// Format Must Support Window
		PFD_SUPPORT_OPENGL |			// Format Must Support OpenGL
		PFD_DOUBLEBUFFER,			// Must Support Double Buffering
		PFD_TYPE_RGBA,				// Request An RGBA Format
		16,					// Select A 16Bit Color Depth
		0, 0, 0, 0, 0, 0,			// Color Bits Ignored (?)
		0,					// No Alpha Buffer
		0,					// Shift Bit Ignored (?)
		0,					// No Accumulation Buffer
		0, 0, 0, 0,				// Accumulation Bits Ignored (?)
		16,					// 16Bit Z-Buffer (Depth Buffer)
		0,					// No Stencil Buffer
		0,					// No Auxiliary Buffer (?)
		PFD_MAIN_PLANE,				// Main Drawing Layer
		0,					// Reserved (?)
		0, 0, 0					// Layer Masks Ignored (?)
	};

	switch (message)						// Tells Windows We Want To Check The Message
	{
		case WM_CREATE:						// Window Creation
			hDC = GetDC(hWnd);				// Gets A Device Context For The Window
			PixelFormat = ChoosePixelFormat(hDC, &pfd);	// Finds The Closest Match To The Pixel Format We Set Above

			if (!PixelFormat)				// No Matching Pixel Format?
			{
				MessageBox(0,"Can't Find A Suitable PixelFormat.","Error",MB_OK|MB_ICONERROR);
				PostQuitMessage(0);			// This Sends A 'Message' Telling The Program To Quit
				break;					// Prevents The Rest Of The Code From Running
			}

			if(!SetPixelFormat(hDC,PixelFormat,&pfd))	// Can We Set The Pixel Mode?
			{
				MessageBox(0,"Can't Set The PixelFormat.","Error",MB_OK|MB_ICONERROR);
				PostQuitMessage(0);			// This Sends A 'Message' Telling The Program To Quit
				break;					// Prevents The Rest Of The Code From Running
			}

			hRC = wglCreateContext(hDC);			// Grab A Rendering Context
			if(!hRC)					// Did We Get One?
			{
				MessageBox(0,"Can't Create A GL Rendering Context.","Error",MB_OK|MB_ICONERROR);
				PostQuitMessage(0);			// This Sends A 'Message' Telling The Program To Quit
				break;					// Prevents The Rest Of The Code From Running
			}

			if(!wglMakeCurrent(hDC, hRC))			// Can We Make The RC Active?
			{
				MessageBox(0,"Can't Activate GLRC.","Error",MB_OK|MB_ICONERROR);
				PostQuitMessage(0);			// This Sends A 'Message' Telling The Program To Quit
				break;					// Prevents The Rest Of The Code From Running
			}

			GetClientRect(hWnd, &Screen);			// Grab Screen Info For The Current Window
			InitGL(Screen.right, Screen.bottom);		// Initialize The GL Screen Using Screen Info
			break;

		case WM_DESTROY:					// Windows Being Destroyed
		case WM_CLOSE:						// Windows Being Closed
			ChangeDisplaySettings(NULL, 0);			// Disable Fullscreen Mode

The next line of code is new. As soon as the GL window is closed, KillFont() will jump to the section of code above that deletes all 256 characters of the font we created. This is more than likely a good thing to do just to be on the safe side. (avoid memory leaks, etc).
			KillFont();					// Deletes The Font Display List

			wglMakeCurrent(hDC,NULL);			// Make The DC Current
			wglDeleteContext(hRC);				// Kill The RC
			ReleaseDC(hWnd,hDC);				// Free The DC

			PostQuitMessage(0);				// Quit The Program
			break;

		case WM_KEYDOWN:					// Key Being Held Down
			keys[wParam] = TRUE;				// Make That Keys Cell True
			break;

		case WM_KEYUP:						// Key Is Released
			keys[wParam] = FALSE;				// Make That Keys Cell False
			break;

		case WM_SIZE:						// Resizing The Screen
			ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));	// Resize To The New Window Size
			break;

		default:
			return (DefWindowProc(hWnd, message, wParam, lParam));	// Pass Windows Messages
	}
return (0);
}

int WINAPI WinMain(	HINSTANCE	hInstance, 
			HINSTANCE	hPrevInstance, 
			LPSTR		lpCmdLine, 
			int		nCmdShow)
{
	MSG		msg;		// Windows Message Structure
	WNDCLASS	wc;		// Windows Class Structure Used To Set Up The Type Of Window
	HWND		hWnd;		// Storage For Window Handle

	wc.style		= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wc.lpfnWndProc		= (WNDPROC) WndProc;
	wc.cbClsExtra		= 0;
	wc.cbWndExtra		= 0;
	wc.hInstance		= hInstance;
	wc.hIcon		= NULL;
	wc.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground	= NULL;
	wc.lpszMenuName		= NULL;
	wc.lpszClassName	= "OpenGL WinClass";

	if(!RegisterClass(&wc))
	{
		MessageBox(0,"Failed To Register The Window Class.","Error",MB_OK|MB_ICONERROR);
		return FALSE;
	}

	hWnd = CreateWindow(
	"OpenGL WinClass",
	"Jeff Molofee's GL Code Tutorial ... NeHe 2000",	// Title Appearing At The Top Of The Window

	WS_POPUP |						// Popup Window
	WS_CLIPCHILDREN |					// Clip Child Windows
	WS_CLIPSIBLINGS,					// Clip Sibling Windows

	0, 0,							// The Position Of The Window On The Screen
	640, 480,						// The Width And Height Of The WIndow

	NULL,
	NULL,
	hInstance,
	NULL);

	if(!hWnd)						// Was Window Created?
	{
		MessageBox(0,"Window Creation Error.","Error",MB_OK|MB_ICONERROR);
		return FALSE;
	}

I've changed the fullscreen code a little bit in this tutorial in an attempt to make these programs more Voodoo 3 friendly, and in an attempt to make the code as clean as possible. The only change is the new line memset(&dmScreenSettings, 0, sizeof(DEVMODE)). What this does is clears out the section of ram where we store all the fullscreen settings before we actually change to fullscreen mode. That way any random values that might have been stored in the ram are wiped out.
	DEVMODE dmScreenSettings;							// Developer Mode

	memset(&dmScreenSettings, 0, sizeof(DEVMODE));					// Clear Room To Store Settings
	dmScreenSettings.dmSize		= sizeof(DEVMODE);				// Size Of The Devmode Structure
	dmScreenSettings.dmPelsWidth	= 640;						// Screen Width
	dmScreenSettings.dmPelsHeight	= 480;						// Screen Height
	dmScreenSettings.dmFields	= DM_PELSWIDTH | DM_PELSHEIGHT;			// Pixel Mode
	ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);			// Switch To Full Screen

	ShowWindow(hWnd, SW_SHOW);							// Show Our Window
	UpdateWindow(hWnd);								// Update The Window
	SetFocus(hWnd);									// Set Focus On The Window
	wglMakeCurrent(hDC,hRC);							// Make DC/RC Current

	while (1)									// Endless Loop
	{
		// Process All Messages
		while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if (GetMessage(&msg, NULL, 0, 0))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			else
			{
				return TRUE;
			}
		}

		DrawGLScene();								// Draw The Scene
		SwapBuffers(hDC);							// Swap Screen Buffers
		if (keys[VK_ESCAPE]) SendMessage(hWnd,WM_CLOSE,0,0);			// If ESC Is Pressed Quit
	}
}

At the end of this tutorial you should be able to use Outline Fonts in your own OpenGL projects. Just like lesson 13, I've searched the net looking for a tutorial similar to this one, and have found nothing. Could my site be the first to cover this topic in great detail while explaining everything in easy to understand C code? Enjoy the tutorial, and happy coding!

Jeff Molofee (NeHe)

* DOWNLOAD Visual C++ Code For This Lesson.
* DOWNLOAD Delphi Code For This Lesson. ( Conversion by Marc Aarts )