3D游戏从入门到精通-6 -10

来源:互联网 发布:免费产品推广软件 编辑:程序博客网 时间:2024/06/05 07:54
/***********************************
 *作者:蔡军生
 *出处:http://blog.csdn.net/caimouse/
 ************************************/
  3D游戏从入门到精通-6
1、             顶点缓冲区
顶点的数据是怎么样保存在内存里的呢?由于渲染需要,就要把顶点保存在内存缓冲区里。最简单的方法就是使用D3D提供的顶点缓冲区,即是使用IDirect3DVertexBuffer9接口的顶点缓冲区管理,它是通过D3D的设备来创建。这个接口实现顶点的资源管理,使用起来非常方便,由于它管理内存分配,这样就省了内存管理。下面就是通过IDirect3DDevice9::CreateVertexBuffer函数来创建一个顶点缓冲区:
 
HRESULT hr;
 
// 创建顶点缓冲区。
 if( FAILED( hr = m_pd3dDevice->CreateVertexBuffer( 3*2*sizeof(VT_CAITRIANGLE),
       0, VT_CAITRIANGLE::dwFVF,
       D3DPOOL_MANAGED, &m_pVB, NULL ) ) )
 {
       //创建顶点缓冲区失败。
       return DXTRACE_ERR( "CreateVertexBuffer", hr );
 }
上面这段代码,就是创建6个顶点的缓冲区。指定的格式为VT_CAITRIANGLE::dwFVF,这个在前面已经定义过,只包括顶点向量数据。D3DPOOL_MANAGED指明了创建的缓冲区在系统内存里,并且需要时就拷贝到显示卡内存里。我们知道显示卡内存越来越大了,如果把很多变化不大的物体放到显示卡内存里,就可以加速显示,减少占用系统局部总线的带宽。像NV新出来的显示卡,都有1G显示内存,显然可以放更多物体在那里,以便显示的速度更快。
由于显示卡内存和系统内存的复杂性,而通过这个接口可以方便地使用了。由于它是还一个内存池,所以产生内存碎片的机会就更加少了。
创建顶点缓冲区之后,就需要往里面填充顶点数据,先来看看下面的代码:
 
// 用两个三角形填充顶点缓冲区。
 VT_CAITRIANGLE* pVertices;
 
 if( FAILED( hr = m_pVB->Lock( 0, 0, (VOID**)&pVertices, 0 ) ) )
 {
       //锁住顶点缓冲区。
       return DXTRACE_ERR( "Lock", hr );
 }
 
 // 前面三角形。
 pVertices[0].vPosition = D3DXVECTOR3( -1.0f, -1.0f, 0.0f );
 pVertices[1].vPosition = D3DXVECTOR3( 0.0f, 1.0f, 0.0f );
 pVertices[2].vPosition = D3DXVECTOR3( 1.0f, -1.0f, 0.0f );
 
 // 后面三角形。
 pVertices[3].vPosition = D3DXVECTOR3( -1.0f, -1.0f, 0.0f );
 pVertices[4].vPosition = D3DXVECTOR3( 1.0f, -1.0f, 0.0f );
 pVertices[5].vPosition = D3DXVECTOR3( 0.0f, 1.0f, 0.0f );
 
 //解锁顶点缓冲区。
 m_pVB->Unlock();
 
这段代码是通过IDirect3DVertexBuffer9::Lock方法来获取到顶点缓冲区的指针,然后把6个顶点赋值。最后调用IDirect3DVertexBuffer9::Unlock方法解锁,如果不解锁,D3D显示不了这个缓冲区里的数据,就会出错的,所以一定要小心地操作。
 
 3D游戏从入门到精通-7
 
1、             世界坐标变换矩阵
在设置顶点缓冲区之后,就需要设置物体的世界坐标变换矩阵。从解析几何里可以知道,无论二维还是三维的图像都可以通过齐次坐标来变换的。目前在三维里的变换,采用的矩阵是4×4的矩阵来实现变换处理。在D3D里定义D3DXMATRIX类型,使用它来表达一个矩阵,并且定义一系列的函数可以实现矩阵的运算。它的定义如下:
 
typedef struct _D3DMATRIX {
    union {
        struct {
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;
 
        };
        float m[4][4];
    };
} D3DMATRIX;
上面代码定义了4×4矩阵的四个元素,全部使用浮点数运算。接着还添加了一些操作方法,如下:
typedef struct D3DXMATRIX : public D3DMATRIX
{
public:
    D3DXMATRIX() {};
    D3DXMATRIX( CONST FLOAT * );
    D3DXMATRIX( CONST D3DMATRIX& );
    D3DXMATRIX( CONST D3DXFLOAT16 * );
    D3DXMATRIX( FLOAT _11, FLOAT _12, FLOAT _13, FLOAT _14,
                FLOAT _21, FLOAT _22, FLOAT _23, FLOAT _24,
                FLOAT _31, FLOAT _32, FLOAT _33, FLOAT _34,
                FLOAT _41, FLOAT _42, FLOAT _43, FLOAT _44 );
 
 
    // access grants
    FLOAT& operator () ( UINT Row, UINT Col );
    FLOAT operator () ( UINT Row, UINT Col ) const;
 
    // casting operators
    operator FLOAT* ();
    operator CONST FLOAT* () const;
 
    // assignment operators
    D3DXMATRIX& operator *= ( CONST D3DXMATRIX& );
    D3DXMATRIX& operator += ( CONST D3DXMATRIX& );
    D3DXMATRIX& operator -= ( CONST D3DXMATRIX& );
    D3DXMATRIX& operator *= ( FLOAT );
    D3DXMATRIX& operator /= ( FLOAT );
 
    // unary operators
    D3DXMATRIX operator + () const;
    D3DXMATRIX operator - () const;
 
    // binary operators
    D3DXMATRIX operator * ( CONST D3DXMATRIX& ) const;
    D3DXMATRIX operator + ( CONST D3DXMATRIX& ) const;
    D3DXMATRIX operator - ( CONST D3DXMATRIX& ) const;
    D3DXMATRIX operator * ( FLOAT ) const;
    D3DXMATRIX operator / ( FLOAT ) const;
 
    friend D3DXMATRIX operator * ( FLOAT, CONST D3DXMATRIX& );
 
    BOOL operator == ( CONST D3DXMATRIX& ) const;
    BOOL operator != ( CONST D3DXMATRIX& ) const;
 
} D3DXMATRIX, *LPD3DXMATRIX;
 
上面的方法,都是对这个矩阵的运算,使用起来更加方便。
有了矩阵的定义,还有了矩阵的运算,那么构造一个从模型坐标到世界坐标变换的矩阵,就是很容易的事情了。由于我不需要变换它,直接使用一个单位矩阵,就可以了。由单位矩阵可知,任何数据乘以单位矩阵都不变会改变。代码如下:
 
// 设置世界坐标矩阵。
D3DXMATRIX matIdentity;
D3DXMatrixIdentity( &matIdentity );
m_pd3dDevice->SetTransform( D3DTS_WORLD, &matIdentity );
 
这段代码里先定义一个矩阵,然后调用函数D3DXMatrixIdentity初始化这个矩阵为单位矩阵,最后通过函数SetTransform设置为世界坐标变换矩阵。
这样显示的三角形就是按模型坐标系显示到世界坐标系里,也就是在世界坐标系的原点位置显示。
在游戏里,所有3D模型都是在外面做好的,因此都是模型坐标系,也就是局部坐标系,要把它们显示到世界坐标系里,一般是需要通过变换的。因为所有模型坐标系都是相同的,都是原点在世界坐标系的原点。如果不通过变换就显示的话,所有的物体都是重叠在原点位置,这样就分不清楚它们的显示。比如显示一个足球场,两个球门就是分别显示的。一个球门在原点,一个球门就在离原点一段距离再显示,还需要通过合适旋转变换才能显示。由于两个球门是同一个局部坐标系的物体构成,必须通过世界坐标变换矩阵才能显示出来。
 
  3D游戏从入门到精通-8
 
 
1、             视图变换矩阵
视图变换矩阵是做什么用的呢?为什么需要视图变换矩阵呢?其实这个问题很好回答和理解的。在3D世界里,所有东西都是跟现实中的实物一样的,当用人的眼睛去观看现实中的实物时,总是从某一方向去观察的,这样就看到实物组成的图像。就像你拿着照相去照相是一样的,首先要调好焦距,对准物体。在D3D里也需要调好焦距,对准物体,否则什么也看不到。因此在D3D里,就需要设置好视图变换矩阵。也就是相机的位置和焦距。在视图变换矩阵里,是使用两个点来表达相机位置和焦距,如下图所示:
在D3D里使用向量来表示相机位置,如下:
D3DXVECTOR3 vFromPt   = D3DXVECTOR3( 0.0f, 0.0f, -5.0f );
这里指明了相机所在位置是Z轴上-5的位置。
 
接着又需要指明焦点所有位置,如下:
D3DXVECTOR3 vLookatPt = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
这里指明了相机焦点所在位置是在世界坐标系的原点。
 
但还有一个因素没有考虑进去,那就是相机放置的方向。比如人站在地球上,头顶着天去观察实物是一种方向,是一种正常的方向,侧着头去看实物又是一种方向,人倒立着看实物又是一个方向。“横看成岭侧成峰,远近高低各不同。”就表达了这种不同方向看东西的不同,因此在D3D里也需要设置方向,用如下向量表示:
 
//设置向上方向为Y轴正方向.
D3DXVECTOR3 vUp(0,1,0);
 
有了这三个向量,就决定了相机需要经过的变换矩阵,也就是看到D3D里的物体是什么样子了。在D3D里,就是构造一个视图变换矩阵,它是使用下面函数来计算的:
D3DXMatrixLookAtLH( &m_mView, &m_vEyePosition, &m_vLookAtPosition, &vUp );
第一个参数,就是返回来的视图变换矩阵,第二个参数是相机位置,第三个参数是焦点位置,第四个参数是相机朝向。
 
构造好视图变换矩阵后,就可以把它设置D3D里,通过下面的函数来实现:
 
//设置视变换矩阵。
 const D3DXMATRIX& mView = CCameraFirstPerson::Instance()->GetViewMatrix();
 m_pd3dDevice->SetTransform( D3DTS_VIEW, &mView);
 
这样就把D3D的设备视图变换矩阵设置好了。
 
 
 3D游戏从入门到精通-9
 
1、             投影变换矩阵
投影变换矩阵是做什么的呢?这个更容易理解了。大家都应看过照片吧,那大家又想过照片是怎么样表现原来的物体呢?看到远处的山是小的,近处的人是大的。并且只有在一定的范围内的物体才照得清楚,其它的就不见了。在D3D里就需要把三维的物体空间投影到二维的屏幕空间显示,这就需要投影了。本来人眼看现实的物体时,总是经过投影变换的,把三维的物体通通变换成二维东西,这样就在人眼上看见东西就是三维的了。现在这个投影变换就是模拟人眼的作用,沿着光线的方向,那些能看见,那些不能看见。那些是近的,那些是远的。通过这样的计算,就会生成二维的图像,跟人眼看到真实的世界是相同的。
如下图所示:
在D3D里通过设置投影前截面、后截面、视角和长宽比来构造投影矩阵,使用下面的函数来构造这个投影变换矩阵:
D3DXMatrixPerspectiveFovLH( &m_mProjection, fFOV, fAspect, fNearPlane, fFarPlane );
第一个参数是返回的矩阵,第二参数是视角,第三个参数是长宽比,第四参数是近截面,第五参数是远截面。
构造好投影变换矩阵,就可以通过下面参数设置:
 
// 设置投影矩阵。
const D3DXMATRIX& mProj = CCameraFirstPerson::Instance()->GetProjectionMatrix();
m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &mProj );
 
这样就可以设置好D3D的设备投影矩阵。
 3D游戏从入门到精通-10
 
1、              渲染物体
经过前面这么多步骤,做足了准备工作,就可以渲染物体,显示三维空间的图像了。这时心情要兴奋起来,就看到自己辛苦的果实了。
仔细地看一下前面的代码,创建的缓冲区还没有设置给D3D设备。下面就来做这方面的工作:
 
// 渲染顶点缓冲区的内容。
m_pd3dDevice->SetStreamSource( 0, m_pVB, 0, sizeof(VT_CAITRIANGLE) );
m_pd3dDevice->SetFVF( VT_CAITRIANGLE::dwFVF );
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 2 );
 
上面这段代码先设置顶点缓冲区给D3D设备,第一个参数选择渲染通道,由于现在的显示卡提供多个通道显示,以便加速显示。这个参数可以根据枚举设备的特性来知道最大的通道数。
第二个参数是上面的顶点缓冲区,就是每个顶点的坐标值。
第三个参数是指明从顶点缓冲区什么位置开始渲染,这里是从缓冲区开始位置开始,所以设置为0。
第四个参数是每个顶点占用的大小,因为每个顶点的数据,可以有三个坐标值、纹理坐标、颜色值和其它数据。
 
接着下来调用SetFVF函数来设置顶点的格式,这里设置为上面的定义的顶点格式VT_CAITRIANGLE::dwFVF。
 
最后一行代码就是真正地画两个三角形在屏幕缓冲区里。它的第一个参数是设置显示顶点的格式,这里按三角形列表的形式来显示。
第二个参数是设置从那个顶点开始,这里是从0顶点开始。
第三个参数是表示有多少个三角形,这里有两个三角形。
 
通过上面的过程来学习D3D是怎么样渲染一帧图像的,不管有多么复杂的物体要显示,也不管有多少个三角形要显示,所有的过程是一样的。开始看到的三角形就显示出来了,也许你发现这里只显示三角形的直线,其实我设置了D3D的设备只显示线框模型。如下面所示:
 
//只显示线框.
 m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
 
使用这种模式,可以很方便调试没有设置光线的模型。也很容易看出来有多少个三角形在那里显示。这个例子非常简单吧,其实3D的图像显示就是这么样简单的,不过要理解它的意思,还是看一下我的例子代码和调试一下,就更会快速度掌握D3D了。
 
 
原创粉丝点击