GPU の活用によりラスタライズはたいしたウエイトを占めておらず、CPU の仕事は
与えるデータの構築だけとなりました。それゆえ Direct2D はただの描画命令の
集まりにはなっていません。
ベクター描画の途中に介入し、ベクトルの情報のままデータを扱うことが可能です。
Direct2D が単なる GDI の置き換えではなく、可能性を秘めているのはこの Geometry
ではないかと思っています。
実際に ID2D1Geometry がどのようなものか興味あるので、機能を 1つ 1つ追いかけて
みました。
● Path の作成
Path の作成は下記の通り。
ID2D1Factory* iFactory; ID2D1PathGeometry* iPath; iFactory->CreatePathGeometry( &iPath ); ID2D1GeometrySink* isink; iPath->Open( &isink ); isink->SetFillMode( D2D1_FILL_MODE_WINDING ); isink->BeginFigure( startpos, D2D1_FIGURE_BEGIN_FILLED ); // = SVG M isink->AddLine( pos1 ); // = SVG L isink->AddBezier( D2D1::BezierSegment( c2, c3, c4 ) ); // = SVG C ... isink->EndFigure( D2D1_FIGURE_END_CLOSED ); // = SVG Z isink->Close();
ID2D1GeometrySink::Close() (ID2D1SimplifiedGeometrySink::Close()) は
SVG 等の Z コマンド相当かと思っていたら EndFigure( D2D1_FIGURE_END_CLOSED )
の方でした。
D2D1_FIGURE_END_OPEN そのまま D2D1_FIGURE_END_CLOSED BeginFigure() の位置に直線で接続(閉じたパスにする)
ID2D1GeometrySink::SetFillMode() では塗りつぶしのルールを決定します。
SVG 等の vector graphics にある evenodd / nonzero に相当します。
・D2D1_FILL_MODE_ALTERNATE
”evenodd” 相当。外からのレイが交差した回数で決定します。
交差回数が偶数なら外、奇数は中。パスの回転方向は影響しません。
・D2D1_FILL_MODE_WINDING
”nonzero” 相当。外からのレイが交差するとき、交わった線の方向を考慮します。
たとえば閉じているパスが仮に右回りなら +1、左回りなら -1、結果が 0 なら外。
つまりパスの回転方向を見ており、逆回転はくり抜き。
● Sink
Sink は値の追加、データをためることだけが出来るバッファです。
Add 系命令のみで Get はありません。
ID2D1Geometry では各オペレーションに対して受け皿である Sink を渡します。
最初にただの box を作ってみます。
ID2D1RectangleGeometry* iRect; iFactory->CreateRectangleGeometry( D2D1::RectF( 0.0f, 0.0f, 60.0f, 50.0f ), &iRect );
この box を元に、線を太らせる処理を行います。
もともとは描画時に太い幅 (strokeWidth) の線で描画するための変換ですが、
描画前に Path として受け取ることが出来るわけです。
// 変換結果を格納する ID2D1PathGeometry を作る ID2D1PathGeometry* iPath2; iFactory->CreatePathGeometry( &iPath2 ); // ID2D1PathGeometry の sink に変換結果を受け取る ID2D1GeometrySink* isink; iPath2->Open( &isink ); // iRect の変換結果を isink へ iRect->Widen( 10.0f, NULL, NULL, isink ); // strokeWidth= 10.0f isink->Close();
実際に描画したのがこちら。
↑左から順に
・元の Box
・元の Box を幅 10 の線で描画したもの
・幅 10 の描画を Path で受け取ったもの
・幅 10 で Path 化したものをさらに 幅 4 の線で描画したもの
// 描画コード iRenderTarget->SetTransform( D2D1::Matrix3x2F::Translation( 20.5f, 20.5f ) ); iRenderTarget->DrawGeometry( iRect, iBrush0 ); iRenderTarget->SetTransform( D2D1::Matrix3x2F::Translation( 120.5f, 20.5f ) ); iRenderTarget->DrawGeometry( iRect, iBrush0, 10.0f ); iRenderTarget->SetTransform( D2D1::Matrix3x2F::Translation( 220.5f, 20.5f ) ); iRenderTarget->DrawGeometry( iPath2, iBrush0 ); iRenderTarget->SetTransform( D2D1::Matrix3x2F::Translation( 320.5f, 20.5f ) ); iRenderTarget->DrawGeometry( iPath2, iBrush0, 4.0f );
RenderTarget には直接描画可能な DrawRectangle() 命令があります。
Direct2D では内部的には全く同じように
(1) CreateRectangleGeometry() 等でプリミティブを作成
(2) Widen() など与えたオプションによって必要な変換を行う
(3) Tessellate() で ID2D1Mesh 化
(4) 最終的にすべて ID2D1RenderTarget::FillMesh()
という手順で描画しているだけかもしれません。この場合一番低レベルな描画は
FillMesh() になります。
ただし現在 FillMesh() での直接描画が成功しておらず、これらは予想の範囲を
超えておりません。
もし予想が合っているのなら、Geometry を扱うことでより柔軟なデータ管理ができます。
たとえば比較的 Mesh に近い状態のままキャッシュすることで、高速化や再利用が
可能となるでしょう。毎回 stroke を指定して Draw するよりも Geometry か Mesh
化しておいた方が高速だと思います。
●変換形状の読み出し
Geometry の中身は隠蔽されており、パスの座標値などデータを直接さわることが
できなくなっています。
ところが SDK には、テセレートされたアウトラインフォントの形状を取り出して立体化し、
3D ポリゴンとして描画するサンプルプログラム Interactive3dTextSample があります。
その仕組みはこうです。
(1) Sink 互換のインターフェースを自分で定義しておく
(2) Geometry の Tessellate() 等に渡して変換結果を受け取る
たとえば ID2D1Geometry::Tessellate() であれば、変換後の結果を格納するために
内部で ID2D1TessellationSink::AddTriangles() を呼び出しています。
互換インターフェースで AddTriangles() を定義しておけばトラップ可能となるわけです。
このように、Tessellate() など Geometry で変換した座標値をアプリケーションで
使うことができます。
関連エントリ
・Direct2D (4) Direct2D の描画
・Direct2D (3) 互換性の検証と Vista で Direct2D
・Direct2D その(2) インターフェース
・Direct2D と Direct3D10.1 の下位互換