第十一章
用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;
}