開発^3

Web開発、宇宙開発、ゲーム開発の3種類についてつらつらと

スクリーン座標からワールド座標を求める

D3DXVec3Unproject関数

引数としてスクリーン座標(x, y, z)、ワールド行列、ビュー行列、射影行列、ビューポートを渡すと
モデル座標(x, y, z)を返してくれるという便利関数


ただし、スクリーンに投影された時点でzの値は意味がなくなってしまっている為、
モデル座標は1点として求まるわけではなく、線として求まる。


ためしに使ってみた。

ワールド行列:単位行列
ビュー行列:(0, 0, -50)から(0, 0, 0)を見つめる。上は(0, 1, 0)
プロジェクション行列:視野角60度、アスペクト比1.33(640 / 480)、zの範囲は1.0〜100.0
ビューポート:640 x 480
スクリーン座標:187, 141

# オブジェクトを指定した座標に表示したい場合、ワールド変換行列で移動させるだろうし、
# ワールド変換行列は単位行列にしておくのが良いと思ふ。


これらの値をD3DXVec3Unprojectに放り込む。その際、スクリーン座標としてzも指定する。
スクリーンz = 0.0 → ワールドz = 視推体の前面のz(1.0)
スクリーンz = 1.0 → ワールドz = 視推体の背面のz(100.0)
としたモデル座標が求まる。


これは直線なので、ワールドのzが指定されればx, yは比例で求められる。
よって、z = 0.0のモデル座標をv0、z = 1.0のモデル座標をv1とした場合、
z = targetZにおけるモデル座標vRは以下の通り。

diffZ = v1.z - v0.z

vR.x = (v1.x - v0.x) / diffZ * targetZ + v0.x
vR.y = (v1.y - v0.y) / diffZ * targetZ + v0.y
vR.z = targetZ

・・・あれ。書いてて思ったけど、カメラの方向がZ軸に沿ってないとこれ使えない・・・か?
ま、まぁ固定カメラの2D部分で使うロジックだから良いか。
カメラぐるぐる回す時にはここで求めた座標にオブジェクト固定すれば良いんだし。

D3DXVec3Unprojectを自作

さて、D3DXVec3Unprojectの中でやっている事は(ソースは見れないけど)いたって単純。
通常モデル座標をスクリーン座標に変換する場合、以下のように変換される。

スクリーン座標 = [モデル座標][ワールド行列][ビュー行列][射影行列][ビューポート行列]

# ワールド行列、ビュー行列、射影行列に関してはマニュアルにもあるとおり。
# ビューポート行列は射影行列によって立方体に変換された-1.0〜1.0の座標をスクリーン上の
# 0〜640、0〜480(など)に変換する行列なので、以下のような形になる。
#
# [[Width / 2 0 0 0] [0 -Height / 2 0 0] [0 0 1 0][Width / 2 Height / 2 0 1]]
# ためしにワールド、ビュー、射影行列を全て単位行列とすることで、
# (0.1, 0.1)に存在するモデルが(352, 216)にレンダリングされることが確認できる。


スクリーン座標をモデル座標に変換する場合、単純に上記の逆処理を行う。

モデル座標 = [スクリーン座標][ビューポート行列]-1[射影行列]-1[ビュー行列]-1[ワールド行列]-1

D3DXMATRIXはD3DVECTOR3との*演算子オーバーロードしていないようなので、
全行を同じにした行列を作って実際に計算させてみた。

D3DXMATRIX mViewPort;
D3DXMatrixIdentity(&mViewPort);
mViewPort._11 = WIDTH / 2;
mViewPort._41 = WIDTH / 2;
mViewPort._22 = -HEIGHT / 2;
mViewPort._42 = HEIGHT / 2;
mViewPort._33 = 1.0f;
mViewPort._44 = 1.0f;

D3DXMATRIX mInvView, mInvProj, mInvViewPort;
D3DXMatrixInverse(&mInvView, NULL, &mView);
D3DXMatrixInverse(&mInvProj, NULL, &mProj);
D3DXMatrixInverse(&mInvViewPort, NULL, &mViewPort);

D3DXMATRIX result;
result._11 = result._21 = result._31 = result._41 = 187;
result._12 = result._22 = result._32 = result._42 = 141;
result._13 = result._23 = result._33 = result._43 = 0.0;
result._14 = result._24 = result._34 = result._44 = 1;

D3DXMATRIX mBase0 = result * mInvViewPort * mInvProj * mInvView;

この場合は以下のようにwで割ってワールド座標を求める。
UnProject互換にするならInvWorldも加えてぷりーず。

v0.x = mBase0._11 / mBase0._14
v0.y = mBase0._12 / mBase0._14
v0.z = mBase0._13 / mBase0._14

参考サイト
http://homepage2.nifty.com/skimp-studio/htm/crawl/1_9_transform4.htm
http://capa.jugem.cc/?eid=144
http://sorceryforce.com/cbbs/cbbs.cgi?mode=al2&mo=481&namber=474&space=60&rev=1&page=0&no=0