Direct3D的渲染过程大致可以分为以下几步
0. 模型表示
所有的三维实体都可以由有限的三角形mesh来逼近地表示
- D3D绘制图形的最小单位(图元)通常为三角形mesh,但D3D也支持点图元和线图元(例如用于粒子系统)
- 为了描述一个三角形mesh,我们需要一个顶点列表,D3D中的顶点和数学上的顶点有所区别,除了数学上顶点所有的位置信息外,D3D的顶点还包含了光照,颜色,法向量等信息,这些信息可以由用户自由组织,即FVF(灵活顶点格式)
#define COLORED_VERTEX D3DFVF_XYZ | D3DFVF_DIFFUSE
struct COLORED_VERTEX
{
FLOAT _x, _y, _z;
DWORD _color;
}
#define NORMAL_TEXED_VERTEX D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1
struct normalTexedVertex
{
FLOAT _x, _y, _z;
FLOAT _nx, _ny, _nz;
FLOAT _u, _v;
}
- 需要注意的是,FVF宏的定义顺序必须和顶点结构体中相应变量的顺序保持一致
1. 顶点缓存和索引缓存
可以通过这样一个顶点数组来描述以上图形
Vertex rect[6] = { {v0, v1, v2,},
{v0, v2, v3,}};
不难发现,这样有一些顶点是被重复缓存的,这样在处理较复杂场景时,会有大量顶点被重复缓存,造成空间的浪费,因而我们进一步采用索引缓存来解决这一问题
- 索引缓存即把所有顶点放在一个缓存中,再通过每个顶点的下标来访问这些顶点,如此可以避免顶点的重复缓存,于是上图可以描述为
Vertex vertices[4] = {v0, v1, v2, v3,};
DWORD indexes[6] = { {0, 1, 2,},
{0, 2, 3,},};
- 顶点缓存中,顶点的绕序不是随便定义的,在后面的背面消隐过程中会使用顶点的绕序作为操作的参考,因此在定义顶点缓存或索引缓存时需要注意绕序的问题
2. 摄像机
摄像机用于指定一个三维场景中的可见区域,视角(FoV),近裁剪面和远裁剪面构成的平头锥体即可视区,这个区域内的三维场景最终会被投影到投影面上,D3D中,投影面被定义为z=1的平面。
3. 局部坐标(Local space)
局部坐标描述了一个三维模型各个顶点的相对位置,使用局部坐标时不用考虑位置,大小,朝向等问题,有利于简化建模过程,大多数模型开始就处于局部坐标系中。
4. 世界坐标系(World Space)
作为一个三维场景,必须要处理各个场景物体的大小,相对位置,朝向等一系列问题,因而需要一个统一的坐标系,即世界坐标。
D3D的变换可以由IDirect3DDevice9::SetTransform方法实现,第一个参数表示变换类型,世界变化适用D3DTS_WORLD,第二个参数即相应的世界变换矩阵。 要注意的是,对于三维场景中的不同物体,世界变换矩阵并不一定相同。
//一个世界变换(平移变换)的例子
D3DXMATRIX objWorldMatrix;
D3DXMATRIXTranslation(&objWorldMatrix, 3.0f, 0.0f, 2.0f);
Device->SetTransform(D3DTX_WORLD, &objWorldMatrix);
drawObj();
5. 观察坐标系(View Space)
在三维场景中,几何体和摄像机是相对于世界坐标系定位的,为了简化运算,我们将摄像机平移到世界坐标远点的位置,朝向和世界坐标z轴正方向重合,为了保持摄像机视场的固定,场景中的其他物体也必须和摄像机进行同样的变换,这种变换即取景变换,变换后的物体位于观察坐标系内
- 取景变换的矩阵由D3DXMatrixLookAtLH函数计算得到
D3DXMATRIX *D3DMatrixLookAtLH(D3DXMATRIX *pOUt,
const D3DXVECTOR3 *pEye,
const D3DXVECTOR3 targetPoint,
const D3DXVECTOR3 *pUp);
- pEye指定摄像机在世界坐标系中的坐标
- pAt指定摄像机观察点在世界坐标系中的坐标
- pUp定义了世界坐标系中“上”方向
6. 背面消隐
每个多边形都有朝向摄像机的一面和偏离摄像机的一面,因为摄像机不能进入场景中实体的内部,所以摄像机不可能观察到多边形的背面,因此我们在绘图时可以将多边形的背面删除,以改善性能,这一过程即背面消隐
- 为了实现北面消隐,D3D需要区分哪些多边形是正面朝向摄像机的,哪些是背面朝向摄像机的,默认情况下,D3D将观察坐标系中顶点绕序为顺时针的mesh认为是正面朝向的,反之认为是背面朝向的进行消隐,这一默认值可以修改RenderState的D3DRS_CULLMODE来改变
Device->SetRenderState(D3DRS_CULLMODE, Value);
- value取值如下
- D3DCULL_NONE 禁用背面消隐
- D3DCULL_CW 对顺时针绕序的三角形mesh进行消隐
- D3DCULL_CCW 对逆时针绕序的三角形mesh进行消隐
7. 光照
光源和几何体一样经过多次变换变换到观察坐标系内,可以通过固定功能流水线和可编程流水线实现光照
8. 裁剪
视域体决定了一个几何体是否会出现在摄像机的视场中,裁剪过程会将视域之外的几何体剔除以改善性能
- 裁剪有三种情况,取决于几何体和视域体的关系
- 几何体完全在视域内部,保留并送入下一阶段处理
- 几何体完全在视域外部,直接剔除
- 几何体部分在视域内部,部分在外部,将几何体拆分成两部分,视域外的部分丢弃,视域内的部分被保留并送入下一阶段处理
9. 投影
在观察坐标系中获取3D场景的2D表示这一过程叫投影,投影有多种方式,我们使用透视投影,用于使用2D图象表示3D场景,使用下列函数创建投影矩阵
D3DXMATRIX *D3DXMatrixPerspectiveFovLH(
D3DXMATRIX *pOUT, //returns matrix
FLOAT fovY, //vertical field of view
FLOAT AspectRatio, //width/height
FLOAT zn, //distance to near panel
FLOAT zf //distance to far panel
);
- 其中AspectRatio用于修正投影窗口和显示屏纵横比不同造成的畸变
10. 视口变换
视口变换是将顶点坐标从投影窗口变换到屏幕上的一个矩形区域中,视口指的就是这个矩形区域
- D3D中,视口用D3DVIEWPORT9结构体表示
typedef struct D3DVIEWPORT9
{
DWORD x;
DWORD y;
DWORD width;
DWORD height;
DWORD minZ;
DWORD maxZ;
}D3DVIEWPORT9;
- 一但填充好D3DVIEWPORT9结构,可以用下列方式设置视口,设置好视口后,D3D会自动为我们完成视口变换
D3DVIEWPORT9 vp = {0, 0, 640 ,480, 0, 1};
device->SetViewport(&vp);
11. 光栅化
顶点坐标转换为屏幕坐标后,我们就有了一个在二维平面上的三角形列表,光栅化就是为屏幕上每个像素计算颜色值,以绘制这些三角形
Written with StackEdit.