本章的主题是渲染管线。渲染管线是什么意思呢?我去查了一下,百度百科里说,
渲染管线也称为渲染流水线或像素流水线或像素管线,是显示芯片内部处理图形信号相互独立的的并行处理单元。在某种程度上可以把渲染管线比喻为工厂里面常见的各种生产流水线,工厂里的生产流水线是为了提高产品的生产能力和效率,而渲染管线则是提高显卡的工作能力和效率。
其作用是创建为 3D世界进行几何描述的 2D 图形并设定一个虚拟照相 机确定这个世界中哪一部分将被透视投影到屏幕上。
表现模型
3D世界中最基本的单元就是三角形,三角形可由其三个顶点坐标唯一确定。
三维坐标中每个点有x,y,z构成,而在Direct3D中,一个点不仅包含三个坐标,
还可以包含点的颜色及法线向量。开发者可以自己定义点的结构。如下所示:
struct NormalTexVertex
{
float _x, _y, _z; // 位置
float _nx, _ny, _nz; // 法线向量
float _u, _v; // 纹理坐标
};
三角形是构建3D物体的基本图像,为了构造物体,我们创建三角形列表(triangle list)来描述物体的形状和轮廓。例如,为了构造一个矩形, 我们把他分为两个三角形。
Vertex rect[6] = {v0, v1, v2 //三角形1
v0, v2, v3}; // 三角形2
注意:指定三角形顶点的顺序是很重要的,将会按一定顺序环绕排列
3D物体中的三角形经常有许多的共用点,这造成了点的重用。为了节省空间,direct3D中引入了索引的概念。他的工作方式是,我们创建一个顶点列表和索引列表。顶点列表中包含所有没有重用的点,索引列表用顶点列表中的点的索引来区分三角形由哪三个点构成。
上面的示例如果用索引来表示,顶点列表为Vertex vertexList[4] = {v0, v1, v2, v3}; 索引列表为
WORD indexList[6] = {0, 1, 2 //三角形1
0, 2, 3}; // 三角形2
虚拟照相机
三维物体在屏幕上显示时,需要转换成2d的图像,在3d世界中照相机被放置和定向,并定义可视体。
渲染管线
一旦我们描述几何学上的 3D场景和设置了虚拟照相机,我们要把这个场景转换成 2D 图象显示在 显示器上。这一系列必须完成的操作就叫做渲染管线。
渲染管线中的许多级都是从一个坐标系到两一个坐标系的几何转换,这些转换都是通过矩阵变换来实现。实用Direct3D进行矩阵转换,我们唯一要做的就是提供从以一个系统变换到另一个系统的变换矩阵就可以了。我们使用IDirect3dDevice9::SetTransform方法提供变换矩阵,它输入一个表示变换矩阵的参数和一个变换矩阵。例如,为了进行一个从自身坐标系到世界坐标系的转换,我们可以这样写:
Device->SetTransform(D3DTS_WORLD, &worldMatrix);
自身坐标系
自身坐标系又叫建模空间。自身坐标系简化了建模的过程。在自身坐标系的建模不像再世界坐标系中要考虑本物体相对于其他物体的位置、大小、方向关系。
世界坐标系
一旦我们构造了各种模型,他们都在自己的自身坐标系中,我们需要把他们都放到同一个世界坐标中。物体从自身坐标系到世界坐标系的变换叫做世界变换。
世界变换由一个矩阵表示,并且在 Direct3D 中调用IDirect3DDevice9::SetTransform方法设置它, 记住将转换类型设为 D3DTS_WORLD。例如我们要在世界坐标系中放置一个立方体定位在(-3,2,6) 和一个球体定位在(5,0,-2),我们可以这样写程序:
//创建立方体的世界矩阵(一个平移矩阵)
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(); // 绘制球体
视图坐标系(View Space)
世界坐标系中的所有物体都将随着照相机的变换而做相同的变换。这个变换就叫做视图坐标系变换(view spacetransformation)
视图坐标的变换矩阵可以通过如下的D3DX 函数计算得到:
D3DXMATRIX *D3DXMatrixLookAtLH(
D3DMATRIX* pOut, // 指向返回的视图矩阵
CONST D3DXVECTOR* pEye, // 照相机在世界坐标系的位置
CONST D3DXVECTOR* pAt, // 照相机在世界坐标系中的目标点
CONST D3DXVECTOR* pUp // 世界坐标系的上方向(0, 1, 0)
);
pEye 参数指定照相机在世界坐标系中的位置,pAt 参数指定照相机所观察的世界坐标系中的一个
目标点,pUp 参数指定3D 世界中的上方向,通常设Y 轴正方向为上方向,即取值为(0,1,0)。
视图坐标系变换也是通过IDirect3DDevice9::SetTransform 来实现的,只是要将变换类型设为
D3DTS_VIEW,如下所示:
Device->SetTransform(D3DTS_VIEW, &V);
背面拣选(Backface Culling)
一个多边形有两个表面,我们将一个标为正面,一个为背面。通常,后表面总是不可见的,这是
因为场景中大多数物体是密封的。例如盒子、圆柱体、箱子、characters 等,并且我们也不能把照相机
放入物体的内部。因此照相机永不可能看到多边形的背面。这是很重要的,如果我们能看背面,那么
背面拣选就不可能工作。
Direct3D 将通过拣选(即删除多余的处理过程)背面多边形来提高效率,这种方法就叫背面拣选
当然,为了完成这项工作,Direct3D 需要知道哪个多边形是正面,哪个是背面。Direct3D 中默认
顶点以顺时针方向(在观察坐标系中)形成的三角形为正面,以逆时针方向形成的三角形为背面。
如果我们不想使用默认的拣选状态,我们可以通过改变D3DRS_CULLMODE 来改变渲染状态:
Device->SetRenderState(D3DRS_CULLMODE, Value);
Value 可以是如下一个值:
D3DCULL_NONE——完全不使用背面拣选
D3DCULL_CW——拣选顺时针方向环绕的三角形
D3DCULL_CCW——拣选逆时针方向环绕的三角形,这是默认值。
投影(Projection)
视图坐标系的主要任务就是将3D 场景转化为2D 图像表示。这种从n 维转换成n-1 维的过程就叫
做投影。投影的方法有很多种,但是我们只对一种特殊的投影感兴趣,那就是透视投影。因为透视投
影可以使离照相机越远的物体投影到屏幕上后就越小,这可以使我们把3D 场景更真实的转化为2D
图像。图2.14 展示了一个3D 空间中的点是如何通过透视投影到投影窗口上去的。
投影变换的实质就是定义可视体,并将可视体内的几何图形投影到投影窗口上去。
使用如下的Direct3D 函数通过给出平截头体的参数来求出投影矩阵。
D3DXMATRIX *D3DXMatrixPerspectiveFovLH(
D3DXMATRIX* pOut, // 返回的投影矩阵
FLOAT fovY, // 用弧度表示的视野角度vertical field of view angle in radians
FLOAT Aspect, // 宽高比
FLOAT zn, // 前裁剪面距离
FLOAT zf // 后裁剪面距离
);
我们还是通过调用IDirect3DDevice9::SetTranform 方法来进行投影变换,当然,要把第一个投影
类型的参数设为D3DTS_PROJECTION。下面的例子基于一个90 度视角、前裁剪面距离为1、后裁剪
面距离为1000 的平截头体创建投影矩阵:
D3DXMATRIX *D3DXMatrixPerspectiveFovLH(
D3DXMATRIX* pOut, // 返回的投影矩阵
FLOAT fovY, // 用弧度表示的视野角度vertical field of view angle in radians
FLOAT Aspect, // 宽高比
FLOAT zn, // 前裁剪面距离
FLOAT zf // 后裁剪面距离
);
D3DXMATRIX proj;
D3DXMatrixPerspectiveFovLH(
&proj, PI * 0.5f, (float)width / (float)height, 1.0, 1000.0f);
Device->SetTransform(D3DTS_PROJECTION, &proj);
视口变换(ViewportTransform)
视口变换主要是转换投影窗口到显示屏幕上。通常一个游戏的视口就是整个显示屏,但是当我们
以窗口模式运行的时候,也有可能只占屏幕的一部分或在客户区内。视口矩形是由它所在窗口的坐标
系来描述的,如图2.16。
在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);
这样,Direct3D 就会自动为我们处理视口变换。现在还是给出视口变换矩阵作为参考: