本文章是由機器翻譯。

DirectX 因數

2D 入口網站進入 3D 世界

Charles Petzold

下載代碼示例

如果你精通 2D 圖形,您可能認為 3D 是相似,但是額外的維度。不完全正確 !已涉足 3D 圖形程式設計的人都知道它是多麼的困難。3D 圖形程式設計都需要你之外什麼都在傳統的二維世界中遇到主新和異國情調的概念。許多準備工作需要在螢幕上,讓只是小 3D 和甚至然後輕微的誤判可以呈現不可見。因此,對學習圖形程式設計這麼重要的視覺回饋延遲,直到所有的程式設計塊是在的地方,在和諧工作為止。

DirectX 承認與 Direct2D 和 Direct3D 之間司的 2D 和 3D 圖形程式設計之間的深刻差異。雖然您可以混合使用 2D 和 3D 內容相同的輸出裝置上,這些都是非常獨特和不同的程式設計介面,並且有沒有中間地帶。DirectX 不允許你做一點小小的國家,有點搖滾。

不是嗎?

有趣的是,Direct2D 包括的一些概念和起源于 3D 程式設計宇宙的設施。通過幾何鑲嵌 (分解複雜的幾何形狀成三角形) 和二維效果圖使用著色器 (其中包括特殊圖形處理單元或 GPU 運行的代碼) 等功能,有可能利用一些功能強大的 3D 概念卻仍能 Direct2D 的範圍內進行。

此外,可以遇到,漸漸地,探討這些 3D 概念,你得到的實際上在螢幕上看到結果滿意。您可以獲得 3D 腳濕在 Direct2D 所以 Direct3D 程式設計以後陷入是少一點令人震驚。

我猜應該不那麼令人驚訝的是 Direct2D 包含了一些 3D 功能。在結構上,Direct2D 是建在 Direct3D,允許 Direct2D 還利用硬體加速的 GPU。Direct2D 和 Direct3D 之間的關係變得更明顯,當您開始探索虛空地區的 Direct2D。

我會開始這個勘探與審查的三維座標和座標系統。

向外大的飛躍

如果你一直跟蹤此列在最近幾個月中,你知道它是可以調用 GetGlyphRunOutline 方法的物件的實現 IDWriteFontFace 介面,以獲得一個 ID2D1PathGeometry 實例,它描述的文本字元的直線和貝茲曲線輪廓。然後,您可以操作這些直線和曲線座標來扭曲中的各種方法的文本字元。

它也是可能的路徑幾何圖形的 2D 座標轉換成三維座標,然後 manip­烏拉特前將它們轉換回 2D 通常顯示的路徑幾何圖形這些三維座標。這聽起來像好玩嗎?

在二維空間中的座標來表示作為數對 (X,Y) 對應到 ; 在螢幕上的位置 3D 座標是 (X,Y,Z) 的形式,並在概念上,Z 軸正交螢幕。除非你在處理一個全息顯示或 3D 印表機,這些 Z 座標不是幾乎一樣的真實作為 X 和 Y 座標。

有其他 2D 和 3D 坐標系之間的差異。傳統的 2D 的起源 — 點 (0,0) — 是顯示裝置的左上角。X 座標向右增加,Y 座標增加下去。在 3D 的起源很多時候是在螢幕的中心和它是一個標準的笛卡爾座標系統:X 座標仍增加往右,但是 Y 座標增加上去了,也有消極的座標以及。(當然,起源、 縮放和這些軸的方向可以改變與矩陣變換,和通常是)。

在概念上,積極的 Z 軸可以的螢幕點或點到螢幕。這兩項公約被稱為"右"和"左"的座標系統,指的一種技術來區分它們:與右側的座標系統中,如果你的 X 軸和中指的正面 Y 方向的積極方向點你的右手的食指拇指指向正 Z。另外,如果你的曲線的右手從 X 軸正向 Y 軸向、 你的拇指點正面積極 Z 到手指。左手座標系統中,它是一樣的只使用左手。

我的目標是取得 2D 路徑幾何圖形的簡短的文本字串,然後把它扭曲圍繞原點成 3D 的戒指所以開始會見結束時,與圖中所示相似圖 1。因為我就會轉換 2D 到 3D 的座標座標,然後返回到 2D,我選擇使用一個三維的座標系統與 Y 座標增加下去,就像在 2D。積極的 Z 軸來的螢幕,但它真的是一個左手座標系統。


圖 1 為這篇文章中的程式使用的坐標系

若要使此整個作業盡可能方便,我使用存儲程式的資源,作為一個字體檔,創建一個 IDWriteFontFile 物件,用於獲得的 IDWriteFontFace 物件。或者,您可以通過從系統字體集合更多迂回方法獲取 IDWriteFontFace。

從 GetGlyphRunOutline 方法生成的 ID2D1PathGeometry 物件然後通過使用 D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES 參數來拼合所有貝茲曲線組成的短行序列的簡化方法。簡化的幾何傳遞到自訂的 ID2D1GeometrySink 實現命名為 FlattenedGeometrySink,進一步分解成更短的直線之間的直線。結果就是僅由組成的線路完全瑪鋼幾何。

易於操縱的這些座標 FlattenedGeometry­接收器生成多邊形物件的集合。圖 2 顯示多邊形結構的定義。它基本上是只連接 2D 點的集合。多邊形的每個物件對應于路徑幾何圖形中的閉合圖。在路徑幾何圖形中的並不是所有數位都已都關閉,但那些在文本字形始終都閉合的所以這種結構是細為此目的。某些字元 (如 C、 E 和 X) 是由一個多邊形 ; 描述 一些 (A、 D 和 O) 組成的兩個多邊形物件的內部和外部 ; 一些 (B,例如) 組成的 3 個 ; 和一些符號字元可能更多。

圖 2 為存儲的閉合的路徑的多邊形類數位

struct Polygon
{
  // Constructors
  Polygon()
  {
  }
  Polygon(size_t pointCount)
  {
    Points = std::vector<D2D1_POINT_2F>(pointCount);
  }
  // Move constructor
  Polygon(Polygon && other) : Points(std::move(other.Points))
  {
  }
  std::vector<D2D1_POINT_2F> Points;
  static HRESULT CreateGeometry(ID2D1Factory* factory,
                                const std::vector<Polygon>& polygons,
                                ID2D1PathGeometry** pathGeometry);
};
HRESULT Polygon::CreateGeometry(ID2D1Factory* factory,
                                const std::vector<Polygon>& polygons,
                                ID2D1PathGeometry** pathGeometry)
{
  HRESULT hr;
  if (FAILED(hr = factory->CreatePathGeometry(pathGeometry)))
    return hr;
  Microsoft::WRL::ComPtr<ID2D1GeometrySink> geometrySink;
  if (FAILED(hr = (*pathGeometry)->Open(&geometrySink)))
    return hr;
  for (const Polygon& polygon : polygons)
  {
    if (polygon.Points.size() > 0)
    {
      geometrySink->BeginFigure(polygon.Points[0],
                                D2D1_FIGURE_BEGIN_FILLED);
      if (polygon.Points.size() > 1)
      {
        geometrySink->AddLines(polygon.Points.data() + 1,
                               polygon.Points.size() - 1);
      }
      geometrySink->EndFigure(D2D1_FIGURE_END_CLOSED);
    }
  }
  return geometrySink->Close();
}

此列的可下載代碼之間 Windows 應用商店的程式命名為 CircularText,它創建一個基於文本"文本在無限的圈子,"結束了旨在連接回圍成一圈開始的多邊形物件的集合。文本字串是實際的程式中指定作為"ext T 無限圈"要避免空間在開始或結束,將會消失時從字形生成路徑幾何圖形。

在專案中包含的兩個方法物件的 CircularText 中的 CircularTextRenderer 類類型稱為 m_srcPolygons (原始多邊形生成的物件從路徑幾何圖形) 和 m_dstPolygons (用於生成呈現的路徑幾何圖形的多邊形物件) 的多邊形。圖 3 顯示的方法將源多邊形轉換為基於螢幕的大小目標多邊形的 CreateWindowSizeDependentResources。

圖 3 從 2D 到 3D 到 2D CircularText Program 中

void CircularTextRenderer::CreateWindowSizeDependentResources()
{
  // Get window size and geometry size
  Windows::Foundation::Size logicalSize = m_deviceResources->GetLogicalSize();
  float geometryWidth = m_geometryBounds.right - m_geometryBounds.left;
  float geometryHeight = m_geometryBounds.bottom - m_geometryBounds.top;
  // Calculate a few factors for converting 2D to 3D
  float radius = logicalSize.Width / 2 - 50;
  float circumference = 2 * 3.14159f * radius;
  float scale = circumference / geometryWidth;
  float height = scale * geometryHeight;
  for (size_t polygonIndex = 0; polygonIndex < m_srcPolygons.size(); polygonIndex++)
  {
    const Polygon& srcPolygon = m_srcPolygons.at(polygonIndex);
    Polygon& dstPolygon = m_dstPolygons.at(polygonIndex);
    for (size_t pointIndex = 0; pointIndex < srcPolygon.Points.size(); pointIndex++)
    {
      const D2D1_POINT_2F pt = srcPolygon.Points.at(pointIndex);
      float radians = 2 * 3.14159f * (pt.x - m_geometryBounds.left) / geometryWidth;
      float x = radius * sin(radians);
      float z = radius * cos(radians);
      float y = height * ((pt.y - m_geometryBounds.top) / geometryHeight - 0.5f);
      dstPolygon.Points.at(pointIndex) = Point2F(x, y);
    }
  }
  // Create path geometry from Polygon collection
  DX::ThrowIfFailed(
    Polygon::CreateGeometry(m_deviceResources->GetD2DFactory(),
                            m_dstPolygons,
                            &m_pathGeometry)
    );
}

在內部迴圈,你會看到 x、 y 和 z 值計算。這是一個 3D 的座標,但它甚至不保存。相反,它是立即折疊回 2D 通過簡單地忽略的 z 值。到得­晚那些 3D 座標,代碼首先將轉換為水準位置上向以弧度表示的角度從 0 到 2 π 的原始路徑幾何圖形。Sin 和 cos 函數計算在 XZ 平面上的單位圓上的位置。Y 值是從垂直座標的原始路徑幾何圖形的更直接轉換。

CreateWindowSizeDependentResources 方法的結論通過從多邊形集合目標獲得一個新的 ID2D1PathGeometry 物件。Render 方法然後設置矩陣變換原點放在螢幕的中心和兩個填充和概述此路徑幾何圖形中所示,結果圖 4


圖 4 CircularText 顯示

該程式工作,嗎?很難告訴 !仔細看,你可以看到在中心一些寬字元和窄字元在左和右。但最大的問題是我開始與路徑幾何圖形與不相交的線,現在回上本身不填補這些重疊區域,結果顯示幾何。這種效果是典型的幾何形狀,和它發生是否由多邊形結構創建的路徑幾何圖形的填充模式為備用或纏繞。

獲取一些觀點

三維圖形程式設計不是只是約座標點。視覺提示是必要的檢視器來解釋代表在 3D 空間中的物件作為一個 2D 螢幕上的圖像。在現實世界中,你很少查看物件從一個恒定的瞭望點。你會得到更好的中的 3D 文本視圖圖 4 如果你可以傾斜它有些讓它看起來更像是在中環圖 1

若要獲得關於三維文字的一些觀點,座標需要在空間中旋轉。正如你所知,Direct2D 支援命名為 D2D1_MATRIX_3x2_F,你可以使用來定義 2D 轉換,你可以通過第一次調用 ID2D1RenderTarget 的 SetTransform 方法將應用於您的 2D 圖形輸出的矩陣變換結構。

最常見的是您將使用來自 D2D1 命名空間命名 Matrix3x2F,為此目的的一類。此類從 D2D1_MATRIX_3x2F_F 派生和提供方法用於定義各種類型的標準翻譯、 縮放、 旋轉和傾斜。

Matrix3x2F 類還定義了一個名為 TransformPoint,它允許您將轉換應用於個別 D2D1_POINT_2F 物件的"手動"方法。這是對於操縱點,他們在呈現之前有用。

你可能覺得我需要一個 3D 旋轉矩陣傾斜顯示的文本。我當然會探索 3D 矩陣變換在未來的列,但現在可以讓做與 2D 旋轉。想像你自己在某個地方位於負 X 軸上的圖 1,請向原點方向看。就像 X 坐落的積極的 Z 和 Y 軸,在一個常規的 2D 中的 Y 軸座標系統,所以它看似合理的將 2D 旋轉矩陣應用於的 Z 和 Y 值,我可以旋轉圍繞三-的所有座標­三維 X 軸。

你可以嘗試用這個 CircularText 程式。在 CreateWindowSizeDependent 中創建一個二維旋轉矩陣­資源方法什麼時候之前操縱的多邊形的座標:

Matrix3x2F tiltMatrix = Matrix3x2F::Rotation(-8);

這是旋轉-8 度,負號表示逆時針旋轉。在內部迴圈後 x、 y 和 z 已計算出,將那變換應用於的 z 和 y 值,猶如他們是 x 和 y 的值:

 

D2D1_POINT_2F tiltedPoint =
     tiltMatrix.TransformPoint(Point2F(z, y));
z = tiltedPoint.x;
y = tiltedPoint.y;

圖 5 顯示你會看到的。


圖 5 傾斜的 CircularText 顯示

這是更好,但它仍然有問題。醜陋的事情發生時幾何重疊,並沒有任何跡象表明哪部分的幾何形狀是接近你,哪個是進一步走。盯著它,和您可能會遇到一些觀點轉變。

應用於此物件的 3D 轉換的能力仍然,表明它可能也很容易將物件繞 Y 軸旋轉 — 它是。如果你想像查看起源從積極的 Y 軸,你就會看到 X 和 Z 軸方向相同的方式在一個二維坐標系中的 X 和 Y 軸。

SpinningCircularText 專案實現兩個旋轉變換旋轉文本和傾斜。以前在 CreateWindowSizeDependentResources 中的所有計算邏輯已被移入的更新方法。3D 點被旋轉兩次:一次繞 X 軸的基礎經過的時間,然後圍繞 Y 軸根據掃一根手指在螢幕上的使用者。此更新方法所示圖 6

圖 6 SpinningCircularText 的更新方法

void SpinningCircularTextRenderer::Update(DX::StepTimer const& timer)
{
  // Get window size and geometry size
  Windows::Foundation::Size logicalSize = m_deviceResources->GetLogicalSize();
  float geometryWidth = m_geometryBounds.right - m_geometryBounds.left;
  float geometryHeight = m_geometryBounds.bottom - m_geometryBounds.top;
  // Calculate a few factors for converting 2D to 3D
  float radius = logicalSize.Width / 2 - 50;
  float circumference = 2 * 3.14159f * radius;
  float scale = circumference / geometryWidth;
  float height = scale * geometryHeight;
  // Calculate rotation matrix
  float rotateAngle = -360 * float(fmod(timer.GetTotalSeconds(), 10)) / 10;
  Matrix3x2F rotateMatrix = Matrix3x2F::Rotation(rotateAngle);
  // Calculate tilt matrix
  Matrix3x2F tiltMatrix = Matrix3x2F::Rotation(m_tiltAngle);
  for (size_t polygonIndex = 0; polygonIndex < m_srcPolygons.size(); polygonIndex++)
  {
    const Polygon& srcPolygon = m_srcPolygons.at(polygonIndex);
    Polygon& dstPolygon = m_dstPolygons.at(polygonIndex);
    for (size_t pointIndex = 0; pointIndex < srcPolygon.Points.size(); pointIndex++)
    {
      const D2D1_POINT_2F pt = srcPolygon.Points.at(pointIndex);
      float radians = 2 * 3.14159f * (pt.x - m_geometryBounds.left) / geometryWidth;
      float x = radius * sin(radians);
      float z = radius * cos(radians);
      float y = height * ((pt.y - m_geometryBounds.top) / geometryHeight - 0.5f);
      // Apply rotation to X and Z
      D2D1_POINT_2F rotatedPoint = rotateMatrix.TransformPoint(Point2F(x, z));
      x = rotatedPoint.x;
      z = rotatedPoint.y;
      // Apply tilt to Y and Z
      D2D1_POINT_2F tiltedPoint = tiltMatrix.TransformPoint(Point2F(y, z));
      y = tiltedPoint.x;
      z = tiltedPoint.y;
      dstPolygon.Points.at(pointIndex) = Point2F(x, y);
    }
  }
  // Create path geometry from Polygon collection
  DX::ThrowIfFailed(
    Polygon::CreateGeometry(m_deviceResources->GetD2DFactory(),
    m_dstPolygons,
    &m_pathGeometry)
    );
    // Update FPS display text
    uint32 fps = timer.GetFramesPerSecond();
    m_text = (fps > 0) ?
std::to_wstring(fps) + L" FPS" : L" - FPS";
}

它是知名的複合矩陣變換相當於矩陣乘法和因為矩陣乘法不是交換,也不是複合變換。請嘗試切換周圍的傾斜應用和旋轉變換的不同的效果 (您可能其實喜歡的)。

在創建時的 SpinningCircularText 程式,我改編由Visual Studio範本創建 SpinningCircularTextRenderer 類中,創建的 SampleFpsTextRenderer 類,但離開了呈現速率的顯示。這使您可以看到性能有多糟糕。在我 Surface Pro,我看到每 25 在偵錯模式下,指示代碼不跟視頻顯示器的刷新率 (FPS) 第二個圖的幀。

如果你不喜歡的表演,我恐怕有一些壞消息:我要讓它甚至更糟。

從背景中分離前景色

3D 的路徑幾何方法的最大問題是重疊區域的影響。是它可以避免那些重疊嗎?此程式正試圖繪製的圖像不是那麼複雜的。在任何時間,有的文本的一部分和其餘文本,背視圖前視圖和前視圖應始終顯示的背視圖之上。如果它是可能的路徑幾何圖形分成兩個路徑幾何圖形 — 一為背景和前景的一個 — 你可以呈現單獨的 FillGeometry 調用這些路徑幾何圖形,因此前景將背景之上。這些兩個路徑幾何圖形甚至可以使用不同的畫筆呈現。

考慮通過 GetGlyphRunOutline 方法創建的原始路徑幾何圖形。這就是只是平面的二維路徑幾何圖形運動佔領的矩形區域。最終,那幾何的一半顯示在前景,而另一半顯示在背景中。但獲得的多邊形物件的時候,就太晚,使那什麼像計算易用性的拆分。

相反,原始路徑幾何圖形需要之前獲得的多邊形物件的一半被打碎。這個斷裂是依賴的旋轉角度,這意味著更多的邏輯必須將移入的更新方法。

原始路徑幾何圖形可以拆分在一半兩個調用 CombineWithGeometry 方法。此方法將以各種方式使第三幾何兩個幾何圖形結合在一起。組合在一起的兩個幾何圖形的描述文本輪廓的原始路徑幾何圖形和矩形幾何形狀,它定義的路徑幾何圖形的一個子集。此子集將出現在前景色或背景,旋轉角度。

例如,如果旋轉角度為 0,使矩形幾何形狀必須覆蓋的文本輪廓路徑幾何圖形的中央一半。這是出現在前臺的原始幾何的一部分。在 D2D1_COMBINE_MODE_INTERSECT 模式下調用 CombineWithGeometry 返回僅由組成的該中心的地區,雖然調用 CombineWithGeometry 與 D2D1_COMBINE_MODE_EXCLUDE 獲取路徑的其餘部分幾何形狀的路徑幾何圖形 — 左側和右側的部分。這些兩個路徑幾何圖形然後可以轉換為多邊形物件分別為座標,其次是轉換回呈現為單獨的路徑幾何圖形的操縱。

這種邏輯是一個名為 OccludedCircularText,通過使用不同的畫筆填充兩個幾何圖形實現 Render 方法,如中所示的專案的一部分圖 7


圖 7 OccludedCircularText 顯示

現在更加明顯的是,什麼是在前景和背景是什麼。然而,這麼多計算已被移動到更新方法性能是很差。

在傳統 2D 程式設計環境中,我將已經用盡我掌握了所有 2D 程式設計的工具,現在被困這可怕的性能。Direct2D,然而,提供一種方法來呈現幾何簡化邏輯並極大地提高了性能。此解決方案使使用的最基本的 2D 多邊形 — 這是一個還在 3D 程式設計中發揮著重要作用的多邊形。

當然,我說的卑微的三角形。

Charles Petzold 是 MSDN 雜誌和作者的"程式設計視窗,第 6 版"長期貢獻 (O'Reilly 媒體,2012年),一本關於編寫應用程式的 Windows 8 書。 他的網站是 charlespetzold.com

由於以下的技術專家對本文的審閱:JimGalasyn (Microsoft)