DirectX 因素

Direct2D 几何图形及其处理

Charles Petzold

下载代码示例

Charles Petzold高中几何来在两个截然不同的特点:欧几里德几何面向建筑、 定理和证明,而解析几何描述几何数字按数字顺序使用点的坐标系统上,通常被称为笛卡尔坐标系统中的先驱的解析几何,勒内 · 笛卡尔的荣誉。

它的形成的整个向量翼计算机图形学的基础,所以它是正确和恰当的接口名为 ID2D1Geometry (和从它派生的六个接口) 很多坐在中心的 Direct2D 矢量图形的解析几何。

在本专栏的上一篇文章中 (msdn.microsoft.com/magazine/dn342879),我讨论了如何使用 ID2D1PathGeometry 来呈现中累死运行的应用程序在 Windows 8 下线。现在我想退后一步和探索几何图形的更广泛的细节,和特别是调查的一些有趣方法由 ID2D1Geometry 定义的操纵,使不同的几何图形的几何图形。

即使你熟悉 Windows 运行时 (WinRT) 中使用的几何图形,ID2D1Geometry 提供的不暴露在 WinRT 几何类的设施。

Overview

几何结构基本上是坐标点的集合。这些坐标点是不被绑在任何特定的设备,所以几何图形是本身独立于设备的对象。为此原因,通过调用方法上的 ID2D1Factory 对象,是独立于设备 Direct2D 工厂创建各种几何的形状。六个几何创建方法所示图 1,以及他们所创建的对象的接口类型。

图 1 六的几何方法和接口

ID2D1Factory 方法 创建的对象 (从 ID2D1Geometry 派生)
CreateRectangleGeometry ID2D1RectangleGeometry
CreateRoundedRectangleGeometry ID2D1RoundedRectangleGeometry
CreateEllipseGeometry ID2D1EllipseGeometry
CreatePathGeometry ID2D1PathGeometry
CreateTransformedGeometry ID2D1TransformedGeometry
CreateGeometryGroup ID2D1GeometryGroup

除了 CreatePathGeometry,这些方法将创建不可变的对象:创建对象所需的所有信息都传递给创建方法,和你不能改变什么关于几何后已创建它。

ID2D1PathGeometry 对象是不同的唯一原因是因为 CreatePathGeometry 返回一个对象,是基本上是空的。我将描述如何你加满不久。

CreateTransformedGeometry 接受现有的 ID2D1Geometry 对象和仿射变换矩阵。由此产生的几何是翻译、 缩放、 旋转或倾斜由该矩阵。创建­GeometryGroup 方法接受 ID2D1Geometry 的数组对象,并创建所有个别的几何形状的复合几何图形。

如果你使用的 Visual Studio Direct2D (XAML) 项目模板创建一个 Windows 应用商店应用程序访问 DirectX,你一般会在 CreateDeviceIndependent 中创建几何对象­资源呈现类的重写,并在期间使用这些程序的生存期中特别是呈现重写。

ID2D1RenderTarget 的一个接口,该接口 (从哪个接口如 ID2D1DeviceContext 派生) 定义绘图图面上呈现几何形状的两种方法:DrawGeometry 和 FillGeometry。

DrawGeometry 方法绘制直线和曲线的几何与指定的画笔、 描边粗细和描边样式,使固体、 点线、 虚线或自定义的短划线-带图案的线条。FillGeometry 方法填充封闭的区域的几何用画笔和一个可选的不透明蒙版。您还可以剪辑,包括一个 D2D1_LAYER_PARAMETERS 结构,包括几何与呈现目标上调用 PushLayer 使用几何图形。

有时你需要对几何形状进行动画处理。最有效的办法在呈现几何前呈现目标应用矩阵变换。然而,如果这不是足够的你就需要重新创建几何形状,可能期间呈现类中的更新方法。(或者,您可能想要在开始时创建的几何形状的一群,并将它们保存。)虽然重新创建几何图形的增加呈现开销,它是有时当然有必要。但是,大多数情况下,如果你不需要重新创建几何图形,不这样做。

几何图形接收器

ID2D1PathGeometry 对象是直线、 曲线、 特别是立方和二次贝塞尔曲线和弧,是在一个椭圆的圆周上的曲线的集合。您可以控制是否连接这些直线和曲线,以及他们是否定义封闭的区域。

用直线和曲线填充路径几何图形涉及使用 ID2D1GeometrySink 类型的对象。此特定的接收器不是洗的几何 !想想看作为盛器 — — 直线和曲线,然后保留路径几何图形的目的地。

这里是如何生成路径几何图形:

  1. 通过 ID2D1Factory 对象上调用 CreatePathGeometry 创建一个 ID2D1PathGeometry 对象。
  2. ID2D1PathGeometry 获取一个新的 ID2D1GeometrySink 对象上调用 Open。
  3. ID2D1GeometrySink 将直线和曲线添加到路径几何图形上调用的方法。
  4. 在 ID2D1GeometrySink 上调用 Close。

ID2D1PathGeometry 是不能用的除非关闭已上的 ID2D1GeometrySink 对象调用。调用结束后,ID2D1PathGeometry 是不可变的:不能以任何方式改变它的内容,你不能再打开放。几何图形接收器不再有目的的你可以摆脱它。

在列表中的第三项通常涉及到最大量的代码。路径几何图形是集合的数字 ; 每个图是一系列相互连接的直线和曲线,称为线段。当调用的函数在 ID2D1GeometrySink 上,你开始对 SetFillMode 可选的调用以指示用于填充封闭区域的算法。然后,为每个系列的相互连接的直线和曲线路径几何图形中:

  1. 调用 BeginFigure,指示第一个点和是否将填充封闭的区域。
  2. 调用的方法开头的单词添加到图添加连接的线、 贝塞尔曲线和圆弧。
  3. 调用 EndFigure,该值指示是否应自动连接的最后一个点,到具有一条直线的第一个点。

当你看看 ID2D1GeometrySink 的文档时,需要注意的是 ID2D1SimplifiedGeometrySink,实际定义最重要的方法从派生的接口。更多关于这个分化不久。

因为 ID2D1PathGeometry 的内容是不可变的一旦你定义了其路径,如果你需要改变 ID2D1Path­你必须重新创建它,并再次经过路径定义过程的几何。然而,如果你只加入附加数字的开头或结尾的现有路径几何图形,快捷方式是可用:你可以创建新的路径几何图形、 添加一些数字,然后转移的现有路径几何图形内容入新路径几何图形通过调用现有路径几何图形与新 ID2D1GeometrySink 的流函数。然后,可以添加附加数字在关闭之前。

绘制和填充

我现在准备向你展示一些代码。可下载的几何­实验项目在 Visual Studio 2012 年使用 Windows 存储 Direct2D (XAML) 模板创建。我重的 SimpleTextRenderer 类命名为 GeometryVarietiesRenderer,和我删除了所有的代码和示例文本呈现与关联的标记。

在 XAML 文件中,我定义了一堆的各种几何渲染技术的单选按钮。每个单选按钮是与我定义 RenderingOption 枚举的成员相关联。一个大开关和渲染器中的 case 语句重写使用枚举的成员这 RenderingOption 治理执行什么代码。

在 CreateDevice 期间发生的所有几何创建­IndependentResources 重写。该程序创建的一个几何是五针对性的星。所示的较广义的方法,生成此几何图 2。它包括一个图与四个线段,但在最后一个点自动连接到的第一个点。

图 2 方法以生成五针对性的星几何

 

HRESULT GeometryVarietiesRenderer::CreateFivePointedStar(   float radius, ID2D1PathGeometry** ppPathGeometry) {   if (ppPathGeometry == nullptr)     return E_POINTER;   HRESULT hr = m_d2dFactory->CreatePathGeometry(ppPathGeometry);   ComPtr<ID2D1GeometrySink> geometrySink;   if (SUCCEEDED(hr))   {     hr = (*ppPathGeometry)->Open(&geometrySink);   }   if (SUCCEEDED(hr))   {     geometrySink->BeginFigure(Point2F(0, -radius), D2D1_FIGURE_BEGIN_FILLED);     for (float angle = 2 * XM_2PI / 5; angle < 2 * XM_2PI; angle += 2 * XM_2PI / 5)     {       float sin, cos;       D2D1SinCos(angle, &sin, &cos);       geometrySink->AddLine(Point2F(radius * sin, -radius * cos));     }     geometrySink->EndFigure(D2D1_FIGURE_END_CLOSED);     hr = geometrySink->Close();   }   return hr; }

图 3 显示了很多的 CreateDeviceIndependentResources 方法。 (若要保持简单的上市,我删除四处游荡的 HRESULT 值的处理。此方法首先创建类似于-方波、 调用以创建五针对性的星和给另一个方法的调用以创建无穷大符号几何。 这些几何形状的两个被转换,而所有三个组合中的 CreateGeometryGroup 方法 (称为底部的图 3) 到名为 m_geometryGroup 的成员。

图 3 多 CreateDeviceIndependentResources 重写

void GeometryVarietiesRenderer::CreateDeviceIndependentResources() {   DirectXBase::CreateDeviceIndependentResources();   // Create square-wave geometry   HRESULT hr = m_d2dFactory->CreatePathGeometry(&m_squareWaveGeometry);   ComPtr<ID2D1GeometrySink> geometrySink;   hr = m_squareWaveGeometry->Open(&geometrySink);   geometrySink->BeginFigure(Point2F(-250, 50), D2D1_FIGURE_BEGIN_HOLLOW);   geometrySink->AddLine(Point2F(-250, -50));   geometrySink->AddLine(Point2F(-150, -50));   geometrySink->AddLine(Point2F(-150,  50));   geometrySink->AddLine(Point2F(-50,   50));   geometrySink->AddLine(Point2F(-50,  -50));   geometrySink->AddLine(Point2F( 50,  -50));   geometrySink->AddLine(Point2F( 50,   50));   geometrySink->AddLine(Point2F(150,   50));   geometrySink->AddLine(Point2F(150,  -50));   geometrySink->AddLine(Point2F(250,  -50));   geometrySink->AddLine(Point2F(250,   50));   geometrySink->EndFigure(D2D1_FIGURE_END_OPEN);   hr = geometrySink->Close();   // Create star geometry and translate it   ComPtr<ID2D1PathGeometry> starGeometry;   hr = CreateFivePointedStar(150, &starGeometry);   hr = m_d2dFactory->CreateTransformedGeometry(starGeometry.Get(), Matrix3x2F::Translation(0, -200),            &m_starGeometry);   // Create infinity geometry and translate it   ComPtr<ID2D1PathGeometry> infinityGeometry;   hr = CreateInfinitySign(100, &infinityGeometry);   hr = m_d2dFactory->CreateTransformedGeometry(infinityGeometry.Get(),             Matrix3x2F::Translation(0, 200),             &m_infinityGeometry);   // Create geometry group   CreateGeometryGroup();   ...
}

CreateDeviceIndependentResources 方法还会创建两个描边样式与圆角和联接。 一个是固体,另一种是虚线。

CreateDeviceDependentResources 方法创建两个画笔:绘图和红色填充为黑色。

当程序启动时,选中第一个单选按钮,则和 DrawGeometry 被称为:

m_d2dContext->DrawGeometry(m_geometryGroup.Get(),                            m_blackBrush.Get());

结果显示在图 4,请看奇怪的是像一些奇怪图形崇拜的标志。

The Startup Screen of GeometryExperimentation
图 4 启动屏幕上的 GeometryExperimentation

由于该程序合并三个单独的几何形状,简化呈现,他们会所有用呈现相同的画笔。 在实际程序中,大概会维持一群个别的几何图形和颜色他们都以不同的方式。

第一次几个选项表明如何可以与较粗笔划和样式化的线,包括一条虚线绘制几何图形。 (整个程序,1、 10 和 20 像素的描边厚度使用,和视觉上应该是很容易区分。

当展示路径几何图形和动画在 XAML 中的,我想将基于 XAML 的动画应用于一个描边样式的短划线偏移导致点的几何周围旅行。 你可以做类似 DirectX 中的东西 (作为动画点偏移选项演示),但您需要显式地在每个屏幕刷新期间重新创建 ID2D1StrokeStyle 对象。 在 Update 方法中发生这种情况。

也可以用画笔填充封闭的区域的几何形状:

m_d2dContext->FillGeometry(m_geometryGroup.Get(),                            m_redBrush.Get());

结果如图 5 所示。 在矩形波没有封闭的领域。 室内五角大楼的五针对性的星不被填补因为填充模式设置为一种算法称为备用。 您可以使用对的单选按钮,在左下角的图 5 选择绕线的填充模式填充五角大楼。 需要创建 ID2D1GeometryGroup,所以需要重新创建或者那些两个单选按钮被单击时该 m_geometryGroup 对象时,指定的填充模式。 如果几何形状重叠几何组中,然后相交区域填充也基于该填充模式。

Filling Geometries
图 5 填充几何图形

通常你会希望同时绘制和填充几何。 通常你会想要第一次调用 FillGeometry,然后 DrawGeometry 保持描边完全可见。

简化和简化几何

假设您的应用程序创建 ID2D1PathGeometry 动态,或许从用户输入,并且您想要"审问"要提取所有图形和线段的路径几何图形。 也许你会喜欢作为一系列的 PathGeometry、 PathFigure、 LineSegment 和 BezierSegment 的标准标记在 XAML 格式保存此信息。

起初,它不会出现,如果这是可能。 ID2D1PathGeometry 有 GetFigureCount 和 GetSegmentCount 的方法,但没有实际提取这些图形和线段的方法。

但记得流方法。 此方法接受 ID2D1­GeometrySink 和路径几何图形中的内容复制到该接收器。 这里的关键是你可以编写您自己的类,实现 ID2D1GeometrySink 接口,并将这类的一个实例传递给流方法。 在此自定义类中,可以处理对 BeginFigure、 AddLine 等等,所有的调用,并做任何你想和他们一起做。

当然,这样的类不是微不足道的。 它将需要的 ID2D1GeometrySink,以及 ID2D1 中的所有方法实现­SimplifiedGeometrySink 和 IUnknown。

然而,有一个能让这份工作比较容易:ID2D1Geometry 接口定义一个名为任何几何对象转换为"简化"的几何仅包含直线和三次贝塞尔样条的简化方法。 这一壮举是可能的因为可以由三次贝塞尔样条接近二次贝塞尔曲线和圆弧。 这意味着您的自定义类只需实施的 ID2D1SimplifiedGeometrySink 和 IUnknown 方法。 只需将此自定义类的实例传递到任何几何的简化方法。

你也可以使用简化简化几何的内容复制到新的路径几何图形。 这里是几何中的代码­不会这 (不包括 HRESULT 检查) 的实验:

m_d2dFactory->CreatePathGeometry(&m_simplifiedGeometry); ComPtr<ID2D1GeometrySink> geometrySink; m_simplifiedGeometry->Open(&geometrySink); m_geometryGroup->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,                           IdentityMatrix(), geometrySink.Get()); geometrySink->Close();

通知的简化方法的第一个参数表示您希望立方贝塞尔曲线和简化的路径几何图形中的直线。 您可以限制为唯一行,在这种情况下的贝塞尔曲线由接近一系列的直线。 这是一种叫做"扁平化"。如果拼合大量的精度来执行你不能直观地看出差别,但是您还可以指定的拼合容差,所以,贝塞尔曲线不很好接近。 下面是一些代码中创建一个"严重简化"几何的 GeometryExperimentation:

m_d2dFactory->CreatePathGeometry(&m_grosslySimplifiedGeometry); m_grosslySimplifiedGeometry->Open(&geometrySink); m_geometryGroup->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES,                            IdentityMatrix(), 20, geometrySink.Get()); geometrySink->Close();

星和方形波相同的外观,但无穷大符号不再是那么顺利,如中所示图 6。 默认情况下,拼合容差为 0.25。

A Grossly Flattened Infinity Sign
图 6 严重单一化无穷大符号

更多几何操作

ID2D1Geometry 接口定义三个额外的方法,类似于简化计算新的几何形状,并将它写入到 ID2D1SimplifiedGeometrySink。 我将讨论大纲和拓宽在这里,但不是 CombineWithGeometry (因为它只能做一些有趣的事情具有多个重叠几何形状的在此程序中没有)。

如你所见,路径几何图形可以有相交的部分。 无穷大符号,在中心有相交的部分权利和五针对性的星有一群相交段。 由 ID2D1Geometry 定义的大纲方法创建基于现有的路径几何图形,消除了这些交集,但保留同一封闭的区域的新路径几何图形。

这里是几何组转换为轮廓的路径几何图形的 GeometryExperimentation 中的代码:

m_d2dFactory->CreatePathGeometry(&m_outlinedGeometry); m_outlinedGeometry->Open(&geometrySink); m_geometryGroup->Outline(IdentityMatrix(), geometrySink.Get()); geometrySink->Close();

因为此 m_outlinedGeometry 对象定义相同的填充的区域为 m_geometryGroup,它是根据 m_geometryGroup 的是否创建使用备用填充模式或缠绕填充模式不同。

原始几何组共有三个数字:一个用于星、 方波和无穷大符号。 如果此几何组创建具有备用的填充模式,勾勒的几何图形包含八个数字:五个星的五个点,一个用于矩形波,两个为无穷大符号。 但是视觉上看起来相同。 如果几何组创建具有缠绕填充模式,然而,勾勒的几何图形有共四个数字:无穷大符号有两个数字只是作为备用的填充模式,但因为星的整个内部被填满,由明星组成的只是一个数字,如中所示图 7

An Outlined Path Geometry
图 7 概述的路径几何图形

定义这种几何从零开始将数学上相当困难,但大纲方法使它很容易。 因为大纲所定义的路径几何图形包含没有相交部分,填充模式不起作用。

我找到的加宽方法是最有趣。 理解加宽是什么,请考虑包含只是一条直线的两个点之间的路径几何图形。 这个几何绘制时,它是用描边特别线条的粗细,所以它实际上呈现为一个实心矩形。 如果线条样式包括圆角,此矩形的装饰与两个实心半圆。

拓宽方法计算描述此呈现对象的轮廓路径几何图形。 为此,拓宽要求指定所需的笔划粗细和描边样式,就像 DrawGeometry 的参数。 这里是 GeometryExperimentation 项目中的代码:

m_d2dFactory->CreatePathGeometry(&m_widenedGeometry); m_widenedGeometry->Open(&geometrySink); m_geometryGroup->Widen(20, m_roundedStrokeStyle.Get(),                         IdentityMatrix(), geometrySink.Get()); geometrySink->Close();

请注意,20 像素笔划粗细。 然后,该程序绘制使用一个像素的描边粗细此扩阔的几何:

m_d2dContext->DrawGeometry(m_widenedGeometry.Get(),                            m_blackBrush.Get(), 1);

结果如图 8 所示。 我发现此过程创建的极为有趣的工件。 这是因为如果我不知何故我窥视的线条绘制算法的内脏。

A Widened Path Geometry
图 8 加宽路径几何图形

GeometryExperimentation 程序还允许灌装加宽的路径几何图形:

m_d2dContext->FillGeometry(m_widenedGeometry.Get(),                            m_redBrush.Get());

用一个特别的描边宽度和描边样式填充已加宽路径几何图形是相同的视觉效果描边的宽度和样式的原始路径。 GeometryExperimentation 中的绘制圆角宽笔触和填充加宽的选项只有视觉差异是颜色,因为我用黑色的绘图和红色为灌装。

你可能会想要填充和绘制加宽的路径几何图形,但您可能希望移除内部文物。 这样做是十分简单的。 只适用于加宽的路径几何图形的大纲方法:

m_d2dFactory->CreatePathGeometry(&m_outlinedWidenedGeometry); m_outlinedWidenedGeometry->Open(&geometrySink); m_widenedGeometry->Outline(IdentityMatrix(), geometrySink.Get()); geometrySink->Close();

现在当你填写并绘制概述加宽的路径几何图形,你获取图像图 9。 它是免费的文物和视觉上相当不同于任何其他呈现此程序中,由不同的东西我会勇敢到代码从零开始。

An Outlined Widened Path Geometry, Stroked and Filled
图 9 概述加宽路径几何图形,描边和填充

任何其他人吗?

几何数据写入 ID2D1SimplifiedGeometrySink 的另一种方法,但您可能需要为它去打猎。 它不是在 Direct2D 接口之间。 它是在 DirectWrite,和它是 IDWriteFontFace 的 GetGlyphRunOutline 方法。 此功能强大的方法从文本字符轮廓,生成路径几何图形和这只是太好玩,忽略。 请继续关注。

Charles Petzold 是 MSDN 杂志的长期撰稿人,他是“Programming Windows, 6th edition”(O'Reilly Media,2012)一书的作者,这本书讲授如何编写 Windows 8 应用程序。他的网站是 charlespetzold.com

衷心感谢以下技术专家对本文的审阅:Wessam 巴赫纳西 (下面Framez 技术)、 Worachai Chaoweeraprasit (Microsoft) 安东尼 Hodsdon (Microsoft) 和迈克尔 · B. 麦克劳克林 (Bob Taco 行业)

自成立以来在 Windows 7 中,安东尼 Hodsdon 一直 Direct2D 团队的开发人员。 他的职业兴趣包括几何 (解析和欧几里德) 和数值算法。

Worachai Chaoweeraprasit 带领 Direct2D 和 DirectWrite 的发展。 他喜欢好排版和得失快速的图形。 他告诉他他教他三年级的学生更容易的方法,程序比移动块 — — 他有空的时候它简单地称为 c。

迈克尔 B. 麦克劳克林是 Visual c + + MVP 和鲍勃 Taco 行业,一个微 ISV 和咨询公司的东主。 他以前是 XNA/DirectX 最有价值球员,是个退休的律师。 他的网站是 bobtacoindustries.com 和他的 Twitter 句柄是 @mikebmcl

Wessam 巴赫纳西是满怀激情的计算机图形和游戏的软件工程师。 他作为导致呈现工程在电子艺术工作了 7 年。 现在他是带领程序员在下面Framez 科技股份有限公司,阿拉伯语的游戏开发商网络、 DirectX MVP 2003 年以来和现在 Visual c + + MVP 监事。