什么是矩阵?这个概念还真不好解释,不过学过线性代数的人肯定都知道矩阵长什么样,那我在这里就不解释了。在D3D中,定义矩阵的结构体是D3DMATRIX:
typedef struct _D3DMATRIX {
union {
struct {
float _11, _12, _13, _14;
float _21, _22, _23, _24;
float _31, _32, _33, _34;
float _41, _42, _43, _44;
};
float m[4][4];
};
} D3DMATRIX;
看这个结构的样子,你就应该很清楚怎么使用它来定义一个矩阵了吧。在这里我顺便说一下C++中union的特性吧。像上面定义的结构体所示,在union里面有两个部分,一个是结构体,另一个是二维数组,它有16个元素。在union中,所有的成员都是共用一个内存块的,这是什么意思呢?继续看上面的代码,结构体中的成员_11和成员m数组的第一个元素是共用一个内存空间,即它们的值是一样的,你对_11赋值的同时也对m[0][0]进行了赋值,_11和m[0][0]的值是一样的。这样有什么好处呢?比如你定义了一个矩阵变量D3DMATRIX mat;你想访问矩阵中第三行第四列的元素,可以这样做:mat._34;另外也可以这样:mat.m[2][3](数组是从位置0开始储存的哦)。看起来使用后者比较麻烦,不过当你把中括号里面的数换成i和j,使用mat.m[i][j]来访问矩阵中的元素,你就应该知道它的好处了吧。
实际上直接使用D3DMATRIX的情况不多,因为在D3DX中有个更好的结构体,那就是D3DXMATRIX。和D3DXVECTOR3相似,D3DXMATRIX是从D3DMATRIX继承过来的,它重载了很多运算符,使得矩阵的运算很简单。矩阵的运算方法我不打算多说了,下面只介绍和矩阵性质有关的三个函数。
产生一个单位矩阵:D3DXMATRIX *D3DXMatrixIdentity(
D3DXMATRIX *pout);//返回结果
求转置矩阵:D3DXMATRIX *D3DXMatrixTranspose(
D3DXMATRIX *pOut,//返回的结果
CONST D3DXMATRIX *pM );//目标矩阵
求逆矩阵:D3DXMATRIX *D3DXMatrixInverse(
D3DXMATRIX *pOut,//返回的结果
FLOAT *pDeterminant,//设为0
CONST D3DXMATRIX *pM );//目标矩阵
至于什么是单位矩阵,什么是转置矩阵,什么是逆矩阵我就不说了,可以看一下线性代数的书,一看就明白了。简单的加减乘除法可以使用D3DXMATRIX结构体里面重载的运算符。两个矩阵相乘也可以用函数来实现,这将在接下来的矩阵变换中讲到。
矩阵变换
矩阵的基本变换有三种:平移,旋转和缩放。
平移:
D3DXMATRIX *D3DXMatrixTranslation(
D3DXMATRIX* pOut,//返回的结果
FLOAT x, //X轴上的平移量
FLOAT y, //Y轴上的平移量
FLOAT z) //Z轴上的平移量
绕X轴旋转:
D3DXMATRIX *D3DXMatrixRotationX(
D3DXMATRIX* pOut, //返回的结果
FLOAT Angle //旋转的弧度
);
绕Y轴旋转:
D3DXMATRIX *D3DXMatrixRotationY(
D3DXMATRIX* pOut, //返回的结果
FLOAT Angle //旋转的弧度
);
绕Z轴旋转:
D3DXMATRIX *D3DXMatrixRotationZ(
D3DXMATRIX* pOut, //返回的结果
FLOAT Angle //旋转的弧度
);
绕指定轴旋转:
D3DXMATRIX *D3DXMatrixRotationAxis(
D3DXMATRIX *pOut,//返回的结果
CONST D3DXVECTOR3 *pV,//指定轴的向量
FLOAT Angle//旋转的弧度
);
缩放:
D3DXMATRIX *D3DXMatrixScaling(
D3DXMATRIX* pOut, //返回的结果
FLOAT sx, //X轴上缩放的量
FLOAT sy, //Y轴上缩放的量
FLOAT sz //Z轴上缩放的量
);
好了,这章就写这么一些东西。如果你觉得好像没学到什么的话,可能是因为不知道上面的知识有什么用吧。下一章我将介绍世界空间、视图空间(也叫摄像机空间)以及投影,这三者对应的是世界矩阵、视图矩阵和投影矩阵。搞清楚这三个空间的作用后,我们就可以利用这章的知识使我们的3D世界动起来了。
无论计算机图形技术如何发展,只要它以二维的屏幕作为显示介质,那么它显示的图像即使多么的有立体感,也还是二维的。有时我会想,有没有以某个空间作为显示介质的的可能呢,不过即使有,也只能是显示某个范围内的图像,不可能有无限大的空间作为显示介质,如果有,那就是现实世界了。
既然显示器的屏幕是二维的,那么我们就要对图像作些处理,让它可以欺骗我们的眼睛,产生一种立体的真实感。在D3D中,这种处理就是一系列的空间变换,从模型空间变到世界空间,再变到视图空间,最后投影到我们的显示器屏幕上。
世界空间与世界矩阵
什么是模型空间呢?每个模型(3D物体)都有它自己的空间,空间的中心(原点)就是模型的中心。在模型空间里,只有模型上的不同点有位置的相对关系。那什么是世界空间呢?世界就是物体(模型)所存在的地方。当我们把一个模型放进世界里面去,那么它就有了一个世界坐标,这个世界坐标是用来标记世界中不同的模型所处的位置的。在世界空间里,世界的中心就是原点(0, 0, 0),也就是你显示器屏幕中间的那一点。我们可以在世界空间里摆放很多个模型,并且设置它们在世界空间中的坐标,这样模型与模型之间就有了相对的位置。
世界矩阵有什么用呢?我们可以利用它来改变世界空间的坐标。这样,在世界空间里面的模型就可以移动、旋转和缩放了。
我们可以使用上一章末尾所讲的那几个函数来产生世界矩阵。例如产生一个绕X轴旋转的转阵:D3DXMatrixRotationX(&matrix,1)。利用matrix这个矩阵,就可以使世界空间中的物体绕X轴转动1弧度。
可以结合后面的例子来理解世界矩阵。
视图空间与视图矩阵
世界空间建立起来后,我们不一定能看到模型,因为我们还没有“眼睛”啊。在视图空间里,我们可以建立我们在三维空间中的眼睛:摄像机。我们就是通过这个虚拟的摄像机来观察世界空间中的模型的。所以视图空间也叫摄像机空间。
要建立起这个虚拟的摄像机,我们需要一个视图矩阵,产生视图矩阵的一个函数是:
D3DXMATRIX *D3DXMatrixLookAtLH(
D3DXMATRIX* pOut,
CONST D3DXVECTOR3* pEye,
CONST D3DXVECTOR3* pAt,
CONST D3DXVECTOR3* pUp
);
pOut:返回的视图矩阵指针
pEye:设置摄像机的位置
pAt:设置摄像机的观察点
pUp:设置方向“上”
这个函数的后缀LH是表示左手系的意思,聪明的你一定能够猜出肯定有个叫D3DXMatrixLookAtRH的函数。至于左手系和右手系的区别,这里就不多说了,记住左手系中的Z正方向是指向显示器里面的就行了。只能弄懂了视图矩阵的含义,建立视图矩阵完成可以不依赖函数,自己手动完成。视图矩阵其实就是定义了摄像机在世界空间中的位置、观察点、方向“上”这些信息。
可以结合后面的例子来理解视图矩阵。
投影与投影矩阵
定义投影矩阵很像是定义摄像机的镜头,下面看它的函数声明:
D3DXMATRIX *D3DXMatrixPerspectiveFovLH(
D3DXMATRIX* pOut,
FLOAT fovY,
FLOAT Aspect,
FLOAT zn,
FLOAT zf
);
pOut:返回的投影矩阵指针
fovY:定义镜头垂直观察范围,以弧度为单位。对于这个参数,下面是我的理解:如果定义为D3DX_PI/4(90度角),那么就是表示以摄像机的观察方向为平分线,上方45度角和下方45度角就是摄像机所能看到的垂直范围了。嗯,可以想象一下自己的眼睛,如果可以把自己眼睛的fovY值设为D3DX_PI/2(180度角),那么我们就可以不用抬头就看得见头顶的东西了。如果设为D3DX_PI的话。。。我先编译一下试试(building…)。哈哈,结果啥也看不见。很难想象如果自己能同时看到所有方向的物体,那么将是一个怎样的画面啊。
Aspect:设置纵横比。如果定义为1,那么所看到的物体大小不变。如果定义为其它值,你所看到的物体就会变形。不过一般情况下这个值设为显示器屏幕的长宽比。(终于明白为什么有些人会说电视上的自己看起来会比较胖了……)
zn:设置摄像机所能观察到的最远距离
zf:设置摄像机所能观察到的最近距离
·一小段代码
请看以下代码片段:
D3DXMATRIXA16 matWorld;
D3DXMatrixIdentity( &matWorld );
D3DXMatrixRotationX( &matWorld, timeGetTime()/1000.0f );
g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );
D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
D3DXMATRIXA16 matView;
D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
D3DXMATRIXA16 matProj;
D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/2, 1.0f, 1.0f, 500.0f );
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
通过上面三个转换,就建立了一个我们可以通过显示器屏幕来观察的3D世界。上面三个转换分别是:
从模型空间到世界空间的世界转换:SetTransform( D3DTS_WORLD, &matWorld )。
从世界空间到视图空间的视图转换:SetTransform( D3DTS_VIEW, &matView )。
从视图空间到到屏幕的投影转换:SetTransform( D3DTS_PROJECTION, &matProj )。
现在来观察matWorld,matView,matProj这三个矩阵的特点。我们使用D3DXMatrixRotationX函数来产生了一个绕X轴旋转的转换矩阵,通过设置世界转换,在世界空间里面的物体将绕X轴作旋转。然后我们定义了三个三维的向量,用来设置摄像机的位置,观察方向和定义方向“上”。使用D3DXMatrixLookAtLH函数来把这三个向量放进视图矩阵里面去。然后通过设置视图转换,我们就建立了一个虚拟的摄像机。最后通过D3DXMatrixPerspectiveFovLH函数,我们得到一个投影矩阵,用来设置虚拟摄像机的镜头。
我还是解释一下上面说的那个方向“上”是什么东西吧。这个“上”其实指的就是摄像机在刚建立的时候是如何摆放的,是向左边侧着摆,还是向右边侧着摆,还是倒过来摆,都是通过这个方向“上”来指定的。按照正常的理解,摄像机的“上”方向就是Y轴的正方向,但是我们可以指定方向“上”为Y轴的负方向,这样世界建立起来后就是颠倒的了。不过颠倒与否,也是相对来说的了,试问在没有引力的世界中,谁能说出哪是上哪是下呢?是不是看得一头雾水啊?只要自己亲手改变一下这些参数,就可以体会到了。
设置上面三个转换的先后顺序并不一定得按照世界到视图到投影这个顺序,不过习惯上按照这种顺序来写,感觉会好一点。
使用矩阵相乘来创建世界矩阵
在世界空间中的物体运动往往是很复杂的,比如物体自身旋转的同时,还绕世界的原点旋转。怎么实现这种运动呢?通过矩阵相乘来把两个矩阵“混”在一起。现在我们假设某一物体建立在世界的原点上,看以下代码:
//定义三个矩阵
D3DXMATRIX matWorld, matWorldY,matMoveLeft;
//一个矩阵把物体移到(30,0,0)处,一个矩阵使物体绕原点(0,0,0)旋转
D3DXMatrixTranslation(&matMoveRight,30,0,0);
D3DXMatrixRotationY(&matWorldY, radian/1000.0f);
//第一次矩阵相乘。先旋转,再平移
D3DXMatrixMultiply(&matWorld, &matWorldY, &matMoveRight);
//第二次矩阵相乘。在第一次矩阵相乘的结果上,再以Y轴旋转
D3DXMatrixMultiply(&matWorld, &matWorld, &matWorldY);
//设置世界矩阵
m_pD3DDevice->SetTransform( D3DTS_WORLD, &matWorld );
近期评论