スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

DirectX9

世間様はDirectX10・11マンセーなのかもしれませんが扱い方がようやっと理解できて来たのでDirectX9です。
自分のイメージ先行なので間違ってはいるかもしれませんが今のところ動いてるんで大丈夫でしょう。

さて今日は四角形の自由変形に関して。
2D描画をGLUT気味にしたいと考えていて、単位四角形を自由変形したいと思っていたわけですが、
これを2次元のみでやるのは難しく、パースを考慮した射影変換をしてやる必要があるわけです。
2次元図形の射影変換にはxyに依存する値での「除算」がかかわってくるためになかなかに悩みました。
通常の行列演算では行列の割り算は定義されておらず、そもそも行列の成分はその変換時には
「定数」であるために、xyに依存する値を行列の成分にすることはできません。
できませんが、xyに依存する値を「頂点自体に」保存することが可能と思い立ちました。
そもそもデフォルトの射影変換はこの方法を用いているようなので大したことじゃないんだろうけど、
自分で気づくとテンションあがるよね、一人で勝手に。
また、四角形の自由変形は、もっと簡単に考えれば各座標に異なる変換を施したいという要望になるわけで。

今回はこの二つの方法を用いた四角形の自由変形でも。



//----------------------------------------------------------------------
//2次元射影を用いた方法
//----------------------------------------------------------------------

まず、DirectXを使って画面にオブジェクトを描画する際には大まかに分けて4つの座標があります。
オブジェクトの定義されたローカル座標、実際にオブジェクトを配置するワールド座標、
射影変換後にオブジェクトがやってくる射影座標、そして実際に表示されるスクリーン座標の4つがそれ。

ローカル座標系はつまるところ物体の形そのものを定義している座標系で、ここにはポリゴンの
形を定義する頂点が、一切の拡大・回転・平行移動をせずにおかれています。
この3次元座標をなんとかして変換し最終的に射影空間へと配置するのが当面の目標となります。

ワールド座標系はいわゆる世界そのもので、ローカル座標に定義された頂点はアフィン変換によって
この座標系の任意の位置に任意の大きさ・角度で配置されます。ここで重要なのが「任意の位置」。
基本的に3次元座標では(x,y,z)3つの座標しかありませんから、このままだと平行移動ができない。
なぜなら、この座標にかけることによって得られる写像は必ずいずれかの頂点に影響を受けてしまうから。
平行移動は頂点の位置に関係なくある一定の値を加算する必要があるわけです。
そこで同時座標と呼ばれる4つ目の座標wを定義し、ローカル空間内ではこれを1と決めます。
これを定義することによって平行移動ができるわけです。
が、今回のお話でのこの座標の役割の本番は射影変換の時に。

そしてやってくるのが射影空間。ワールド空間におかれたオブジェクトを写真のように切り取る変換です。
これの設定いかんでどう表示されるかが決まってくる上に、そのときのw座標に注意しなければならないわけで。
DirectXの場合には(1,1,0)と(-1,-1,1)を対角線とする空間内に変換されたオブジェクトのみが描画されることになります。
3Dの場合には、ワールド座標系は数学的な直交座標系ですから、ある平面に垂直にその方向を見た際に、
いわゆるパースがきかないために遠近感が発揮されないという事実があります。
これを解決するのが射影変換(透視射影変換)で、カメラからのオブジェクトの位置であるz座標に依存する形で
オブジェクトを縮小拡大できるわけです。2Dの場合もまぁ大体おんなじ感じで奥行きの位置を大体同一平面にしてやれば。
で、この射影変換ですが、これを施したところでその後すぐに二次元でのxy座標に変換される、
「わけではありません」。この時w座標には奥行きに比例した大きさの値が入っており、
最終的に出力される座標はxy座標をwで割って、wを1に正規化するという作業が挟まってくるわけです。

さて何が言いたかったか。それは、「出力座標は最後wで割られる」。ここがポイントで、
つまるところ変換の最後に割ってほしい値をwに入れておくことで座標を「個別に」操作できるわけです。
普通にやっていると、乗じられる行列はアフィン変換であるために平行な部分は平行のまま写像されますが、
この事実は言い換えればwの値を工夫することで「行列の乗算により単位四角形を自由な形に変換できる」といえます。

実装的には次のような感じ。参考はここ。
http://www.teu.ac.jp/clab/kondo/research/cadcgtext/ChapE/ChapE02.html

入力として4つの座標があったとして、次のような計算をします。
頂点バッファには(0,0)(1,1)を対角線とする単位正方形を格納しておきます。

//原点に正規化
float x2 =_x2-_x1, y2 =_y2-_y1, x3 =_x3-_x1, y3 =_y3-_y1, x4 =_x4-_x1, y4 =_y4-_y1;

//二次元射影の計算準備
float XY34 =x3*y4-x4*y3, XY23 =x2*y3-x3*y2 ,XY24 =x2*y4-x4*y2, XY234 =XY23+XY34-XY24;

//射影行列の作成
m_Point4Mat._11 =x2*XY34; m_Point4Mat._12 =y2*XY34; m_Point4Mat._14 =XY34-XY234;
m_Point4Mat._21 =x4*XY23; m_Point4Mat._22 =y4*XY23; m_Point4Mat._24 =XY23-XY234;
m_Point4Mat._44 =XY234;

//オフセットの作成
m_Mat._41 =_x1;
m_Mat._42 =_y1;

//最終的な変換行列を確定
m_Point4Mat *=m_Mat;

変換行列m_Point4Matは初めに単位行列に初期化されていたとして、上記のような行列を作成して
「ワールド変換行列」としてDirectXに登録してやることで自由変形が実装されます。
ビュー変換・射影変換は適当なのかデフォルトでも設定しといてください。


//----------------------------------------------------------------------
//頂点ブレンディングを用いた方法
//----------------------------------------------------------------------

英語のマニュアル直訳ですからMSのヘルプ作った人に非はないんでしょうが、「頂点ブレンド」より、
「頂点干渉」とか「行列合成」とかのほうがイメージがわくんだけど何か違うんだろうか。
さて頂点ブレンディングですが、これは各頂点にブレンディングウェイトと呼ばれる「重み」を
設定することで、複数の変換行列を一つの頂点に関して作用させることができます。
なんかよく知らないけど、人間の皮膚みたいなうにょっとした動きやリップシンクなどの
柔らかさを表現する手法として用いられるのが主要な用途っぽいです。

こちらは上みたいに長い話は全くいりません。各頂点に複数の行列をかけられるのだから、
4つの行列を登録しておいて、各頂点のブレンディングウェイトを
それぞれ一個ずつ使うようにしとけばいいのです。頂点宣言変えるだけだし簡単ですな。


//----------------------------------------------------------------------
//まとめ
//----------------------------------------------------------------------

さて比較。おそらく実行速度面でいうと後者のが早い気がする。なぜなら浮動小数点計算がそんなにないから。
比べて前者の方法は行列を作成するためにいろいろ計算が必要なために効率的ではないかも。

が、一番見た目に決定的なのはテクスチャのゆがみ。
前者の方法ですと、結局のところ本来設定されているはずの奥行きを、
指定された4頂点と単位正方形の存在する面を利用することでxy座標から求めることで、
奥行き0のままパースを聞かせている感じなため、テクスチャもいい感じにゆがむ。
しかし、後者の方法だと分割された三角形単位で変換しているようなものなので、
4頂点の対角線の中心がテクスチャの中心にならないというデメリットがある。

どっちもどっちってことですな。素直に頂点を書き込みなおすかシステムメモリから送信したほうがいいかも。
実際に比べてないし、そもそももっと賢い方法があるかもしれないですのでなんかあったら教えてくださいませ。






スポンサーサイト

Comment

コメントの投稿

Comment
管理者にだけ表示を許可する
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。