一、 局部坐标系
局部坐标系或建模坐标系,是用于定义构成物体的三角形单元列表的坐标系。采用局部坐标系的优势体现在他 / 她可以简化建模过程。在物体的局部坐标系中建模要比直接在世界坐标系中容易得多。例如,局部坐标系允许我们构建模型时无需考虑位置、大小或对于场景中其他物体的朝向。
二、 世界坐标系
构建各种物体模型时,每个模型都位于其自身的局部坐标系中。我们还需要将这些物体组织在一起构成世界坐标系(全剧坐标系)中的场景。位于局部坐标系中的物体通过一个称为世界变换的运算(过程)变换得到世界坐标系中,该变换通常包括平移、旋转以及比例运算,分别用于设定该物体在世界坐标系中的位置、方向以及模型的大小。世界变换依据位置、大小和方向的相对关系将所有物体放置在世界坐标系中。
世界变换用一个矩阵来表示,并由 Direct3D 通过 Idirect3Ddevice9::SetTransform 方法来加以应用,该方法的第一个参数表示变换的类型,若要进行世界变换,可设为 D3DTS_WORLD ,第二个参数表示所采用的世界变换矩阵。
实际应用中,物体往往需要同时做平移、方向和比例的运算。
// 创建立方体的世界矩阵(一个平移矩阵) D3DXMATRIX cubeWorldMatrix; D3DXMatrixTranslation(&cubeWorldMatrix, -3.0f, 2.0f, 6.0f);
// 创建球体的世界矩阵(一个平移矩阵) D3DXMATRIX sphereWorldMatrix; D3DXMatrixTranslation(&sphereWorldMatrix, 5.0f, 0.0f, -2.0f);
// 变换立方体,然后绘制它 Device->SetTransform(D3DTS_WORLD, &cubeWorldMatrix); drawCube(); // draw the cube
// 因为球体使用一个不同的世界变换,我们必须更改世界矩阵为球体的~, // 如果不更改,球体将绘制在上一个世界矩阵的位置上(立方体的世界矩阵) Device->SetTransform(D3DTS_WORLD, &sphereWorldMatrix); drawSphere(); // 绘制球体 |
三、 观察坐标系
在世界空间中,几何体和摄像机都是相对世界坐标系定义的。但是当摄像机的位置和朝向任意时,投影变换及其他类型的变换就略显困难或效率不高。为了简化运算,我们将摄像机交换至世界坐标系的原点,并将其旋转,使摄像机的光轴与世界坐标系z轴正方向一致。同时,世界空间中的所有几何体都随着摄像机一同进行变换,以保证摄像机的视场恒定。这种变换称为取景变换,我们称变换后的几何体位于观察坐标系中。 视图坐标的变换矩阵可以通过如下的 D3DX 函数计算得到:
D3DXMATRIX *D3DXMatrixLookAtLH( D3DXMATRIX* pOut, // 指向返回的视图矩阵 CONST D3DXVECTOR3* pEye, // 照相机在世界坐标系的位置 CONST D3DXVECTOR3* pAt, // 照相机在世界坐标系的目标点 CONST D3DXVECTOR3* pUp // 世界坐标系的上方向 (0, 1, 0) ); |
pEye 参数指定照相机在世界坐标系中的位置, pAt 参数指定照相机所观察的世界坐标系中的一个目标点, pUp 参数指定 3D 世界中的上方向,通常设 Y 轴正方向为上方向,即取值为( 0 , 1 , 0 )。
例如:假设我们要把照相机放在点( 5 , 3 , -10 ),并且目标点为世界坐标系的中点( 0 , 0 , 0 ),我们可以这样获得视图坐标系变换矩阵:
D3DXVECTOR3 position(5.0f, 3.0f, – 10.0f ); D3DXVECTOR3 targetPoint(0.0f, 0.0f, 0.0f); D3DXVECTOR3 worldUp(0.0f, 1.0f, 0.0f);
D3DXMATRIX V; D3DXMatrixLookAtLH(&V, &position, &targetPoint, &worldUp); |
视图坐标系变换也是通过 IDirect3DDevice9::SetTransform 来实现的,只是要将变换类型设为 D3DTS_VIEW ,如下所示:
Device->SetTransform(D3DTS_VIEW, &V); |
四、 背面消隐
每个多边形都有两个侧面,我们将其中一个侧面标记为正面,另一个侧面标记为背面。通常,多边形的背面是不可见的。这是由于场景中的多数物体是封闭体,而且摄像机总是禁止进入物体内部的实体空间的。所以,摄像机是不可能观察到多边形的侧面的。
当然,为了实现背面消隐,Direct3D需要区分哪些是多边形是正面朝向的,哪些是背面朝向的。默认状态下,Direct3D认为顶点排列顺序为顺时针(观察坐标系中)的三角形单元是正面朝向的,顶点排列顺序为逆时针(观察坐标系中)的三角形单元是背面朝向的。
注意:之所以强调是在观察坐标系中,是因为如果是一个多边形旋转180度,其绕序将会是完全相反的。所以一个在局部坐标系中定义为顺时针绕序的三角形单元旋转变换到观察坐标系中时,未必仍保持顺时针绕序。
如果由于某些原因默认的消隐方式不能满足应用的要求,我们可通过修改绘制状态D3DRS_CULLMODE来达到目的。
Device->SetRenderState(D3DRS_CULLMODE, Value); |
八、 视口变换
视口变换的任务是将顶点坐标从投影窗口转换到屏幕的一个矩形区域中,该矩形区域称为视口。在游戏中,视口通常是整个矩形屏幕区域。但视口也可以是屏幕的一个子区域或客户区(如果程序运行在窗口模式下)。矩形的视口是相对窗口来描述的,因为视口总是处于窗口内部并且其位置要用窗口坐标指定。
在 Direct3D 中,视口矩形通过 D3DVIEWPORT9 结构来表示。它的定义如下:
typedef struct _D3DVIEWPORT9 { DWORD X; DWORD Y; DWORD Width; DWORD Height; DWORD MinZ; DWORD MaxZ; } D3DVIEWPORT9; |
前四个参数定义了视口矩形与其所在窗口的关系。 MinZ 成员指定最小深度缓冲值, MaxZ 指定最大深度缓冲值。 Direct3D 使用的深度缓冲的范围是 0~1 ,所以如果不想做什么特殊效果的话,将它们分别设成相应的值就可以了。
一旦我们填充完 D3DVIEWPORT9 结构后,就可以如下设视口:
D3DVIEWPORT9 vp{ 0, 0, 640, 480, 0, 1 }; Device->SetViewport(&vp); |
Value 可以是如下一个值:
l D3DCULL_NONE ——完全不使用背面拣选
l D3DCULL_CW ——拣选顺时针方向环绕的三角形
l D3DCULL_CCW ——拣选逆时针方向环绕的三角形,这是默认值。
五、 光照
光源是在世界坐标系中定义的,但必须经取景变换至观察坐标系方可使用。在观察坐标系中,光源照亮了场景中的物体,从而可以获得较为逼真的显示效果。
六、 裁剪
现在我们想将那些位于视域体外的几何体剔除掉,这个过程称为裁剪。一个三角形单元与视域体的相对位置有一下3种:
l 完全包含——三角形完全在可视体内,这会保持不变,并进入下一级
l 完全在外——三角形完全在可视体外部,这将被拣选
l 部分在内(部分在外)——三角形一部分在可视体内,一部分在可视体外,则三角形将被分成两部分,可视体内的部分被保留,可视体之外的则被拣选
七、 投影
观察坐标系中,我们的任务是获取3D场景中的2D表示。从n维到n-1维的过程称为投影。实现投影有多种方式,但我们只对其中的一种感兴趣,即透视投影。透视投影会产生“透视缩短”的视觉效果,即近大远小。这类投影使得我们可以用2D图像表示3D场景。
投影变换的实质就是定义可视体 , 并将可视体内的几何图形投影到投影窗口上去。 投影矩阵的计算太复杂了,这里我们不会给出推导过程,而是使用如下的 Direct3D 函数通过给出平截头体的参数来求出投影矩阵。
D3DXMATRIX *D3DXMatrixPerspectiveFovLH( D3DXMATRIX* pOut, // 返回的投影矩阵 FLOAT fovY, // 用弧度表示的视野角度 vertical field of view angle in radians FLOAT Aspect, // 宽高比 FLOAT zn, // 前 裁剪 面距离 FLOAT zf // 后 裁剪 面距离 ); |
( fovY 定义镜头垂直观察范围,以弧度为单位。对于这个参数,下面是我的理解:如果定义为 D3DX_PI/4 ( 90 度角),那么就是表示以摄像机的观察方向为平分线,上方 45 度角和下方 45 度角就是摄像机所能看到的垂直范围了。嗯,可以想象一下自己的眼睛,如果可以把自己眼睛的 fovY 值设为 D3DX_PI/2 ( 180 度角),那么我们就可以不用抬头就看得见头顶的东西了。如果设为 D3DX_PI 的话。。。我先编译一下试试( building… )。哈哈,结果啥也看不见。很难想象如果自己能同时看到所有方向的物体,那么将是一个怎样的画面啊)
Aspect 参数为投影平面的宽高比例值,由于最后都为转换到屏幕上,所以这个比例一般设为屏幕分辨率的宽和高的比值(见 2.3.8 节)。如果投影窗口是个正方形,而我们的显示屏一般都是长方形的,这样转换后就会引起拉伸变形。
我们还是通过调用 IDirect3DDevice9::SetTranform 方法来进行投影变换,当然,要把第一个投影类型的参数设为 D3DTS_PROJECTION 。下面的例子基于一个 90 度视角、前裁剪面距离为 1 、后裁剪面距离为 1000 的平截头体创建投影矩阵:
D3DXMATRIX proj; D3DXMatrixPerspectiveFovLH( &proj, PI * 0.5f, (float )width / (float )height, 1.0, 1000.0f); Device->SetTransform(D3DTS_PROJECTION, &proj); |