ながらく更新を止めてしまいまして、大変お待たせしました。
11月中旬から引っ越し関係で忙しくてこちらのページの更新をストップしていました。
現在はだいぶ片付き、新しい場所にも徐々に慣れてきたところです。
さて、本題を進めます。
見える虚像と見えない実体
見えるからといって、それが本当に触れるものなのかわかりません。
虚像と言い切った時点で、それは触れないものなのです。
見えないものが実体をもつというのも妙な話ですが、コンピュータグラフィックスにおいてはあり得ます。
計算上、そこに衝突物体は存在するのです。
…変な始まり方ですみません。
このタイトルですが、今回の機能追加の指針の一つなのです。
ここまで SimpleConverter で表示してきたものはすべて見える虚像なんです。
行列指定で動かすことはできますが、物理演算の対象には含まれていませんでした。
これから物理演算ライブラリ Bullet Physics を導入して、物理演算を行えるようにするのですが
そこで追加する物理演算の対象が「見えない実体」というものなのです。
「虚像」と「実体」両者を結び付ける「アタッチ」という操作
これを実現できるようにするのが、次の SimpleConverter への機能要求となります。
Bullet 入門編の 039話 でさんざん見てきたのが btCollisionShape を継承している衝突形状です。
そう、これが見えない実体の正体、定義、そのままです。
クラスの設計もなにも、既に Bullet が用意しているクラスをそのまま使うので何の作業もいりません。
これを現在の SimpleConverter 内で扱っている MDLData (モデルデータクラス)の各要素にアタッチできれば良いのです。
これから以下に記述することは、シンプルスター本人への作業メモだと思ってください。
かなーりつまらないものなので読む必要はありません。
ではまず、モデルに一つしかシェイプをアタッチできない場合を考えてみます。
麻雀牌の場合は問題は起こりません。
モデルの中にはオブジェクトが一つ、素材は均質で、衝突形状はボックスの剛体ですから。
同じく一つのシェイプで剛体と括れる点棒やトンシーマットも大丈夫でしょう。
ただし、人間の手はそうはいきません。
指の関節にそれぞれシェイプを設定する場合、アタッチ先はボーンになる感じでしょうか。
オブジェクトに対してもアタッチできないとうまくないケースも考えられます。
ロボットアームのように複数のオブジェクトで構成されているモデルの動きとかそうです。
ふむ、となると動きに関してはモデル、オブジェクト、ボーンにアタッチできれば事足りる感じですね。
頂点にアタッチすることも考えましたが、頂点アニメが未実装な上、オーバーヘッドが気になります。
その2点から実装は必要性に迫られるまで見送ることにします。
アタッチするものについては、Bullet のシェイプ、剛体、ソフトボディ、ソフトボディの頂点、Constraintといったところでしょう。
次に当たり判定に関してはモデル、オブジェクト、プリミティブ、ボーン、頂点に対して
Bullet のシェイプをアタッチ出来るようになる必要があると思います。
アタッチ先を線で結んでみると、こんなイメージでしょうか?
こうしたアタッチ編集操作がどんな操作になるのか想像してみましょう。
まず、見えない実体の作成作業が最初に必要になります。
3D空間の指定の位置に作成するのですが
基本的にはアタッチ先の見える虚像にフィットします。
となるとアタッチ先のモデル、オブジェクト、ボーンを選択することができる必要がありますね。
もっと細かく言うと、プリミティブや頂点単位でも選択出来る必要があります。
いきなりなんでも選択できるのは、それはそれでややこしいですね。
Blenderのように、Edit モードに切り替わるとすっきりしそうです。
ならば、モデル単位選択モード、オブジェクト(ボーンも)単位選択モード
プリミティブと頂点単位選択モードを設けて、それぞれのモード時は
それぞれの単位でしか選択できないようにします。
いや、それはそれで不便ですね。
たとえば、頂点と別のモデルを一緒に選択したい場合もあるかもしれません。
ビットフラグのように、モードにも重なりを持たせるようにしましょう。
Blender に習って、Tab キーがショートカットで、ほかツールバー、メニュー項目から切り替えられるようにします。
選択ですが、左クリックで選択、右クリックでコンテキストメニューの表示としたいと思います。
Blender の右クリック選択には最後まで慣れることはありませんでしたので…。
選択できるようになったら右クリックメニュー、またはショートカットキーにて
「見えない実体をアタッチ」を選択できるようにします。
アタッチできるものにもたくさんあるので、最初は適当にフィットする実体を設定するようにします。
アタッチ先の虚像が何なのかを解析して、これに見合うものを自動でアタッチするのです。
もちろん複数選択も可とします。オブジェクト、ボーン単位選択で全選択して実体をフィットさせるだけで
ほぼ完全なラグドールが完成するのが基本仕様とします。
見えない実体を追加した後、これらを確認できないことには不便です。
最初は虚像だけ表示されていますが、ボーンを描画するボタン、見えない実体を描画するボタン
を用意したいと思います。
描画できたら、これらも選択できるようになります。
選択対象を移動、回転、拡大縮小することもできるようにします。
アタッチ先を別のオブジェクトに切り替えたり出来るようにします。
(ということで、マスク選択機能を用意します。どちらも選択できるモードと
実体と虚像を選択分けするモードのことです)
見えない実体が複数の対象にアタッチすることはできませんが、複数の見えない実体が
一つの虚像の要素にアタッチするのは問題ないものとします。
球体、ボックス、カプセルなどシェイプも後でどうにでも変えられるようにします。
また、見えない実体を複数選択して、これらのパラメータを一度に変更できるようにします。
たとえば麻雀牌の質量や、表面の摩擦係数など、同じ値に揃えたいときに
いちいち一個一個設定するのは面倒なので…。
実装よりの話ですが、実体が持つパラメータをすべて MDLData に持たせるのは少々もったいない
ディフォルト値からずれたものだけその値を保存するようにするのが良いかと思います。
その見えない実体を再現する最低限の要素の情報だけ持つようにするのです。
3Dカーソルを用意して、任意の場所に実体を作成できるようにします。
あとは上記と同じ、アタッチ先を選択してアタッチなどができるようになります。
シェイプについて詰めていますが、Constraint のアタッチや種類の決定
制限角度などの設定もできるようにします。
剛体、ソフトボディ、ソフトボディに至っては頂点も選択、アタッチできるようにします。
ソフトボディの頂点選択については、これもモードで分けるようにします。
編集操作がだいたい完了したら、今度は物理演算を行いならが再生します。
何度も再生しては、編集してを繰り返すのでコマ割りで進める、戻すを行えるようにして
再生ボタン、AB再生などで動きをこまめにチェックしてパラメータを調節します。
再生中にいじれるパラメータはすべてその場で反映されるのが重要ですね。
どんなパラメータもスライドバーにて変更できて(いつもスライドバーではない)
ステップや最大値、最小値なども動的に変更できるようにします。(スライドの位置により)
目的の動き、制約と拘束を確認して編集が完了したら、これを保存します。
アタッチするデータが何なのか、内部でどのようにデータを取り扱うのか考えなければなりません。
また、操作は一つ一つアンドゥ、リドゥできるようにします。
操作はそれぞれコマンドとして管理出来る必要がありそうです。
とまぁ、こんな感じで操作できるように機能を追加してみましょう。
どんな操作も、コマンドとして扱います。
たとえばシェイプを作成したとします。
何のシェイプを、どこにどんな姿勢で作成する。
この情報をコマンドとして、コマンダー(オペレータ)に渡します。
アンドゥスタックに、このコマンドが積まれ、Ctrl + Z などのアンドゥ操作で
そのコマンドが逆戻りします。(作成の反対の削除が実行されるのです。)
こんどはリドゥスタックにシェイプ作成コマンドが積まれ、Ctrl + Y などでリドゥが実行され
先ほどのシェイプ追加操作が実行されます。
メモリは十分に節約する必要があるので、このスタックは無限に積むことはできませんが
数十から百コマンドくらいメモリに記憶して、それでも残したい場合はファイルに書き出して
必要になったら読み込むようにします。
話はコマンドの定義に戻って、とにかく何でも基底コマンドクラスを継承し
扱う側は、基底クラスの純粋仮想関数のUndo, Redo だけ実行すればよいものとします。
コマンドごとにクラスが用意されるということです。
たとえば、シェイプ作成コマンド、シェイプ移動コマンド(拡大縮小、回転を含む)など
それぞれのコマンドのメンバには、シェイプの種類、位置と姿勢、移動コマンドなら姿勢情報だけとか
そんな感じです。
やりたい操作が具体的になったら、それを実行するコマンドを定義してアンドゥスタックに積むようにします。
アンドゥ、リドゥに関連してですが、対象を選択するという操作もアンドゥ、リドゥできるようにします。
こういったモデリングツール系は選択操作が命、複数選択で選択が途中で解除されたら
最初からやり直しなんて馬鹿げています。解除前に戻ってから作業続行できるのが普通です。
操作が複数同時に起こるものは、その複数の単位で一回のアンドゥ、リドゥで行えるようにします。
あとは、選択の仕様ですね。
3次元空間の物体を2D表示で選択するということは、つまり3Dを投影している2D座標のエリアを
表示物体ごとに把握する必要があります。
重なって表示されている場合は、手前にあるものが最初に選択され、同じ所で何度もクリックすると
奥行きのそれぞれの物体に選択状態が移行します。
選択した場合、そのオブジェクトのワイヤフレームがオレンジ色に輝く仕様です。
はい、ということで最初は選択操作できるようになること
選択操作がアンドゥ・リドゥできるようになるというのを実装してみたいと思います。
チケットを切りました、対応します。
選択操作の裏で何をやるか考えてみました。
3Dの対象を2Dに投影して、そのエリアをクリックしたら…という操作なわけで
簡単に考えると次のリンクのような、モデルのシルエットに識別情報を仕込む方法が思い浮かびます。
この方法を採用することにしたいのですが、複数のレンダリングターゲットを用意して、クライアント座標から
色情報を取ってくるということを実現しなければなりません。
DirectX 10 のこの辺の操作を調べるところから進めます。
DirectX 10 についてのドキュメントは 031話で入手しているので、これを参考にします。
031話で一読はしていたのですが、もう一度 ID3D10Device::CreateRenderTargetView について読んでみます。
読み読み…
なるほど、既知の解像度からあらかじめ 2Dリソースを作成しておき、そのリソースから
レンダリングターゲットビュー、深度ステンシルビューを作して、出力ステージにバインドしろということですか。
この2Dリソースの管理とか、たとえばリサイズ処理にて全部リサイズしておいてほしいものです。
SimpleDX10 にすべてまかせたいと思うので SimpleDX10 に機能追加のチケットを切ります。
…とその前に、レンダリングターゲットから色情報を取ってくるやり方を確認したいです。
どうやるかというと…
Windows DirectX Graphics Documentation の 「リソース データのコピーとアクセス (Direct3D 10)」
を読めば一発で判明します。以下↓、そのページ(クリックで拡大)
書いてあることを要約すると…
テクスチャリソース作成時に CPU からアクセスできるよう D3D10_USAGE_STAGING フラグを設定し
ID3D10Device::CopySubresourceRegion 関数で、目的の矩形領域の結果を上記で作成したリソースに GPU からコピーし
パフォーマンスが落ちないよう 2 フレーム後に ID3D10Texture2D::Map を呼びだして CPU からリソースの情報を取り出し
取り出し終えたら ID3D10Texture2D::Unmap を即座に呼びだして事なきを得てください。
ということだそうです。
つまり毎フレーム描画結果をシステム側で利用するなら、書き込み先のテクスチャリソースを2枚用意し
交互にコピーするループをおこなわなければなりません。
関連して、調べていてわかったことに、頂点バッファについても USAGE に D3D10_USAGE_DYNAMIC フラグを渡せば
CPU から頂点の位置をいじれるので、それで頂点アニメが実現できます。
(棚ぼただなぁ)
調べれば調べるほど SimpleDX10 の中に隠ぺいするのは難しそうです。
ユーザーにすべて任せるのが良さそう。
上級者向けにデバイスに加えてスワップチェインを公開します。
…お?スワップチェインを確認していたところ
IDXGISwapChain::GetContainingOutput → IDXGIOutput::GetDisplaySurfaceData → IDXGISurface::Map
で、出力サーフェイスのデータを取り出せることが判明。
(当初の目的である、クライアント座標の色情報の取得はこちらで解決するようです。)
ということで、SimpleDX10 にはスワップチェインを公開する関数と
レンダリングターゲット、深度ステンシル、深度ステンシルビューを公開する関数
レンダリングターゲットをディフォルト設定に戻す関数を追加する変更を行います。
チケットを切りました。対応中…
対応しました。
ver 1.05r1 に更新。 ダウンロード にて入手できます。
さて、SimpleConverter に上記の ver1.05r1 を組み込んで
ひとまずクリックした点の色情報を取得し、デバッグ出力してみます。
っと、おおーい?、IDXGISurface の作成に失敗するのですけど…
ならば、その前に SwapChain の GetBuffer で ID3D10Texture2D を取り出す方で試してみます。
確認中…うーん、Map に失敗しました。
Descを取得して詳細をチェックしてみます。
ああ、やっぱり D3D10_USAGE_DEFAULT な上、CPUAccessFlags = 0 でした。
これでは Map には失敗しますね。
ということで、出力した色を取ってくるには手元でテクスチャリソースを作成して
コピーして2フレーム後に Map するということで OK ?
試してみます。
できました!
以下にやり方をまとめます。
まずは、テクスチャリソースをデバイスに確保しておきます。
レンダリングターゲットと設定を合わせますので、こんな感じの作り方↓
気を付けるのがハイライトしている部分、バインドフラグには 0 を設定すること!
また、リサイズ関数にしたのは、レンダリングターゲットをリサイズするたびに
こちらも作り直さなければならないためです。
(解像度合わないとコピーに失敗しますので…)
続いて、マウス左ボタン押し上げ時に次の関数を呼びます。
コメントにある通り、コピー命令の後タイマーを起動するのがポイントです。
タイマー起動後は、タイマーイベントにて次のように処理します。
デバッグ用に選択したマウスポインタの位置の色を出力するようにしています。
結果はこんな感じ↓
ひとまずこれでクリックした位置のレンダリングターゲットの色情報を取ってくることができました。
あとは選択モード切り替えの機構を用意し、モードごとに特別に用意したレンダリングターゲットに
インデックス情報をべた塗りし、これを上記の方法で拾って選択対象を処理するという流れで動作確認が取れます。
残りの作業は DirectX 10 固有の決まりごと等を気にすることは無いので、特に悩むことはありません。
さっさと動作確認できるところまで実装してみます。
CPUアクセスバッファと同じように2Dリソースを作成し(CPUからはアクセスできないがレンダリングターゲットとしてバインドできるやつ)
そのリソースを使ってレンダリングターゲットを作成します。
あとは、キーボードでレンダリングターゲットを切り替えられるデバッグ機能を入れてみました。
さて、モデルのシルエットを写すようにべた塗りシェーダなるものを作って、描画しようと思います。
いろいろ悩んだ結果、エフェクトファイルにテクニックを新設して対応します。
つまりテクニックを切り替えることでべた塗りシェーダを実現しようというものです。
(機能の確認までできました。)
べた塗りシェーダ用(Daub)テクニックを新設したエフェクトファイルがコレです↓
ソフトウェア側で、インデックスを小分けにして Draw し、面のインデックスをカラーに渡したり
頂点のインデックスを同じく IndexInfoColor に渡すことで、面選択、頂点選択が行えるようになるという算段です。
一応頂点インデックス情報をべた塗り出来るようになりましたが
ここでインデックス情報が何なのかについて迷うことになりました。
選択対象リストにはモデル、オブジェクト、ボーン、プリミティブ、面、頂点といった形でリストアップしたいところ。
面IDや頂点インデックスについてはプリミティブ内での順番で良いかもしれませんが、モデルとモデル名
ボーンやオブジェクトとそれらの名前を一意に決定する番号というものが欲しくなります。
作りますか…ってコード確認していたら作っていたし。
ほんの少しの間コードから離れているとすっかり忘れてしまうのですね…。
ひとまずモデル内でオブジェクトとボーンフレームに一意のインデックスを振ってみました。
面のインデックスはプリミティブ内での順番とし、頂点のインデックスはその総面数*3となる形です。
頂点を頂点リストトポロジで描画すると点が1ピクセルになるわけですが、
簡単に選択できるように、この頂点のサイズを大きくしたい。
調べてみるとこれはポイントスプライトと呼ぶ技術で
DirectX10からはジオメトリシェーダで頑張ってくれとドキュメントにありました。
これも早速実装してみましょう。
で、ジオメトリシェーダのサンプル(ParticlesGS)がこちらです↓
これを読んで分からなかったら詰むらしいですが…
(にしても2005年のデモか…けっこう昔からジオメトリシェーダは用意されていたんですね。)
ドキュメントと合わせてなんとか理解できました。
ピクセルシェーダの入力構造体を用意して、そこに新しい頂点データを入れて
ジオメトリシェーダの決まりごととして、TriangleStream に Append するんです。
これで頂点が生成されたことになり、最後に RestartStrip で三角形ストリップのトポロジを指定します。
うまい具合に g_positions の配列を作成してあげればスプライトが完成するんですね。
ひとまず、ジオメトリシェーダで頂点を生成する方法を覚えました。
描画を行わずに計算だけ行うようなこともやってますね Advance~とかで…
こんな使い方もあるのか、覚えておこうと思います。
さて、現在目の前に抱えている問題はこちら↓
頂点リストトポロジの描画結果(原寸)
頂点の面積が 1pixel 四方しかなくて、マウスポインタからではとても選択できない…
調べてみたところ、頂点の大きさを指定して描画する方法は存在しないようです。(DirectX 10以降は…)
ということで、早速ジオメトリシェーダを使ってポイントスプライトにより
頂点のサイズを大きくしてみましょう。
やり方は、ジオメトリシェーダを次のように実装するだけ。(ハイライトした行の関数ね)
(サンプルがあるので、トライ&エラーを繰り返せばだれでもできます。)
書式などは、こちらを確認しました。
あと描画コード側から、頂点情報カラーと各種行列の情報を渡してあげなければなりませんね。
これを行った結果がこちら↓(クリックで原寸)
左から面のインデックスのべた塗り、ポイントスプライト、見える虚像です。
単純にポイントスプライトをやってしまうと、遠くの頂点の面積が小さくなるので
距離に依存せず常にビューポート内では頂点が 5 pixel 四方となるようにしました。
遠くなるほどスプライトをでかくする…これは、シェーダのコードを読めばわかるよね。
ちなみに、赤く表示される理由はインデックス情報がRから埋まっていくからです。
インデックスからカラー情報に変換するコードはこちら↓(泥臭い部分ですが理解できます?)
ビットマスク、シフトとか使っているので、言語に慣れていない人には読みづらいかも…
さてさて、まだ選択操作自体出来ていませんが44話はこのへんで区切らせていただきます。
次回は選択操作に続いて、コマンド単位によるアンドゥ、リドゥの部分をやる予定です。
お楽しみに!
2011/10/23 初記。
2011/11/06 更新。
2011/12/25 更新。
2012/01/09 更新。