News
DirectX
Links
Contact Me

In Association with Amazon.com
In Association with Amazon.ca

$5 via PayPal


3D Font Tutorial



3D Font
First a few vertex-related declarations. Nothing too exciting. We want our mesh to have a diffuse colour component, untransformed coordinates, and because we're going to light it, a normal.
//This is the format we want our mesh to be in
#define MESH_FVF (D3DFVF_DIFFUSE|D3DFVF_XYZ|D3DFVF_NORMAL)

//The corresponding vertex structure
struct my_vertex{
    FLOAT x, y, z;
    FLOAT nx,ny,nz;
    DWORD colour;
};

This tutorial uses the dhEngine, so we create one. Since we want to light the mesh, we also need a light. And finally a pointer for our mesh.
dhEngine g_engine;

D3DLIGHT8 g_light;

//Our mesh which will contain our 3D Text
ID3DXMesh *g_mesh=NULL;

There's the standard engine initialization code, nothing exciting there so we won't cover it here. Though it's worth noting that we need a Z-Buffer to make sure our mesh is drawn properly. Here's the InitScene function which builds our light.
void InitScene(void){

   //Here we set up a simple light, nothing we haven't seen in previous tutorials

   ZeroMemory( &g_light, sizeof(D3DLIGHT8) );

   g_light.Type=D3DLIGHT_POINT;
   g_light.Diffuse.r=1.0f;
   g_light.Diffuse.g=1.0f;
   g_light.Diffuse.b=1.0f;
   g_light.Range=1000.0f;

   g_light.Position=D3DXVECTOR3(-8.0f,8.0f,-18.0f);

   g_light.Attenuation0=1.0f;

   g_engine.GetDevice()->SetLight( 0, &g_light );

   g_engine.GetDevice()->LightEnable( 0, TRUE);

   g_engine.SetRenderState(D3DRS_LIGHTING,TRUE);

}

There's no KillScene, since InitScene doesn't allocate anything there is nothing to free. There is a KillFont, here it is. All it does is safely free our mesh.
void KillFont(void){

   if(g_mesh!=NULL){
      g_mesh->Release();
      g_mesh=NULL;
   }
}

The BuildFont procedure is where all the magic happens. It's a little long, but there really isn't much to it.
void BuildFont(void){
HRESULT hr;
HFONT my_font;
HDC win_hdc;
ID3DXMesh *mesh=NULL;

   //D3DXCreateText take an HDC as one of it's parameters, so we get one
   //from our main window
   win_hdc=GetDC(g_engine.GetWindow());

   //This is a standard Win32 CreateFont call.  For detail on it's parameters
   //you may want to buy a good Win32 API reference.  It's beyond the scope of
   //this tutorial
   my_font=CreateFont(0,         //Height
                      0,         //Width
                      0,         //Escapement
                      0,         //Orientation
                      FW_NORMAL, //Font weight
                      FALSE,     //Italic
                      FALSE,     //Underline
                      FALSE,     //StrikeOut
                      DEFAULT_CHARSET, //Character Set Identifier
                      OUT_TT_ONLY_PRECIS, //OutputPrecision
                      CLIP_DEFAULT_PRECIS,   //ClipPrecision
                      ANTIALIASED_QUALITY,//Quality
                      DEFAULT_PITCH,         //Pitch
                      "Arial");     //Font Name
 
   if(!my_font){
      g_engine.FatalError("Error creating font.");
   }

   //The font has to be 'Select'ed into the HDC so Windows can get
   //the information it requires
   SelectObject(win_hdc,my_font);

   //Here we actually create the mesh based on our font/text
   //A high deviation will give a 'chunky' looking font, low
   //deviation gives a smoother one.
   hr=D3DXCreateTextA(g_engine.GetDevice(),//Our D3D Device
                      win_hdc, //Our windows HDC
                      "Drunken Hyena", //Our text
                      0.001f, //Deviation
                      0.4f,   //Extrusion Depth (How deep the mesh should be)
                      &mesh, //our Mesh
                      NULL);  //GlyphMetrics, advanced stuff
   if(FAILED(hr)){
      g_engine.FatalError("Error creating font mesh");
   }

   //Since the mesh created by D3DXCreateText doesn't have all the
   //vertex components in it that we want, so we clone it and specify
   //the FVF that we want used.
   hr=mesh->CloneMeshFVF(D3DXMESH_MANAGED, //Options, see the SDK docs
                         MESH_FVF,         //The FVF we want our new mesh to have
                         g_engine.GetDevice(),//Our D3D Device
                         &g_mesh);
   if(FAILED(hr)){
      g_engine.FatalError("Error cloning mesh");
   }

   //Clean up the resources we don't need anymore
   mesh->Release();

   ReleaseDC(g_engine.GetWindow(),win_hdc);

   DeleteObject(my_font);


   //Generate normals for our mesh, by cloning it we created a normal for each
   //vertex, but the value hasn't been initialized.
   hr=D3DXComputeNormals(g_mesh);
   if(FAILED(hr)){
      g_engine.FatalError("Error cloning mesh");
   }

   //Randomly colour our mesh
   colour_font();

}

To make our font show up better (and look more interesting) we assign a random colour to each vertex. This is done in the colour_font procedure.
void colour_font(void){
HRESULT hr;
ULONG vert_num,count;
my_vertex *vertices;
unsigned char red,green,blue;

   //Find out how many vertices there are in the mesh
   vert_num=g_mesh->GetNumVertices();

   //Lock the vertex buffer with the mesh
   hr=g_mesh->LockVertexBuffer(0,(unsigned char **)&vertices);

   //Loop through each vertex assigning a random colour.
   //Each channel (rgb) is assign (0-239 + 15), which keeps the
   //colour from being too dark.
   for(count=0;count < vert_num; count++){
      red=(rand()%240)+15;
      green=(rand()%240)+15;
      blue=(rand()%240)+15;
      vertices[count].colour=D3DCOLOR_XRGB(red,green,blue);
   }

   //Now that we can unlock our buffer and it's ready to go.
   g_mesh->UnlockVertexBuffer();

}

After that the only left to do is draw the font. Here's our Render function.
void Render(void){
D3DXMATRIX matWorld;
D3DXMATRIX tmp_matrix;

   //Clear the ZBuffer & colour
   g_engine.ClearDZ();

   //Notify the device that we're ready to render
   if(SUCCEEDED(g_engine.BeginScene())){

      g_engine.GetDevice()->SetVertexShader(MESH_FVF);

      //By setting the world to the Identity and then multiplying each matrix
      //one by one, we get code that's slightly less efficient than we could write it.
      //However, this makes it easy to drop in new transformations or remove them
      //with minimal fuss.  You can always optimize afterwards if this really did turn
      //out to be a bottleneck (unlikely)
      D3DXMatrixIdentity(&matWorld);

      //Roughly center the mesh, I came to this value by guessing rather than
      //calculation.
      D3DXMatrixTranslation(&tmp_matrix,-3.0f,0,0);
      D3DXMatrixMultiply(&matWorld,&matWorld,&tmp_matrix);

      //Spin!
      D3DXMatrixRotationY(&tmp_matrix,GetTickCount()/1500.0f);
      D3DXMatrixMultiply(&matWorld,&matWorld,&tmp_matrix);


      //Set our World Matrix
      g_engine.GetDevice()->SetTransform(D3DTS_WORLD,&matWorld );

      //This is all it takes to draw the mesh.  Nice and easy.
      g_mesh->DrawSubset(0);


      g_engine.EndScene();
   }

   //Show the results
   g_engine.GetDevice()->Present( NULL, NULL, NULL, NULL );
   
}