Direct3D绘制流水线

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Spring_24/article/details/77775205

一、绘制流水线简介


            场景(scene)是物体或模型的集合。任何物体都可以用三角形网格(triangle mesh)逼近表示。三角形网格是构建物体模型的基本单元。

         摄像机指定了场景对观察者的可见部分,即我们将依据哪部分3D场景来创建2D图像。在世界坐标系中,摄像机有一定的位置和方向,定义了可见的空间体积(volume of space)。下图展示了一个虚拟摄像机的模型:

         
        

           用几何学中的术语讲,上述的空间体积是一个平截头体(frustum)。在3D图形学中也可以称为视域体或视截体。采用视域体的主要原因是显示屏为矩形。那些位于视域体之外的物体是不可见的,在进一步处理时就应将其丢弃。丢弃这类数据的运算过程称为裁剪(clipping)

           投影窗口(projection window)是一个2D区域,位于视域体中的3D几何体通过投影映射到该区域中,这样便创建了3D场景的2D表示。很重要的一点是,矩形投影窗口在X和Y方向上的坐标范围是[-1,1]

          一旦建立了3D场景的几何描述,并设置好摄像机,我们下面的任务就是在显示器中建立该场景的2D表示。为实现这一目标所必需实施的一系列运算统称为绘制流水线(rendering pipeline),即 绘制流水线的功能是:在给定3D场景和指定观察方向的虚拟摄像机(virtual camera)的几何描述时,创建一副2D图像。

         下图为绘制流水线的大致流程:

         
 

二、绘制流水线详细介绍


一、局部坐标系

        局部坐标系(local space)或建模坐标系(modeling space),是用于定义构成物体的三角形单元列表的坐标系。采用局部坐标系的优势体现在它可以简化建模过程,局部坐标系允许我们构建模型时无需考虑位置、大小或相对于其他物体的朝向。下图是一个在自身局部坐标系中构建的茶壶:

       

二、世界坐标系

         构建各种物体模型时,每个模型都位于其自身的局部坐标系中。我们还需要将这些物体组织在一起构成世界坐标系(全局坐标系)中的场景。位于局部坐标系中的物体通过一个称为世界变换(world transform)的运算过程变换到世界坐标系(world space)中,该变换通常包括平移(translation)、旋转(rotation)以及缩放(scaling)运算,分别用于设定该物体在世界坐标系中的位置、方向以及模型的大小。

        世界变换用一个矩阵表示,并有Direct3D通过IDirect3DDevice::SetTransform方法来加以应用,该方法的声明如下:
        IDirect3DDevice::SetTransform(
                D3DTRANSFORMSTATETYPE State,
                const D3DMATRIX* pMatrix);

        第一个参数表示变换的类型:
            进行世界变换时,取值D3DTS_WORLD;
            进行取景变换时,取值D3DTS_VIEW;
            进行投影变换时,取值D3DTS_PROJECTION。

1、平移


      Direct3D的功能扩展库d3dx9.lib提供了函数D3DXMatrixTranslation(),生成一个平移矩阵。

      函数声明如下:
      
D3DXMATRIX* WINAPI D3DXMatrixTranslation
    ( D3DXMATRIX *pOut, FLOAT x, FLOAT y, FLOAT z );


       下面示例代码将物体在X,Y,Z轴上分别平移了1、2、3个单位:

	D3DXMATRIXA16 matTrans;
	D3DXMatrixTranslation(&matTrans,1,2,3);
	g_pd3dDevice ->SetTransform(D3DTS_WORLD, &matTrans);
      

2、旋转


      Direct3D的功能扩展库d3dx9.lib提供了如下函数,生成一个旋转矩阵:

      D3DXMATRIX* D3DXMatrixRotationX( D3DXMATRIX*pOut, FLOAT Angle);
          D3DXMATRIX* D3DXMatrixRotationY( D3DXMATRIX*pOut, FLOAT Angle);
      D3DXMATRIX* D3DXMatrixRotationX(D3DXMATRIX*pOut, FLOAT Angle);

      参数Angle为旋转的弧度值,旋转方向为顺着旋转轴看向原点时,逆时针方向为正方向

     下面示例代码将物体绕着X轴逆时针旋转了90度:

	D3DXMATRIXA16 matRot;
	FLOAT fAngle = 90 * (2.0f * D3DX_PI) / 360.0f;
	D3DXMatrixRotationX(&matRot, fAngle);
       

3、缩放


      Direct3D的功能扩展库d3dx9.lib提供了函数D3DXMatrixScaling(),生成一个平移矩阵。

      函数声明如下:
      
D3DXMATRIX* WINAPI D3DXMatrixScaling
    ( D3DXMATRIX *pOut, FLOAT sx, FLOAT sy, FLOAT sz );


      下面示例代码将物体在X、Y、Z方向上分别放大了1、2、3倍。

	D3DXMATRIXA16 matScal;
	D3DXMatrixScaling(&matScal, 1, 2, 3);
        
 

       下图为相对于世界坐标系描述的几个3D物体:
     
        

三、观察坐标系


       在世界空间中,几何体和摄像机都是相对于世界坐标系定义的。但是,当摄像机的位置和朝向任意时,投影变换及其他类型的变换就显得困难或效率不高。为了简化运算,我们将摄像机变换至世界坐标系的原点,并将其旋转,使摄像机的光轴与世界坐标系Z轴方向一致。同时,世界空间中的所有几何体都随着摄像机一同进行变换,以保证摄像机的视场恒定。这种变换称为取景变换(view space transformation),我们称变换后的几何体位于观察坐标系(view space)中。

        

      
       Direct3D的功能扩展库d3dx9.lib提供了函数D3DXMatrixLookAtLH(),生成一个基于左手坐标系的取景变换矩阵。

       函数声明如下:
       
D3DXMATRIX* WINAPI D3DXMatrixLookAtLH(
      D3DXMATRIX *pOut, 
      CONST D3DXVECTOR3 *pEye,   //观察点位置坐标
      CONST D3DXVECTOR3 *pAt,     //被观察点位置坐标
      CONST D3DXVECTOR3 *pUp     //向上的向量
      );


        例如:假定摄像机位于(5,3,-10),其观察点为世界坐标系的原点(0,0,0)。我们可以这样创建取景变换矩阵:

	D3DXVECTOR3 vEyePt(5.0f, 3.0f, -10.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);


四、背面消隐


        每个多边形都有两个侧面(sides),我们将其中一个侧面标记为正面(front side),另一个侧面标记为背面(back side)。通常,多边形的背面是不可见的。这是由于场景中的多数物体都是封闭体(enclosed volume),而且摄像机总是禁止进入物体内部的实体空间。所以,摄像机是不可能观察到物体的背面的。这一点很重要,如果某一多边形的背面可见,背面消隐(backface culling)就失去了意义。

     
        下图展示了观察坐标系中的一个物体,其正面有一个伸出的箭头。正面朝向摄像机的多边形称为正面朝向(front facing)多边形,正面偏离摄像机的多边形称为背面朝向(back facing)多边形。

         

            从上图中我们可以看到正面朝向的多边形挡住了位于其后的背面朝向多边形。Direct3D正式利用了这一点将背面朝向多边形加以剔除,这称为背面消隐。物体经过背面消隐后,从摄像机的视点(view point)看,所绘制的场景别无二致,因为背面总是会被挡住,所以永远都看不见。

            下图展示物体经过背面消隐后的结果:

     



          为了实现背面消隐,Direct3D需要区分哪些多边形是正面朝向的,哪些是背面朝向的。默认状态下,Direct3D认为顶点排列顺序为顺时针(观察坐标系中)的三角形单元是正面朝向的,顶点排列顺序为逆时针(观察坐标系中)的三角形单元是背面朝向的。


          我们可以通过修改绘制状态(render state)D3DRS_CULLMODE来改变消隐方式:

          SetRenderState(D3DRS_CULLMODE,Value);
          其中,Value可取以下值:
          D3DCULL_NONE,完全禁用背面消隐
          D3DCULL_CW,只对顺时针绕序的三角形单元进行消隐
          D3DCULL_CCW,默认值,只对逆时针绕序的三角形单元进行消隐


五、光照


       光源是在世界坐标系中定义的,但必须经取景变换至观察坐标系方可使用。在观察坐标系中,光源照亮了场景中的物体,从而可以获得较为逼真的显示效果。


六、裁剪


       将那些位于视域体外的几何体剔除掉,这个过程称为裁剪(clipping)。一个三角形单元与视域体的相对位置关系有以下3种。

      1、完全在内部     如果三角形单元完全在视域体内,便被保留并转向下一阶段的处理。
      2、完全在外部     如果三角形单元完全在视域体外,将被剔除。
      3、部分在内(部分在外)  如果三角形单元部分与视域体的关系是部分在内部分在外,该单元将被分为两部分。位于视域体内部的那部分将被保留,视域体外的那部分将被剔除。

      下图展示了这3种情况:

       

七、投影


           观察坐标系中,我们的任务是获取3D场景的2D表示。从n维变换为n-1维的过程称为投影(projection)。实现投影有多种方式,接下来只介绍透视投影(perspective projection)。透视投影会产生“透视缩短”(foreshortening)的视觉效果,即近大远小。

       下图展示了一个经过透视投影变换到投影窗口的3D点:

        

          投影变换定义了视域体,并负责将视域体中的几何体投影到投影窗口中。
          Direct3D的功能扩展库d3dx9.lib提供了函数D3DXMatrixPerspectiveFovLH(),生成一个基于左手坐标系的投影变换矩阵。

          
D3DXMATRIX* WINAPI D3DXMatrixPerspectiveFovLH(
	      D3DXMATRIX *pOut, 
		  FLOAT fovy,             //Y轴上的成像角度
		  FLOAT Aspect,          //纵横比
		  FLOAT zn,              //截头体距离相机的最近距离
		  FLOAT zf               //截头体距离相机的最远距离
		  );


         上述函数中值得一提的是纵横比(aspect ratio)参数。投影窗口中的几何体最终将变换到屏幕显示区。从方形(投影窗口)到矩形的显示屏的变换会导致拉伸畸变(stretching distortion)。所谓纵横比就是显示屏纵横两维尺寸的比率,通常用于校正由正方形到矩形的映射而引发的畸变。

           纵横比 = 屏幕宽度 / 屏幕高度。

          下面的示例代码中视域体的视域角(field of view)为90度,近裁剪面到坐标原点的距离是1,远裁剪面到原点的距离是1000:

           
	D3DXMATRIXA16 matProj;
	D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 2, (float)width/(float)height, 1.0f, 1000.0f);
	g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);


八、视口变换


        视口变换(viewport transform)的任务是将顶点坐标从投影窗口转换到屏幕的一个矩形区域中,该矩形区域称为视口(viewport)。视口是相对于其所在的窗口来描述的,视口的位置要用窗口坐标来指定

      下图展示了一个视口:

        

          在Direct3D中,视口用结构体D3DVIEWPORT9来表示,其定义如下:

          
typedef struct _D3DVIEWPORT9 {
    DWORD       X;
    DWORD       Y;            /* Viewport Top left */
    DWORD       Width;
    DWORD       Height;       /* Viewport Dimensions */
    float       MinZ;         /* Min/max of clip Volume */
    float       MaxZ;
} D3DVIEWPORT9;


        该结构体的前4个数据成员定义了视口相对于其父窗口的位置及大小。成员变量MinZ、MaxZ指定了深度缓存中的最小深度值和最大深度值,其范围为[0,1]。


         设置视口的示例代码:
	RECT rect;
	GetClientRect(hWnd, &rect);
	D3DVIEWPORT9 vp;
	vp.X = 100;
	vp.Y = 100;
	vp.Width= rect.right/2;
	vp.Height = rect.bottom/2;
	vp.MinZ = 0;
	vp.MaxZ = 0.1;
	g_pd3dDevice->SetViewport(&vp);


九、光栅化

     
        顶点坐标变换为屏幕坐标后,我们就有了一个2D三角形单元的列表。光栅化(rasterization)的任务是计算构成三角形单元的每个像素的颜色值。光栅化的计算量非常大,我们应尽量借助专用图形卡。光栅化的最终结果是显示在屏幕上的一副2D图像。

猜你喜欢

转载自blog.csdn.net/Spring_24/article/details/77775205
今日推荐