注意:從這一章起到後面的全部項目無一例外都利用了Direct2D與Direct3D互操做性,但系統要求爲Win10, Win8.x 或 Win7 SP1且安裝了KB2670838補丁以支持Direct3D 11.1(DXGI1.2)。不然將沒法顯示全部文本。若是你的Win7系統運行程序沒法顯示文本,強烈建議打上上述補丁html
在DX11,要顯示文字能夠說是一件比較麻煩的事情。DX9諸如Id3dXFont
用於顯示文字的接口類都已經被拋棄掉了。目前行之有效的兩種顯示文字的方法以下:git
使用包含文字的位圖/矢量圖,而後經過必定的方式來獲取對應文字的矩形區域,最後渲染出來。github
經過實現Direct2D與Direct3D互操做性,而後配合DWrite在程序寫入文字。windows
對於我的來講,第一種方式作起來比較麻煩。對於第二種方法,我經過查閱MSDN文檔,並進行了必定嘗試,很快就實現了文字顯示。所以接下來將圍繞第二種方法進行討論(這裏不關注貼位圖和繪製幾何體等在Direct2D的其他操做,這些均可以在Direct3D作到)app
DirectX11 With Windows SDK完整目錄函數
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。測試
從 Direct3D 10.1開始, Direct3D Runtime使用DXGI進行資源管理。DXGI Runtime提供了跨進程共享視頻內存圖面的功能,而且可用做其餘基於視頻內存的運行時平臺的基礎。Direct2D 使用 DXGI 與 Direct3D 交互,而且交互下的Direct2D的內容繪製實際上也是基於Direct3D來實現的。下圖經過圖形調試器能夠佐證這一點:字體
這裏的對象2爲D3D當即設備上下文。ui
可是,在系統不支持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互操做,並顯示文字,須要經歷下面的準備步驟:
d3dApp.h
添加頭文件d2d1.h
和dwrite.h
,並添加靜態庫d2d1.lib
和dwrite.lib
ID3D11Device
和IDXGISwapChain
時的一些配置參數ID2D1Factory
IDXGISwapChain
獲取接口類IDXGISurface
,並經過它來建立ID2D1RenderTarget
以進行綁定。這樣就能夠經過該渲染目標進行具體操做了。因爲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共享表面的操做。
在建立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>
類型
如今咱們要建立的是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
。
要顯示文字,須要通過下面的步驟:
IDWriteFactory
工廠對象IDWriteTextFormat
文本格式對象ID2D1RenderTarget
建立顏色刷函數原型以下:
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())));
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
對象提供了多種刷子供建立,但最經常使用的仍是建立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
類中,新增了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宏包住。
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,以及有什麼問題也能夠在這裏彙報。
參考文章以下: