分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow
本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
文章链接: http://blog.csdn.net/zhmxy555/article/details/8657656
作者:毛星云(浅墨) 邮箱: happylifemxy@163.com
本篇文章中,我们以核心思想为突破口,从原理介绍到一个C++类的写法,一步一步带领大家实现了一个第一人称三维摄像机的C++类。然后我们在这个摄像机类的帮助下,放出了一个几乎贯穿了我们之前学到的所有DirectX相关知识的“三维场景漫游”示例程序,算是对我们之前学的固定功能流水线这套渲染体系的总结。这个“三维场景漫游”示例程序的代码量有一千行,包括了Direct3D初始化,DirectInput输入处理,顶点缓存,光照与材质,文字输出,颜色,纹理贴图,四大变换,网格模型,X文件载入等等知识(当然还有默认被开启的深度缓存)。下面我们先放出一张运行截图:
好吧,正文开始,来卡看如何一步一步实现一个第一人称三维摄像机。
一、一些概述
回想我们之前文章的配套示例程序中,我们都是通过封装的DirectInput类来处理键盘和鼠标的输入,对应地改变我们人物模型的世界矩阵来达到移动物体,改变观察点的效果。其实我们的观察方向乃至观察点都是没有变的,变的只是我们3D人物的位置。举个例子吧,我们之前的示例程序就像是在小区里面安放着的摄像头,位置和观察点都是固定的,变的只是来来往往的人群。贴几张我们之前文章配套的示例程序截图来让大家体会一下:
之前的示例程序,和三维观察相关的四大矩阵变换我们都封装在了一个叫Matrix_Set()的函数中,而Matrix_Set()通常只用在初始化资源的时候调用一次而已。其中,四大变换中的取景变换,投影变换和视口变换都是一直老老实实地在Matrix_Set()中尽职尽责地呆着,当然,除了世界矩阵变换这个调皮的野孩子之外。世界矩阵由于实时处理的需要,自从第一次介绍完四大变换的时候让他在Matrix_Set()中呆了一次,在此之后的示例程序中我们都是把世界矩阵的设置放在Direct3D_Update()以及Direct3D_Render()之中的。因为我们必须实时地处理输入,让人物模型的世界矩阵能根据我们的输入进行相应的调整,这样才能做出人物模型随着键盘和鼠标的输入,随心所欲地受我们控制的效果出来。
但其实我们之前实现的那种所谓的“视角改变”还是来得不彻底不爽快,说白了就是用D3DXMatrixLookAtLH在资源初始化时固定住视角,在程序运行过程中接收到消息并改变三维人物模型的世界矩阵而已。我们这篇文章的主要讲解点就是手把手地教大家创建出一个可以在三维空间中自由移动的摄像机类,这样利用这个摄像机类写出来的示例程序就像是我们身临其境地在三维空间中真正地自由翱翔一样。浅墨怎么在写上面这段文字的时候,脑海中闪现的竟然是凤凰传奇的那句“在你的心上,自由地飞翔”,黑了- -
我们准备给这个摄像机类取名为CameraClass,这个类的功能非常适合用于三维飞行模拟类游戏(比如王牌空战)以及第一人称视角游戏(比如反恐精英(CS),穿越火线(CF))中。
二、开始设计摄像机类
首先,给大家介绍一下这个摄像机类的核心思想,那就是用四个分量:右分量(rightvector)、上分量(up vector)、观察分量(lookvector)和位置分量(position vector),来确定一个摄像机相对于世界坐标系的位置和朝向。并根据这四个分量计算出一个取景变换矩阵,完全取代之前的示例程序用D3DXMatrixLookAtLH创建的取景变换矩阵。
在世界坐标系中,这几个分量都是通过向量表示的,并且实际上他们为摄像机定义了一个局部坐标系。
其中,摄像机的左分量、上分量和观察分量定义了摄像机在世界坐标系中的朝向,因此他们也被称为方向向量。在通常的情况下,方向向量都是单位向量(模为1),并且两两之间相互垂直,也就是我们常说的标准正交。
其实,这三个向量我们完全可以理解为三维坐标系中的X,Y,Z轴。
另外,我们需要了解标准正交矩阵的一个重要性质,那就是标准正交矩阵的逆矩阵与其转置矩阵相等。
用上面提到的右分量(right vector)、上分量(up vector)、观察分量(look vector)和位置分量(position vector)这四个向量来描述摄像机的话,其中的位置分量其实我们可以把他看做一个描述位置的点,那么有用的就还3个分量,每个分量我们可以进行沿着其平移和绕着其旋转两种操作,那么我们可以想到的方式就是2x 3=6种,就是以下这六种运动方式:
● 沿着右分量平移
● 沿着上分量平移
● 沿着观察分量平移
● 绕着右分量旋转
● 绕着上分量旋转
● 绕着观察分量旋转
另外,我们新创建的这个摄像机类在我们的示例程序中完全“上位”了,取代了之前我们在Matrix_Set()中实现的取景变换和投影变换,所以在我们新的程序中,Matrix_Set()函数将不复存在,单单用一个摄像机类就行了。
根据上面的讲解,我们可以勾勒出这个CameraClass类的轮廓如下:
//=============================================================================// Name: CameraClass.h// Des: 一个封装了实现虚拟摄像机的类的头文件// 2013年 3月10日 Create by 浅墨 //=============================================================================#pragma once#include <d3d9.h>#include <d3dx9.h>class CameraClass{ private: //成员变量的声明 D3DXVECTOR3 m_vRightVector; // 右分量向量 D3DXVECTOR3 m_vUpVector; // 上分量向量 D3DXVECTOR3 m_vLookVector; // 观察方向向量 D3DXVECTOR3 m_vCameraPosition; // 摄像机位置的向量 D3DXVECTOR3 m_vTargetPosition; //目标观察位置的向量 D3DXMATRIX m_matView; // 取景变换矩阵 D3DXMATRIX m_matProj; // 投影变换矩阵 LPDIRECT3DDEVICE9 m_pd3dDevice; //Direct3D设备对象public: //一个计算取景变换的函数 VOID CalculateViewMatrix(D3DXMATRIX *pMatrix); //计算取景变换矩阵 //三个Get系列函数 VOID GetProjMatrix(D3DXMATRIX *pMatrix) { *pMatrix = m_matProj; } //返回当前投影矩阵 VOID GetCameraPosition(D3DXVECTOR3 *pVector) { *pVector = m_vCameraPosition; } //返回当前摄像机位置矩阵 VOID GetLookVector(D3DXVECTOR3 *pVector) { *pVector = m_vLookVector; } //返回当前的观察矩阵 //四个Set系列函数,注意他们都参数都有默认值NULL的,调用时不写参数也可以 VOID SetTargetPosition(D3DXVECTOR3 *pLookat = NULL); //设置摄像机的目标观察位置向量 VOID SetCameraPosition(D3DXVECTOR3 *pVector = NULL); //设置摄像机所在的位置向量 VOID SetViewMatrix(D3DXMATRIX *pMatrix = NULL); //设置取景变换矩阵 VOID SetProjMatrix(D3DXMATRIX *pMatrix = NULL); //设置投影变换矩阵public: // 沿各分量平移的三个函数 VOID MoveAlongRightVec(FLOAT fUnits); // 沿right向量移动 VOID MoveAlongUpVec(FLOAT fUnits); // 沿up向量移动 VOID MoveAlongLookVec(FLOAT fUnits); // 沿look向量移动 // 绕各分量旋转的三个函数 VOID RotationRightVec(FLOAT fAngle); // 绕right向量选择 VOID RotationUpVec(FLOAT fAngle); // 绕up向量旋转 VOID RotationLookVec(FLOAT fAngle); // 绕look向量旋转public: //构造函数和析构函数 CameraClass(IDirect3DDevice9 *pd3dDevice); //构造函数 virtual ~CameraClass(void); //析构函数};
三、关于向量计算的六个函数讲解
下面我们得先介绍一下Direct3D中,与向量计算有关的这六个函数,在写这个类的过程中有用到。
Ⅰ.D3DXVec3Normalize函数
首先我们来介绍对向量进行规范化的D3DXVec3Normalize函数 ,在MSDN中我们查到它原型如下:
D3DXVECTOR3* D3DXVec3Normalize( _Inout_ D3DXVECTOR3 *pOut, _In_ const D3DXVECTOR3 *pV);
这个函数的第一个参数为输出的结果,在第二个参数中填想要被规范化的向量就行了,一般我们把这两个参数填一摸一样的,就表示把填的这个向量规范化后的结果替代原来的向量。
举个例子就是这样写:
//其中的m_vLookVector为向量D3DXVec3Normalize(&m_vLookVector, &m_vLookVector);//规范化m_vLookVector向量
Ⅱ.D3DXVec3Cross函数
然后我们来介绍用于计算两个向量叉乘结果的D3DXVec3Cross函数,在MSDN中我们查到它原型如下:
D3DXVECTOR3* D3DXVec3Cross( _Inout_ D3DXVECTOR3 *pOut, _In_ const D3DXVECTOR3 *pV1, _In_ const D3DXVECTOR3 *pV2);
第一个参数依然是计算的结果。第二和第三两个参数当然就是填参加叉乘运算的两个向量了。
另外需要注意,D3DXVec3Cross函数的返回值和第一个参数pOut 参数是一样的,为指向D3DXVECTOR3 结构的两个向量叉乘结果。
依然是一个实例:
D3DXVec3Cross(&m_vRightVector, &m_vUpVector,&m_vLookVector); // 右向量与上向量垂直
Ⅲ.D3DXVec3Dot函数
下面我们来讲一下用于计算向量点乘的D3DXVec3Dot函数,在MSDN中我查到它的原型如下:
FLOAT D3DXVec3Dot( _In_ const D3DXVECTOR3 *pV1, _In_ const D3DXVECTOR3 *pV2);
这个函数倒不是和我们上面刚讲的那两个函数一样有个用于存放结果的pOut,它的结果就存放在返回值中,而两个参数就填参与运算的两个向量。
举一个实例吧:
pMatrix->_42 =-D3DXVec3Dot(&m_vUpVector, &m_vCameraPosition); // -P*U
Ⅳ.D3DXMatrixRotationAxis函数
接下来介绍可以创建一个绕任意轴旋转一定角度的矩阵的D3DXMatrixRotationAxis函数。其函数原型如下:
D3DXMATRIX* D3DXMatrixRotationAxis( _Inout_ D3DXMATRIX *pOut, _In_ const D3DXVECTOR3 *pV, _In_ FLOAT Angle);
第一个参数显然就填生成好的矩阵了,第二个参数填要绕着旋转的那根轴,第三个参数就填上要绕指定的轴旋转的角度。
依然是一个实例:
D3DXMatrixRotationAxis(&R,&m_vRightVector, fAngle);//创建出绕m_vRightVector旋转fAngle个角度的R矩阵
Ⅴ.D3DXVec3TransformCoord函数
下面我们看一个D3DXVec3TransformCoord函数,它可以根据给的的矩阵来变换一个向量,并且把变换后的向量规范化后输出来。这个函数原型如下:
D3DXVECTOR3* D3DXVec3TransformCoord( _Inout_ D3DXVECTOR3 *pOut, _In_ const D3DXVECTOR3 *pV, _In_ const D3DXMATRIX *pM);
第一个参数就是得到的结果向量了。第二个参数填要被变换的那个向量,而第三个参数填用于变换的矩阵。
依然是一个实例:
D3DXVec3TransformCoord(&m_vUpVector, &m_vCameraPosition, &R);//让m_vCameraPosition向量绕m_vRightVector旋转fAngle个角度
Ⅵ.D3DXVec3Length函数
最后我们介绍计算一个三维向量长度的D3DXVec3Length函数,这个函数原型如下:
FLOAT D3DXVec3Length( _In_ const D3DXVECTOR3 *pV);
唯一的一个参数填要计算长度的那个向量,返回值就是计算出的给定向量的三维长度。
依然是一个实例:
float length=D3DXVec3Length(&m_vCameraPosition);
四、计算取景变换矩阵
看完整个CameraClass类的轮廓,下面就准备开始讲解其中各个函数的实现代码应该怎么写。
首先就是我们最关心的取代了之前用D3DXMatrixLookAtLH无脑计算出取景变换矩阵,而自立门户的CalculateViewMatrix函数的写法。
为了讲解方便,我们令向量
表示位置向量,
向量
表示右向量,
向量
表示上向量,
向量
表示观察向量。
我们知道,取景变换所解决的其实就是世界坐标系中的物体在以摄像机为中心的坐标系中如何来表示的问题。这就是说,需要将世界坐标系中的物体随着摄像机一起进行变换,这样摄像机的坐标系就与世界坐标系完全重合了。
如下图所示:
上面的(a)图到(b)图,是一个平移的过程,而(b)图到(c)图则是一个旋转的过程。另外需要注意的一点是,空间中的物体也应该随着摄像机一同进行变换,这样摄像机中看到景物才没有变化。
我们的目的,就是通过一系列的矩阵变换,得到最终的取景变换矩阵V。
我们要得到取景变换矩阵V,说白了就是能够满足如下的条件:
pV=(0,0,0) 矩阵V将摄像机移动到世界坐标系的原点
rV=(1,0,0)矩阵V将摄像机的右向量与世界坐标系的x轴重合
uV=(