作者: i_dovelemon
来源:CSDN
日期:2014 / 9 / 25
主题: View Space, Perspective Matrix
在游戏中,我们能够很容易的在3D世界中漫游。要完成这样的功能,我们就需要定制自己的相机。在这里,我们来一起实现一个类似FPS游戏中的第一人称相机,让你能够自由自在的在3D世界中遨游。
要想实现FPS的相机,那么我们首先要做的就是确定FPS游戏中相机的功能有哪些了。我们可以想象下,在CF或者COD中,我们能够通过鼠标的移动来改变相机对着的方向。更具体的说,我们将鼠标上下移动的时候,就好像我们在上下抬头的感觉。而当我们左右移动鼠标的时候,我们就感觉好像在左右的转动我们的脖子,所看到的景色。当我们按下前进键或者后退键的时候,我们就会朝着,我们观望的方向进行移动。按下左右移动键的时候,我们就会横向的左右移动。
通过上面简单的描述,大家基本应该明白了应该需要实现的功能。我们来将上面的文字转换成更加3D化的概念。
我们知道,在3D图形学中,我们是通过相机的三个基坐标在与世界坐标的关联向量来表示相机的方向的。也就是Right, Up, Look向量。他们分别对应了坐标系中的X,Y和Z坐标轴。
也就是说,我们想要实现如下的功能:
1.上下移动鼠标的时候,我们希望相机能够绕着它的Right(X轴)向量进行旋转;
2.左右移动鼠标的时候,我们希望相机能够绕着世界坐标的Up(Y轴)向量进行旋转;
3.按下左右移动键的时候,我们希望相机能够沿着Right(X轴)向量进行平移;
4.按下上下移动键的时候,我们希望能够沿着Look(Z轴)向量进行平移;
好了,在了解到我们需要完成的功能之后,我们来进行相机的设计。
下面是相机类的申明:
//-----------------------------------------------------------------------------------
// declaration : Copyright (c), by XJ , 2014 . All right reserved .
// brief : This file will define the First Perspective Shooter(FPS) camera.
// file : Camera.h
// author : XJ
// date : 2014 / 9 / 25
// version : 1.0
//----------------------------------------------------------------------------------
#pragma once
#include
/**
* Define the FPS Camera
*/
class Camera
{
public:
Camera();
public:
const D3DXMATRIX& view() const ;
const D3DXMATRIX& proj() const ;
const D3DXMATRIX& viewproj() const ;
const D3DXVECTOR3& right() const ;
const D3DXVECTOR3& up() const ;
const D3DXVECTOR3& look() const ;
D3DXVECTOR3& pos();
//Create the view matrix
void lookAt(D3DXVECTOR3& pos,
D3DXVECTOR3& target,
D3DXVECTOR3& up);
//Create the perspective matrix
void setLens(float fov, float aspect, float nearZ, float farZ);
//Set the camera speed
void setSpeed(float s);
// Set the mouse speed
void setMouseSpeed(float s);
//Update the camera
void update(float dt);
protected:
void _buildView();
protected:
//The matrix
D3DXMATRIX m_mView;
D3DXMATRIX m_mProj;
D3DXMATRIX m_mViewProj;
//The basis relative to the world space
D3DXVECTOR3 m_vPosW ;
D3DXVECTOR3 m_vRightW;
D3DXVECTOR3 m_vLookW;
D3DXVECTOR3 m_vUpW ;
//Camera speed
float m_fSpeed ;
float m_fMouseSpeed ;
};// end for class
下面我们来一一的看下这个函数的实现。
首先我们来看下构造函数:
/**
* Constructor
*/
Camera::Camera()
{
D3DXMatrixIdentity(&m_mView);
D3DXMatrixIdentity(&m_mProj);
D3DXMatrixIdentity(&m_mViewProj);
m_fSpeed = 0.1f ;
m_fMouseSpeed = 300.0f;
m_vPosW = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
m_vUpW = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
m_vLookW = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
m_vRightW = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
}// end constructor
/**
* Create the look at matrix
*/
void Camera::lookAt(D3DXVECTOR3& pos,
D3DXVECTOR3& target,
D3DXVECTOR3& up)
{
//Calculate the look vector
D3DXVECTOR3 look = target - pos;
D3DXVec3Normalize(&look, &look);
m_vLookW = look ;
//Calculate the right vector
D3DXVECTOR3 right ;
D3DXVec3Cross(&right, &up, &look);
D3DXVec3Normalize(&right, &right);
m_vRightW = right ;
//Calculate the up vector
D3DXVec3Cross(&up, &look, &right);
D3DXVec3Normalize(&up, &up);
m_vUpW = up ;
//Fill in the view matrix
float x = -D3DXVec3Dot(&right, &pos);
float y = -D3DXVec3Dot(&up, &pos);
float z = -D3DXVec3Dot(&look, &pos);
m_mView(0,0) = right.x ;
m_mView(1,0) = right.y ;
m_mView(2,0) = right.z ;
m_mView(3,0) = x ;
m_mView(0,1) = up.x ;
m_mView(1,1) = up.y ;
m_mView(2,1) = up.z ;
m_mView(3,1) = y ;
m_mView(0,2) = look.x ;
m_mView(1,2) = look.y ;
m_mView(2,2) = look.z ;
m_mView(3,2) = z ;
m_mView(0,3) = 0.0f ;
m_mView(1,3) = 0.0f ;
m_mView(2,3) = 0.0f ;
m_mView(3,3) = 1.0f ;
m_mViewProj = m_mView * m_mProj ;
m_vPosW = pos ;
}// end for lookAt
zaxis = normal(At - Eye)
xaxis = normal(cross(Up, zaxis))
yaxis = cross(zaxis, xaxis)
xaxis.x yaxis.x zaxis.x 0
xaxis.y yaxis.y zaxis.y 0
xaxis.z yaxis.z zaxis.z 0
-dot(xaxis, eye) -dot(yaxis, eye) -dot(zaxis, eye) 1
/**
* Set the lens and then create the perspective matrix
*/
void Camera::setLens(float fov, float aspect, float nearZ, float farZ)
{
float yScale = 1.0f / (tan(fov/2));
float xScale = yScale / aspect ;
D3DXMatrixIdentity(&m_mProj);
m_mProj(0, 0) = xScale ;
m_mProj(1, 1) = yScale ;
m_mProj(2, 2) = farZ / (farZ - nearZ);
m_mProj(3, 2) = - nearZ * farZ / (farZ - nearZ);
m_mProj(3, 3) = 0 ;
m_mProj(2, 3) = 1 ;
}// end for setLens
xScale 0 0 0
0 yScale 0 0
0 0 zf/(zf-zn) 1
0 0 -zn*zf/(zf-zn) 0
where:
yScale = cot(fovY/2)
xScale = yScale / aspect ratio
由于篇幅所限,这里将不向大家讲述计算相机变换矩阵和透视投影矩阵的数学原理。感兴趣的读者,可以查看书籍来寻找答案。我将在后期专门写一篇文章来讲解3D空间中的各种矩阵变换操作。
/**
* Update the camera
*/
void Camera::update(float dt)
{
//Get the net direction that the camera will travel
D3DXVECTOR3 dir(0.0f, 0.0f, 0.0f);
//Get the input device
MyInput* pInput = MyInput::getMyInput(0,0) ;
if(pInput->keyDown(DIK_W))
dir += m_vLookW ;
else if(pInput->keyDown(DIK_S))
dir -= m_vLookW ;
if(pInput->keyDown(DIK_A))
dir -= m_vRightW ;
else if(pInput->keyDown(DIK_D))
dir += m_vRightW ;
//Normalize the net direction vector
D3DXVec3Normalize(&dir, &dir);
dir *= m_fSpeed ;
//Move the position along the direction by m_fSpeed
m_vPosW += dir * m_fSpeed ;
//Angle to rotate around right vector
float ditch = pInput->mouseDY() / m_fMouseSpeed ;
//Angle to rotate around world's up vector
float yAngle = pInput->mouseDX() / m_fMouseSpeed ;
//Rotate the camera's look and up vector around the camera's right vector
D3DXMATRIX R ;
D3DXMatrixRotationAxis(&R, &m_vRightW, ditch);
D3DXVec3TransformCoord(&m_vLookW, &m_vLookW, &R);
D3DXVec3TransformCoord(&m_vUpW, &m_vUpW, &R);
//Rotate the camera's axis around the world's y axie
D3DXMatrixRotationY(&R, yAngle);
D3DXVec3TransformCoord(&m_vLookW, &m_vLookW, &R);
D3DXVec3TransformCoord(&m_vUpW, &m_vUpW, &R);
D3DXVec3TransformCoord(&m_vRightW, &m_vRightW, &R);
//Rebuild the view matrix
_buildView();
m_mViewProj = m_mView * m_mProj ;
}// end for update
/**
* Build the view matrix
*/
void Camera::_buildView()
{
//Do some float-error fixing
//We assume the look vector is accurate
D3DXVec3Normalize(&m_vLookW, &m_vLookW);
//Calcuate the up vector
D3DXVec3Cross(&m_vUpW, &m_vLookW, &m_vRightW);
D3DXVec3Normalize(&m_vUpW, &m_vUpW);
//Calculate the right vector
D3DXVec3Cross(&m_vRightW, &m_vUpW, &m_vLookW);
D3DXVec3Normalize(&m_vRightW, &m_vRightW);
//Fill in the view matrix
float x = -D3DXVec3Dot(&m_vRightW, &m_vPosW);
float y = -D3DXVec3Dot(&m_vUpW, &m_vPosW);
float z = -D3DXVec3Dot(&m_vLookW, &m_vPosW);
m_mView(0,0) = m_vRightW.x ;
m_mView(1,0) = m_vRightW.y ;
m_mView(2,0) = m_vRightW.z ;
m_mView(3,0) = x ;
m_mView(0,1) = m_vUpW.x ;
m_mView(1,1) = m_vUpW.y ;
m_mView(2,1) = m_vUpW.z ;
m_mView(3,1) = y ;
m_mView(0,2) = m_vLookW.x ;
m_mView(1,2) = m_vLookW.y ;
m_mView(2,2) = m_vLookW.z ;
m_mView(3,2) = z ;
m_mView(0,3) = 0.0f ;
m_mView(1,3) = 0.0f ;
m_mView(2,3) = 0.0f ;
m_mView(3,3) = 1.0f ;
}// end for _buildView
这个函数,就是根据我们的功能描述来完成我们想要完成的功能的。代码已近清晰明了,这里不再解释。
有一个地方需要注意,在上面我们说:进行鼠标左右移动的时候,是按照世界坐标的Y轴进行旋转,为什么不是绕着相机坐标的Y轴进行旋转了?
我们先来看下在使用绕相机坐标的Y轴进行旋转后的效果图:
读者可以看到,在进行多次旋转之后,我们就好像要摔倒了一样,是倾斜的看着这个世界的。我们当然不希望是这样的效果(至少这我们的例子中,我们不希望有这种效果,但是在游戏中可能会故意为了营造地震,眩晕等效果,故意倾斜相机),所以这就是为什么,我们要绕着世界坐标的Y坐标进行旋转。因为这样,我们就能够时刻的保证,我们的头部总是向上的和世界坐标的Y坐标轴方向一致。
好了,其他的几个函数,仅仅是对Camera类中的变量进行获取而已,没有什么好说的。
今天就记到这里!!!
话说,周围的同学都开始找工作了,不知道像我这样的能够找到什么工作,哎!!!