DirectX11 With Windows SDK--08 Direct2D與Direct3D互操做性以及利用DWrite顯示文字

前言

注意:從這一章起到後面的全部項目無一例外都利用了Direct2D與Direct3D互操做性,但系統要求爲Win10, Win8.x 或 Win7 SP1且安裝了KB2670838補丁以支持Direct3D 11.1(DXGI1.2)。不然將沒法顯示全部文本。若是你的Win7系統運行程序沒法顯示文本,強烈建議打上上述補丁html

在DX11,要顯示文字能夠說是一件比較麻煩的事情。DX9諸如Id3dXFont用於顯示文字的接口類都已經被拋棄掉了。目前行之有效的兩種顯示文字的方法以下:git

  1. 使用包含文字的位圖/矢量圖,而後經過必定的方式來獲取對應文字的矩形區域,最後渲染出來。github

  2. 經過實現Direct2D與Direct3D互操做性,而後配合DWrite在程序寫入文字。windows

對於我的來講,第一種方式作起來比較麻煩。對於第二種方法,我經過查閱MSDN文檔,並進行了必定嘗試,很快就實現了文字顯示。所以接下來將圍繞第二種方法進行討論(這裏不關注貼位圖和繪製幾何體等在Direct2D的其他操做,這些均可以在Direct3D作到)app

DirectX11 With Windows SDK完整目錄函數

Github項目源碼佈局

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。測試

經過DXGI進行互操做

從 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互操做,並顯示文字,須要經歷下面的準備步驟:

  1. 若是是Win7系統須要更新至SP1,並安裝KB2670838補丁
  2. d3dApp.h添加頭文件d2d1.hdwrite.h,並添加靜態庫d2d1.libdwrite.lib
  3. 修改建立ID3D11DeviceIDXGISwapChain時的一些配置參數
  4. 建立ID2D1Factory
  5. 經過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宏,以及md2dFactoryComPtr<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顯示文字

要顯示文字,須要通過下面的步驟:

  1. 建立IDWriteFactory工廠對象
  2. 經過DWrite工廠對象建立IDWriteTextFormat文本格式對象
  3. 爲文本格式對象設置好文本格式
  4. 經過ID2D1RenderTarget建立顏色刷
  5. 在繪製完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完整目錄

Github項目源碼

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。

參考文章以下:

Direct2D與Direct3D互操做性概述

相關文章
相關標籤/搜索