遊戲開發基礎(十二)

 

第十一章
用D3DXMatrixLookAtLH函數計算觀察矩陣(即取景變換矩陣),當在某一固定地點固定攝像機方位,該函數十分有用,但其用戶對於一個能夠根據用戶輸入做出響應的移動攝像機來說,就顯得力不從心,

用4個攝像機向量:右向量(right vector),上向量(up vector),觀察向量(look vector)以及位置向量(position vector)來定義攝像機相對於世界座標系的位置和朝向.這些向量實質上爲相對世界座標系描述的攝像機定義了一個局部座標系,由於右向量,上向量和觀察向量定義了攝像機在世界座標系中的朝向,有時也將這三個向量統稱爲方向向量(orientation vector)方向向量必須是標準正交的,如果一個向量集中的向量都彼此正交,且模均爲1,則稱該向量時標準正交的,引入這些約束的原因是在後面要將這些向量插入到一個矩陣的某些行中,以使該矩陣成爲標準正交矩陣(如果一個矩陣的行向量是標準正交的),標準正交矩陣的一個重要性質是其逆矩陣與其轉置矩陣相等

用4個向量來描述攝像機,就可對攝像機實施6種變換
#繞向量right的旋轉(俯仰,pitch)
#繞向量up的旋轉(偏航,yaw)
#繞向量look的旋轉(滾動,roll)
#沿向量right方向的掃視(strafe)
#沿向量up的方向的升降(fly)
#沿向量look的平動

通過上述6種運算,攝像機可沿3個軸平動已經繞3個軸轉動,即攝像機具有6個自由度

class Camera
{
public:
 //支持兩種攝像機模型 AIRCRAFT 允許在空間自由運動,具有6個自由度
 // LANDOBJECT 沿某些特定軸進行移動
 enum CameraType{LANDOBJECT,AIRCRAFT} ;
 
 Camera();
 Camera(CameraType cameraType) ;
 ~Camera();

 void strafe(flaot units);
 void fly(float units);
 void walk(float units);
  
 void pitch(float angle);
 void yaw(float angle);
 void roll(float angle);

 void getViewMatrix(D3DXMATRIX* V);
 void setCameraType(CameraType cameraType);
 void getPosition(D3DXVECTOR3* pos);
 void setPosition(D3DXVECTOR3* pos);

 void getRight(D3DXVECTOR3* right);
 void getUp(D3DXVECTOR3* up);
 void getLook(D3DXVECTOR3* look);
private:
 CameraType    _cameraType;
 D3DXVECTOR3   _right;
 D3DXVECTOR3   _up;
 D3DXVECTOR3   _look;
 D3DXVECTOR3   _pos;
};


計算取景變換矩陣,令向量 p = (px,py,pz),r = (rx,ry,rz),u = (ux,uy,uz),d = (dx,dy,dz)分別表示position,right,up和 look這4個向量

取景變換所解決的問題其實就是世界座標系中的物體在以攝像機爲中心的座標系中如果進行描述,等價於將世界座標系中的物體隨攝像機一起進行變換,以使攝像機座標系與世界座標系完全重合

所以希望變換矩陣V能夠實現:
#pV = (0,0,0) 矩陣V將攝像機移至世界座標系的原點
#rV = (1,0,0) 矩陣V將攝像機的right向量與世界座標系的x軸重合
#uV = (0,1,0) 矩陣V將攝像機的up向量與世界座標系的y軸重合
#dV = (0,0,1) 矩形V使攝像機的look向量與世界座標系的z軸重合
這樣就可將計算這種矩陣的任務分爲兩步:首先將攝像機平移到世界座標系的原點,然後通過旋轉變換使攝像機各向量與世界座標系對應各軸重合

平移:
攝像機的位置向量p平移到原點可通過將其與向量-p做向量加法輕鬆實現,因爲p-p = 0
旋轉
攝像機各向量與世界座標系各軸重合的工作量稍大一些,需要一個3X3的旋轉矩陣A以使向量right,up和look分別與世界座標系的x,y,z軸重合

使用3X3矩陣,因爲不需要用齊次座標來表示旋轉,在後面的部分中,再將其擴展爲4X4矩陣

使攝像機各軸與世界座標系各軸重合的變換可用如下矩陣表示:
A = rx ux dx
   [ry uy dy ]
    rz uz dz

前兩步的整合
將A擴展爲4X4矩陣,並將取景變換的前兩步整合,得到完整的觀察矩陣 V
     rx   ux  dx  0
     ry   uy  dy  0
V =[ rz   uz  dz  0  ]
    -pr  -pu -pd  1    

在Camera類中,用Camera::getVieMatrix方法來計算觀察矩陣
void Camera::getViewMatrix(D3DXMATRIX * V)
{
 // Keep camera's axes orthogonal to eachother
 D3DXVec3Normalize(&_look,&_look);
 
 D3DXVec3Cross(&_up,&_look,&_right);
 D3DXVec3Normalize(&_up,&_up);
 
 D3DXVec3Cross(&_right,&_up,&_look);
 D3DXVec3Normalize(&_right,&_right);

 //Build the view matrix
 float x = -D3DXVec3Dot(&_right,&_pos);
 float y = -D3DXVec3Dot(&_up,&_pos);
 float z = -D3DXVec3Dot(&_look,&_pos);

 (*V)(0,0) = _right.x;
 (*V)(0,1) = _up.x;
 (*V)(0,2) = _look.x;
 (*V)(0,3) = 0.0f;

 (*V)(1,0) = _right.y ;
 (*V)(1,1) = _up.y;
 (*V)(1,2) = _look.y;
 (*V)(1,3) = 0.0f;

 (*V)(2,0) = _right.z;
 (*V)(2,1) = _up.z;
 (*V)(2,2) = _look.z;
 (*V)(2,3) = 0.0f;
 
 (*V)(3,0) = x;
 (*V)(3,1) = y;
 (*V)(3,2) = z;
 (*V)(3,3) = 1.0f;
}

由於浮點數運算的誤差,攝像機的各向量可能不再是標準正交,所以,每次調用該函數時,必須重新根據向量look計算向量up,right以保證三者相互正交,新的正交向量up可由up=look * right得到;新的正交向量right可由right =up * look 計算得到

實現攝像機的旋轉方法時,應使得能夠繞任意軸進行旋轉,
D3DXMATRIX *D3DXMatrixRotationAxis(
 D3DXMATRIX *pOut,// returns rotation matrix
 CONST D3DXVECTOR3 *pV,// axis to rotate around
 FLOAT Angle // angle ,in radians to rotate
);

例:繞由向量(0.707f,0.707f,0.0f)所確定的軸旋轉∏/2

D3DXMATRIX R;
D3DXVECTOR3 axis(0.707f,0.707f,0.0f);
D3DXMatrixRotationAxis(&R,&axis,&D3DX_PI / 2.0f);

由於方向向量描述了攝像機在世界座標系中的朝向,所以當攝像機發生俯仰,偏航或滾動時,必須指定方向向量應如何更新

攝像機發生俯仰時,需要將向量up和look繞着向量right轉動指定的角度,類似地,發生偏航時,需要將向量look和right繞向量up轉動指定的角度,發生翻滾時,需要將向量right和up繞着向量look轉動指定的角度

使用函數D3DXMatrixRotationAxis的必要性,因爲確定了旋轉軸的這3個方向在世界座標系中可能會沿着任意方向

方法pitch,yaw和roll的實現也應遵循上述討論中提到的原則,但是還需要對LANDOBJET類型的攝像機增加一些約束
尤其是當一個地面物體俯仰之後又發生了偏航或滾動,這看起來總有些問題,所以,對於LANDOBJECT類型的攝像機,應使其繞世界座標系的y軸旋轉而非yaw方法中的up向量,而且完全禁止地面物體,有一點必須清楚,我們可以改變Camera類來適應應用程序的不同要求

方法pitch,yaw和roll的實現:
//俯仰
void Camera::pitch(float angle)
{
 D3DXMATRIX T;
 D3DXMatrixRotationAxis(&T,&_right,angle);

 // rotate _up and _look around _right vector
 D3DXVec3TransfromCoord(&_up,&_up,&T);
 D3DXVec3TransfromCoord(&_look,&_look,&T);
}
//偏航
void Camera::yaw(float angle)
{
 D3DXMATRIX T;
 
 // rotate around world y(0,1,0) always for land object
 if(_cameraType == LANDOBJECT)
  D3DXMatrixRotationY(&T,angle);

 // rotate around own vector for aircraft
 if(_cameraType == AIRCRAFT)
  D3DXMatrixRotationAxis(&T,&_up,angle);
 
 // rotate _right and _look around _up or y-axis
 D3DXVec3TransformCoord(&_right,&right,&T);
 D3DXVec3TransformCoord(&_look,&_look,&T);
  
}
//滾動
void Camera::roll(float angle)
{
 // only roll for aircraft type
 if (_cameraType == AIRCRAFT)
 {
  D3DXMATRIX T;
  D3DXMatrixRotationAxis(&T,&_look,angle);
 
  // rotate _up and _right around _look vector
  D3DXVect3TransformCoord(&_right,&right,&T);
  D3DXVec3TransformCoord(&_up,&_up,&T);
 }

}

"行走"是指沿着攝像機的觀察方向(即沿着向量look的方向)的平動
'掃視(strafing)'是指保持觀察方向不變,沿向量right方向從一邊平移到另一邊
"升降(flying)"是指沿着向量up方向的平動
爲了能夠沿這些軸中的任意一個進行平動,只需將攝像機當前位置向量和一個與該軸方向相同的向量相加即可

像旋轉一樣,也必須給地面物體的運動增加一些約束,例LANDOBJECT類型的攝像機不應在觀察方向朝下時沿着其up向量升降或平動,也不應在一個斜面上進行掃視,所以應將攝像機的運動限制在xz平面上,然後,由於LANDOBJECT類型的攝像機可以改變其高度(爬樓或爬山),我們提供了Camera::setPosition方法,使可手工將攝像機指定在一個合適的高度和位置上


//行走
void Camera::walk(float units)
{
 // move only on xz plane for land object
 if(_cameraType == LANDOBJECT)
  _pos += D3DXVECTOR3(_look.x,0.0f,_look.z) * units;

 if(_cameraType == AIRCRAFT)
  _pos += _look * units;
}
//掃視
void Camera::strafe(float units)
{
 // move only on xz plane for land object
 if (_cameType ==  LANDOBJECT)
  _pos += D3DXVECTOR3(_right.x,0,0f,_right.z) * units;

 if (_cameraType == AIRCRAFT)
  _pos += _right * uints;
}
//升降
void Camera::fly(float units)
{
 // move only on y-axis for land object
 if (_cameraType == LandOBJECT)
  _pos.y += units;
 if (_cameraType == AIRCRAFT)
  _pos += _up * units;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章