DirectX вики

Введение[]

В этом уроке мы научимся работать с текстом. В трехмерной графике есть два основных способа вывода текста – вывод обычного ttf и вывод текста из графического изображения.

Первый способ предоставляет нам массу возможностей по выводу текста (полная поддержка Unicode, возможность менять на лету шрифт, размер, начертание и т.д.), но при этом сам по себе обычно сильно снижает производительность.

Второй способ немного хитрый, первоначально готовится текстура со всеми нужными символами, затем просто выводим изображения этих символов. Вот так выглядит такая текстура:

Font13254435

Такой способ наиболее эффективен по производительности, но слишком ограничен по возможностям. Но как я сказал, это очень эффективный способ по производительности, и если вы знаете, что в проекте не нужна поддержка локализаций и при этом очень низкие требования к тексту, то это то, что стоит выбрать.

На самом деле очень часто эти два способа смешивают, например, где-то в памяти из ttf шрифта генерируют текстуру символов, или даже всего выводимого текста и потом работают с нею как с обычным изображением, но с возможностью в любой момент пере генерировать с другими параметрами. Но это сложная тема и она имеет мало отношения к DirectX, так что я ее рассматривать не буду.

Мы в этом уроке будем рассматривать только вывод текста из изображения. Возможно, в будущем мы еще вернемся к тексту и попробуем поработать с ttf.

Идея вывода текста из изображения в следующем:

  • Сначала подготавливается изображение со всеми нужными символами.
  • Далее часто готовится отдельный файл с описанием этих символов (идентификатор символа, его позиция и размеры на текстуре и т.д.). Хотя в некоторых случаях это не обязательно.
  • Затем грузится изображение, и файл с описаниями. По описаниям символов создается массив с помощью которого мы сможем позже находить характеристики, позицию и размер символа на текстуре.
  • Когда выводится текст, каждый символ ищется в массиве описаний, и затем выводится изображение с символом из текстуры.


Создаем шрифт
[]

Для создания изображения и файла описания мы будем использовать программу bmfont. Программа очень легка в использовании, и думаю не вызовет у вас сложностей, потыкайте туда-сюда, и разберетесь. Я ее так и освоил :).
В большом окне вы выбираете нужные символы (просто мышкой щелкайте по буквам, выбранные будут подсвечиваться светло-серым). Справа, даже не знаю, как называется. Вас скорее всего будут интересовать latin (английские буквы, цифры и знаки препинания) и Cyrillic (русские буквы). Вы можете передвигаться по этим пунктам либо с помощью прокрутки колесика мышки, либо щелчком по самим надписям (не там где флажок ставится, а по самому тексту).

После того как выберите нужные символы, переходим к настройкам. Заходите Option->Font Setting (F). Важным является Font, где можно выбрать шрифт и Size – размер текста. Помните при выборе размера, ваш шрифт, это простое растровое изображение и при увеличении текст будет резко терять в качестве, поэтому лучше сделайте текст большего размера и потом уменьшайте, чем наоборот. Остальные опции можно оставить по умолчанию, хотя вам, наверное, будет интересно сглаживание шрифта.

Option->Visualize. Позволяет примерно посмотреть на результат. Нам нужна только из-за одного умышленного упрощения, мы не будем работать со шрифтами из нескольких графических файлов, потому что это усложнит урок и не сильно нужно.

Option->Export Setting (T). Здесь вам важен размер текстуры, помните, мы работаем только с одной текстурой (здесь называемой pagе – страница). Поэтому увеличивайте размер пока все символы не вместятся в одну текстуру. В Presents выбирайте только «White text with alpha». И в Textures выбирайте png.

Вот теперь сохраняйте текстуру. Вы получите изображение и файл *.fnt с текстовым описанием символов на этой текстуре. Посмотрите оба файла.

Если у вас нет желания или что-то не получилось, скачайте вот этот уже готовый шрифт - шрифт

Пишем код[]

Как обычно, создаем проект и подключаем 4 фреймворк.

main.cpp

#include "MyRender.h"

int main()
{
	Framework framework;

	MyRender *render = new MyRender();

	FrameworkDesc desc;
	desc.render = render;

	framework.Init(desc);

	framework.Run();

	framework.Close();

	return 0;
}

Для работы с текстом мы создадим два класса – BitmapFont и Text. Первый будет описывать наш графический шрифт, хранить текстуру, шейдеры, массив описаний. Text же будет представлять обычную надпись которую можно вывести на экран. Как вы понимаете, надписей может быть много а шрифтов мало, поэтому ради экономии памяти я и разделил эти два класса.

В этом уроке мы загрузим один шрифт и выведем три надписи.

Создайте BitmapFont.h

#pragma once

#include "MyRender.h"
#include <vector>
#include <string>
#include <map>

struct VertexFont
{
	XMFLOAT3 pos;
	XMFLOAT2 tex;
};

class BitmapFont
{
private:
	struct CharDesc
	{
		CharDesc() : srcX(0), srcY(0), srcW(0), srcH(0), xOff(0), yOff(0), xAdv(0) {}

		short srcX;
		short srcY;
		short srcW;
		short srcH;
		short xOff;
		short yOff;
		short xAdv;
	};	

	struct ConstantBuffer
	{
		XMMATRIX WVP;
	};
	struct PixelBufferType
	{
		XMFLOAT4 pixelColor;
	};
public:
	BitmapFont(MyRender *render);

	bool Init(char *fontFilename);
	void Render(unsigned int index, float r, float g, float b, float x, float y);
	void BuildVertexArray(VertexFont *vertices, const wchar_t *sentence,int screenWidth, int screenHeight);
	void Close();

	ID3D11ShaderResourceView* GetTexture(){return m_texture;}

private:
	bool m_parse(char *fontFilename);
	bool m_InitShader(wchar_t *, wchar_t *);
	void m_SetShaderParameters(float r, float g, float b, float x, float y);
	void m_RenderShader(unsigned int index);

	MyRender *m_render;
	ID3D11Buffer *m_constantBuffer;
	ID3D11Buffer *m_pixelBuffer;
	ID3D11VertexShader *m_vertexShader;
	ID3D11PixelShader *m_pixelShader;
	ID3D11InputLayout *m_layout;
	ID3D11SamplerState *m_sampleState;
	ID3D11ShaderResourceView *m_texture;
	
	unsigned short m_WidthTex;
	unsigned short m_HeightTex;
	std::wstring m_file;
	std::map <int, CharDesc> m_Chars;
};

Создаем структуру для описания вершины, состоящую из двух характеристик – позиции и текстуры.

Структура CharDesc описывает один символ из текстуры – позицию, размер, опции. Откройте *.fnt вашего шрифта и вы увидите, откуда эти числа будут взяты.

Также две структуры для двух константных буферов – один хранит итоговую матрицу, а второй будет использоваться для передачи текста, что позволит нам менять цвет нашему шрифту.

Метод BuildVertexArray() получает пустой массив вершин и строку. Он будет «строить» массив вершин нашего текста.

Так же, как вы видите, я перенес шейдеры в описание шрифта, а не в текст, потому что обычно они одинаковы для всех надписей и нет смысла тратить память.

m_file – имя текстуры с символами.

m_Chars – массив описаний символов

BitmapFont.cpp

#include "BitmapFont.h"
#include <fstream>
#include <string>
#include <sstream>

using namespace std;

inline wchar_t* CharToWChar(char *mbString)
{ 
	int len = 0; 
	len = (int)strlen(mbString) + 1; 
	wchar_t *ucString = new wchar_t[len]; 
	mbstowcs(ucString, mbString, len); 
	return ucString; 
}

BitmapFont::BitmapFont(MyRender *render)
{
	m_render = render;
	m_texture = nullptr;
	m_constantBuffer = nullptr;
	m_vertexShader = nullptr;
	m_pixelShader = nullptr;
	m_layout = nullptr;
	m_sampleState = nullptr;
	m_pixelBuffer = nullptr;
	m_WidthTex = 0;
	m_HeightTex = 0;
}

bool BitmapFont::Init(char *fontFilename)
{
	if ( !m_parse(fontFilename) )
		return false;

	HRESULT hr = D3DX11CreateShaderResourceViewFromFile( m_render->m_pd3dDevice,m_file.c_str(), NULL, NULL, &m_texture, NULL );
	if( FAILED( hr ) )
		return false;

	if( !m_InitShader(L"BitmapFont.vs", L"BitmapFont.ps") )
		return false;

	return true;
}

В методе Init() мы сначала вызовем метод m_parse() который загрузит текстовую информацию о шрифте. Затем грузим текстуру, и напоследок инициализируем шейдеры.

Функция CharToWChar() Нам понадобится далее.

bool BitmapFont::m_parse(char *fontFilename)
{
	ifstream fin;
	fin.open(fontFilename);
	if(fin.fail())
		return false;

	string Line;
	string Read, Key, Value;
	size_t i;
	while( !fin.eof() )
	{
		std::stringstream LineStream;
		std::getline( fin, Line );
		LineStream << Line;
		
		LineStream >> Read;
		if( Read == "common" )
		{
			while( !LineStream.eof() )
			{
				std::stringstream Converter;
				LineStream >> Read;
				i = Read.find( '=' );
				Key = Read.substr( 0, i );
				Value = Read.substr( i + 1 );

				Converter << Value;
				if( Key == "scaleW" )
					Converter >> m_WidthTex;
				else if( Key == "scaleH" )
					Converter >> m_HeightTex;
			}
		}
		else if( Read == "page" )
		{
			while( !LineStream.eof() )
			{
				std::stringstream Converter;
				LineStream >> Read;
				i = Read.find( '=' );
				Key = Read.substr( 0, i );
				Value = Read.substr( i + 1 );

				std::string str;
				Converter << Value;
				if( Key == "file" )
				{
					Converter >> str;
					wchar_t *name = CharToWChar((char*)str.substr(1, Value.length()-2).c_str());
					m_file = name;
					_DELETE_ARRAY(name);
				}
			}
		}
		else if( Read == "char" )
		{
			unsigned short CharID = 0;
			CharDesc chard;

			while( !LineStream.eof() )
			{
				std::stringstream Converter;
				LineStream >> Read;
				i = Read.find( '=' );
				Key = Read.substr( 0, i );
				Value = Read.substr( i + 1 );

				Converter << Value;
				if( Key == "id" )
					Converter >> CharID;
				else if( Key == "x" )
					Converter >> chard.srcX;
				else if( Key == "y" )
					Converter >> chard.srcY;
				else if( Key == "width" )
					Converter >> chard.srcW;
				else if( Key == "height" )
					Converter >> chard.srcH;
				else if( Key == "xoffset" )
					Converter >> chard.xOff;
				else if( Key == "yoffset" )
					Converter >> chard.yOff;
				else if( Key == "xadvance" )
					Converter >> chard.xAdv;
			}
			m_Chars.insert(std::pair<int,CharDesc>(CharID,chard));
		}
	}

	fin.close();

	return true;
}

В этом большом методе мы открываем текстовый файл и просто считываем его данные, заполняя массив m_Chars.

bool BitmapFont::m_InitShader(wchar_t *vsFilename, wchar_t *psFilename)
{
	ID3DBlob *vertexShaderBuffer = nullptr;
	HRESULT hr = m_render->m_compileshaderfromfile(vsFilename,"VS", "vs_4_0", &vertexShaderBuffer);
	if( FAILED( hr ) )
		return false;

	ID3DBlob *pixelShaderBuffer = nullptr;
	HRESULT result = m_render->m_compileshaderfromfile(psFilename,"PS", "ps_4_0", &pixelShaderBuffer);
	if( FAILED( hr ) )
		return false;

	result = m_render->m_pd3dDevice->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
	if(FAILED(result))
		return false;

	result = m_render->m_pd3dDevice->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
	if(FAILED(result))
		return false;

	D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
	polygonLayout[0].SemanticName = "POSITION";
	polygonLayout[0].SemanticIndex = 0;
	polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[0].InputSlot = 0;
	polygonLayout[0].AlignedByteOffset = 0;
	polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[0].InstanceDataStepRate = 0;
	polygonLayout[1].SemanticName = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	unsigned int numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	result = m_render->m_pd3dDevice->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout);
	if(FAILED(result))
		return false;

	_RELEASE(vertexShaderBuffer);
	_RELEASE(pixelShaderBuffer);

	D3D11_BUFFER_DESC BufferDesc;
	BufferDesc.Usage = D3D11_USAGE_DEFAULT;
	BufferDesc.ByteWidth = sizeof(ConstantBuffer);
	BufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	BufferDesc.CPUAccessFlags = 0;
	BufferDesc.MiscFlags = 0;
	BufferDesc.StructureByteStride = 0;

	result = m_render->m_pd3dDevice->CreateBuffer(&BufferDesc, NULL, &m_constantBuffer);
	if( FAILED(result) )
		return false;

	BufferDesc.Usage = D3D11_USAGE_DEFAULT;
	BufferDesc.ByteWidth = sizeof(PixelBufferType);
	BufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	BufferDesc.CPUAccessFlags = 0;
	BufferDesc.MiscFlags = 0;
	BufferDesc.StructureByteStride = 0;

	result = m_render->m_pd3dDevice->CreateBuffer(&BufferDesc, NULL, &m_pixelBuffer);
	if( FAILED(result) )
		return false;

	D3D11_SAMPLER_DESC samplerDesc;
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 1;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	samplerDesc.BorderColor[0] = 0;
	samplerDesc.BorderColor[1] = 0;
	samplerDesc.BorderColor[2] = 0;
	samplerDesc.BorderColor[3] = 0;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

	result = m_render->m_pd3dDevice->CreateSamplerState(&samplerDesc, &m_sampleState);
	if(FAILED(result))
		return false;

	return true;
}

Все как раньше.

void BitmapFont::BuildVertexArray(VertexFont *vertices, const wchar_t *sentence,int screenWidth, int screenHeight)
{
	int numLetters = (int)wcslen(sentence);
	
	float drawX = (float) -screenWidth/2;
	float drawY = (float) screenHeight/2;

	int index = 0;		
	for(int i=0; i<numLetters; i++)
	{
		float CharX = m_Chars[sentence[i]].srcX;
		float CharY = m_Chars[sentence[i]].srcY;
		float Width = m_Chars[sentence[i]].srcW;
		float Height = m_Chars[sentence[i]].srcH;
		float OffsetX = m_Chars[sentence[i]].xOff;
		float OffsetY = m_Chars[sentence[i]].yOff;

		float left = drawX + OffsetX;
		float right = left + Width;
		float top = drawY - OffsetY;
		float bottom = top - Height;
		float lefttex = CharX/m_WidthTex;
		float righttex = (CharX+Width)/m_WidthTex;
		float toptex = CharY/m_HeightTex;
		float bottomtex = (CharY+Height)/m_HeightTex;
					
		vertices[index].pos = XMFLOAT3(left, top, 0.0f);
		vertices[index].tex = XMFLOAT2(lefttex, toptex);
		index++;		
		vertices[index].pos = XMFLOAT3(right, bottom, 0.0f);
		vertices[index].tex = XMFLOAT2(righttex, bottomtex);
		index++;
		vertices[index].pos = XMFLOAT3(left, bottom, 0.0f);
		vertices[index].tex = XMFLOAT2(lefttex, bottomtex);
		index++;
		vertices[index].pos = XMFLOAT3(left, top, 0.0f);
		vertices[index].tex = XMFLOAT2(lefttex, toptex);
		index++;
		vertices[index].pos = XMFLOAT3(right, top, 0.0f);
		vertices[index].tex = XMFLOAT2(righttex, toptex);
		index++;
		vertices[index].pos = XMFLOAT3(right, bottom, 0.0f);
		vertices[index].tex = XMFLOAT2(righttex, bottomtex);
		index++;
		
		drawX += m_Chars[sentence[i]].xAdv;
	}
}

Эта функция и есть центр нашего урока :). Мы получаем пустой массив вершин и строку. Далее мы просто собираем прямоугольник из шести вершин (два треугольника) для каждого символа. Советую рассмотреть этот метод.

Завершаем:

void BitmapFont::Render(unsigned int index,float r, float g, float b, float x, float y)
{
	m_SetShaderParameters(r, g, b, x, y);
	m_RenderShader(index);
}

void BitmapFont::m_SetShaderParameters(float r, float g, float b, float x, float y)
{
	XMMATRIX objmatrix = XMMatrixTranslation(x,-y,0);
	XMMATRIX wvp = objmatrix*m_render->m_Ortho;
	ConstantBuffer cb;
	cb.WVP = XMMatrixTranspose(wvp);
	m_render->m_pImmediateContext->UpdateSubresource( m_constantBuffer, 0, NULL, &cb, 0, 0 );

	m_render->m_pImmediateContext->VSSetConstantBuffers(0, 1, &m_constantBuffer);

	m_render->m_pImmediateContext->PSSetShaderResources(0, 1, &m_texture );

	PixelBufferType pb;
	pb.pixelColor = XMFLOAT4(r, g, b, 1.0f);
	m_render->m_pImmediateContext->UpdateSubresource( m_pixelBuffer, 0, NULL, &pb, 0, 0 );	

	m_render->m_pImmediateContext->PSSetConstantBuffers(0, 1, &m_pixelBuffer);
}

void BitmapFont::m_RenderShader(unsigned int index)
{
	m_render->m_pImmediateContext->IASetInputLayout(m_layout);
	m_render->m_pImmediateContext->VSSetShader(m_vertexShader, NULL, 0);
	m_render->m_pImmediateContext->PSSetShader(m_pixelShader, NULL, 0);
	m_render->m_pImmediateContext->PSSetSamplers(0, 1, &m_sampleState);
	m_render->m_pImmediateContext->DrawIndexed(index, 0, 0);
}

void BitmapFont::Close()
{
	_RELEASE(m_constantBuffer);
	_RELEASE(m_pixelBuffer);
	_RELEASE(m_vertexShader);
	_RELEASE(m_pixelShader);
	_RELEASE(m_layout);
	_RELEASE(m_sampleState);
	_RELEASE(m_texture);
}

Из интересного здесь только передача цвета в пиксельный шейдер в методе m_SetShaderParameters():

PixelBufferType pb;
pb.pixelColor = XMFLOAT4(r, g, b, 1.0f);
m_render->m_pImmediateContext->UpdateSubresource( m_pixelBuffer, 0, NULL, &pb, 0, 0 );
m_render->m_pImmediateContext->PSSetConstantBuffers(0, 1, &m_pixelBuffer);

Также как и с матрицей, заполняем структуру, введенная переменная – это цвет, которым мы окрасим наш текст. Далее мы передаем этот буфер в пиксельный шейдер (тогда как матрицу мы передавали в вершинный шейдер).
Все остальное мы уже рассматривали. Хотя, напомню еще один момент –

XMMATRIX objmatrix = XMMatrixTranslation(x,-y,0);

Что мы здесь делаем? Мы сдвигаем матрицу объекта (то есть текста) на позицию x и –y. Почему y с минусом? В уроке по выводу 2D графики мы уже рассматривали, что сверху положительные числа, а снизу отрицательные, и для этого мы и добавили минус.

Создайте Text.h:

#pragma once

#include <string>
#include "BitmapFont.h"

class Text
{
public:
	Text(MyRender *render, BitmapFont *font);

	bool Init(const std::wstring &text, int scrW, int scrH);
	void Render(float r, float g, float b, float x, float y);
	void Close();

private:
	bool m_InitBuffers(const std::wstring &text, int scrW, int scrH);
	void m_RenderBuffers();

	MyRender *m_render;

	BitmapFont *m_font;
	ID3D11Buffer *m_vertexBuffer; 
	ID3D11Buffer *m_indexBuffer;	
	int m_numindex;
};

Text.cpp

#include "Text.h"

Text::Text(MyRender *render, BitmapFont *font)
{
	m_render = render;
	m_font = font;
	m_vertexBuffer = nullptr;
	m_indexBuffer = nullptr;
	m_numindex = 0;
}

bool Text::Init(const std::wstring &text,int screenWidth, int screenHeight)
{
	if ( !m_InitBuffers(text, screenWidth, screenHeight ) )
		return false;	

	return true;
}

bool Text::m_InitBuffers(const std::wstring &text,int screenWidth, int screenHeight)
{
	int vertnum = text.size()*6;
	VertexFont *vertex = new VertexFont[vertnum];
	if(!vertex)
		return false;

	m_font->BuildVertexArray(vertex,text.c_str(),screenWidth, screenHeight);

	m_numindex = text.size()*6;
	unsigned long *indices = new unsigned long[m_numindex];

	for (int i = 0; i <m_numindex; i++)
		indices[i] = i;

	HRESULT result;
	D3D11_BUFFER_DESC BufferDesc;
	D3D11_SUBRESOURCE_DATA Data;

	BufferDesc.Usage = D3D11_USAGE_DEFAULT;
	BufferDesc.ByteWidth = sizeof(VertexFont) * vertnum;
	BufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	BufferDesc.CPUAccessFlags = 0;
	BufferDesc.MiscFlags = 0;
	BufferDesc.StructureByteStride = 0;

	Data.pSysMem = vertex;
	Data.SysMemPitch = 0;
	Data.SysMemSlicePitch = 0;

	result = m_render->m_pd3dDevice->CreateBuffer(&BufferDesc, &Data, &m_vertexBuffer);
	if(FAILED(result))
		return false;

	BufferDesc.ByteWidth = sizeof(unsigned long) * m_numindex;
	BufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;

	Data.pSysMem = indices;

	result = m_render->m_pd3dDevice->CreateBuffer(&BufferDesc, &Data, &m_indexBuffer);
	if(FAILED(result))
		return false;	

	_DELETE_ARRAY(vertex);
	_DELETE_ARRAY(indices);

	return true;
}

void Text::Render(float r, float g, float b, float x, float y)
{
	m_RenderBuffers();
	m_font->Render(m_numindex,r,g,b,x,y);
}

void Text::Close()
{
	_RELEASE(m_vertexBuffer);
	_RELEASE(m_indexBuffer);
}

void Text::m_RenderBuffers()
{
	unsigned int stride = sizeof(VertexFont); 
	unsigned int offset = 0;
	m_render->m_pImmediateContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);
	m_render->m_pImmediateContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);
	m_render->m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}

В методе m_InitBuffers() мы создаем и заполняем оба буфера.

Определяем число вершин. Число вершин равно числу символов * на 6. Вы кстати, как и в уроке двухмерной графики, можете сами попробовать уменьшить число вершин на символ до 4, поколдовав над индексами :).

Затем зовем BuildVertexArray() передав ему массив вершин и строку. Эта функция, напомню, заполнит массив вершин данными.

Дальше делаем тоже самое что и в предыдущих уроках.

MyRender.h

#pragma once

#include "D3D11_Framework.h"

using namespace D3D11Framework;

class BitmapFont;
class Text;

class MyRender : public Render
{
public:
	MyRender();
	bool Init(HWND hwnd);
	bool Draw();
	void Close();

	void TurnOnAlphaBlending();
	void TurnOffAlphaBlending();

private:
	friend BitmapFont;
	friend Text;

	BitmapFont *m_font;
	Text *text1;
	Text *text2;
	Text *text3;

	XMMATRIX m_Ortho;

	ID3D11BlendState* m_alphaEnableBlendingState;
	ID3D11BlendState* m_alphaDisableBlendingState;
};

Как вы помните из урока по выводу двухмерного изображения, чтобы наши классы могли использовать Device, Context и проекционную матрицу, они должны быть друзьями рендера.

Два новых метода TurnOnAlphaBlending() и TurnOffAlphaBlending(), будут включать и отключать альфа-цвет. Для этого мы просто будем подключать нужный Blend State. Интерфейс ID3D11BlendState мы кратко изучили в прошлом уроке, там мы делали прозрачной трехмерную модель, здесь же мы просто включим альфа канал.

MyRender.cpp

#include "MyRender.h"
#include "BitmapFont.h"
#include "Text.h"

MyRender::MyRender()
{
	m_font = nullptr;
	text1 = nullptr;
	text2 = nullptr;
	text3 = nullptr;
}

bool MyRender::Init(HWND hwnd)
{
	D3D11_BLEND_DESC bSDesc;
	ZeroMemory(&bSDesc, sizeof(D3D11_BLEND_DESC));
	bSDesc.RenderTarget[0].BlendEnable = TRUE;
	bSDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
	bSDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
	bSDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
	bSDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
	bSDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
	bSDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
	bSDesc.RenderTarget[0].RenderTargetWriteMask = 0x0f;
	HRESULT result = m_pd3dDevice->CreateBlendState(&bSDesc, &m_alphaEnableBlendingState);
	if(FAILED(result))
		return false;

	bSDesc.RenderTarget[0].BlendEnable = FALSE;
	result = m_pd3dDevice->CreateBlendState(&bSDesc, &m_alphaDisableBlendingState);
	if(FAILED(result))
		return false;
		
	m_Ortho = XMMatrixOrthographicLH(800.0f, 600.0f, 0.0f, 1000.0f);
	
	m_font = new BitmapFont(this);
	if ( !m_font->Init("font.fnt") )
		return false;

	text1 = new Text(this,m_font);
	text1->Init(L"Hello", 800, 600);

	text2 = new Text(this,m_font);
	text2->Init(L"World", 800, 600);

	text3 = new Text(this,m_font);
	text3->Init(L"Привет Мир", 800, 600);

	return true;
}

bool MyRender::Draw()
{
	TurnZBufferOff();
	TurnOnAlphaBlending();
	
	text1->Render(1.0, 1.0, 0.0, 100,100);
	text2->Render(1.0, 0.0, 1.0, 290,100);
	text3->Render(0.0, 1.0, 1.0, 100,180);

	TurnOffAlphaBlending();
	TurnZBufferOn();
	return true;
}

void MyRender::Close()
{
	_RELEASE(m_alphaEnableBlendingState);
	_RELEASE(m_alphaDisableBlendingState);
	_CLOSE(text1);
	_CLOSE(text2);
	_CLOSE(text3);
	_CLOSE(m_font);
}

void MyRender::TurnOnAlphaBlending()
{
	float blendFactor[4];
	blendFactor[0] = 0.0f;
	blendFactor[1] = 0.0f;
	blendFactor[2] = 0.0f;
	blendFactor[3] = 0.0f;
	m_pImmediateContext->OMSetBlendState(m_alphaEnableBlendingState, blendFactor, 0xffffffff);
}

void MyRender::TurnOffAlphaBlending()
{
	float blendFactor[4];
	blendFactor[0] = 0.0f;
	blendFactor[1] = 0.0f;
	blendFactor[2] = 0.0f;
	blendFactor[3] = 0.0f;
	m_pImmediateContext->OMSetBlendState(m_alphaDisableBlendingState, blendFactor, 0xffffffff);
}

BitmapFont.vs

cbuffer PerFrameBuffer
{
	float4x4 WVP;
};

struct VertexInputType
{
    float4 pos : POSITION;
    float2 tex : TEXCOORD;
};

struct PixelInputType
{
    float4 pos : SV_POSITION;
    float2 tex : TEXCOORD;
};

PixelInputType VS(VertexInputType input)
{
    PixelInputType output;

    output.pos = mul(input.pos, WVP);    
	output.tex = input.tex;
    
    return output;
}

BitmapFont.ps

Texture2D shaderTexture;
SamplerState SampleType;

cbuffer PixelBuffer
{
    float4 pixelColor;
};

struct PixelInputType
{
    float4 pos : SV_POSITION;
    float2 tex : TEXCOORD;
};

float4 PS(PixelInputType input) : SV_TARGET
{
	return shaderTexture.Sample(SampleType, input.tex) * pixelColor;
}

В пиксельном шейдере мы берем пиксель из текстуры и умножаем его на полученный из константного буфера цвет.

На этом все, вот что должно получиться:

Test 2012-10-05 21-23-41-10

Итог[]

Исходный код - скачать