DirectX11 绘制字体

发布于 2020-05-17  121 次阅读


1. DirectX中如何实现绘制字体?

由于Direct3D11中微软移除了ID3DXFont这个在Direct3D 9中非常好用的字体接口,这样导致了目前的Direct3D11中竟然没有一个官方的字体解决方案。所以如果要绘制字体,必须要采取手动绘制文本的方式。
我们这里介绍一种图形贴图的方式来绘制字体,即将游戏中要绘制的字体做成一副图片(见下图),然后在游戏里面切开一个个字符图片,按字符串所需字符来绘制。

2. 如何将一幅字体纹理切开每一个字符图片?

其实我们并没有真正的切开一幅字体纹理,而是通过修改UV来选中不同的字符。实际上,我们只需要增加一些代码来将之前 Demo 中的顶点缓存修改为动态缓存,和增加一个函数来将我们的贴图精灵填充到缓存中即可。 动态缓存对于我们需要修改一块缓存中的内容的这种情况来说是很合适的。 不推荐多次创建和销毁静态缓存块,特别是逐帧这样做,你应该使用动态缓存来做这样的任务(上一节已经介绍过如何创建动态缓存)。
也就是说我们就修改动态顶点缓存中的顶点位置(字符的位置)和UV(字符的纹理)就可以了。

3. 如何修改动态顶点缓存的数据?

想修改动态缓存,首先要调用D3D设备的Map函数,来获得子资源的指针(ID3D11Buffer继承自 ID3D11Resource,所以缓存也是一种资源)。
Map函数的原型如下:

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

第一个参数pResource,是映射的源头。
第二个参数Subresource,是子资源的索引(设置为 0,因为我们没有多个子资源)。
第三个参数D3D11_MAP,在本 Demo 中映射类型是 D3D11_MAP_WRITE_DISCARD,它指示 Direct3D 将缓存中的之前的值作为未定义考虑。
第四个参数MapFlags,映射标识。对于其它的映射类型,映射标识可以是 D3D11_MAP_FLAG_DO_NOT_WAIT。但是当使用映射类型D3D11_MAP_WIRTE_DISCARD,则映射标识必须是 0,因为D3D11_MAP_FLAG_DO_NOT_WAIT标识不能用于此种映射类型。
第五个参数pMappedResource,用一个D3D11_MAPPED_SUBRESOURCE结构体类型的指针来保存该映射子资源。(我们通过它来修改动态缓存。为了更新缓存,我们只需要简单的拷贝任何数据给 D3D11_MAPPED_SUBRESOURCE 结构的 pData 成员。 )

4. 绘制字体示例代码

(完整代码太多就不贴了,请自行查看该书附带源码)

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;
}

5. Free Type Direct11 绘制字体方案

使用FreeType实现DirectX11的文本输入

          D3D11取消了文本输入的接口(DX9有提供D3DX9Font接口),这使得在D3D11中想要输入文本变成一件棘手的事,当然这个棘手仅仅只是对于娱乐玩家来说,因为真正从事游戏编程的大牛们自然有自己的解决方案,但是我那奇怪了,为什么网上搜了不少就是找不到一个想要的解决方案呢?大多都是说使用FreeType来渲染字体而且还是针对OpenGL的,好吧,搜了很多关于怎么在DirecteX11里使用FreeType的方案却也没有找到多少有用的信息,要知道,很多人研究D3D11并非真正的拿去工作,也许只是工作之余想要研究一下,于是对D3D11或者是FreeType并没有了解多少,又或许只极限于搭一个框架,又或者仅构架一个自己方便娱乐研究使用的小引擎而已——像我这种蛋疼的人,所以这时候遇到问题可能不能很快的解决,然后网上寻找解决方案,但是怎么在D3D11里面渲染字体的文章相对很少,所以这里提供一种简单的方法,以供大家参考。

          如果不想要输入显示中文文本的话,那么有一个简单的方案:准备一张拥有所有英文字符和标点符号等等可打印字符的一个图,按照纹理坐标的定义[0,1]提取每个字符的纹理坐标,然后使用正方形对纹理坐标进行贴图,最后得到我们想要的结果,当然这不是这里要说的主题,因为这个方式相对来最简单,因为我们不可能仅使用英文字符,很多时候还是愿意输入中文的。

        好吧,下面开始进入正题,首先定义一个顶点结构,该结构保存顶点位置,颜色以及纹理坐标。

struct FontVertex{
	XMFLOAT3 pos;
	XMFLOAT4 color;
	XMFLOAT2 tex;
};
在DirectX里面使用4个顶点再加6个索引便可构成一个矩形:
FontVertex points[] = {
		XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT4{ 1.f, 0.f, 0.f, 1.f }, XMFLOAT2{ 0, 0 },
		XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT4{ 1.f, 0.f, 0.f, 1.f }, XMFLOAT2{ 1, 0 }, 
		XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4{ 1.f, 0.f, 0.f, 1.f }, XMFLOAT2{ 0, 1 },
		XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4{ 1.f, 0.f, 0.f, 1.f }, XMFLOAT2{ 1, 1 } 
	};
 
unsigned index[6] = { 0, 1, 2, 2, 1, 3 };

  那么现在再加一字体纹理图就渲染上一个字体。只是这个字体纹理怎么获取呢?当然是使用FreeType生成bitmap,然后装载成D3D11纹理(因为本人不是从事游戏开发的,对D3D11的了解也只是懂点而已,所以要是有些说得不对的地方或者是说不清楚的地方还望大家多多指教)。

         对于FreeType的使用这里不作详细介绍,因为网上可以搜到很多相关资料,只要查阅一下那些资料就算不懂FreeType的也能够看懂这里的代码。那么这里就详细的说一下怎么在D3D11里面使用他的细节,先来看看头文件:

#pragma once
#include <MString.h>
#include "MSize.h"
#include <unordered_map>
#include "D3D11/MDx11BufferManage.h"
#include "D3D11/MDx11LayoutManage.h"
#include <tuple>
#include <HContainer.h>
#include <ft2build.h>
#include FT_FREETYPE_H
 
#define MCheneseFontTTF "simhei.ttf"  // 预定义一个中文的字体
#define MEnglishFontTTF "Arial.ttf"  // 预定义一个英文的字体
 
struct FontVertex{
	XMFLOAT3 pos;
	XMFLOAT4 color;
	XMFLOAT2 tex;
};
 
 
typedef std::tuple<float, float, float, float, ID3D11ShaderResourceView*> CharBitmapType;
class MFTFont
{
public:
	MFTFont(const mj::MString& fontname, const float size, const bool antialiased);
	~MFTFont();
	void InitFont(ID3D11Device*  pDevice, ID3D11DeviceContext* pContext);
 
	void RenderText(const std::wstring& text);
	void OnSizeChanged(const MRectF& size);
	void StartPoint(const MPointF& pt);
	void SetFontColor(const XMFLOAT4& color);
	void SetFontName(const mj::MString& fontname);
	void SetFontSize(unsigned fontsize);
	void SetFont(const mj::MString& fontname, const unsigned fontsize);
 
	MFTFont(const MFTFont& other) = delete;
	MFTFont& operator=(const MFTFont& other) = delete;
protected:
	void UpdateFont();
	void Free();
	ID3D11ShaderResourceView* GetShaderView(wchar_t ch);
private:
	ID3D11Device*			p_Device;
	ID3D11DeviceContext*		p_DeviceContex;
 
	FT_Face				mFTFace;
	FT_GlyphSlot			mFTSlot;
 
	unsigned			mCharWidth;
	unsigned			mCharHeight;
 
	ID3D11Buffer*			pVBBuffer;
	ID3D11Buffer*			pIndexBuffer;
 
	ID3DX11EffectShaderResourceVariable* pFontTextureVariable;
 
	float		mFontSize;	// 字体大小
 
	bool		bIsAntiAliased;  // 反走样
 
	//==========================================
	// 下面几个FreeType变量用于排版字体
	//==========================================
	float		mAscender;
	float		mDescender;
	float       	mFontHeight;
	float		mAddance;
	float       	mToplineHeigh;
 
	mj::MString	mFontName;   // 字体名称
 
	MDx11LayoutManage*				pLayoutManage;
	ID3D11InputLayout*				pLayout;
	ID3DX11Effect*					pEffect;
	MDx11BufferManage*				pBufferManage;
	ID3D11ShaderResourceView*			pFontTexture{ nullptr };
	std::unordered_map<wchar_t, CharBitmapType> 	mCharTextMap;
	MRectF						mWindowRect;    // 保存渲染目标的窗口大小
	MRectF						mRendArear;     // 渲染区域
	MPointF						mPointPen; 	// 开始渲染位置
	FontVertex					mFontVectex[4]; // 字体所处的矩形区域
	XMFLOAT4					mFontColor;     // 字体颜色
};

MRectF 保存一个矩形的左上角顶点和宽高,类型为float,MPointF保存的是一个float类型的xy,MString是一个字符串处理类,可以使用std::string来代替。MDx11LayoutManage管理D3D11的输入布局以及ID3DX11Effect的框架信息。MDx11BufferManage管理D3D11常用的buffer信息。mCharTextMap用于缓存字符纹理。该类禁止复制和赋值。

#include "MFTFont.h"
#include <HPath_File.h>
#include "MSize.cpp"
 
#ifdef _DEBUG | __DEBUG
 
# pragma comment(lib,"FreeType_D")
#else
# pragma comment(lib,"FreeType")
#endif
 
#define INTER_GLYPH_PAD_SPACE 2
#define INTER_FONT_STEP 4
#define SPACE_STEP 10.0
 
#define FT_POS_COEF  (1.0/64.0)
 
static FT_Library ft_lib;
static int Ft_Usage_Count = 0;
 
#undef __FTERRORS_H__
#define FT_ERRORDEF( e, v, s ) s,
#define FT_ERROR_START_LIST static const char* ft_errors[] = {
#define FT_ERROR_END_LIST 0};
#include FT_ERRORS_H
 
 
MFTFont::MFTFont(const mj::MString& fontname, const float size, const bool antialiased) :
	mFontName(fontname), mFontSize(size), bIsAntiAliased(antialiased)
{
	if (!Ft_Usage_Count++)
		FT_Init_FreeType(&ft_lib);
	UpdateFont();
	mWindowRect = MRectF(0, 0, 130, 60);        // 定义一个默认渲染窗口大小
	mFontColor = XMFLOAT4(1.f, 0.f, 0.f, 1.f);  // 定义一个默认颜色
 
	//=============================================
	// 设定纹理坐标
	//=============================================
	mFontVectex[0].tex = XMFLOAT2(0.f, 0.f);
	mFontVectex[1].tex = XMFLOAT2(1.f, 0.f);
	mFontVectex[2].tex = XMFLOAT2(0.f, 1.f);
	mFontVectex[3].tex = XMFLOAT2(1.f, 1.f);
 
	mPointPen = MPointF(0, 40);  // 默认开始为,字体的渲染使用窗口坐标
				     // 所以在真正渲染的时候需要转换为世界坐标	
}
 
 
MFTFont::~MFTFont()
{
	Free();
	if (!--Ft_Usage_Count)
		FT_Done_FreeType(ft_lib);
	safe_delete(pLayoutManage);
	safe_delete(pBufferManage);
}

在构造函数中加载一次FreeType库,然后将字体初始化,初始化函数字体如下:

void MFTFont::UpdateFont()
{
	Free();
	if (mFontName.find(".") == -1){
		mFontName += ".ttf";
	}
 
	if (!file::fileisexist(mFontName)){
		int n = mFontName.find_last("/");
		if (n == -1){
			n = mFontName.find_last("\\\\");
		}
 
		mj::MString newStr;
		if (n != -1){
			newStr = mFontName.copy<int>(n, mFontName.length());
		}
		mj::MString oldString = mFontName;
		mj::MString header = "C:\\Windows\\Fonts\\";
		if (n != -1){
			mFontName = header + newStr;
		}
		else{
			mFontName = header + mFontName;
		}
		if (!file::fileisexist(mFontName)){
			mj::HParseString par;
			par("%1 File isn't exist", oldString);
			throw std::runtime_error(par);
		}
	}
		
	FT_Error error;
 
	if ((error = FT_New_Face(ft_lib, mFontName.c_str(),
		0, &mFTFace)) != 0)
	{
		mj::HParseString par;
		par("Failed to create face from font file ' %1 ' error was: %2 " ,
			mFontName ,
			((error < FT_Err_Max) ? ft_errors[error]:"unknown error" ));
		throw std::runtime_error(par);
	}
		
	FT_Select_Charmap(mFTFace, FT_ENCODING_UNICODE);
	FT_Set_Pixel_Sizes(mFTFace, mFontSize, mFontSize);
 
	if (!mFTFace->charmap)
	{
		FT_Done_Face(mFTFace);
		mFTFace = nullptr;
		mj::HParseString par;
		par("The font ' %1 ' does not have a Unicode charmap, and cannot be used.", mFontName);
		throw std::runtime_error(par);
	}
 
	//===================================================
	// DPI
	//===================================================
	unsigned horzdpi = 96;
	unsigned vertdpi = 96;
 
	float hps = mFontSize * 64;
	float vps = mFontSize * 64;
	
 
	if (FT_Set_Char_Size(mFTFace, FT_F26Dot6(hps), FT_F26Dot6(vps), horzdpi, vertdpi))
	{
		float ptSize_72 = (mFontSize * 72.0f) / vertdpi;
		float best_delta = 99999;
		float best_size = 0;
		for (int i = 0; i < mFTFace->num_fixed_sizes; i++)
		{
			float size = mFTFace->available_sizes[i].size * float(FT_POS_COEF);
			float delta = fabs(size - ptSize_72);
			if (delta < best_delta)
			{
				best_delta = delta;
				best_size = size;
			}
		}
 
		if ((best_size <= 0) ||
			FT_Set_Char_Size(mFTFace, 0, FT_F26Dot6(best_size * 64), 0, 0))
		{
			mj::HParseString par;
			par("The font ' %1 ' cannot be "
				"rasterised at a size of %2  points, and cannot be "
				"used.", mFontName,mFontSize);
			throw std::runtime_error(par);
		}
	}
 
	if (mFTFace->face_flags & FT_FACE_FLAG_SCALABLE)
	{
		float y_scale = mFTFace->size->metrics.y_scale * float(FT_POS_COEF) * (1.0f / 65536.0f);
		mAscender = mFTFace->ascender * y_scale;
		mDescender = mFTFace->descender * y_scale;
		mFontHeight = mFTFace->height * y_scale;
	}
	else
	{
		mAscender = mFTFace->size->metrics.ascender * float(FT_POS_COEF);
		mDescender = mFTFace->size->metrics.descender * float(FT_POS_COEF);
		mFontHeight = mFTFace->size->metrics.height * float(FT_POS_COEF);
	}
}
在更换字体或者重新设置字体大小时都必须要调用UpdateFont(),
因为他会重新计算排版信息,Free()将旧字体信息清除。
void MFTFont::Free()
{
	if (!mFTFace)
		return;	
	FT_Done_Face(mFTFace);
	mFTFace = nullptr;
	for (auto & m : mCharTextMap){
		auto it = std::get<4>(m.second);
		safe_release(it);
	}
	mCharTextMap.clear();
}

初始化字体后,我们要将他和D3D11结合起来,那就是InitFont(ID3D11Device* pDevice, ID3D11DeviceContext* pContext)函数。

void MFTFont::InitFont(ID3D11Device* pDevice, ID3D11DeviceContext* pContext){
 
	if (pDevice == 0 || pContext == 0)
		throw std::runtime_error("Device invalid");
 
	p_Device = pDevice;
	p_DeviceContex = pContext;
 
	//=================================================
	// 创建布局管理器
	//=================================================
	if (pLayoutManage == nullptr){
		pLayoutManage = new MDx11LayoutManage(pDevice);
	}
 
	//=====================================================
	// 创建Effect
	//=====================================================
	try{
		pEffect = pLayoutManage->dx_FxComplieFile("./FX/Font.fx");
	}
	catch (std::runtime_error e){
		mj::HParseString par;
		par("\nFILE:%1\nFun:%2\nLine:%3", __FILE__, __FUNCTION__, __LINE__);
		box::Error_MessageBox(std::string(e.what()) + par.getresult());
	}
 
	D3D11_INPUT_ELEMENT_DESC solidColorLayout[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT,
		0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT,
		0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 
		0, 28, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};
 
	unsigned len = ARRAYSIZE(solidColorLayout);
 
	try{
		pLayout = pLayoutManage->dx_CreatInputLayout(solidColorLayout, len,"FontRenderTech");
	}
	catch (std::runtime_error e){
		mj::HParseString par;
		par("\nFILE:%1\nFun:%2\nLine:%3", __FILE__, __FUNCTION__, __LINE__);
		box::Error_MessageBox(std::string(e.what()) + par.getresult());
		return;
	}
 
	pFontTextureVariable = pLayoutManage->dx_GetShaderResourceFromEF("FontTexture");
 
	// 默认情况下也会渲染一个矩形出来的
	FontVertex points[] = {
		XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT4{ 1.f, 0.f, 0.f, 1.f }, XMFLOAT2{ 0, 0 },
		XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT4{ 1.f, 0.f, 0.f, 1.f }, XMFLOAT2{ 1, 0 }, 
		XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4{ 1.f, 0.f, 0.f, 1.f }, XMFLOAT2{ 0, 1 },
		XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4{ 1.f, 0.f, 0.f, 1.f }, XMFLOAT2{ 1, 1 } 
	};
 
	pBufferManage = new MDx11BufferManage(pDevice);
	//-----------------------------------
	// 创建纹理资源
	//-----------------------------------
	try{
		pFontTexture = pBufferManage->dx_CreateTexTureResouceOnly("./Res/test2.bmp");
	}
	catch (std::runtime_error e){
		mj::HParseString par;
		par("\nFILE:%1\nFun:%2\nLine:%3", __FILE__, __FUNCTION__, __LINE__);
		box::Error_MessageBox(std::string(e.what()) + par.getresult());
		return;
	}
 
 
	//========================================================
	// 创建一个动态的顶点buffer
	//========================================================
	D3D11_SUBRESOURCE_DATA pointssubdata;
	pointssubdata.pSysMem = points;
 
	D3D11_BUFFER_DESC pointBufDesc;
	pointBufDesc.ByteWidth = sizeof(FontVertex)* 4;
	pointBufDesc.Usage = D3D11_USAGE_DYNAMIC;
	pointBufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	pointBufDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	pointBufDesc.MiscFlags = 0;
	HRESULT hr = pDevice->CreateBuffer(&pointBufDesc, &pointssubdata, &pVBBuffer);
	if (FAILED(hr)){
		throw std::runtime_error("Create Vertex Buffer fail.......");
	}
 
	//============================================================
	// 创建一个索引buffer
	//============================================================
	unsigned index[6] = { 0, 1, 2, 2, 1, 3 };
 
	D3D11_SUBRESOURCE_DATA subdata{ 0 };
	subdata.pSysMem = index;
	D3D11_BUFFER_DESC BufDesc{ 0 };
	BufDesc.ByteWidth = sizeof(unsigned)* 6;
	BufDesc.Usage = D3D11_USAGE_DEFAULT;
	BufDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	BufDesc.MiscFlags = 0;
	hr = pDevice->CreateBuffer(&BufDesc, &subdata, &pIndexBuffer);
	if (FAILED(hr)){
		throw std::runtime_error("Create Index Buffer fail.......");
	}
}

Font.fx 文件待会儿给出。在InitFont函数中主要创建一个输入布局和一个动态顶点缓冲buffer,还有一个索引buffer。
           不过在正式进行渲染前还有一个重量级的函数,GetShaderView(wchar_t ch),参数是我们要打印的字符,返回的是该字符的纹理资源,他的实现如下:

ID3D11ShaderResourceView* MFTFont::GetShaderView(wchar_t ch){
	ID3D11ShaderResourceView* __result{ nullptr };
	
	if (FT_Load_Char(mFTFace, ch, FT_LOAD_RENDER))
		return __result;
 
	mFTSlot = mFTFace->glyph;
	FT_Bitmap& bitmap = mFTSlot->bitmap;
 
	mCharHeight = bitmap.rows;
	mCharWidth = bitmap.width;
 
 
	D3D11_SUBRESOURCE_DATA __subData;
	__subData.pSysMem = bitmap.buffer;
	__subData.SysMemPitch = mCharWidth;
	__subData.SysMemSlicePitch = mCharWidth*mCharHeight ; // 只对3D纹理有效
 
	ID3D11Texture2D*      Tex2D;
	D3D11_TEXTURE2D_DESC  Tex2Dtdesc;
 
	Tex2Dtdesc.Width = mCharWidth;
	Tex2Dtdesc.Height = mCharHeight;
	Tex2Dtdesc.MipLevels = 1;
	Tex2Dtdesc.ArraySize = 1;
 
	Tex2Dtdesc.SampleDesc.Count = 1;
	Tex2Dtdesc.SampleDesc.Quality = 0;
	Tex2Dtdesc.Usage = D3D11_USAGE_DEFAULT;
	Tex2Dtdesc.Format = DXGI_FORMAT_A8_UNORM; 
	Tex2Dtdesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
 
	Tex2Dtdesc.CPUAccessFlags = 0;
	Tex2Dtdesc.MiscFlags = 0;
 
	if (FAILED(p_Device->CreateTexture2D(&Tex2Dtdesc, &__subData, &Tex2D))){
		return __result;
	}
 
	D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc;
	viewDesc.Format = Tex2Dtdesc.Format;
	viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
	viewDesc.Texture2D.MipLevels = Tex2Dtdesc.MipLevels;
	viewDesc.Texture2D.MostDetailedMip = 0;
 
	HRESULT hr = p_Device->CreateShaderResourceView(Tex2D, &viewDesc, &__result);
	if (FAILED(hr)){
		mj::HParseString par;
		par("CreateShaderResourceView Failed\nFILE:%1\nFun:%2\nLine:%3", __FILE__, __FUNCTION__, __LINE__);
		throw std::runtime_error(par);
	}
	safe_release(Tex2D);
 
	//==================================================
	// 获取该字符的排版信息
	//==================================================
	mToplineHeigh = mFTSlot->metrics.horiBearingY >> 6;
	float h = mFTSlot->metrics.height>>6;
	mToplineHeigh = h - mToplineHeigh;
	mAddance = mFTSlot->metrics.horiAdvance >> 6;
	return __result;
}

其实最需要说明的就是这个函数,这里是关键,FT_Load_Char(mFTFace, ch, FT_LOAD_RENDER)渲染后得到的位图是一张灰度图,他只有8位,我们要做的就是用这个8bit的灰度图创建出我们想要的2D纹理图。所以,我们将该8bit的数据作为我们创建纹理的alpha通道,所以我们使用DXGI_FORMAT_A8_UNORM作为2D纹理格式,而这种格式生成的纹理的rgb通道应该都是0,因为打印出来的字体是黑色的(^_^,我又开始连蒙带猜了,有木有大神出来解惑啊?)因为一开始使用如下方式但是居然没有显示出文字,我也是醉了:

int* buffer = new int[mCharHeight*mCharWidth];
	unsigned char *__buffer = reinterpret_cast<unsigned char*>(buffer);;
	for (int i = 0; i < bitmap.rows; ++i)
	{
		unsigned char *src = bitmap.buffer + (i *bitmap.pitch);
		switch (bitmap.pixel_mode)
		{
		case FT_PIXEL_MODE_GRAY:
			
			for (int j = 0; j < bitmap.width; ++j)
			{
				// RGBA
				*__buffer++ = 0xFF;
				*__buffer++ = 0xFF;
				*__buffer++ = 0xFF;
				*__buffer++ = *src++;
			}
			break;
 
		case FT_PIXEL_MODE_MONO:
			for (int j = 0; j < bitmap.width; ++j)
				buffer[j] = (src[j / 8] & (0x80 >> (j & 7))) ? 0xFFFFFFFF : 0x00000000;
			break;
 
		default:
			throw std::runtime_error("The glyph could not be drawn because the pixel mode is "
				"unsupported.");
			break;
		}
	}
	D3D11_SUBRESOURCE_DATA __subData;
	__subData.pSysMem = buffer*4;
	__subData.SysMemPitch = mCharWidth;
	__subData.SysMemSlicePitch = mCharWidth*mCharHeight*4 ;
 
	ID3D11Texture2D*      Tex2D;
	D3D11_TEXTURE2D_DESC  Tex2Dtdesc;
 
	Tex2Dtdesc.Width = mCharWidth;
	Tex2Dtdesc.Height = mCharHeight;
	Tex2Dtdesc.MipLevels = 1;
	Tex2Dtdesc.ArraySize = 1;
 
	Tex2Dtdesc.SampleDesc.Count = 1;
	Tex2Dtdesc.SampleDesc.Quality = 0;
	Tex2Dtdesc.Usage = D3D11_USAGE_DEFAULT;
	Tex2Dtdesc.Format = DXGI_FORMAT_A8_UNORM;

有没有D3D11大神帮忙解释下为什么吗?这对一个业余的人来说实在是太纠结了,主要是关于D3D11的书籍国内还没多少。好吧,不纠结,我们使用DXGI_FORMAT_A8_UNORM格式一样很easy的达到我们想要的目的,纠结那么多干嘛呢?因为我们只是想要能够输入文本就好了,有没啥需求。 

好吧,现在我们可以开始渲染了:

void MFTFont::RenderText(const std::wstring& text){
	UINT stride = sizeof(FontVertex);
	UINT offset = 0;
	MPointF oldPoint = mPointPen;  // 渲染之前我们需要保存笔的位置
 
	p_DeviceContex->IASetInputLayout(pLayout);
	p_DeviceContex->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	p_DeviceContex->IASetVertexBuffers(0, 1, &pVBBuffer, &stride, &offset);
	p_DeviceContex->IASetIndexBuffer(pIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
	D3DX11_TECHNIQUE_DESC techDesc = pLayoutManage->dx_GetTichDescFromEF("FontRenderTech");
 
	//===============================================
	// 每个顶点的颜色都一样
	//===============================================
	mFontVectex[0].color = mFontColor;
	mFontVectex[1].color = mFontColor;
	mFontVectex[2].color = mFontColor;
	mFontVectex[3].color = mFontColor;
	//=====================================================================
	// 开始循环渲染字体
	//=====================================================================
	int n = text.length();
	for (int i = 0; i < n; ++i){
 
		//==============================================
		// 对空格和回车进行处理
		//==============================================
		if (text[i] == L' '){
			float step = SPACE_STEP;
			mPointPen.X(mPointPen.X() + step);
			continue;
		}
		if (text[i] == L'\n'){
			mPointPen.X(oldPoint.X());
			mPointPen.Y(mPointPen.Y() + mFontHeight);
			continue;
		}
 
		//========================================================
		// 获取字符纹理资源以及该字符的相关尺寸信息
		//========================================================
		if (mCharTextMap.count(text[i])){
			CharBitmapType& chartype = mCharTextMap.at(text[i]);
			mCharWidth = std::get<0>(chartype);
			mCharHeight = std::get<1>(chartype);
			mToplineHeigh = std::get<2>(chartype);
			mAddance = std::get<3>(chartype);
			pFontTexture = std::get<4>(chartype);
		}
			
		else{
			auto it = GetShaderView(text[i]);
			if (it){
				pFontTexture = it;
				CharBitmapType chartype(mCharWidth, mCharHeight, mToplineHeigh, mAddance, it);
				mCharTextMap[text[i]] = chartype;
			}
		}
 
		if (mPointPen.X() + mCharWidth >= mWindowRect.X() + mWindowRect.Width() - 10){
			mPointPen.X(oldPoint.X());
			mPointPen.Y(mPointPen.Y() + mFontHeight);
		}
 
		MRectF rect(mPointPen.X(), mPointPen.Y() - mAscender + mToplineHeigh, mCharWidth, mCharHeight);
 
		//======================================================
		// 变换坐标
		//======================================================
		mRendArear = LocalSpaceToWorldSpace(mWindowRect, rect);
 
		//======================================================
		// 设置字符的渲染区域
		//=======================================================
		mFontVectex[0].pos = XMFLOAT3(mRendArear.X(), mRendArear.Y() + mRendArear.Height(), 1.0);
		mFontVectex[1].pos = XMFLOAT3(mRendArear.X() + mRendArear.Width(), mRendArear.Y() + mRendArear.Height(), 1.0);
		mFontVectex[2].pos = XMFLOAT3(mRendArear.X(), mRendArear.Y(), 1.0);
		mFontVectex[3].pos = XMFLOAT3(mRendArear.X() + mRendArear.Width(), mRendArear.Y(), 1.0);
		mPointPen.X(mPointPen.X() + mAddance);
 
		//======================================================
		// 更新顶点坐标信息
		//======================================================
		FontVertex* data = pBufferManage->dx_GetNeedUpdateBuffer<FontVertex>(p_DeviceContex, pVBBuffer);
		for (int i = 0; i < 4; ++i){
			data[i] = mFontVectex[i];
		}
		pBufferManage->dx_EndUpdateBuffer(p_DeviceContex, pVBBuffer);
		
		pFontTextureVariable->SetResource(pFontTexture);
		for (unsigned int p = 0; p < techDesc.Passes; p++)
		{
			ID3DX11EffectPass* pass = pLayoutManage->dx_GetPassFromEF("FontRenderTech", p);
			if (pass != 0)
			{
				pass->Apply(0, p_DeviceContex);
				p_DeviceContex->DrawIndexed(6, 0, 0);
			}
		}
	}
	mPointPen = oldPoint;

 剩下的就是一些小函数了,没啥好说:

//==================================================
// 在窗体size改变的时候调用该函数以保证字体不会模糊
//===================================================
void MFTFont::OnSizeChanged(const MRectF& size){
	mWindowRect = size;
}
 
//===================================================
// 设置开始渲染位置
//===================================================
void MFTFont::StartPoint(const MPointF& pt){
	mPointPen = pt;
}
 
//===================================================
// 设置字体颜色
//===================================================
void MFTFont::SetFontColor(const XMFLOAT4& color){
	mFontColor = color;
}
 
//======================================================
// 设置字体名称
//======================================================
void MFTFont::SetFontName(const mj::MString& fontname){
	mFontName = fontname;
	UpdateFont();
}
 
//=====================================================
// 设置字体大小
//=====================================================
void MFTFont::SetFontSize(unsigned fontsize){
	mFontSize = fontsize;
	UpdateFont();
}
 
//=======================================================
// 对字体名称和大小同时设置
//=======================================================
void MFTFont::SetFont(const mj::MString& fontname, const unsigned fontsize){
	mFontName = fontname;
	mFontSize = fontsize;
	UpdateFont();
}

最后就是Font.fx文件:

//--------------------Font.fx---------------------------------------------//
Texture2D FontTexture;
 
 
struct VSSceneIn
{
	float3 pos : POSITION;
	float4 colour : COLOR;
	float2 tex : TEXCOORD;
};
 
struct PSSceneIn
{
	float4 pos : SV_Position;
	float4 colour : COLOR;
	float2 tex : TEXCOORD;
};
 
DepthStencilState DisableDepth
{
	DepthEnable = FALSE;
	DepthWriteMask = ZERO;
};
 
BlendState Font_Blend
{
	AlphaToCoverageEnable = false;
	BlendEnable[0] = true;
	SrcBlendAlpha = INV_DEST_ALPHA;	
	DestBlendAlpha = ONE;
	SrcBlend = SRC_ALPHA;
	DestBlend = INV_SRC_ALPHA;
};
 
 
SamplerState FontSampler
{
	Filter = MIN_MAG_MIP_LINEAR;
	AddressU = Border;
	AddressV = Border;
};
 
RasterizerState clipRasterstate
{
	DepthClipEnable = false;
	FillMode = Solid;
	CullMode = None;
	ScissorEnable = true;
};
 
 
 
// Vertex shader
PSSceneIn VSMain(VSSceneIn input)
{
	PSSceneIn output = (PSSceneIn)0.0;
   	output.pos = float4(input.pos, 1);
   	output.tex = input.tex;
	output.colour = input.colour;
	return output;
}
 
// Pixel shader
float4 PSMain(PSSceneIn input) : SV_Target
{
	float4 colour = FontTexture.Sample(FontSampler, input.tex);
	colour.rgb = 1;	  // 这里如果不这么做,将永远是黑色,如果上面所说的rgb全是0
	return colour* input.colour;
}
 
 
 
// Techniques
technique11 FontRenderTech
{
	pass P0
	{
		SetVertexShader(CompileShader(vs_5_0, VSMain()));
		SetGeometryShader(NULL);
		SetPixelShader(CompileShader(ps_5_0, PSMain()));
		SetDepthStencilState(DisableDepth, 0);
		SetBlendState(Font_Blend, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xFFFFFFFF);
		SetRasterizerState(clipRasterstate);
	}
}

到此大功告成,我们可以对比一下他和使用贴图纹理(DXUT使用的方法)的效果差异:

          首先是使用我们上面的类进行渲染的字体,这个图片上有两个画面是D3D11渲染的,一个按钮和一个子窗口,其中按钮使用的是中文字体,所以显示英文变成等宽显得有些丑,子窗口使用的是Arial字体,大小为12号。:

接下来我们使用图片纹理渲染方式的:

可以明显的看到有些模糊,字体越大越模糊,下面都使用我们上面的类进行14号Arial字体渲染,可以看到按钮上面的英文字符变得很漂亮:

最后我们和CEGUI的文本对比一下,发现都是一样一样的,button上面的绿色字体是上面的类进行渲染的,子窗口上的控件是CEGUI控件: