在上篇中讲到,我们只要知道:
1.眼睛在世界坐标系中的位置
2.右手方向在世界坐标系中的向量表示
3.头顶方向在世界坐标系中的向量表示
4.正前方方向在世界坐标系中的向量表示
就能求出观察坐标系转换矩阵了
所以我们的摄像机类里面必须保存这几个数据,可以这样去声明:
class CCamera
{
public:
CCamera();
~CCamera();
void GetViewMatrix(D3DXMATRIX* const V); //获得变换矩阵
protected:
D3DXVECTOR3 m_pos; //眼睛在世界坐标系中的位置
D3DXVECTOR3 m_right; //右手方向在世界坐标系中的向量表示
D3DXVECTOR3 m_up; //头顶方向在世界坐标系中的向量表示
D3DXVECTOR3 m_look; //正前方方向在世界坐标系中的向量表示
};
设为protected是因为这只是一个基础类,它的子类有可能会要直接对这些数据进行操作,以后我们可以通过继承使得它的功能更加强大。
我们先来用上篇的知识完成获得变换矩阵的函数:
void CCamera::GetViewMatrix(D3DXMATRIX* const V)
{
D3DXVec3Normalize(&m_look,&m_look);
D3DXVec3Cross(&m_up,&m_look,&m_right);
D3DXVec3Normalize(&m_up,&m_up);
D3DXVec3Cross(&m_right,&m_up,&m_look);
D3DXVec3Normalize(&m_right,&m_right);
float x = -D3DXVec3Dot(&m_pos,&m_right);
float y = -D3DXVec3Dot(&m_pos,&m_up);
float z = -D3DXVec3Dot(&m_pos,&m_look);
(*V)(0,0) = m_right.x; (*V)(0,1) = m_up.x; (*V)(0,2) = m_look.x; (*V)(0,3) = 0.0f;
(*V)(1,0) = m_right.y; (*V)(1,1) = m_up.y; (*V)(1,2) = m_look.y; (*V)(1,3) = 0.0f;
(*V)(2,0) = m_right.z; (*V)(2,1) = m_up.z; (*V)(2,2) = m_look.z; (*V)(2,3) = 0.0f;
(*V)(3,0) = x; (*V)(3,1) = y; (*V)(3,2) = z; (*V)(3,3) = 1.0f;
}
因为m_right,m_up,m_look这三个向量可能存在一点偏差,并不是完全相互垂直的,所以我们可以通过向量叉乘的方法使它们垂直(作为坐标轴,XYZ之间必须保证两两垂直)
我们以m_look为基准,假设它的方向是正确的,所以直接将它单位化就好。D3DXVec3Normalize函数的功能是使一个向量单位化并保存到另一个向量,这里让它保存给原来的向量就好
D3DXVec3Normalize(&m_look,&m_look);
看,我们用 m_look 叉乘 m_right是不是得到了一个垂直于m_look和m_right的向量?
D3DXVec3Cross函数可以实现叉乘的计算。计算出来的向量就是剩下的m_up的正确方向,因为在调用D3DXVec3Cross计算m_up的时候,m_up不一定是单位化的,保险起见我们还是把它单位化一下。
(两个向量叉乘产生的向量与原来的两个向量都垂直,你不要告诉我你连这个都不知道......还有因为D3DXVec3Cross是满足左手定律的,我们要注意叉乘的顺序用m_look 叉乘 m_right才能的到正确的m_up。如果是m_righ 叉乘 m_look 的话得到向量与正确的向量方向向反 )
D3DXVec3Cross(&m_up,&m_look,&m_right);
D3DXVec3Normalize(&m_up,&m_up);
m_up,m_look都有了,同理可以得到m_right,
D3DXVec3Cross(&m_right,&m_up,&m_look);
D3DXVec3Normalize(&m_right,&m_right);
还记得上篇最后我们求到的转换矩阵吗?
-pos·R = -posX * Rx - posY * Ry - posZ * Rz
-pos·U = -posX * Ux - posY * Uy - posZ *Uz
-pos·L = -posX * Lx - posY * Ly - posZ * Lz
这样不就求到了转换矩阵?
float x = -D3DXVec3Dot(&m_pos,&m_right);
float y = -D3DXVec3Dot(&m_pos,&m_up);
float z = -D3DXVec3Dot(&m_pos,&m_look);
(*V)(0,0) = m_right.x; (*V)(0,1) = m_up.x; (*V)(0,2) = m_look.x; (*V)(0,3) = 0.0f;
(*V)(1,0) = m_right.y; (*V)(1,1) = m_up.y; (*V)(1,2) = m_look.y; (*V)(1,3) = 0.0f;
(*V)(2,0) = m_right.z; (*V)(2,1) = m_up.z; (*V)(2,2) = m_look.z; (*V)(2,3) = 0.0f;
(*V)(3,0) = x; (*V)(3,1) = y; (*V)(3,2) = z; (*V)(3,3) = 1.0f;
好了,我们完成了观察矩阵的获得。但这时候我们的摄像机只是固定在了一个地方,我们要让它动起来才行,所以增加一个函数去设置摄像机的位置:
void SetPos(const D3DXVECTOR3* const pos){m_pos = *pos;}
但每一次都要计算好位置然后去设置,不是很麻烦吗?最好能让它自己计算位置,我们只要告诉它往前后走,还是往左右走,甚至是飞天上就好了,所以我们再加几个成员函数:
void Walk(float units); //前后走
void Fly(float units); //上下飞
void Strafe(float units); //左右走
当然我们还考虑到不同类型的摄像机可能有不同的运动方式,所以我们增加一个成员变量去表示不同类型的摄像机,在增加一个成员函数去让用户设置摄像机类型。
CameraType m_cameraType;
void SetCameraType(CameraType cameraType){m_cameraType = cameraType;}
m_cameraType有两种取值:不能飞的和能飞的,CameraType的定义如下:
enum CameraType {CT_LANDOBJECT,CT_AIRCRAFT};
好规定好了摄像机类型后我们就按照不同的类型去计算运动后的位置:
void CCamera::Walk(float units)
{
if(m_cameraType == CT_LANDOBJECT)
m_pos += D3DXVECTOR3(m_look.x, 0.0f, m_look.z) * units;
else
m_pos += m_look * units;
}
往前后走就是位置沿自己的前方运动一段距离,units就表示运动的距离,正数表示向前,负数表示向后。如果是不能飞的,它只能在与xoz平面平行的平面里运动,所以在y轴的位移为0
void CCamera::Strafe(float units)
{
if(m_cameraType == CT_LANDOBJECT )
m_pos += D3DXVECTOR3(m_right.x, 0.0f,m_right.z) * units;
else
m_pos += m_right * units;
}
往左右走就是位置沿自己的右手方向运动一段距离,units就表示运动的距离,正数表示向右,负数表示向左。同样,如果是不能飞的,它只能在与xoz平面平行的平面里运动,所以在y轴的位移为0
void CCamera::Fly(float units)
{
if(m_cameraType == CT_LANDOBJECT)
m_pos.y+= units;
else
m_pos += m_up * units;
}
虽说我们的摄像机可能不会飞,但当它踩着楼梯往上时,它的高度依然会增加,这是它的高度增加方向是固定的是(0 1 0)。如果它会飞则不一定,如果你是歪着站的(或者躺着的),飞的时候应该是沿着你头顶的方向移动的,向这幅图一样:
前后左右上下的运动完成了,但我们还想实现扭头和打滚怎么办?没错让 m_right, m_up, m_look 这三个方向不与世界坐标系的XYZ轴平行即可。(怎样让它们不平行?......旋转啊!)
同样增加三个成员函数:
void Pitch(float angle);
void Yaw(float angle);
void Roll(float angle);
void CCamera::Pitch(float angle)
{
D3DXMATRIX T;
D3DXMatrixRotationAxis(&T,&m_right,-angle);
D3DXVec3TransformCoord(&m_look,&m_look,&T);
D3DXVec3TransformCoord(&m_up,&m_up,&T);
}
以你的右手为轴旋转,实现的是抬头和低头的效果。
D3DXMatrixRotationAxis(&T,&m_right,-angle) 可以求出以 m_right 为轴旋转 -angle 度的变换矩阵
然后通过 D3DXVec3TransformCoord(&m_look,&m_look,&T) 和 D3DXVec3TransformCoord(&m_up,&m_up,&T) 使得 m_look 和 m_up 以 m_right 为轴旋转 -angle 度
void CCamera::Roll(float angle)
{
D3DXMATRIX T;
D3DXMatrixRotationAxis(&T,&m_look,angle);
D3DXVec3TransformCoord(&m_right,&m_right,&T);
D3DXVec3TransformCoord(&m_up,&m_up,&T);
}//以你往前看的方向为轴旋转,实现的是左右滚的效果
void CCamera::Yaw(float angle)
{
D3DXMATRIX T;
if(m_cameraType == CT_LANDOBJECT )
D3DXMatrixRotationY(&T,angle);
else
D3DXMatrixRotationAxis(&T,&m_up,angle);
D3DXVec3TransformCoord(&m_right,&m_right,&T);
D3DXVec3TransformCoord(&m_look,&m_look,&T);
}//以的头顶方向为轴旋转,实现的是转身或扭头的效果
当然你还要有一些成员函数可以令用户获得当前摄像机类的状态,这里每个功能的函数都有一个重载是为了提高摄像机类的灵活性
(当然你会看到很多const,这是为了保护数据,使得在得到数据时不会不小心改变了原来的数据。可能你还是很头晕函数后面紧跟着的const有什么用,我只能说.......百度去吧)
//得到当前的位置
void GetPos(D3DXVECTOR3* const pos)const {*pos = m_pos;}
const D3DXVECTOR3& GetPos(void)const {return m_pos;}
//得到m_up
void GetUp(D3DXVECTOR3* const up)const {*up = m_up;}
const D3DXVECTOR3& GetUp(void)const {return m_up;}
//得到m_right
void GetRight(D3DXVECTOR3* const right)const {*right = m_right;}
const D3DXVECTOR3& GetRight(void)const {return m_right;}
//得到m_look
void GetLook(D3DXVECTOR3* const look)const {*look = m_look;}
const D3DXVECTOR3& GetLook(void)const {return m_look;}
最后我还提供了一个函数,将它放在Windows消息处理函数里调用,使得用户可以用鼠标去控制摄像机的转向。按着鼠标右键移动鼠标,镜头会跟着转动(当然你可以改良一下,让用户去决定按鼠标左键甚至直接移动鼠标去改变镜头方向)。这里就不细说了
BOOL CCamera::OnMessage(UINT message ,LPARAM lParam)
{
static BOOL bIsLButtonDown = FALSE;
static int x,y;
switch(message)
{
case WM_RBUTTONDOWN:
bIsLButtonDown = TRUE;
x = LOWORD(lParam);
y = HIWORD(lParam);
return TRUE;
case WM_RBUTTONUP:
bIsLButtonDown = FALSE;
return TRUE;
case WM_MOUSEMOVE:
if(bIsLButtonDown)
{
Yaw((LOWORD(lParam) - x)/100.0f);
Pitch((y - HIWORD(lParam))/100.0f);
x = LOWORD(lParam);
y = HIWORD(lParam);
return TRUE;
}
break;
default:
break;
}
return FALSE;
}
最后的最后,完整代码奉上:
//.h
enum CameraType {CT_LANDOBJECT,CT_AIRCRAFT};
class CCamera
{
public:
CCamera();
CCamera(CameraType cameraType);
~CCamera(){}
void SetCameraType(CameraType cameraType){m_cameraType = cameraType;}
void SetPos(const D3DXVECTOR3* const pos){m_pos = *pos;}
void Walk(float units);
void Fly(float units);
void Strafe(float units);
void Pitch(float angle);
void Yaw(float angle);
void Roll(float angle);
void GetPos(D3DXVECTOR3* const pos)const {*pos = m_pos;}
const D3DXVECTOR3& GetPos(void)const {return m_pos;}
void GetUp(D3DXVECTOR3* const up)const {*up = m_up;}
const D3DXVECTOR3& GetUp(void)const {return m_up;}
void GetRight(D3DXVECTOR3* const right)const {*right = m_right;}
const D3DXVECTOR3& GetRight(void)const {return m_right;}
void GetLook(D3DXVECTOR3* const look)const {*look = m_look;}
const D3DXVECTOR3& GetLook(void)const {return m_look;}
void GetViewMatrix(D3DXMATRIX* const V);
virtual BOOL OnMessage(UINT message,LPARAM lParam);
protected:
CameraType m_cameraType;
D3DXVECTOR3 m_pos;
D3DXVECTOR3 m_up;
D3DXVECTOR3 m_right;
D3DXVECTOR3 m_look;
};
inline CCamera::CCamera():
m_cameraType(CT_LANDOBJECT),
m_pos(D3DXVECTOR3(0.0f,0.0f,0.0f)),
m_up(D3DXVECTOR3(0.0f,1.0f,0.0f)),
m_right(D3DXVECTOR3(1.0f,0.0f,0.0f)),
m_look(D3DXVECTOR3(0.0f,0.0f,1.0f))
{}
inline CCamera::CCamera(CameraType cameraType):
m_cameraType(cameraType),
m_pos(D3DXVECTOR3(0.0f,0.0f,0.0f)),
m_up(D3DXVECTOR3(0.0f,1.0f,0.0f)),
m_right(D3DXVECTOR3(1.0f,0.0f,0.0f)),
m_look(D3DXVECTOR3(0.0f,0.0f,1.0f))
{}
//.cpp
void CCamera::Walk(float units)
{
if(m_cameraType == CT_LANDOBJECT)
m_pos += D3DXVECTOR3(m_look.x, 0.0f, m_look.z) * units;
else
m_pos += m_look * units;
}
void CCamera::Strafe(float units)
{
if(m_cameraType == CT_LANDOBJECT)
m_pos += D3DXVECTOR3(m_right.x, 0.0f,m_right.z) * units;
else
m_pos += m_right * units;
}
void CCamera::Fly(float units)
{
if(m_cameraType == CT_LANDOBJECT )
m_pos.y+= units;
else
m_pos += m_up * units;
}
void CCamera::Pitch(float angle)
{
D3DXMATRIX T;
D3DXMatrixRotationAxis(&T,&m_right,-angle);
D3DXVec3TransformCoord(&m_look,&m_look,&T);
D3DXVec3TransformCoord(&m_up,&m_up,&T);
}
void CCamera::Roll(float angle)
{
D3DXMATRIX T;
D3DXMatrixRotationAxis(&T,&m_look,angle);
D3DXVec3TransformCoord(&m_right,&m_right,&T);
D3DXVec3TransformCoord(&m_up,&m_up,&T);
}
void CCamera::Yaw(float angle)
{
D3DXMATRIX T;
if(m_cameraType == CT_LANDOBJECT)
D3DXMatrixRotationY(&T,angle);
else
D3DXMatrixRotationAxis(&T,&m_up,angle);
D3DXVec3TransformCoord(&m_right,&m_right,&T);
D3DXVec3TransformCoord(&m_look,&m_look,&T);
}
void CCamera::GetViewMatrix(D3DXMATRIX* const V)
{
D3DXVec3Normalize(&m_look,&m_look);
D3DXVec3Cross(&m_up,&m_look,&m_right);
D3DXVec3Normalize(&m_up,&m_up);
D3DXVec3Cross(&m_right,&m_up,&m_look);
D3DXVec3Normalize(&m_right,&m_right);
float x = -D3DXVec3Dot(&m_pos,&m_right);
float y = -D3DXVec3Dot(&m_pos,&m_up);
float z = -D3DXVec3Dot(&m_pos,&m_look);
(*V)(0,0) = m_right.x; (*V)(0,1) = m_up.x; (*V)(0,2) = m_look.x; (*V)(0,3) = 0.0f;
(*V)(1,0) = m_right.y; (*V)(1,1) = m_up.y; (*V)(1,2) = m_look.y; (*V)(1,3) = 0.0f;
(*V)(2,0) = m_right.z; (*V)(2,1) = m_up.z; (*V)(2,2) = m_look.z; (*V)(2,3) = 0.0f;
(*V)(3,0) = x; (*V)(3,1) = y; (*V)(3,2) = z; (*V)(3,3) = 1.0f;
}
BOOL CCamera::OnMessage(UINT message,LPARAM lParam)
{
static BOOL bIsLButtonDown = FALSE;
static int x,y;
switch(message)
{
case WM_RBUTTONDOWN:
bIsLButtonDown = TRUE;
x = LOWORD(lParam);
y = HIWORD(lParam);
return TRUE;
case WM_RBUTTONUP:
bIsLButtonDown = FALSE;
return TRUE;
case WM_MOUSEMOVE:
if(bIsLButtonDown)
{
Yaw((LOWORD(lParam) - x)*0.001f);
Pitch((y - HIWORD(lParam))*0.001f);
x = LOWORD(lParam);
y = HIWORD(lParam);
return TRUE;
}
break;
default:
break;
}
return FALSE;
}