Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9

Drawing Text - Custom Rasterizers and normal 2D Text
Author : Jack Hoxley
Written : 20th December 2000
Contact : [Web] [Email]
Download : Graph_06.Zip [21 Kb]

Contents of this lesson:
1. Introduction
2. Normal 2D Text
3. Custom 2D Font rasterizers

1. Introduction

Welcome back to another lesson on DirectX Graphics; This lesson isn't a very complicated one, but you'll probably need to learn how to draw text onto the screen sooner or later - so we're going to cover it before we get onto some of the more advanced topics. Drawing text is drawing text and doesn't really need much explanation or theory work - so we may as well just get straight on with it.

2. Normal 2D Text

This is the easiest to use, and the one you'll probably use the most. Almost from square one all my projects have a little frame rate indicator in the corner - I like to keep track of the speed as I add more and more features (usually starts around about 1000fps and ends up at about 50fps). In DirectX7 (which is where I do most of my work) I just draw the text to the top corner of the backbuffer (or wherever I want it); we'll now have a look at how this has changed in DirectX8.

The good thing is that, In my opinion, it has changed for the better. You can now allow DirectX to do some fairly simple text manipulations (like centering) that in DirectX7 you would have to of done manually. It is slightly more complicated than the text interfaces in DirectX7, but it's still pretty simple:

'//Some new variables that are needed
Dim MainFont As D3DXFont '//This will be *created* in a minute
Dim MainFontDesc As IFont '//We use this temporarily to setup the font
Dim TextRect As RECT '//This defines where it will be
Dim fnt As New StdFont '//This is also used to describe and setup the font

Dim TextToDraw As String '//This is the text that will be displayed...

Not too difficult really; a few new variables. Bare in mind that the "fnt" object is late bound - if you intend to keep switching the font around an altering it you should switch this to being early bound. also, the IFont variable may well not appear in the Intellisense list - but if you just type it then you should be able to get it working...

    fnt.Name = "Verdana"
    fnt.Size = 18
    fnt.Bold = True
Set MainFontDesc = fnt

Set MainFont = D3DX.CreateFont(D3DDevice, MainFontDesc.hFont)

A little complicated and a bit odd, but thats how to do it - first we fill out our "fnt" object with the relevent details and the get VB to convert/typecast it into our IFont variable; which in turn is then used to create the final font.

TextRect.Top = 1
TextRect.Left = 1
TextRect.bottom = 32
TextRect.Right = 640
D3DX.DrawText MainFont, &HFFCCCCFF, "Current Frame Rate: " & FPS_Current, TextRect, DT_TOP Or DT_CENTER

This final part goes between the "D3DDevice.BeginScene" and "D3DDevice.EndScene" calls in the render procedure. It's not greatly complicated but there's a little more to think about than usual....

Firstly there's the rectangle that you must specify - whilst it doesn't matter if you get the wrong size it will look a little silly if it's too small (you'll lose parts of your text) and if it's too big you'll be inefficient.

Secondly there are the flags to be used; these can be any combination of the following (Although some cancel each other out):

DT_LEFT          - The text appears along the left of the RECT (Left Justify)
DT_TOP           - The text appears across the top of the RECT      
DT_CENTER        - The text is centered horizontally in the RECT (Center Justify)  
DT_RIGHT         - The text is along the right edge of the RECT (Right Justify)      
DT_VCENTER       - The text is centered vertically
DT_BOTTOM        - The text appears along the bottom edge
DT_SINGLELINE    - The string passed is a single line - line feeds and carriage returns are
                 - ignored

There isn't really much more too it than that; there are a few other flags to be used, but the above should be fine - if not, you can look up the others in the object browser...

3. Custom 2D Font Rasterizers

These are slightly more complicated than the above method, but if done properly they can look much much nicer. As you're probably aware, the above method uses a font registered with the system - the same ones used in your graphics package and office suite. But if you want something a little more interesting - a font with a shadow/3D effect to it, or a font that has a texture...

This is where custom fonts come in, you're font can be whatever you can represent using a single texture - be that a weird alien language, or a normal font coloured to suit the theme of your game. The only really difficult part is the drawing of your text; but if you're a half decent artist or know a half-decent artist this shouldn't be too much of a challenge..

The basics of a custom font rasterizer is to take a string, work out which parts of the texture you need to use, then generate the relevent geometry. Think of the texture as a palette - where each n*n segment holds one letter - you then need to work out which segment represents each letter in the string. Below is an example of what the texture looks like for our custom font.

The bright green background will be our transparent colour later on

Note that each character is 16x16 pixels in size; from a 256x128 texture this allows us 128 characters. A 256x256 texture would allow us 256 characters - the same number there are in the ascii character set (0 to 255).

To work out what letter we need we'll just use VB's built in functions "Mid$( ) " and "Asc( )" to read through a string that's passed to a function. The code looks like this:

'//This must be called during the .BeginScene and .EndScene lines...
Private Sub RenderStringFromCustomFont_2D(strText As String, startX As Single, StartY As Single, Height As Integer, Width As Integer)
Dim I As Integer '//Loop variable
Dim CharX As Integer, CharY As Integer '//Grid coordinates for our character 0-15 and 0-7
Dim Char As String '//The current Character in the string
Dim LinearEntry As Integer 'Without going into 2D entries, just work it out if it were a line

If Len(strText) = 0 Then Exit Sub '//If there is no text dont try to render it....

For I = 1 To Len(strText) '//Loop through each character

    '//1. Choose the Texture Coordinates
        'To do this we just need to isolate which entry in the texture we
        'need to use - the Vertex creation code sorts out the ACTUAL texture coordinates
            Char = Mid$(strText, I, 1) '//Get the current character
                        If Asc(Char) >= 65 And Asc(Char) <= 90 Then
                            'A character number from 65 through to 90 are the upper case A-Z letters
                            'which if we wrap around our texture are entries 0 - 25.
                            LinearEntry = Asc(Char) - 65 '//Make it so character 65 references entry 0 in our texture
                        ElseIf Asc(Char) >= 97 And Asc(Char) <= 122 Then
                            'We have a lower case letter.
                            LinearEntry = Asc(Char) - 71 '//Make it so that the lower case letters reference values 26-51
                        ElseIf Asc(Char) >= 48 And Asc(Char) <= 57 Then
                            'We have a numerical character, which occupy entries 52-62 in our texture
                            LinearEntry = Asc(Char) + 4
                        '//Finally: Special Cases. I couldn't be bothered to work out a formula
                        '           for full-stop/spaces/punctuation characters, so I'm going to hardcode them
                        ElseIf Char = " " Then
                            LinearEntry = 63
                        ElseIf Char = "." Then
                            'Full stop
                            LinearEntry = 62
                        ElseIf Char = ";" Then
                            'Semi colon
                            LinearEntry = 66
                        ElseIf Char = "/" Then
                            'Forward slash
                            LinearEntry = 64
                        ElseIf Char = "," Then
                            'Guess what; its a comma..
                            LinearEntry = 65
                        End If
            'We now need to process the actual coordinates.
                        If LinearEntry <= 15 Then
                            CharY = 0
                            CharX = LinearEntry
                        End If
                        If LinearEntry >= 16 And LinearEntry <= 31 Then
                            CharY = 1
                            CharX = LinearEntry - 16
                        End If
                        If LinearEntry >= 32 And LinearEntry <= 47 Then
                            CharY = 2
                            CharX = LinearEntry - 32
                        End If
                        If LinearEntry >= 48 And LinearEntry <= 63 Then
                            CharY = 3
                            CharX = LinearEntry - 48
                        End If
                        If LinearEntry >= 64 And LinearEntry <= 79 Then
                            CharY = 4
                            CharX = LinearEntry - 64
                        End If
                            'Fill in the rest if you really need them...
    '//2. Generate the Vertices
    vertChar(0) = CreateTLVertex(startX + (Width * I), StartY, 0, 1, &HFFFFFF, 0, (1 / 16) * CharX, (1 / 8) * CharY)
    vertChar(1) = CreateTLVertex(startX + (Width * I) + Width, StartY, 0, 1, &HFFFFFF, 0, ((1 / 16) * CharX) + (1 / 16), (1 / 8) * CharY)
    vertChar(2) = CreateTLVertex(startX + (Width * I), StartY + Height, 0, 1, &HFFFFFF, 0, (1 / 16) * CharX, ((1 / 8) * CharY) + (1 / 8))
    vertChar(3) = CreateTLVertex(startX + (Width * I) + Width, StartY + Height, 0, 1, &HFFFFFF, 0, ((1 / 16) * CharX) + (1 / 16), ((1 / 8) * CharY) + (1 / 8))
    '//3. Render the vertices
    D3DDevice.SetTexture 0, fntTex '//Set the device to use our custom font as a texture
    D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLESTRIP, 2, vertChar(0), Len(vertChar(0))
Next I
End Sub

The above is fairly self explanatory, but to give you a general idea how it works:
1. We recieve our string
2. We go through our string character by character rendering it
3. Whilst each character has a 2D coordinate in our texture, it makes it easier if we calculate it in 1D first - the LinearEntry value
4. We then convert this linear entry into a 2D coordinate for our texture.
5. Now we generate the vertices - note that we reuse the same 4 vertices every time. We also convert the characters X/Y coordinate into a valid texture coordinate
6. Finally we render it to the screen. Nothing greatly new here. NB: we must only call this function during a Device.BeginScene and Device.EndScene block.

There are two rather large limitations to this method:
1. Characters - you're limited to the number of characters the you draw. This example only uses about 70 or so.
2. Any alignment must be done manually by you - you cant just jam "DT_CENTER" into it and hope it appears correctly....

Other than that there's not really much more to learn about drawing text. It's unlikely you'll really want to use anything more than this - and if you do it'll probably be based on, or similiar to the two methods already used.

You can download the source code to this lesson from the top of this page;

Once you've got this extremely long and complicated [hehe] lesson under your belt you can move onto the next lesson: An introduction to lighting. (Which is complicated)

DirectX 4 VB 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster