はかせのラボ

私の頭の中を書いていく雑記ブログです

DirectX 文字描画始めました

あいさつ

最近毎日投稿を続けることが出来ててちょっと上機嫌なはかせです。
今回から文字の描画をやってみたいと思います。
ただ色々調べる限り難しかったりめんどくさかったりするので
ブログに書きながらちょこちょこ進めていきます。

なので今までみたいなこれ出来た!あれ出来た!ではなく
今こんな感じです~みたいな経過報告っぽい感じでやります。

実装方法について

文字の描画には大きく分けて3種類あるようです。
①DirectXTKを使う
②DirectX2DのDirectWriteを使う
③文字用のテクスチャを用意して表示する

①はライブラリ入れてやる方法で一番簡単そうです。
ただMakeSpriteFontなるものがイケてないみたいな話をよく聞きます。

②はネット上の情報が少ない&古いものばかりでやり方がよくわかりませんw
(分かる方いましたらご教授願いますm(__)m)

なので私は③の方法でテクスチャはフォントから作るやり方で
やってみようと思います。

やり方の参考は相変わらず〇✖さんです。

やること

今回は大分長くなると思うので先にやることだけ書きます。
①フォント読み込み
②テクスチャに書き込み
③シェーダーとつなげる

この3つです。

簡単そうですがその分長いですw

フォント読み込み

フォントはWindowsAPIのGetGlyphOutlineW
という関数を使って読み込みます。
ざっくり言うとフォント情報から
ビットマップデータを返してくれる関数です。
関数の詳細はこちらをどうぞ
docs.microsoft.com

この関数を使い手に入れたビットマップをテクスチャに書き込み
そのテクスチャを描画することで文字を出します。

ではやっていきます。
GetGlyphOutlineWを使うためにはまずフォントハンドルを
作成しウィンドウに適用する必要があるらしいです。

// フォントハンドルの生成
int fontSize = 64;
int fontWeight = 1000;
LOGFONT lf =
{
	fontSize, 0, 0, 0, fontWeight, 0, 0, 0,
	SHIFTJIS_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS,
	PROOF_QUALITY, DEFAULT_PITCH | FF_MODERN,
	(WCHAR)"MS P明朝"
};
HFONT hFont = CreateFontIndirectW(&lf);

// 現在のウィンドウに適用
// デバイスにフォントを持たせないとGetGlyphOutline関数はエラーとなる
HDC hdc = GetDC(NULL);
HFONT oldFont = (HFONT)SelectObject(hdc, hFont);

どうやらこんな感じのコードがテンプレみたいです。
この部分をしっかりやりたいわけではないので詳しくはやりません。

では使っていきましょう

// フォントビットマップ取得
const wchar_t* c = L"S";
UINT code = (UINT) * c;
const int gradFlag = GGO_GRAY4_BITMAP;
// 階調の最大値
int grad = 0;
switch (gradFlag)
{
case GGO_GRAY2_BITMAP:
	grad = 4;
	break;
case GGO_GRAY4_BITMAP:
	grad = 16;
	break;
case GGO_GRAY8_BITMAP:
	grad = 64;
	break;
}

TEXTMETRIC tm;
GetTextMetrics(hdc, &tm);
GLYPHMETRICS gm;
CONST MAT2 mat = { {0,1},{0,0},{0,0},{0,1} };
DWORD size = GetGlyphOutlineW(hdc, code, gradFlag, &gm, 0, NULL, &mat);
BYTE *pMono = new BYTE[size];
GetGlyphOutlineW(hdc, code, gradFlag, &gm, size, pMono, &mat);

ほぼ〇✖さんのパクりですw

さてこれでフォントデータがやっと読めました( ´Д`)=3 フゥ
ここまででブログの文字数が2000文字近くあります。
もう既に私のブログにしては長編の部類に入りますがまだあります。

テクスチャに書き込み

〇✖さんの方ではD3DLOCKED_RECTを使っていましたが、
私はDirectX11なのでメモリ書き込みはMap/Unmapで行います。

//フォントの幅と高さ
int fontWidth = gm.gmCellIncX;
int fontHeight = tm.tmHeight;

//フォントを書き込むテクスチャ作成
D3D11_TEXTURE2D_DESC fontTextureDesc;
ZeroMemory(&fontTextureDesc, sizeof(fontTextureDesc));
fontTextureDesc.Width = fontWidth;
fontTextureDesc.Height = fontHeight;
fontTextureDesc.MipLevels = 1;
fontTextureDesc.ArraySize = 1;
fontTextureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
fontTextureDesc.SampleDesc.Count = 1;
fontTextureDesc.SampleDesc.Quality = 0;
fontTextureDesc.Usage = D3D11_USAGE_DYNAMIC;
fontTextureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
fontTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
fontTextureDesc.MiscFlags = 0;
ID3D11Texture2D* fontTexture = 0;
auto manager = mGameObject->GetDXManager();
HRESULT hr = manager->GetDevice()->CreateTexture2D(&fontTextureDesc, NULL, &fontTexture);
//デバイスコンテキスト
auto deviceContext = manager->GetDeviceContext();
// フォント情報をテクスチャに書き込む部分
D3D11_MAPPED_SUBRESOURCE hMappedResource;
hr = deviceContext->Map(
	fontTexture,
	0,
	D3D11_MAP_WRITE_DISCARD,
	0,
	&hMappedResource);
// ここで書き込む
BYTE* pBits = (BYTE*)hMappedResource.pData;
// フォント情報の書き込み
// iOfs_x, iOfs_y : 書き出し位置(左上)
// iBmp_w, iBmp_h : フォントビットマップの幅高
// Level : α値の段階 (GGO_GRAY4_BITMAPなので17段階)
int iOfs_x = gm.gmptGlyphOrigin.x;
int iOfs_y = tm.tmAscent - gm.gmptGlyphOrigin.y;
int iBmp_w = gm.gmBlackBoxX + (4 - (gm.gmBlackBoxX % 4)) % 4;
int iBmp_h = gm.gmBlackBoxY;
int Level = 17;
int x, y;
DWORD Alpha, Color;
memset(pBits, 0, hMappedResource.RowPitch * tm.tmHeight);
for (y = iOfs_y; y < iOfs_y + iBmp_h; y++)
{
	for (x = iOfs_x; x < iOfs_x + iBmp_w; x++)
	{
		Alpha =
			(255 * pMono[x - iOfs_x + iBmp_w * (y - iOfs_y)])
			/ (Level - 1);
		Color = 0x00ffffff | (Alpha << 24);
			memcpy(
			(BYTE*)pBits
			+ hMappedResource.RowPitch * y + 4 * x,
			&Color,
			sizeof(DWORD));
	}
}
deviceContext->Unmap(fontTexture, 0);
//不要なので削除
delete[] pMono;

長いのでざっくり概要だけ。
①空のテクスチャを作る
②ビットマップデータに従いテクスチャに点を打つ

以上です。

シェーダーとつなげる

シェーダーは今までのテクスチャ表示用のを
そのまま使うので割愛します。

ShaderResourceViewとSamplerの作成だけ

// ShaderResourceViewの情報を作成する
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(srvDesc));
srvDesc.Format = fontTextureDesc.Format;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = fontTextureDesc.MipLevels;

manager->GetDevice()->CreateShaderResourceView(fontTexture, &srvDesc, &mShaderResorceView);

// シェーダ用にサンプラを作成する
D3D11_SAMPLER_DESC samDesc;
ZeroMemory(&samDesc, sizeof(samDesc));
samDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
samDesc.AddressU = samDesc.AddressV = samDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
samDesc.MaxAnisotropy = 1;
samDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
samDesc.MaxLOD = D3D11_FLOAT32_MAX;

manager->GetDevice()->CreateSamplerState(&samDesc, &mSampler);

これで全行程終わりです。

結果

f:id:hakase0274:20181117170112p:plain

無事Sの文字が出ました(*´ω`*)

ただ今現状だと
1文字しか出せないし初期座標等も固定されているしで
正直使いづらいですorz

次回以降はこの使いづらい文字表示を
使いやすくしていこうと思います。

今日はもう疲れたのでこれにて終わりますノシ

今回作ったものはgithubに上げました
github.com