DirectX11 Draw Fonts

  • Tags : directx11
  • time :

DirectX11 Draw Fonts

1 How to implement font drawing in DirectX

Due to Microsoft removing ID3DXFont, a very useful font interface in Direct3D 9, there is currently no official font solution available in Direct3D 11. So if you want to draw the font, you must manually draw the text
We introduce a method of graphic mapping to draw fonts, which involves creating a picture of the font to be drawn in the game (as shown in the figure below), then cutting open character images in the game and drawing them according to the required characters in the string


.

2 How to cut the texture of a font into each character image

In fact, we did not actually cut open a font texture, but instead selected different characters by modifying the UV. In fact, we only need to add some code to modify the vertex cache in the previous demo to dynamic cache, and add a function to fill our texture sprites into the cache. Dynamic caching is very suitable for situations where we need to modify the content of a cache. It is not recommended to create and destroy static cache blocks multiple times, especially frame by frame. You should use dynamic cache to do such tasks (as discussed in the previous section on how to create dynamic cache)
That is to say, we can modify the vertex position (character position) and UV (character texture) in the dynamic vertex cache.

3 How to modify the data of dynamic vertex cache

To modify the dynamic cache, the first step is to call the Map function of the D3D device to obtain pointers to sub resources (ID3D11Buffer inherits from ID3D11Resource, so cache is also a type of resource)
The prototype of the Map function is as follows:

HRESULT Map(
[in]            ID3D11Resource           *pResource,
[in]            UINT                     Subresource,
[in]            D3D11_MAP                MapType,
[in]            UINT                     MapFlags,
[out, optional] D3D11_MAPPED_SUBRESOURCE *pMappedResource
);

The first parameter pResource is the source of the mapping
The second parameter, Subresource, is the index of the sub resource (set to 0 because we do not have multiple sub resources)
The third parameter D3D11_ MAP, in this demo, the mapping type is D3D11_ MAP_ WRITE_ DISCARD, which instructs Direct3D to consider previous values in the cache as undefined
The fourth parameter MapFlags is the mapping identifier. For other mapping types, the mapping identifier can be D3D11_ MAP_ FLAG_ DO_ NOT_ WAIT. But when using mapping type D3D11_ MAP_ WIRTE_ DISCARD, then the mapping identifier must be 0 because D3D11_ MAP_ FLAG_ DO_ NOT_ The WAIT identifier cannot be used for this mapping type
The fifth parameter pMappedResource, using a D3D11_ MAPPED_ A pointer to the SURESOURCE structure type is used to store the mapped sub resource. (We use it to modify the dynamic cache. In order to update the cache, we simply copy any data to the pData member of the D3D11MAPPED-SUBRESOURCE structure.).

4 Draw font example code

(If there are too many complete codes, I won't post them. Please refer to the source code included in the book for yourself.).

bool D3DTextDemo::DrawString( char* message, float startX, float startY )
{
// Size in bytes for a single sprite.
const int sizeOfSprite = sizeof( VertexPos ) * 6;
// Demo's dynamic buffer setup for max of 24 letters.
const int maxLetters = 24;
int length = strlen( message );
// Clamp for strings too long.
if( length > maxLetters )
  length = maxLetters;
// Char's width on screen.
float charWidth = 32.0f / 800.0f;
// Char's height on screen.
float charHeight = 32.0f / 640.0f;
// Char's texel width.
float texelWidth = 32.0f / 864.0f;
// verts per-triangle (3) * total triangles (2) = 6.
const int verticesPerLetter = 6;
D3D11_MAPPED_SUBRESOURCE mapResource;
HRESULT d3dResult = d3dContext_->Map( vertexBuffer_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapResource );
if( FAILED( d3dResult ) )
{
  DXTRACE_MSG( "Failed to map resource!" );
  return false;
}
// Point to our vertex buffer's internal data.
VertexPos *spritePtr = ( VertexPos* )mapResource.pData;
const int indexA = static_cast<char>( 'A' );
const int indexZ = static_cast<char>( 'Z' );
for( int i = 0; i < length; ++i )
{
  float thisStartX = startX + ( charWidth * static_cast<float>( i ) );
  float thisEndX = thisStartX + charWidth;
  float thisEndY = startY + charHeight;
  spritePtr[0].pos = XMFLOAT3( thisEndX,   thisEndY, 1.0f );
  spritePtr[1].pos = XMFLOAT3( thisEndX,   startY,   1.0f );
  spritePtr[2].pos = XMFLOAT3( thisStartX, startY,   1.0f );
  spritePtr[3].pos = XMFLOAT3( thisStartX, startY,   1.0f );
  spritePtr[4].pos = XMFLOAT3( thisStartX, thisEndY, 1.0f );
  spritePtr[5].pos = XMFLOAT3( thisEndX,   thisEndY, 1.0f );
  int texLookup = 0;
  int letter = static_cast<char>( message[i] );
  if( letter < indexA || letter > indexZ )
  {
      // Grab one index past Z, which is a blank space in the texture.
      texLookup = ( indexZ - indexA ) + 1;
  }
  else
  {
      // A = 0, B = 1, Z = 25, etc.
      texLookup = ( letter - indexA );
  }
  float tuStart = 0.0f + ( texelWidth * static_cast<float>( texLookup ) );
  float tuEnd = tuStart + texelWidth;
  spritePtr[0].tex0 = XMFLOAT2( tuEnd, 0.0f );
  spritePtr[1].tex0 = XMFLOAT2( tuEnd, 1.0f );
  spritePtr[2].tex0 = XMFLOAT2( tuStart, 1.0f );
  spritePtr[3].tex0 = XMFLOAT2( tuStart, 1.0f );
  spritePtr[4].tex0 = XMFLOAT2( tuStart, 0.0f );
  spritePtr[5].tex0 = XMFLOAT2( tuEnd, 0.0f );
  spritePtr += 6;
}
d3dContext_->Unmap( vertexBuffer_, 0 );
d3dContext_->Draw( 6 * length, 0 );
return true;
}