注意:从这一章起到后面的全部项目无一例外都利用了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,以及有什么问题也能够在这里汇报。
参考文章以下: