游戏程序设计之渲染管道
发表于2018-07-19
本篇文章主要给大家介绍下渲染管道的一些基本概念,希望对初学者能提供一定的帮助。
1、顶点格式
一个场景是由物体或者模型组成,物体可以通过三角形网格进行近似的描述。网格中的三角形是物体的组成部分,通常下列属于都是描述网格中三角形:多边形,图元,网格几何体。我们可以通过指定三角形的三个顶点来描述三角形。
另外在DX中顶点还可以有颜色属性以及法线向量属性;D3D为用户提供了构建顶点格式的灵活性,换句话说就是用户可以设置顶点的组成属性。
创建一个自定义的顶点格式,首先需要创建一个顶点数据结构,用于保存顶点数据。举个例子,下面两个不同的顶点格式,一种顶点格式保存了位置和颜色信息。一种保存了位置和法向量和纹理坐标信息。
struct ColorVertex { float _x,_y,_z;//位置 DWORD _color;//颜色 }
struct ColorVertex { float _x,_y,_z;//位置 float _nx,_ny,_nz;//法向量 float _u,_v;//纹理坐标 }
定义完顶点结构后,需要使用灵活顶点格式(FVF)标志来描述顶点的组成格式,例如对于前面定义的顶点结构可以通过如下的灵活格式进行描述。
1)要描述包含位置属性和散射(diffuse)颜色属性的顶点结构时,灵活顶点格式标志为:
#define FVF_COLOR (D3DFVF_XYZ|D3DFVF_DIFFUSE)
#define FVF_COLOR (D3DFVF_XYZ|D3DFVF_DIFFUSE)
2)描述包含位置,法向量,纹理的顶点结构时,标志为:
#define FVF_NORMAL_TEX1 (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1)
有一个必须要考虑的限制是:在定义灵活顶点格式标志中,方案与属性的描述顺序必须与顶点数据结构中属性描述的顺序相同。
2、三角形
三角形是构成三维某型的基本元素,通过创建三角形的李彪爱描述物体的形状和轮廓来创建一个物体。
三角形李彪中包含了每个需要渲染的三角形的数据。
例如,如果需要构建一个矩形,可以将矩形分成两个矩形,并且指定所有三角形的顶点。
Vertex rect[6]={v0,v1,v2,//三角形0
v0,v2,v3};//三角形1
3、索引
通常组成三维物体的三角形都会使用许多相同的顶点,在矩形中虽然只重复使用了两个顶点,但是被重复使用的顶点数将随着模型的细节程度以及复杂度的增加而增加。
为了解决这个问题,就引入了索引的概念,索引的工作原理是:在创建一个顶点坐标的同时创建一个索引列表。顶点列表中存储了所有独立顶点的数据,索引列表中存储所有三角形的顶点对应独立顶点数据在顶点列表中的索引。
对于上一个例子通过不同的索引来定义顶点如何组合以组成三角形:注意类型。
WORD indexList[6]={0,1,2,//三角形0;
0,2,3}//三角形1;
4、虚拟摄像机
摄像机决定观察者可以看到的三维世界的范围,即决定了要把三维世界的哪些部分变成一个二维的图片。摄像机可以具有不同的位置和方向。通过改变摄像机的状态可以决定三角形的可见范围。
摄像机的可见体积范围是一个平截台体,有视角范围,近平面,远平面来决定,只所以使用平截台体来描述摄像机的可见范围,主要是因为显式器的屏幕是矩形的,不在摄像机可见范围内的物体将不可见,要进行更多的处理来去除这些物体,这种去除不可见物体的过程被称为“裁剪”。
投影窗口是一个二维区域,平截台体内的三维几何体都将投影到这个二维窗口上,从而生成一个代表三维世界的二维图像。投影窗口被定义在最小为(-1,-1)到最大值(1,1)
的范围之间,这一点非常重要。
为了简化图像渲染的难度,我们假设近平面和投影平面(投影窗口所在的平面)是一个平面。同时,需要注意在D3D中将z=1的平面设置为投影平面。
5、渲染管道
在三维场景中建立三维世界的几何描述,并且设置好了摄像机的位置,接下来的任务就是生成用于在显示器上显式的二维场景图像,为了是完成这一功能而完成的一系列必要操作被称为渲染管道。
渲染管道中的很多步骤中,都要将几何物体从一个坐标系中变换到另一个坐标系中去。D3D(Driect3D)提供了矩阵变换的计算功能,这样做的好处是非常明显的,如果硬件提供了对这些计算功能的支持。就可以讲这些变换操作交给图形硬件来完成。
主要步骤:本地坐标->世界坐标->视图坐标->背面裁剪->光照->裁剪->投影->视图变换->光栅化。
在使用D3D来进行变换时,用户只需要提供描述系统变换的完整的变换矩阵。可以通过IDirect3DDevice->SetTransform方法设置Direct3D需要变换矩阵,该方法接受两个参数,一个用于描述变换的类型,一个用于提供变换的矩阵,举个例子:假如需要设置从本地矩阵到世界矩阵转换,可以进行如下设置:
Device->SetTransform(D3DTS_WORLD,&worldMatrax);
下面对每个步骤做具体介绍:
5.1 本地空间:
或者成为模型空间,指定义组成物体的三角形列表是所使用的坐标系统,本地空间在简化模型处理工作上是相当有用的,在模型的本地空间中创建模型比在世界空间坐标中创建模型要简单些,比如,本地空间中创建空间时,不需要考虑物体的位置,大小以及与世界空间中其他物体的关系。
5.2 世界空间:
在完成个模型的创建后,所有的物体都只在相应的本地空间中,需要把这些模型组合到一个统一的世界空间中来构造一个场景,这杯称为世界变换。这些变换包括评议操作,旋转操作以及缩放操作,分别用于设置模型在世界坐标系中的威化纸,方向以及大小。世界变换完成世界空间中所有物体位置,包括大小,方向以及相互之间关系的设置。
世界变换用矩阵来表示,在D3D中,用IDirect3DDevice->SetTransform方法来设置世界变换矩阵,调用此方法是需要设置参数1的变换类型为D3DTS_WORLD。
例如,如果想在(-3,2,6)处放一个立方体,在位置(5,0,-2)处放置一个球体,程序将书写如下:
//创建立方体的世界矩阵,它只是表示一个平移变换:
D3DXMATRIX cubeWorldMatrix;
D3DXMatrixTranlation(&cubeWorldMatrix,-3.0f,2.0f.6.0f);
//创建球体世界矩阵,它也只包含一个平移变换。
D3DMATRIX sphereWorldMatrix;
D3DMatrixTranslation(&sphereWorldMatrix,5.0f,0.0,-2.0f);
//进行立方体变换
Device->SetTransform(D3DTS_WORLD,&cubeWorldMatrix);
drawcube();//画出立方体
5.3 视图空间
在世界空间中,几何物体和摄像机都是相对于世界坐标系来定义的,但是如果将摄像机放置在世界空间中的任意位置和方向,那么投影操作以及其他操作就会变得相当困难并且效率低下,为了简化操作,摄像机将被放置到世界坐标系的原点。并指向z轴的正方向,这是世界空间中的所有几何体都和摄像机进行了相投个变换,所以通过摄像机看到的世界并没有发上改变,这种在射界坐标系中移动摄像机的变换被称为试图空间变换,在这个变换之后几何物体就就位于试图空间中。
视图空间变换矩阵可以通过以下的D3DX函数来计算:
D3DMATRIX* D3DMAtrixLookAtLH(
D3DMATRIX *pOut;//指向视图矩阵的指针
CONST D3DXVECTOR3 *pEye;//摄像机在世界坐标系中的位置。
CONST D3DXVECTOR3 *pAt;//摄像机的观察点。
CONST D3DXVECTOR3 *pUp;//摄像机的向上向量;通常就是Y轴;
)
例如,如果需要将摄像机放在位置(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);
D3DMATRIX V;
D3DMatrixLookAtLH(&V,position,targetPoint,worldUp);
Device->SetTransform(D3DTS_VIEW,&V);
5.4 背面裁剪:
根据朝向的不同,可以认为多边形具有两个面,其中一个为前表面,一个为后表面,一般来说多边形的背面是不可见的。场景中大部分物体都是闭合的,由于摄像机不可能进入这些物体的内部,因此也看不见组成这些物体的背面,如果循序多边形背面可见,就不需要使用背面裁剪了。
当然,为了使背面裁剪能够正确的工作,D3D就需要知道哪些多边形是前向多边形,哪些是背向多边形;
默认情况下,定义顶点按照顺时针方式排列(在视图空间)的三角形为前向多边形;
如果因为某些原因需要不使用默认裁剪方法。就可以通过改变D3DRS_CULLMODE渲染状态来改变裁剪方法:
Devive->SetRenderState(D3DRS_CULLMODE,Value);
这里的参数Value可以为以下值:
D3DCULL_NONE;//不进行背面裁剪
D3DCULL_CW;//顺时针方式
D3DCULL_CCW;//逆时针方式
5.5 关照
光源是在世界空间中定义的,但是经过视图变换后,光源就变换到视图空间中了,在视图空间中,光源可以照亮场景中的物体,这样场景看起来就更加真实。
5.6 裁剪
到了这一步需要剔除可视范围以外的物体,这种操作叫做裁剪;
5.7投影:
在视图空间中,要使用二维的形式来描述三维场景,将n维变换成n-1维的过程就称为投影。
有很多种投影的方法,在这样只讨论透视投影,其实质就是按照近大远小的原则进行投影,也就是说对于相同大小的物体,离摄像机远的物体显式的就小。
投影矩阵相当复杂,在这里不进行投影矩阵的推导,在D3D中使用下面的函数,就可以得到一个使用平截台体进行描述投影矩阵。
D3DMATRIX* D3DMatrixPerspectiveFovLH(
D3DMATRIX* pOut;//返回投影矩阵;
FLOAT fovY;//垂直方向的视角,用弧度表示
FLOAT Aspect//宽高比
FLOAT zn;//与近面的距离
FLOAT ZF//与远面的距离
)
其中需要详细说明的是宽高比参数。投影窗口中的几何体最终将变换到屏幕空间中,因为投影窗口时正方形的,而屏幕是矩形的,他们之间的变换会造成拉伸失真;所以宽高比可以简单地设置为显式屏幕的宽高比;从而可以正确地实现投影窗口屏幕空间的映射;
举个例子:
D3DMATRIX V;
D3DMatrixPerspectiveFovLH(&V,pI*0.5f,(float)width/(float)height,1.0,1000.0f);
Device->SetTransform(D3DTS_PROJECTION,&V);
5.8视口变换
视口变换负责将视口空间转换到屏幕空间。这个屏幕空间区域就是视口,在游戏中,视口经常是整个屏幕,但是视口也可能是屏幕的一部分或者是出于窗口模式下窗口的客户区。视口语气所在的窗口相关并且通过所在窗口的相对坐标来进行描述。
在D3D中,使用D3DVIEWPORT9结构来描述视口,该结构定义如下:
typedef struct _D3DVIWPORT9{
DWORD X;//
DWORD Y;
DWORD WIDTH;
DWORD Height;
DWORD Minz;//深度缓冲区的最小值
DWORD MAXZ;//深度缓冲的最大值
}D3DVIEWPORT9;
前四个参数描述视口相对于所在窗口的位置。深度缓冲区值的范围0-1;除非用户因为需要实现某个特殊的效果而更改深度缓冲区的范围,否则必须设置在这个范围内。
举个例子:
D3DVIEWPORT9 VP(0,0,640,480,0,1);
Device->setViewport(&vp);
5.9光栅化:
当所有顶点都变换到屏幕坐标系的时候,就有了一个三维形式的三角形列表。光栅化所做的工作就是计算每个独立像素点的颜色值。
光栅化会消耗大量的计算,因此需要专门的图形设备来完成,光栅化后就得到了一张在显式器上显式的二维图片。
总结:
三维物体使用三角形网格进行描述,三角形网格是一个三角形的列表这些三角形组成一个与原来物体形状轮廓相似的三维图形。
虚拟相机的可是范围被设置为一个平截台体,它的范围就是摄像机的可视范围。
三维物体在本地坐标系中定义在进行变换时,集合物体首先被变换到一个世界空间中,为了方便进行摄影,裁剪以及其他操作,物体将被变换到视图空间中。视图空间中摄像机处于坐标系的原点,观察方向为z轴的正方向,几何物体被变换到视图空间后,物体将再次被变换到投影窗口中,最后,视口变换敬爱几何物体从投影窗口变换到视口中,最后通过光栅化,计算是所有独立像素点的颜色值,得到一张描述三维世界的二维图片。
来自:https://blog.csdn.net/liulong1567/article/details/50523892