2022年8月13日土曜日

カメラを動かす

もしかしたら、3Dのプログラムで一番最初に用意しないといけなかったものかもしれない。
今まで見てきたサンプルプログラムでもカメラクラスが存在するものもあり、いろいろな機能を持っていた。
自分でもそのうち用意しなくては、とは思っていた。

カメラクラスを作る


そもそもカメラってなんだ。
3Dのプログラムを作る時に出てくるのが、World、View、Projectionの行列。
この行列のViewがカメラではないかと思う。Projectionはどうなんだろ?アスペクト比を出すのにスクリーンの幅と高さが必要になる。この点が、カメラに入らない感じがするんだけど、視野角とZの描画範囲はカメラのような気もするがとりあえず含めないでおく。
具体的にどんな機能が必要になるかもわからないので、最低限コントローラの左スティックで、XZ平面を移動、右スティックで横(Y軸)回転、縦(XZ軸)回転、縦(Y軸)移動ができるようにする。

XZ平面移動


横軸がXで奥行きがZなのでXZ平面になる。
カメラに保持する変数何を持ったらいいか考えたけど、View行列を作るために必要な3つのVectorを持つことにした。
1つは視点、もう1つは注視点、最後にカメラの上方向。

視点(eye) 


視点はカメラの位置。これを動かせば移動するだろう。

注視点(focus)


注視点はカメラが見る場所。移動する場合はこれも一緒に動かす必要がある。
動かさない場合はそこをずっと見ることになるので、例えば注視点を中心に回転した場合は見てる場所を中心に回れる。

カメラの上方向(up)


これはイマイチよくわからないんだけど、通常のカメラを持った状態であれば{0.0f,1.0f,0.0f}とすれば良い。これを回すと見てる映像が回転するのかと思ったけど回転はせず、ある一定値を超えると反転するだけだった。

移動

tV	Move( tF4 x, tF4 y, tF4 z ) {
	this->oEye.x += x ;
	this->oEye.y += y ;
	this->oEye.z += z ;
	this->oFocus.x += x ;
	this->oFocus.y += y ;
	this->oFocus.z += z ;
}

コントローラの左スティックのxとyの移動量をeyeとfocusのxとzに足してみると、とりあえず思い通りに動いた。yは後で上下移動のために用意したので0を渡している


横(Y軸)回転


次に周りを見渡せるようにY軸で回転させてみる。
右スティックのxの移動量を角度として利用する。

tV	Rotate( tF4 x ) {
	tDVector eye = this->Eye() ;
	tDVector focus = this->Focus() ;
	focus = DirectX::XMVectorSubtract( focus, eye ) ;
	tDVector q = DirectX::XMQuaternionRotationAxis( mDVector3( this->Up()), DirectX::XMConvertToRadians( x )) ;
	focus = DirectX::XMVector3Rotate( focus, q ) ;
	mDVectorToF3( this->oFocus, DirectX::XMVectorAdd( focus, eye )) ;
}

これで左右を見渡せるようになった。

だけど、ちょっとおかしい。
回転自体は問題ないけど、回転した後の移動がおかしくなる。
左スティックを前に倒したとき、向いている方向に進むのではなく、常に最初向いていた方向に進む。これは当たり前で、eye.xとeye.zにそのまま加算しているから。
向いた方向に対し進むようにするには現在の回転状況を加味してxとzを加算する必要がある。

移動修正版

tV	Move( tF4 x, tF4 y, tF4 z ) {
	tF4 r = ::atan2( this->oFocus.x - this->oEye.x, this->oFocus.z - this->oEye.z ) ;	// 目線の角度を求める
	tDVector qt = DirectX::XMQuaternionRotationAxis( mDVector3( this->Up()), r ) ;
	tDMatrix mq = DirectX::XMMatrixRotationQuaternion( qt ) ;
	tDFloat3 d ;
	mDVectorToF3( d, DirectX::XMVector3TransformCoord( mDVector3( x, y, z ), mq )) ;
	this->oEye.x += d.x ;
	this->oEye.y += d.y ;
	this->oEye.z += d.z ;
	this->oFocus.x += d.x ;
	this->oFocus.y += d.y ;
	this->oFocus.z += d.z ;
}

どうやって前に進ませるか調べてみたが、現在の角度を持ってそれを元に計算していた。
角度は持ちたくなくて、視点の注視点から角度が求められるはずと思い調べてみると、アークタンジェントを使ってラジアンが得られることがわかった。
求めた角度を使ってクォータニオン(qt)を作って、それをマトリックス(mq)にする。
肝の関数がXMVector3TransformCoordらしく、これにクォータニオンのマトリックスを渡すと、実際に動かす量が得られる。

縦(XZ軸)回転


tV	Rotate( tF4 x, tF4 y ) {
	tDVector eye = this->Eye() ;
	tDVector focus = this->Focus() ;
	tDVector up = this->Up() ;
	focus = DirectX::XMVectorSubtract( focus, eye ) ;
	tDVector axis = DirectX::XMVector3Normalize( DirectX::XMVector3Cross( focus, up )) ;
	tDVector qx = DirectX::XMQuaternionRotationAxis( axis, DirectX::XMConvertToRadians( y )) ;
	tDVector qy = DirectX::XMQuaternionRotationAxis( mDVector3( this->Up()), DirectX::XMConvertToRadians( x )) ;
	focus = DirectX::XMVector3Rotate( focus, DirectX::XMQuaternionMultiply( qx, qy )) ;
	mDVectorToF3( this->oFocus, DirectX::XMVectorAdd( focus, eye )) ;
}

回転の関数を改良して、右スティックの上下の移動量も渡せるようにした。
qxを算出するためのaxisを最初{ 1.0f, 0.0f, 0.0f }としていた。
これも最初の向きの場合は問題なく動くが、90度回転した状態では変な動きになり、180度回転した状態では上下が反対になってしまった。これも今の回転状態に合わせてx軸だけではなく、xz軸で動かす必要がある。この軸を作るのが、XMVector3Crossで外積の結果を正規化したものが軸になる。
出来上がったqxとqyをXMQuaternionMultiplyで合成して回転させる。

縦(Y軸)移動


縦移動は、Move関数のyにコントローラの左右のトリガーを渡すようにした。
	auto oLStick = this->oIDev.LStick() ;
	auto oRStick = this->oIDev.RStick() ;
	auto oLTrigger = this->oIDev.LTriger() ;
	auto oRTrigger = this->oIDev.RTriger() ;
	this->oCam.Move( oLStick.x, oLTrigger > 0.0f ? -oLTrigger : oRTrigger, oLStick.y ) ;
	this->oCam.Rotate( oRStick.x, oRStick.y ) ;
oIDevがコントローラで、oCamがカメラ。
トリガーは両方同時に押せてしまうので、左トリガーの値がある場合はマイナスの左トリガー、ない場合は右のトリガーの値を渡すようにした。

おっさんの後ろ姿

これでマイクラのクリエイティブモードみたいな感じで3D空間を自由に動かせて、見渡せるようになった。

0 件のコメント:

コメントを投稿