DirectX11 With Windows SDK--08 Direct2D与Direct3D互操作性以及利用DWrite显示文字
前言
注意:从这一章起到后面的所有项目无一例外都利用了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,以及有什么问题也可以在这里汇报。
参考文章如下:
DirectX11 With Windows SDK--08 Direct2D与Direct3D互操作性以及利用DWrite显示文字的更多相关文章
- 粒子系统与雨的效果 (DirectX11 with Windows SDK)
前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用W ...
- DirectX11 With Windows SDK--00 目录
前言 (更新于 2019/4/10) 从第一次接触DirectX 11到现在已经有将近两年的时间了.还记得前年暑假被要求学习DirectX 11,在用龙书的源码配置项目运行环境的时候都花了好几天的时间 ...
- DirectX11 With Windows SDK--11 混合状态与光栅化状态
前言 虽然这一部分的内容主要偏向于混合(Blending),但这里还需提及一下,关于渲染管线可以绑定的状态主要有如下四种: 光栅化状态(光栅化阶段) 采样器状态(像素着色阶段) 混合状态(输出合并阶段 ...
- DirectX11 With Windows SDK--11 混合状态
原文:DirectX11 With Windows SDK--11 混合状态 前言 这一章会着重讲述混合状态,在下一章则会讲述深度/模板状态 DirectX11 With Windows SDK完整目 ...
- DirectX11 With Windows SDK--29 计算着色器:内存模型、线程同步;实现顺序无关透明度(OIT)
前言 由于透明混合在不同的绘制顺序下结果会不同,这就要求绘制前要对物体进行排序,然后再从后往前渲染.但即便是仅渲染一个物体(如上一章的水波),也会出现透明绘制顺序不对的情况,普通的绘制是无法避免的.如 ...
- DirectX11 With Windows SDK--10 摄像机类
前言 DirectX11 With Windows SDK完整目录:http://www.cnblogs.com/X-Jun/p/9028764.html 由于考虑后续的项目需要有一个比较好的演示环境 ...
- DirectX11 With Windows SDK--26 计算着色器:入门
前言 现在开始迎来所谓的高级篇了,目前计划是计算着色器部分的内容视项目情况,大概会分3-5章来讲述. DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群 ...
- DirectX11 With Windows SDK--24 Render-To-Texture(RTT)技术的应用
前言 尽管在上一章的动态天空盒中用到了Render-To-Texture技术,但那是针对纹理立方体的特化实现.考虑到该技术的应用层面非常广,在这里抽出独立的一章专门来讲有关它的通用实现以及各种应用. ...
- DirectX11 With Windows SDK--01 DirectX11初始化
前言 由于个人觉得龙书里面第4章提供的Direct3D 初始化项目封装得比较好,而且DirectX SDK Samples里面的初始化程序过于精简,不适合后续使用,故选择了以Init Direct3D ...
随机推荐
- 用SQL语句实现:当A列大于B列时选择A列否则选择B列,当B列大于C列时选择B列否则选择C列。
数据库中有A B C三列,用SQL语句实现:当A列大于B列时选择A列否则选择B列,当B列大于C列时选择B列否则选择C列. 方法一: select (case when a>b then a el ...
- 010 Editor v8.0.1(32 - bit) 算法逆向分析、注册机编写
010 Editor 的逆向分析整体算下来还是比较简单的,将程序拖入OD,通过字符串搜索定位到核心代码,经过分析,主要是如下图所示的两个关键函数,返回正确的值,才算是注册成功. 00409C9B 这个 ...
- C# -- 使用委托 delegate 执行异步操作
C# -- 使用委托 delegate 执行异步操作 委托是一种安全地封装方法的类型,它与 C 和 C++ 中的函数指针类似. 与 C 中的函数指针不同,委托是面向对象的.类型安全的和保险的. 委托的 ...
- LeetCode算法题-Assign Cookies(Java实现)
这是悦乐书的第234次更新,第247篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第101题(顺位题号是455).假设你是一个很棒的父母,并想给你的孩子一些饼干.但是,你 ...
- 爬虫系列二(数据清洗--->xpath解析数据)
一 xpath介绍 XPath 是一门在 XML 文档中查找信息的语言.XPath 用于在 XML 文档中通过元素和属性进行导航. XPath 使用路径表达式在 XML 文档中进行导航 XPath 包 ...
- 解析Object.defineProperty的作用
对象是由多个名/值对组成的无序的集合.对象中每个属性对应任意类型的值. 定义对象可以使用构造函数或字面量的形式: 除了以上添加属性的方式,还可以使用Object.defineProperty定义新属性 ...
- SQL LEN() 函数
LEN() 函数 LEN 函数返回文本字段中值的长度. SQL LEN() 语法 SELECT LEN(column_name) FROM table_name SQL LEN() 实例 我们拥有下面 ...
- Openssl x509命令
一.简介 x509指令是一个功能很丰富的证书处理工具.可以用来显示证书的内容,转换其格式,给CSR签名等 二.语法 openssl x509 [-inform DER|PEM|NET] [-outfo ...
- 监控glusterfs
监控集群状态 [4ajr@elk1 scripts]$ cat glusterfs_peer_status.sh #!/bin/bash peer_status=`sudo gluster peer ...
- Python排序算法——冒泡排序
有趣的事,Python永远不会缺席! 如需转发,请注明出处:小婷儿的python https://www.cnblogs.com/xxtalhr/p/10786904.html 一.冒泡排序(Bubb ...