DirectX 9.0 (12) FPS相机

作者: 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轴)向量进行平移;

好了,在了解到我们需要完成的功能之后,我们来进行相机的设计。

相机类

下面是相机类的申明:

<span style="font-family:Microsoft YaHei;">//-----------------------------------------------------------------------------------
// 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<d3dx9.h>

/**
* 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</span>

这个类,不仅仅保存了我们根据相机能够构造的View Transform Matrix(相机变换矩阵),而且还将对透视投影的矩阵的设置创建也包含了。除了上面我们讲解的相机的坐标系的基坐标在世界空间中的表示之外,我们还需要知道相机在世界坐标系中的位置。我们使用向量m_vPosW来进行保存。m_fSpeed这个值,保存了用户在按下上下左右按键时,相机移动的速度。而m_fMouseSpeed控制了用户滑动鼠标时镜头转向的灵敏程度。

下面我们来一一的看下这个函数的实现。

首先我们来看下构造函数:

<span style="font-family:Microsoft YaHei;">/**
* 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</span>

构造函数很简单,只是将里面的数据成员做了一些初始化的设置工作。但是这个初始化的设置,并不能够完成一个基本的相机的功能,在设置了这些之后,我们还需要调用lookAt函数和setLens函数来对相机变换和透视投影变换进行设置。

<span style="font-family:Microsoft YaHei;">/**
* 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</span>

lookAt函数的实现,和DirectX中的LookAt函数实现一致。我们根据输入的相机位置pos,相机看向的目标target,以及世界坐标的up向量,来构建相机的基坐标相对于世界坐标系关联的向量m_vRightW, m_vUpW, m_vLookW。然后构建我们的相机矩阵。这个内容可以在DirectX SDK的文档中,关于函数D3DXMatrixLookAtLH的介绍中可以得到如下的公式:

<span style="font-family:Microsoft YaHei;">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
</span>

上面的实现,就是基于此公式来实现的。

<span style="font-family:Microsoft YaHei;">/**
* 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</span>

同样的,setLens函数,使用的方法也和DirectX中计算透视投影矩阵的公式一致。可以在DirectX SDK的文档中关于D3DXMatrixPerspectiveFovLH的解释中得到如下的公式:

<span style="font-family:Microsoft YaHei;">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
</span>

这里的公式,只限于左手坐标系的3D空间系统,并且只是在矩阵对向量进行变换时使用的是行向量的方法时才适用。如果你的系统是基于OpenGL的,那么就需要将上面的公式进行转置,更复杂的还要进行其他的变换,来达到你的要求。

由于篇幅所限,这里将不向大家讲述计算相机变换矩阵和透视投影矩阵的数学原理。感兴趣的读者,可以查看书籍来寻找答案。我将在后期专门写一篇文章来讲解3D空间中的各种矩阵变换操作。

<span style="font-family:Microsoft YaHei;">/**
* 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
</span>

update方法,是相机的更新方法。每一帧的时候,都要调用这个方法来检测用户的输入,进行判断。在update中,我使用了我自己的对Direct Input的包装类MyInput。读者可以替换成自己的代码。但是更好的设计是将检测用户的输入作为数据传递给相机,而不是将输入设备绑定在相机中。这里仅仅是为了方便起见,才这样做。

这个函数,就是根据我们的功能描述来完成我们想要完成的功能的。代码已近清晰明了,这里不再解释。

有一个地方需要注意,在上面我们说:进行鼠标左右移动的时候,是按照世界坐标的Y轴进行旋转,为什么不是绕着相机坐标的Y轴进行旋转了?

我们先来看下在使用绕相机坐标的Y轴进行旋转后的效果图:

读者可以看到,在进行多次旋转之后,我们就好像要摔倒了一样,是倾斜的看着这个世界的。我们当然不希望是这样的效果(至少这我们的例子中,我们不希望有这种效果,但是在游戏中可能会故意为了营造地震,眩晕等效果,故意倾斜相机),所以这就是为什么,我们要绕着世界坐标的Y坐标进行旋转。因为这样,我们就能够时刻的保证,我们的头部总是向上的和世界坐标的Y坐标轴方向一致。

好了,其他的几个函数,仅仅是对Camera类中的变量进行获取而已,没有什么好说的。

今天就记到这里!!!

话说,周围的同学都开始找工作了,不知道像我这样的能够找到什么工作,哎!!!

时间: 2024-12-29 11:45:20

DirectX 9.0 (12) FPS相机的相关文章

全功能试验SebecTec.IPTimelapse.v2.7.1005.0 1CD延时相机

全功能试验SebecTec.IPTimelapse.v2.7.1005.0 1CD延时相机 IPTimelapse上传jpeg图像安排和延时视频到你的网站.功能包括图像天气.标志.裁剪和日出/日落选 项.容易操作mulitple相机.IPTimelapse低带宽是它只读取jpeg快照而不是连续的视频.7天全功能试验 .选择你的相机选项下面的软件下载和购买选择.IPTimelapse连接到你的相机自动产生一个施工间隔拍摄 视频监控.网站推广.天气摄像头和安全.可以把任何IP相机变成一个延时相机.

FPS相机

提要 FPS常用于FPS游戏中,Minecraft默认的视角也是第一人称的.在网上找了半天也没有找到比较好的现成的,还是自己写好了. 相机建模 不管是FPS相机还是TPS的相机,都包含了相机的两个操作,Yaw和Pitch Roll在一些特殊情况下才会出现,比如角色死亡. 理论上只要只要Camera的position,yaw,pitch, roll,fov,就可以确定一个Viewport了. 角色结构 角色的层次结构应该是这样的: 最外层的Player,上面用来挂脚本. 里面有Model,就是角色

DirectX 9.0 3D 笔记

1.3.0 预备 1.HAL,硬件抽象层,由D3DDEVTYPE_HAL指定 2.REF,参考光栅设备 3.COM,组件对象模型,使之向下兼容,视为C++类. 1.3.1 表面 4.IDirect3DSurface9,描述表面. (1)LockRect:获取指向表面存储区的指针,通过指针对每一个像素进行读写. (2)UnlockRect:执行完LockRect后,必须调用此来解锁. (3)GetDesc:填充结构D3DSURFACE_DESC来获取表面的描述信息. 1.3.2 多重采样 5.多重

【适配整理】Android 7.0 调取系统相机崩溃解决android.os.FileUriExposedException

一.写在前面 最近由于廖子尧忙于自己公司的事情和 OkGo (一款专注于让网络请求更简单的网络框架) ,故让LZ 接替维护 ImagePicker(一款支持单.多选.旋转和裁剪的图片选择器),也是处理了诸多bug,最近总算趋于稳定了,这里就把Android N (API 24) 以上的相机适配方案分享给大家. Android Nougat 也是被更新很久了,作为一名 Andorid 开发者,我们有义务时刻准备自己调整 targetSdkVersion 为最近的一个,于是从之前的 23 直接提高到

CentOS6.8编译安装Nginx1.10.2+MySQL5.7.16+PHP7.0.12

1.    下载 #MySQL下载地址 http://dev.mysql.com/downloads/mysql/ #Nginx下载地址 http://nginx.org/en/download.html #PHP下载地址 http://php.net/downloads.php 使用WinSCP把下载好的压缩包上传到/usr/local/src目录 mysql-5.7.16.tar.gz nginx-1.10.2.tar.gz php-7.0.12.tar.gz 2.    安装 2.1   

基于USB3.0的双目相机测试小结之CC1605配合CS5642 双目 500w摄像头

基于USB3.0的双目相机测试小结之CC1605配合CS5642  双目 500w摄像头 CC1605双目相机评估板可以配合使用柴草电子绝大多数摄像头应用 如:OV5640.OV5642.MT9P031.MT9V034.MT9M001.MT9F002等等 本次测试以CS5642V3摄像头为例,sensor为OV5642 测试分辨率为 1280*720:720p 1280*1024:SXGA 1920*1080:1080p 2048*1024 2048*1536:3M 测试帧率:15fps 一.U

Linux 0.12的编译与链接

昨天花了很长时间去编译链接linux 0.12版的kernel,发现在64位ubuntu下,这位兄台的文章写得最全最好,几乎涵盖了我遇到的所有问题,在此记录一下. 编译linux 0.12 链接linux 0.12

《DirectX 9.0 3D游戏开发编程基础》 第一章 初始化Direct3D 读书笔记

REF设备 参考光栅设备,他能以软件计算方式完全支持Direct3D Api.借助Ref设备,可以在代码中使用那些不为当前硬件所支持的特性,并对这此特性进行测试. D3DDEVTYPE 在程序代码中,HAL设备用值D3DDEVTYPE_HAL来表示.该值是一个枚举变量.REF设备用D3DDEVTYPE_REF来表示.这种类型非常重要,你需要铭记,因为在创建设备的时候,我们必须指定使用哪种设备类型. COM(组件对象模型) 创建COM接口时不可以使用c++关键字new.此外使用完接口,应调用Rel

Windows64位安装GPU版TensorFlow 0.12,Power Shell下输入:安装Tensorflow的全教程

推荐使用powershell,只需要在cmd指令窗口输入powershell即可 下载64位Python3.5(一定要3.5!!)可以通过Python 3.5 from python.org 或 Python 3.5 from Anaconda 下载并安装Python3.5.2(注意选择正确的操作系统). 具体教程可以查看Python3.5.2百度经验安装,里面有个细节,自动设置环境变量,不能忘 安装VS2015,如果是2013的后面要加个插件,这是后话 为了使用国内镜像加速pip安装,需要如下