此文章由机器翻译。

借助 C++ 进行 Windows 开发

DirectComposition:转换和动画

Kenny Kerr

Kenny KerrDirectComposition 的视觉效果提供了更多只是偏移量和我已经说明了我最后的几个列的内容属性。当你开始影响到他们与变换和动画,视觉效果真的会苏醒过来。在这两种情况下,Windows 排版引擎是一种处理器,该轮到你来计算或兴建的变换矩阵,以及三次函数和正弦波的动画曲线。值得庆幸的是,Windows API 提供了必要的支持,有一双非常好的补充的 Api。Direct2D 提供大力支持用于定义变换矩阵,使轻的描述旋转、 规模、 角度来看、 翻译和更多的工作。同样,Windows 动画管理器使您免于要在数学中,向导使您得以描述动画使用丰富的动画过渡库、 演示图板和关键帧和更多的负载。当您将 Windows 动画管理器、 Direct2D 和 DirectComposition 合并到单个应用程序你可以真正体验说一不二的权力,在您的指尖。

在我以前的专栏 (msdn.microsoft.com/magazine/dn759437) 我展示如何 DirectComposition API 可用于同时 Direct2D 得到最好的保留模式和实时模式的图形。该列所附带的样例项目说明了这一概念有一个简单的应用程序,允许您创建圈子,向四周移动和控制它们的 Z 顺序很简单。在本专栏中,我想告诉你它是多么容易添加一些功能强大的效果与变换和动画。首先要知道是 DirectComposition 为很多它们的标量性质提供重载。你可能已经注意到这如果你一直遵循过去几个月,我已经了解的 API。例如,可视化对象的偏移量可以如下设置的 SetOffsetX 和 SetOffsetY 的方法中:

ComPtr<IDCompositionVisual2> visual = ...
HR(visual->SetOffsetX(10.0f));
HR(visual->SetOffsetY(20.0f));

但是来自于 IDCompositionVisual2 的 IDCompositionVisual 接口还提供的这些重载方法接受的动画对象,而不是一个浮点数。此动画对象具体化为 IDCompositionAnimation 接口。例如,我可以设置可视化对象的偏移量与一个或两个动画对象,取决于是否需要进行动画处理的一个或两个轴,就像这样:

ComPtr<IDCompositionAnimation> animateX = ...
ComPtr<IDCompositionAnimation> animateY = ...
HR(visual->SetOffsetX(animateX.Get()));
HR(visual->SetOffsetY(animateY.Get()));

但组合引擎也能远远超过只是可视化对象的偏移量进行动画处理。视觉效果也支持 2D 和 3D 转换。Visual SetTransform 方法可用于应用二维变换,给出一个标量值:

D2D1_MATRIX_3X2_F matrix = ...
HR(visual->SetTransform(matrix));

在这里,DirectComposition 实际上依靠 3 x 2 矩阵由 Direct2D API 定义的。你可以像旋转、 转换、 缩放和扭曲视觉的东西。此坐标空间的视觉内容预计,但它只局限在两个三维图形的 X 轴和 Y 轴的影响。

当然,IDCompositionVisual 接口提供过载的 SetTransform 方法,但它并不直接接受动画对象。你看,动画对象是只负责对一个单一的值随着时间的推移进行动画处理。一个矩阵,根据定义,由组成的值的数目。你可能想要进行动画处理任何数目的其成员,具体取决于你想要达到的效果。所以,相反,SetTransform 重载接受一个 transform 对象:

ComPtr<IDCompositionTransform> transform = ...
HR(visual->SetTransform(transform.Get()));

它是变换对象,具体的各种接口来自 IDCompositionTransform,提供接受标量值或动画对象的重载的方法。这种方式,您可能定义动画的旋转角度但一个固定的中心点和轴的旋转矩阵。您的动画是,当然,取决于你。下面是一个简单的例子:

ComPtr<IDCompositionRotateTransform> transform = ...
HR(transform->SetCenterX(width / 2.0f));
HR(transform->SetCenterY(height / 2.0f));
HR(transform->SetAngle(animation.Get()));
HR(visual->SetTransform(transform.Get()));

IDCompositionRotateTransform 接口从 IDCompositionTransform 派生,并表示影响视觉绕 z 轴的旋转 2D 转换。我在这里,我设置为一个固定的值,基于一定的宽度和高度,并使用动画对象来控制角度的中心点。

这就是基本的模式。我只描述二维变换,但 3D 转换的工作在很多方式相同。现在让我说明更为实际的是由向您展示如何才能我以前的专栏所附带的样例项目和变换和对进行动画处理以各种方式帮助 Direct2D 和 Windows 动画管理器。

说明转换和打印的杂志中的动画并开始推动什么可以轻易被盯着一个静态页面的极限。如果你想要看到它在行动中,查阅我的在线课程,你在哪里可以看作为所有涉及到的生命 (bit.ly/WhKQZT)。若要使概念有点更多打印,我会调整示例项目从我以前的专栏,使用方块,而不是圈子里的自由。这应该使各种转换更加明显一点在纸上。首先,我会用矩形几何形状替换 SampleWindow m_geometry 成员变量:

ComPtr<ID2D1RectangleGeometry> m_geometry;

然后在 SampleWindow CreateFactoryAndGeometry 方法中我会 Direct2D 工厂来创建矩形几何形状而不是椭圆几何:

D2D1_RECT_F const rectangle =
  RectF(0.0f, 0.0f, 100.0f, 100.0f);
HR(m_factory->CreateRectangleGeometry(
  rectangle,
  m_geometry.GetAddressOf()));

这就是全部。该应用程序的其余部分只是将用于呈现几何抽象和命中测试像以前一样。您可以看到在结果图 1

说明了变换和动画而不是圆的方格子
图 1 说明了变换和动画而不是圆的方格子

接下来,我要去添加一个简单的消息处理程序,对 WM_KEYDOWN 消息做出响应。SampleWindow MessageHandler 方法中,我将添加一个 if 语句为此:

else if (WM_KEYDOWN == message)
{
  KeyDownHandler(wparam);
}

像往常一样,该处理程序将需要必要的错误处理,以从设备损失中恢复过来。图 2 提供了释放的设备资源和无效的窗口,所以 WM_PAINT 消息处理程序可以重建设备堆栈的常规模式。我也限制对 Enter 键要避免混乱与控制的关键,添加形状时所使用的处理程序。

设备丢失恢复图 2 脚手架

void KeyDownHandler(WPARAM const wparam)
{
  try
  {
    if (wparam != VK_RETURN)
    {
      return;
    }
    // Do stuff!
  }
  catch (ComException const & e)
  {
    TRACE(L"KeyDownHandler failed 0x%X\n",
      e.result);
    ReleaseDeviceResources();
    VERIFY(InvalidateRect(m_window,
                          nullptr,
                          false));
  }
}

在这一点上,我准备好开始尝试变换与动画。让我们开始只是一个基本的二维旋转变换。首先,我需要确定将代表 Z 轴的中心点或围绕的旋转点。因为 DirectComposition 预计物理像素坐标,只可用 GetClientRect 函数在这里旅游:

RECT bounds {};
VERIFY(GetClientRect(m_window, &bounds));

我可以然后派生的窗口客户区的中心点,如下所示:

D2D1_POINT_2F center
{
  bounds.right / 2.0f,
  bounds.bottom / 2.0f
};

我也可以靠 Direct2D 矩阵 helper 函数来构造描述 30 度的二维旋转变换的矩阵:

D2D1_MATRIX_3X2_F const matrix =
  Matrix3x2F::Rotation(30.0f, center);

然后,可以简单地设置一个视觉变换属性将更改提交到可视化树。我只会将此更改应用到的根视觉效果为简单起见:

HR(m_rootVisual->SetTransform(matrix));
HR(m_device->Commit());

当然,可以将任意数量的变化应用到任意数量的视觉效果和组合引擎将照顾协调它都不费吹灰之力。你可以看到在这简单的二维变换的结果图 3。你可能会注意到有些走样。即使 Direct2D 默认为抗锯齿,它假定,绘图将出现在呈现它的坐标空间。组合引擎没有任何知识的几何形状组成表面用,呈现,所以它也没有办法纠正这个。在任何情况下,一旦动画添加到组合中,频谱混叠将短暂而且很难辨认。

简单二维变换
图 3 简单二维变换

要将动画添加到这种转变,我需要切换出成分变换的矩阵结构。我会用一个单一的旋转变换替换的 D2D1_POINT_2F 和 D2D1_MATRIX_3X2_F 的结构。首先,我需要创建使用组成设备的旋转变换:

ComPtr<IDCompositionRotateTransform> transform;
HR(m_device->CreateRotateTransform(transform.GetAddressOf()));

请记住,即使是这种看似简单的对象必须被丢弃,如果设备已丢失并重新创建。然后,我可以设置的中心点和使用接口方法,而不是一个 Direct2D 矩阵结构的角度:

HR(transform->SetCenterX(bounds.right / 2.0f));
HR(transform->SetCenterY(bounds.bottom / 2.0f));
HR(transform->SetAngle(30.0f));

编译器将选取适当的重载来处理组成变换:

HR(m_rootVisual->SetTransform(transform.Get()));

运行这将产生同样的效果,如图 3,因为我没有添加的任何动画。创建动画对象是很简单的:

ComPtr<IDCompositionAnimation> animation;
HR(m_device->CreateAnimation(animation.GetAddressOf()));

然后我可以使用此动画对象而不是恒定值设置角度时:

HR(transform->SetAngle(animation.Get()));

当您尝试配置动画时,它会更有趣。简单的动画是相对简单的。正如我刚才所说,三次函数与正弦波描述了动画。我可以制作动画这旋转角度与线性过渡,哪里值进展从 0 到 360 超过 1 第二通过添加一个立方的函数:

float duration = 1.0f;
HR(animation->AddCubic(0.0,
                       0.0f,
                       360.0f / duration,
                       0.0f,
                       0.0f));

AddCubic 方法的第三个参数指示线性系数,所以,很有意义。如果我把它忘在那里的视觉会永恒每秒旋转 360 度。我可以决定带动画到结束,一旦它达到 360 度,如下所示:

HR(animation->End(duration, 360.0f));

End 方法的第一个参数指示从动画,无论动画函数的最值的开头的偏移量。第二个参数是动画的最终值。牢记这一点因为动画将"捕捉"到该值如果它不符合动画曲线,并且这会产生不和谐的视觉效果。

这种线性的动画是很容易的原因,但更复杂的动画可以变得极其复杂。这是 Windows 动画管理器进来的地方。 而无需调用 IDCompositionAnimation 添加正弦波和三次多项式片段、 重复的段,和更多的各种方法,我可以与 Windows 动画管理器和转换其丰富图书馆构建动画演示图板。当这样做了时,我可以使用生成的动画变量来填充组成动画。这涉及到更多的代码,但好处是更大的权力和控制您的应用程序的动画。首先,我需要创建动画管理器本身:

ComPtr<IUIAnimationManager2> manager;
HR(CoCreateInstance(__uuidof(UIAnimationManager2),
  nullptr, CLSCTX_INPROC, __uuidof(manager),
  reinterpret_cast<void **>(manager.GetAddressOf())));

是的 Windows 动画管理器依赖于 COM 激活,所以一定要致电 CoInitializeEx 或 RoInitialize 来初始化运行时。我还需要创建转换库:

ComPtr<IUIAnimationTransitionLibrary2> library;
HR(CoCreateInstance(__uuidof(UIAnimationTransitionLibrary2),
  nullptr, CLSCTX_INPROC, __uuidof(library),
  reinterpret_cast<void **>(library.GetAddressOf())));

通常情况下,应用程序将紧紧抓住这两个对象他们一辈子因为他们需要连续的动画,并针对速度匹配。接下来,我需要创建动画演示图板:

ComPtr<IUIAnimationStoryboard2> storyboard;
HR(manager->CreateStoryboard(storyboard.GetAddressOf()));

演示图板是什么转换关联的动画变量,并定义随着时间的推移他们相对的日程安排。演示图板是能够聚合各种转换应用于不同的动画变量 ; 它可以确保它们保持同步 ; 并计划作为一个整体的演示图板。当然,您可以创建多个故事板来安排独立的动画。现在,我需要问动画管理器来创建动画变量:

ComPtr<IUIAnimationVariable2> variable;
  HR(manager->CreateAnimationVariable(
    0.0, // initial value
    variable.GetAddressOf()));

一旦计划了演示图板,动画管理器负责该变量保持为最新的所以应用程序可以在任何时间请求有效的值。在这种情况下,我只要去使用动画变量来填充与线段组成动画,然后丢弃它。现在我可以使用强大的转换库创建一个有趣的过渡效果的动画变量:

ComPtr<IUIAnimationTransition2> transition;
HR(library->CreateAccelerateDecelerateTransition(
  1.0,   // duration
  360.0, // final value
  0.7,   // acceleration ratio
  0.3,   // deceleration ratio
  transition.GetAddressOf()));

过渡将导致动画变量来加快和放慢在给定的持续时间内直到它来到休息在其最终值。比例用来影响如何相对快速的变量将加速,然后减速。请记住,比率结合不能超过一个价值之一。随着动画变量和过渡准备去,我可以将它们添加到情节提要:

HR(storyboard->AddTransition(variable.Get(),
                             transition.Get()));

演示图板是现在准备安排:

HR(storyboard->Schedule(0.0));

进度计划方法的单一参数告诉调度程序目前的动画时间。这是协调动画和协调与组合引擎刷新率,更有用,但它将做为现在。在这一点上,动画变量引和我可以通过它来填充曲线组成的动画:

HR(variable->GetCurve(animation.Get()));

在这种情况下,就好像我打电话就是组成动画,如下所示:

HR(animation->AddCubic(0.0, 0.0f, 0.0f, 514.2f, 0.0f));
HR(animation->AddCubic(0.7, 252.0f, 720.0f, -1200.0f, 0.0f));
HR(animation->End(1.0, 360.0f));

那是当然很少的代码比花了使用 Windows 动画管理器中,但是,数学的意义并不是那么简单。Windows 动画管理器还提供了协调能力和顺利过渡运行动画,事情会变得非常困难,若要手动执行。


Kenny Kerr 是一个位于加拿大,以及作者 Pluralsight 和微软最有价值球员的计算机程序员。他的博客 kennykerr.ca ,你可以跟着他在 Twitter 上 twitter.com/kennykerr

感谢以下的微软技术专家对本文的审阅:Leonardo 布兰科和James克拉克