前言
注意:從這一章起到后面的所有項目無一例外都利用了Direct2D與Direct3D互操作性,但系統要求為Win10, Win8.x 或 Win7 SP1且安裝了KB2670838補丁以支持Direct3D 11.1(DXGI1.2)。否則將無法顯示所有文本。如果你的Win7系統運行程序無法顯示文本,強烈建議打上上述補丁
在DX11,要顯示文字可以說是一件比較麻煩的事情。DX9諸如Id3dXFont
用於顯示文字的接口類都已經被拋棄掉了。目前行之有效的兩種顯示文字的方法如下:
-
使用包含文字的位圖/矢量圖,然后通過一定的方式來獲取對應文字的矩形區域,最后渲染出來。
-
通過實現Direct2D與Direct3D互操作性,然后配合DWrite在程序寫入文字。
對於個人來說,第一種方式做起來比較麻煩。對於第二種方法,我通過查閱MSDN文檔,並進行了一定嘗試,很快就實現了文字顯示。因此接下來將圍繞第二種方法進行討論(這里不關注貼位圖和繪制幾何體等在Direct2D的其余操作,這些都可以在Direct3D做到)
DirectX11 With Windows SDK完整目錄
歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。
通過DXGI進行互操作
從 Direct3D 10.1開始, Direct3D Runtime使用DXGI進行資源管理。DXGI Runtime提供了跨進程共享視頻內存圖面的功能,並且可用作其他基於視頻內存的運行時平台的基礎。Direct2D 使用 DXGI 與 Direct3D 交互,並且交互下的Direct2D的內容繪制實際上也是基於Direct3D來實現的。下圖通過圖形調試器可以佐證這一點:
這里的對象2為D3D立即設備上下文。
但是,在系統不支持Direct3D 11.1的情況下,DXGI的版本為1.1。而DXGI1.1只能通過Direct3D 10.1進行互操作。由於實現過程十分繁瑣,需要用到紋理和混合部分的內容,加上本人系統又是Win10,沒法做低版本DXGI下的測試(找一部不支持Direct3D 11.1的Win7系統的電腦都有些困難),故不考慮實現。
這里給出Direct3D 11和Direct3D 10.1共享表面來互操作的方法:
Simple Font
在系統支持Direct3D 11.1的情況下,DXGI的版本為1.2,而DXGI1.2可以與Direct3D 11.X進行互操作。
為了實現Direct2D和Direct3D互操作,並顯示文字,需要經歷下面的准備步驟:
- 如果是Win7系統需要更新至SP1,並安裝KB2670838補丁
- 在
d3dApp.h
添加頭文件d2d1.h
和dwrite.h
,並添加靜態庫d2d1.lib
和dwrite.lib
- 修改創建
ID3D11Device
和IDXGISwapChain
時的一些配置參數 - 創建
ID2D1Factory
- 通過
IDXGISwapChain
獲取接口類IDXGISurface
,並通過它來創建ID2D1RenderTarget
以進行綁定。這樣就可以通過該渲染目標進行具體操作了。
D3D11設備和DXGI交換鏈的創建屬性修改
由於Direct2D需要支持BGRA的數據格式,因此在創建D3D11設備前需要修改如下部分:
// 創建D3D設備 和 D3D設備上下文
UINT createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; // Direct2D需要支持BGRA格式
#if defined(DEBUG) || defined(_DEBUG)
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
然后在創建DXGI交換鏈的時候也要將DXGI格式修改為DXGI_FORMAT_B8G8R8A8_UNORM
:
// 檢測 MSAA支持的質量等級
md3dDevice->CheckMultisampleQualityLevels(
DXGI_FORMAT_B8G8R8A8_UNORM, 4, &m4xMsaaQuality); // 注意此處DXGI_FORMAT_B8G8R8A8_UNORM
assert(m4xMsaaQuality > 0);
...
// 如果包含,則說明支持DX11.1
if (dxgiFactory2 != nullptr)
{
...
// 填充各種結構體用以描述交換鏈
DXGI_SWAP_CHAIN_DESC1 sd;
ZeroMemory(&sd, sizeof(sd));
...
sd.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // 注意此處DXGI_FORMAT_B8G8R8A8_UNORM
...
}
else
{
// 填充DXGI_SWAP_CHAIN_DESC用以描述交換鏈
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(sd));
...
sd.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // 注意此處DXGI_FORMAT_B8G8R8A8_UNORM
...
}
最后就是在D3DApp::OnReSize
方法中修改重設交換鏈的數據格式部分:
// 重設交換鏈並且重新創建渲染目標視圖
ComPtr<ID3D11Texture2D> backBuffer;
HR(m_pSwapChain->ResizeBuffers(1, m_ClientWidth, m_ClientHeight, DXGI_FORMAT_B8G8R8A8_UNORM, 0)); // 注意此處DXGI_FORMAT_B8G8R8A8_UNORM
HR(m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf())));
HR(m_pd3dDevice->CreateRenderTargetView(backBuffer.Get(), nullptr, m_pRenderTargetView.GetAddressOf()));
做完這些后,緊接着就是要實現Direct3D與Direct2D共享表面的操作。
D2D1CreateFactory函數--創建D2D工廠對象
在創建D2D渲染目標前,還需要先創建一個ID2D1Factory
對象,可以用來創建各種資源:
template<class Factory>
HRESULT D2D1CreateFactory(
D2D1_FACTORY_TYPE factoryType, // [In]枚舉值
Factory **factory // [Out]獲取的工廠對象
);
創建操作如下:
HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, m_pd2dFactory.GetAddressOf()));
注意這里用了HR
宏,以及md2dFactory
是ComPtr<ID2D1Factory>
類型
ID2D1Factory::CreateDxgiSurfaceRenderTarget方法--創建一個DXGI表面渲染目標
現在我們要創建的是ID2D1RenderTarget
對象。
接下來的操作需要在每次窗口大小變化且調用了IDXGISwapChain::ReSizeBuffers
方法之后進行。通常建議寫在GameApp::OnReSize
內調用D3DApp::OnReSize
之后。
首先釋放之前創建的D2D資源(如果有的話),通過IDXGISwapChain::GetBuffer
方法來獲取后備緩沖區的IDXGISurface
接口:
m_pd2dRenderTarget.Reset();
ComPtr<IDXGISurface> surface;
HR(m_pSwapChain->GetBuffer(0, __uuidof(IDXGISurface), reinterpret_cast<void**>(surface.GetAddressOf())));
然后填充D2D1_RENDER_TARGET_PROPERTIES
結構體屬性:
typedef struct D2D1_RENDER_TARGET_PROPERTIES
{
D2D1_RENDER_TARGET_TYPE type; // 渲染目標類型枚舉值
D2D1_PIXEL_FORMAT pixelFormat;
FLOAT dpiX; // X方向每英寸像素點數,設為0.0f使用默認DPI
FLOAT dpiY; // Y方向每英寸像素點數,設為0.0f使用默認DPI
D2D1_RENDER_TARGET_USAGE usage; // 渲染目標用途枚舉值
D2D1_FEATURE_LEVEL minLevel; // D2D最小特性等級
} D2D1_RENDER_TARGET_PROPERTIES;
typedef struct D2D1_PIXEL_FORMAT
{
DXGI_FORMAT format; // DXGI格式
D2D1_ALPHA_MODE alphaMode; // 混合模式
} D2D1_PIXEL_FORMAT;
可以借用D2D1::RenderTargetProperties
函數來創建,這里使用默認DPI:
D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED));
最后ID2D1Factory::CreateDxgiSurfaceRenderTarget
方法如下:
HRESULT ID2D1Factory::CreateDxgiSurfaceRenderTarget(
IDXGISurface *dxgiSurface, // [In]DXGI表面
const D2D1_RENDER_TARGET_PROPERTIES *renderTargetProperties, // [In]D2D渲染目標屬性
ID2D1RenderTarget **renderTarget // [Out]得到的D2D渲染目標
);
具體調用如下:
HRESULT hr = m_pd2dFactory->CreateDxgiSurfaceRenderTarget(surface.Get(), &props, m_pd2dRenderTarget.GetAddressOf());
surface.Reset();
至此,Direct2D就可以和Direct3D通過DXGI實現互操作了。通過ID2D1RenderTarget
,你可以創建各種類型的顏色刷子,並進行繪制操作。但由於我們需要繪制文字,下面會介紹DWrite
。
使用DWrite顯示文字
要顯示文字,需要經過下面的步驟:
- 創建
IDWriteFactory
工廠對象 - 通過DWrite工廠對象創建
IDWriteTextFormat
文本格式對象 - 為文本格式對象設置好文本格式
- 通過
ID2D1RenderTarget
創建顏色刷 - 在繪制完3D部分后以及最終呈現之前進行文本繪制
DWriteCreateFactory函數--創建DWrite工廠對象
函數原型如下:
HRESULT DWriteCreateFactory(
DWRITE_FACTORY_TYPE factoryType, // [In]工廠類型枚舉
const IID & iid, // [In]接口標識ID
IUnknown **factory // [Out]獲得工廠對象
);
下面演示了創建過程:
HR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(m_pdwriteFactory.GetAddressOf())));
IDWriteFactory::CreateTextFormat方法--創建文本格式對象
HRESULT IDWriteFactory::CreateTextFormat(
const WCHAR * fontFamilyName, // [In]字體系列名稱
IDWriteFontCollection * fontCollection, // [In]通常用nullptr來表示使用系統字體集合
DWRITE_FONT_WEIGHT fontWeight, // [In]字體粗細程度枚舉值
DWRITE_FONT_STYLE fontStyle, // [In]字體樣式枚舉值
DWRITE_FONT_STRETCH fontStretch, // [In]字體拉伸程度枚舉值
FLOAT fontSize, // [In]字體大小
const WCHAR * localeName, // [In]區域名稱
IDWriteTextFormat ** textFormat); // [Out]創建的文本格式
字體系列的名稱可以用中文來引用,比如L"宋體"
,L"微軟雅黑"
等。
字體粗細看個人喜好,用DWRITE_FONT_WEIGHT_NORMAL
就差不多了吧
字體樣式如下:
枚舉值 | 樣式 |
---|---|
DWRITE_FONT_STYLE_NORMAL | 默認 |
DWRITE_FONT_STYLE_OBLIQUE | 斜體 |
DWRITE_FONT_STYLE_ITALIC | 意大利體 |
字體拉伸程度用DWRITE_FONT_STRETCH_NORMAL
就可以了
字體大小建議在Word文檔提前感受一下
區域名稱這里默認用L"zh-cn"
創建演示如下:
HR(m_pdwriteFactory->CreateTextFormat(L"宋體", nullptr, DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 20, L"zh-cn",
m_pTextFormat.GetAddressOf()));
創建了IDWriteTextFormat
對象后,可以調用它的一系列Get方法獲取文本格式的詳細信息,也可以用一系列Set方法來設置。這里不展開說明。
ID2D1RenderTarget::CreateSolidColorBrush方法--創建單色刷對象
雖然ID2D1RenderTarget
對象提供了多種刷子供創建,但最常用的還是創建ID2D1SolidColorBrush
單色刷。
該方法是經過重載的,現在只討論其中一種重載方法:
HRESULT ID2D1RenderTarget::CreateSolidColorBrush(
const D2D1_COLOR_F &color, // [In]顏色
ID2D1SolidColorBrush **solidColorBrush // [Out]輸出的顏色刷
);
這里會默認指定Alpha
值為1.0
D2D1_COLOR_F
是一個包含r
,g
,b
,a
浮點數的結構體,但其實還有一種辦法可以指定顏色,就是利用它的繼承類D2D1::ColorF
中的構造函數,以及D2D1::ColorF::Enum
枚舉類型來指定要使用的顏色,可以進里面去查看,這里就不給出所有的顏色枚舉了。
下面演示了怎么創建一個單色刷:
// 創建固定顏色刷和文本格式
HR(m_pd2dRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::White),
m_pColorBrush.GetAddressOf()));
D3DApp類、GameApp類的變化以及開始文本繪制
這里以上一個項目為例,進行修改。
在D3DApp
類中,新增了D3DApp::InitDirect2D
方法用於創建D2D工廠和DWrite工廠:
bool D3DApp::InitDirect2D()
{
HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, m_pd2dFactory.GetAddressOf()));
HR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(m_pdwriteFactory.GetAddressOf())));
return true;
}
該方法在D3DApp::Init
中被調用。
而在GameApp::OnReSize
方法中也進行了修改:
void GameApp::OnResize()
{
assert(m_pd2dFactory);
assert(m_pdwriteFactory);
// 釋放D2D的相關資源
m_pColorBrush.Reset();
m_pd2dRenderTarget.Reset();
D3DApp::OnResize();
// 為D2D創建DXGI表面渲染目標
ComPtr<IDXGISurface> surface;
HR(m_pSwapChain->GetBuffer(0, __uuidof(IDXGISurface), reinterpret_cast<void**>(surface.GetAddressOf())));
D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED));
HRESULT hr = m_pd2dFactory->CreateDxgiSurfaceRenderTarget(surface.Get(), &props, m_pd2dRenderTarget.GetAddressOf());
surface.Reset();
if (hr == E_NOINTERFACE)
{
OutputDebugString(L"\n警告:Direct2D與Direct3D互操作性功能受限,你將無法看到文本信息。現提供下述可選方法:\n"
"1. 對於Win7系統,需要更新至Win7 SP1,並安裝KB2670838補丁以支持Direct2D顯示。\n"
"2. 自行完成Direct3D 10.1與Direct2D的交互。詳情參閱:"
"https://docs.microsoft.com/zh-cn/windows/desktop/Direct2D/direct2d-and-direct3d-interoperation-overview""\n"
"3. 使用別的字體庫,比如FreeType。\n\n");
}
else if (hr == S_OK)
{
// 創建固定顏色刷和文本格式
HR(m_pd2dRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::White),
m_pColorBrush.GetAddressOf()));
HR(m_pdwriteFactory->CreateTextFormat(L"宋體", nullptr, DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 20, L"zh-cn",
m_pTextFormat.GetAddressOf()));
}
else
{
// 報告異常問題
assert(m_pd2dRenderTarget);
}
}
在這里D2D的相關資源需要在D3D相關資源釋放前先行釋放掉,然后在D3D重設后備緩沖區后重新創建D2D渲染目標。至於D2D后續的相關資源也需要重新創建好來。
最后在GameApp::DrawScene
方法中,繪制2D部分需要在3D部分繪制完后,呈現之前進行。
首先需要調用ID2D1RenderTarget::BeginDraw
方法,開始D2D繪制。該方法沒有參數
繪制完成后,就調用ID2D1RenderTarget::EndDraw
方法,結束D2D繪制。該方法的返回值為HRESULT,若之前繪制出現問題,在EndDraw才會進行反饋。可以用HR宏包住。
ID2D1RenderTarget::DrawTextW方法--繪制文本
DrawText在這里進行了宏定義:
#ifdef UNICODE
#define DrawText DrawTextW
#else
#define DrawText DrawTextA
#endif // !UNICODE
我們的項目是只能使用Unicode
字符集的(dxerr.h
只允許該字符集),所以直接討論DrawTextW
方法
該方法也經過了重載。現在只討論其中一種,且使用默認參數:
void ID2D1RenderTarget::DrawTextW(
WCHAR *string, // [In]要輸出的文本
UINT stringLength, // [In]文本長度,用wcslen函數或者wstring::length方法獲取即可
IDWriteTextFormat *textFormat, // [In]文本格式
const D2D1_RECT_F &layoutRect, // [In]布局區域
ID2D1Brush *defaultForegroundBrush, // [In]使用的前景刷
D2D1_DRAW_TEXT_OPTIONS options = D2D1_DRAW_TEXT_OPTIONS_NONE,
DWRITE_MEASURING_MODE measuringMode = DWRITE_MEASURING_MODE_NATURAL);
D2D1_RECT_F
結構體包含了left
,top
,right
,bottom
四個成員。
現給出GameApp::DrawScene
方法Direct2D部分的實現:
void GameApp::DrawScene()
{
assert(m_pd3dImmediateContext);
assert(m_pSwapChain);
// 繪制Direct3D部分
...
// 繪制Direct2D部分
if (m_pd2dRenderTarget != nullptr)
{
m_pd2dRenderTarget->BeginDraw();
std::wstring textStr = L"切換燈光類型: 1-平行光 2-點光 3-聚光燈\n"
"切換模型: Q-立方體 W-球體 E-圓柱體 R-圓錐體\n"
"S-切換模式 當前模式: ";
if (m_IsWireframeMode)
textStr += L"線框模式";
else
textStr += L"面模式";
m_pd2dRenderTarget->DrawTextW(textStr.c_str(), textStr.size(), m_pTextFormat.Get(),
D2D1_RECT_F{ 0.0f, 0.0f, 600.0f, 200.0f }, m_pColorBrush.Get());
HR(m_pd2dRenderTarget->EndDraw());
}
HR(m_pSwapChain->Present(0, 0));
}
最終效果如下:
DirectX11 With Windows SDK完整目錄
歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。
參考文章如下: