SharpDX のサンプルは基本的にC#の Windows.Form を使います。
では WPF で SharpDX を使いたい場合はどうすれば良いのでしょうか?
実はサンプルをよく見ると WPF での使い方が示されています。
サンプルの使い方は 053話 SharpDX 使い方 で示しました。
プロジェクト名は WPFHost です。
ビルドして実行すると次の結果が得られます。
1秒間隔で固定された三角形の色が変化する様子が確認できます。
WPF についてはもう大丈夫かな?
ソースを読んだところ、CompositionTarget.Rendering イベントにハンドラを引っ掛けて
メッセージループを完成させています。(約16ms間隔の更新)
実装の詳細を見てみましょう。
以下にクラス図を示します。(クリックで拡大)
注目すべきは D3DImage クラスを継承して DX10ImageSource クラスを作成しているのと
Image クラスを継承して DPFCanvas クラスを作成している点です。
DPFCanvas の Source に DX10ImageSource を設定し、その Canvas コントロールを MainWindow に配置しています。
それぞれの実装コードを次に示します。
ImageCanvas がこちら↓
なるほど、解説はほとんどいらないですね。
目についた部分だけメモを残します。
フィールドの ActiveClients はシェアードポインタで言う参照カウンタと同じですね。
誰かが class DX10ImageSource のインスタンスをまだ利用しているならば正の値を取ります。
StartD3D(), EndD3D() メソッドにて利用されています。
(初めてのインスタンス化ならデバイスの初期化を行う、誰も参照しなくなったらデバイスを解放する、といった具合)
レンダリングした後に呼ばれる InvalidateD3DImage() メソッドについて
base.AddDirtyRect このメソッドで Canvas のクライアント領域全体を更新します。
(画面が全部汚れたよ、描き直す必要があるよと、システムに連絡しているのですね)
で、その StartD3D(), EndD3D() を見てみると
StartD3D() 関数内では D3DContext と D3DDevice の作成をしているのが伺えます。
EndD3D() 関数では、作ったものを逆順に Dispose していますね。(C++でいう delete です。)
気になったのが次の関数です。
RenderTarget が null じゃなかったら null を代入って、うーん C# は慣れないなぁ。
EndD3D() では Dispose してたのに…これ安全なんでしょうか?
とにかく引数の renderTarget を OpenShared か調べて、検査に合格したらデバイスを介して
テクスチャを作成、フィールドの RaenderTarget に設定しています。
using のくくりはレベル0の surface を基底クラスのバックバッファにセットしています。
(DirectX10 の Texture2D を受け取って、DirectX 9 のサーフェイスにキャストしてセット)
引数を DirectX10 から 11 にしたいときに触るくらいで、この関数、今は特に変更を加える必要はなさそう…
その他の関数はこのSetRenderTargetDX10関数内で使われています。
(テクスチャリソースからハンドルを取得したり、フォーマットを調べたり)
続いて、DPFCanvas の方を見てみましょう。
フィールドと初期化部分は特に述べることはないですね。
これらは確かに描画に必要だと思います。
xaml を使わずにコードからイベントハンドラの追加を書いていますね。
StartD3D(), EndD3D() 関数が気になるところですね、見てみましょう。
おお、ここで DX10ImageSource クラスを作成し何やらバッファ更新イベントにハンドラを追加して
Source に作成したその D3DSurface フィールドを設定しています。
EndD3D() は開放、クリアしかしていませんね。(Scene の Detach もあれば行う)
まだ詳細な初期化がつかめないですね。 CreateAndBindTargets 関数を見てみましょう。
Description を設定して RenderTarget, DepthStencil とそれらの View を作成していますね。
でここで D3DSurface.SetRenderTargetDX10 に RenderTarget を渡すというわけですか。納得です。
※リマインダ:既に書いたことをもう一度ここで書きます。
(とにかく引数の renderTarget を OpenShared か調べて、検査に合格したらデバイスを介して
テクスチャを作成、フィールドの RaenderTarget に設定しています。
using のくくりはレベル0の surface を基底クラスのバックバッファにセットしています。
DirectX10 の Texture2D を DirectX9 のサーフェイスにキャストして設定)
このクラスで重要なのが次の Render 関数です。
メンバ変数をいちいちローカル変数に代入している部分が妙だけど、やっていることは出力ステージの設定と
ビューポートの設定、レンダリングターゲットと深度ステンシルビューのクリアですね。
で Scene があるなら Update して Render させています。
最後に device.Flush() を呼んでいます。(ドキュメントによるとこの関数、結構重いらしい)
コードを読んではっきりしたのは、Scene を外から設定するだけで
後のことは良しなにやってくれる構成であるということでした。
ではその Scene というものをどうやって用意すれば良いのか見て行きましょう。
Scene を作ってキャンバスに設定出来ればそのシーンをレンダリングできるという理解までしました。
早速その Scene というものを見てみましょう。
単・純・明・快!
Attach で初期化(入力レイアウトとか、頂点バッファを作ったりする)
Detach で作ったものを Dispose する。
Update は単に覆い隠す色のアルファ値を変更するだけ。
Rander には DirectX10 お決まりのエフェクト、テクニック、パスループ→描画。
C++ で DirectX10 を触ったことがあれば簡単に理解できるはずです。
IScene という interface という C# のキーワードを使って Scene を
任意のものに交換できるようになっています。
新しい Scene を作って遊んでみましょう。
Scene.cs ファイルを複製して SceneCube.cs にリネームします。
頂点バッファを立方体の8点にし、インデックスバッファも作ってみましょう。
で作った SceneCube.cs を次に示します。
あ、シェーダー見たら透視投影行列使ってませんでしたね。
DrawIndexed で描画するだけでは立方体は描画できません。
しかし、頂点位置の奥行き方向の値を 0.0f〜1.0f に設定すれば平板として描画されるはずです。
例にならい、インデックスリストを使って描画してみました。
上記のコードで変更を加えた箇所をハイライトしました。
シーンの切り替えは MainWindows.xaml.cs にて次のように書きます。
これでシーンを切り替えは完了です。
実行結果を次に示します。
ここまでシーンの切り替え、インデックスリストを使った描画例を示しました。
WPF の 一種の Canvas コントロールがレンダリングターゲットであることがポイントです。
他の UI コントロール(例ではスライダーとボタン)と簡単に組み合わせる事ができるので
エディタ、ツールなど、ゲーム中のパラメータを調整するアプリを作成するのがとても簡単になりそうですね。
次回、Collada ファイルから読み込んだモデルデータを WPF + SharpDX で描画してみたいと思います。
ここまで確認できていることを使えば、そんなに難しいことではないと思っています。
それでは次回、お楽しみに!
2012/06/15 初記。