3D游戏从入门到精通-11 -15

  /***********************************
 *作者:蔡军生
 *出处:http://blog.csdn.net/caimouse/
 ************************************/
 
 
 3D游戏从入门到精通-11
 
 
2.10.1索引缓冲区
上面已经学习了最简单的三角形显示,了解了整个3D物体的显示流程,下面来学习一个复杂一点的物体显示,使用12个三角形构造成的正方体显示。并且通过个例子学会使用索引缓冲区,提高图形绘制的速度和效率。
1、             使用三角形构造立方体
由前面可知,任何复杂的物体,都是由三角形组成的。现在就用12三角形构造一个正方体。先创建12点个顶点的缓冲区,然后再往里填写合适的数据,然后显示它出来。下面来仔细分析这段代码:
在函数HRESULT CCAICube::Init(IDirect3DDevice9* pd3dDevice)里,先创建12顶点数据。
const int nVTCount = 6*2*3;
 // 创建顶点缓冲区。
 if( FAILED( hr = m_pd3dDevice->CreateVertexBuffer( nVTCount * sizeof(VT_CAICUBE),
       0, VT_CAICUBE::dwFVF,
       D3DPOOL_MANAGED, &m_pVB, NULL ) ) )
 {
       //创建顶点缓冲区失败。
       return DXTRACE_ERR( "CreateVertexBuffer", hr );
 }
           这段代码,跟以前的代码一样创建顶点缓冲区。
接着下来就指明所有三角形的坐标:
// 用三角形填充顶点缓冲区。
 VT_CAICUBE* 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, -1.0f );
 pVertices[1].vPosition = D3DXVECTOR3( -1.0f, 1.0f, -1.0f );
 pVertices[2].vPosition = D3DXVECTOR3( 1.0f, 1.0f, -1.0f );
 
 //
 pVertices[3].vPosition = D3DXVECTOR3( -1.0f, -1.0f, -1.0f );
 pVertices[4].vPosition = D3DXVECTOR3( 1.0f, 1.0f, -1.0f );
 pVertices[5].vPosition = D3DXVECTOR3( 1.0f, -1.0f, -1.0f );
 
 
 //第二个面
 pVertices[6].vPosition = D3DXVECTOR3( -1.0f, 1.0f, -1.0f );
 pVertices[7].vPosition = D3DXVECTOR3( -1.0f, 1.0f, 1.0f );
 pVertices[8].vPosition = D3DXVECTOR3( 1.0f, 1.0f, -1.0f );
 
 //
 pVertices[9].vPosition = D3DXVECTOR3(   -1.0f, 1.0f, 1.0f );
 pVertices[10].vPosition = D3DXVECTOR3( 1.0f, 1.0f, 1.0f );
 pVertices[11].vPosition = D3DXVECTOR3( 1.0f, 1.0f, -1.0f );
 
 
 //第三个面
 pVertices[12].vPosition = D3DXVECTOR3( -1.0f, 1.0f, 1.0f );
 pVertices[13].vPosition = D3DXVECTOR3( -1.0f, -1.0f, 1.0f );
 pVertices[14].vPosition = D3DXVECTOR3( 1.0f, 1.0f, 1.0f );
 
 //
 pVertices[15].vPosition = D3DXVECTOR3( 1.0f, 1.0f, 1.0f );
 pVertices[16].vPosition = D3DXVECTOR3( -1.0f, -1.0f, 1.0f );
 pVertices[17].vPosition = D3DXVECTOR3( 1.0f, -1.0f, 1.0f );
 
 //第四个面
 pVertices[18].vPosition = D3DXVECTOR3( -1.0f, -1.0f, 1.0f );
 pVertices[19].vPosition = D3DXVECTOR3( 1.0f, -1.0f, -1.0f );
 pVertices[20].vPosition = D3DXVECTOR3( 1.0f, -1.0f, 1.0f );
 
 //
 pVertices[21].vPosition = D3DXVECTOR3( -1.0f, -1.0f, 1.0f );   
 pVertices[22].vPosition = D3DXVECTOR3( -1.0f, -1.0f, -1.0f );
 pVertices[23].vPosition = D3DXVECTOR3( 1.0f, -1.0f, -1.0f );
 
 
 //第五个面
 pVertices[24].vPosition = D3DXVECTOR3( 1.0f, 1.0f, 1.0f );   
 pVertices[25].vPosition = D3DXVECTOR3( 1.0f, -1.0f, -1.0f );
 pVertices[26].vPosition = D3DXVECTOR3( 1.0f, 1.0f, -1.0f );
 
 //
 pVertices[27].vPosition = D3DXVECTOR3( 1.0f, -1.0f, -1.0f );   
 pVertices[28].vPosition = D3DXVECTOR3( 1.0f, 1.0f, 1.0f );
 pVertices[29].vPosition = D3DXVECTOR3( 1.0f, -1.0f, 1.0f );
 
 //第六个面
 pVertices[30].vPosition = D3DXVECTOR3( -1.0f, 1.0f, -1.0f );
 pVertices[31].vPosition = D3DXVECTOR3( -1.0f, -1.0f, -1.0f );    
 pVertices[32].vPosition = D3DXVECTOR3( -1.0f, 1.0f, 1.0f );
 
 //
 pVertices[33].vPosition = D3DXVECTOR3( -1.0f, 1.0f, 1.0f );    
 pVertices[34].vPosition = D3DXVECTOR3( -1.0f, -1.0f, -1.0f );
 pVertices[35].vPosition = D3DXVECTOR3( -1.0f, -1.0f, 1.0f );
 
 //解锁顶点缓冲区。
 m_pVB->Unlock();
上面从第一个面开始,就每个面两个三角形地填写,并且是按左中手坐标系的方向来填写三角形的顶点。
接着修改渲染的代码,改为渲染12个三角形,代码如下:
// 渲染顶点缓冲区的内容。
 m_pd3dDevice->SetStreamSource( 0, m_pVB, 0, sizeof(VT_CAICUBE) );
 m_pd3dDevice->SetFVF( VT_CAICUBE::dwFVF );
 m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 12 );
这样显示出来的立方体是那个面都可以看得到的,如果不按左手坐标系填写,就会有一些面看不到的。
 
通过上面的代码,是否看到要显示一个简单的立方体,就需要复杂地填写顶点了,并且要小心翼翼地写代码,一不小心写错了,就看不见了。因此,再复杂的物体,一般是通过使用3D建模软件来构造的。通过上面的代码,也发现了一个问题,就是整个正方体,其实只有8个顶点,而上面的三角形列表里有很多顶点是重复的,对于只有12个三角形来说,也许不是很重要。但是对于数以万计的三角形来说,这样的重复顶点就会浪费很多内存,浪费系统的带宽,并且需要重复渲染顶点。有没有更好的方法来解决这个问题呢?答案是肯定的,下面接着就来学习索引缓冲区。
 3D游戏从入门到精通-12
 
1、             使用索引缓冲区
什么是索引缓冲区呢?其实索引缓冲区,就像指针一样的工具。顶点缓冲区里保存的是真实的顶点,而索引缓冲区只记录顶点缓冲区的顶点编号。比如顶点缓冲区里有4个顶点,而这4个顶点就可以构成两个三角形来显示。如果直接使用顶点缓冲区的话,就需要写6个顶点。这样就可以使用索引缓冲区,指明第一个三角形是由顶点编号1、2、3构成一个三角形,而第二个三角形就是1、3、4组成。

现在把上面的立方体改为使用索引缓冲区,先创建8个顶点,如下:
pVertices[0].vPosition = D3DXVECTOR3(    -1.0f, -1.0f, -1.0f );
pVertices[1].vPosition = D3DXVECTOR3(    -1.0f, 1.0f, -1.0f );   
pVertices[2].vPosition = D3DXVECTOR3(    1.0f, 1.0f, -1.0f );    
pVertices[3].vPosition = D3DXVECTOR3(    1.0f, -1.0f, -1.0f );   
 
pVertices[4].vPosition = D3DXVECTOR3(    -1.0f, 1.0f, 1.0f );    
pVertices[5].vPosition = D3DXVECTOR3(    1.0f, 1.0f, 1.0f );
pVertices[6].vPosition = D3DXVECTOR3(    1.0f, -1.0f, 1.0f );    
pVertices[7].vPosition = D3DXVECTOR3(    -1.0f, -1.0f, 1.0f );
这样,就可省下许多顶点占用的内存了,然后创建36个索引点的缓冲区,如下:
 
//创建索引缓冲区。
 hr = m_pd3dDevice->CreateIndexBuffer( 12*3 *sizeof(WORD),
       0, D3DFMT_INDEX16, D3DPOOL_DEFAULT,
       &m_pIB, NULL );
 
 if( FAILED( hr ) )
 {
       return E_FAIL;
 }   
 
上面创建了36个WORD大小的索引点,这样省了很多内存空间。由于顶点是由3个浮点数和其它值组成,肯定比两个字节索引值占用空间大。接着下来就是设置索引缓冲区,如下:
 
void* pIndices;
 
 if( FAILED( m_pIB->Lock( 0, sizeof(WORD)*12*3, &pIndices,0 ) ) )          
 {
       m_pIB->Release();
       m_pIB = NULL;
       return E_FAIL;
 }
 
 //
 WORD* pIndex = reinterpret_cast<WORD*>(pIndices);
 int nPos = 0;
 
 //1
 pIndex[nPos++] = 0;
 pIndex[nPos++] = 1;
 pIndex[nPos++] = 2;
 
 pIndex[nPos++] = 0;
 pIndex[nPos++] = 2;
 pIndex[nPos++] = 3;
 
 
 //2
 pIndex[nPos++] = 1;
 pIndex[nPos++] = 4;
 pIndex[nPos++] = 2;
 
 pIndex[nPos++] = 2;
 pIndex[nPos++] = 4;
 pIndex[nPos++] = 5;
 
 //3
 pIndex[nPos++] = 2;
 pIndex[nPos++] = 5;
 pIndex[nPos++] = 3;
 
 pIndex[nPos++] = 3;
 pIndex[nPos++] = 5;
 pIndex[nPos++] = 6;
 
 //4
 pIndex[nPos++] = 0;
 pIndex[nPos++] = 7;
 pIndex[nPos++] = 1;
 
 pIndex[nPos++] = 1;
 pIndex[nPos++] = 7;
 pIndex[nPos++] = 4;
 
 //5
 pIndex[nPos++] = 0;
 pIndex[nPos++] = 3;
 pIndex[nPos++] = 7;
 
 pIndex[nPos++] = 3;
 pIndex[nPos++] = 6;
 pIndex[nPos++] = 7;
 
 
 //6
 pIndex[nPos++] = 4;
 pIndex[nPos++] = 7;
 pIndex[nPos++] = 5;
 
 pIndex[nPos++] = 5;
 pIndex[nPos++] = 7;
 pIndex[nPos++] = 6;
 //
 m_pIB->Unlock();
 
上面这段代码,先Lock函数取得索引缓冲区地址,然后依次设置索引值,这里使用三角形列表,所以顶点顺序一样是按顺时针方向来设置的,也就是按左手坐标系来设置的。
接着下来,就需要渲染这个立方体了,如下:
m_pd3dDevice->SetStreamSource( 0, m_pVB, 0, sizeof(VT_CAICUBE) );
 m_pd3dDevice->SetFVF( VT_CAICUBE::dwFVF );
 
m_pd3dDevice->SetIndices(m_pIB);
m_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,8,0,12);
上面代码先设置了8个顶点缓冲区到管道里,然后再调用SetIndices设置索引缓冲区,最后调用DrawIndexedPrimitive函数来显示所有三角形列表。DrawIndexedPrimitive函数的第一个参数是索引列表里的类型,第二个参数是索引缓冲区在顶点缓冲区里的起始位置,比如顶点缓区是0,1,3的,当设置这个值为50时,那么相当于访问了50,51,53的顶点值。第三个参数是最小索引值。第四个参数是有多少个顶点会使用。第五个参数是从那个索引值开始。第六个参数是基本图形个数。
 
通过这样学习,就学会使用了索引缓冲区,如果有很多顶点,有很多三角形,就需要使用优化的算法,以便有最少的顶点,使用最多的索引,这样有利于减少内存占用,提高带宽利用,提高显示卡显示更多图形,提高游戏的性能。
 
 
 3D游戏从入门到精通-13
2.10.1基本图形显示
在D3D里只有几种基本图形可以显示的,它们分别是:
点列表、线列表、线带列表、三角形列表、三角形带列表、三角形扇形列表。
其它任何的图形,都可以由这向这几种基本图形组合出来。上面已经介绍了三角形显示了,所以这里就不再具体地说明它了。
1、             点列表
点列表主要显示为一个个像素点的集合,每个点都是单独显示,分离的。点列表作用是用来显示点元素,或者显示点线的。如下图所示:
上面这个图就是使用点列表来显示一条正弦曲线。其实它们都是由分理的点组成,每个点可显示为不同的颜色。
现在就看看怎么样用代码显示这条正弦曲线的。
 
HRESULT hr;
 
 // 创建顶点缓冲区。
 if( FAILED( hr = m_pd3dDevice->CreateVertexBuffer(
       m_nPointListCount * sizeof(VT_CAIPRIMITIVE),
       0, VT_CAIPRIMITIVE::dwFVF,
       D3DPOOL_MANAGED, &m_pvbPointList, NULL ) ) )
 {
       //创建顶点缓冲区失败。
       return DXTRACE_ERR( "CreateVertexBuffer", hr );
 }
 
 //
 VT_CAIPRIMITIVE* pVertices;
 if( FAILED( hr = m_pvbPointList->Lock( 0, 0, (VOID**)&pVertices, 0 ) ) )
 {
       //锁住顶点缓冲区。
       return DXTRACE_ERR( "Lock", hr );
 }
 
 for (int i = 0; i < m_nPointListCount; i++)
 {
       float fX = -10.0f + (float)i * 0.1f;
       float fY = sinf(fX);
       pVertices[i].vPosition = D3DXVECTOR3( fX, fY, 0.0f );    
       pVertices[i].crDiffuse = D3DCOLOR_COLORVALUE( 1.0, 0.0, 0.0, 1.0 );
 }
 
 
 //解锁顶点缓冲区。
 m_pvbPointList->Unlock();
 
上面这段代码,先创建点列表的顶点缓冲区,然后使用正弦函数计算点坐标的值,并把它们设置到顶点缓冲区里。接着就需要调用渲染函数:
m_pd3dDevice->SetStreamSource( 0, m_pvbPointList, 0, sizeof(VT_CAIPRIMITIVE) );
m_pd3dDevice->SetFVF( VT_CAIPRIMITIVE::dwFVF );
m_pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, 0, m_nPointListCount );  
先设置顶点缓冲区,然后调用DrawPrimitive函数来显示,这里第一个参数不一样,使用了D3DPT_POINTLIST类型,这个类型就是显示为点列表方式。
如果使用直线或者三角形不能显示的图形,就可以使用它来显示了。
 
 
 3D游戏从入门到精通-14
 
2、             线列表
D3D还提供直线的显示,由于很多自然现现象需要它来显示。比如大雨,就需要使用直线来模拟出来。如下图所示:
这里显示三条红、绿、蓝的直线。它的代码如下:
pVertices[0].vPosition = D3DXVECTOR3( -2.0f, 0.0f, 0.0f );    
 pVertices[0].crDiffuse = D3DCOLOR_COLORVALUE( 1.0, 0.0, 0.0, 1.0 );
 
 pVertices[1].vPosition = D3DXVECTOR3( 2.0f, 0.0f, 0.0f );    
 pVertices[1].crDiffuse = D3DCOLOR_COLORVALUE( 1.0, 0.0, 0.0, 1.0 );
 
 //
 pVertices[2].vPosition = D3DXVECTOR3( 0.0f, -2.0f, 0.0f );   
 pVertices[2].crDiffuse = D3DCOLOR_COLORVALUE( 0.0, 1.0, 0.0, 1.0 );
 
 pVertices[3].vPosition = D3DXVECTOR3( 0.0f, 2.0f, 0.0f );    
 pVertices[3].crDiffuse = D3DCOLOR_COLORVALUE( 0.0, 1.0, 0.0, 1.0 );
 
 //
 pVertices[4].vPosition = D3DXVECTOR3( -2.0f, -2.0f, 0.0f );
 pVertices[4].crDiffuse = D3DCOLOR_COLORVALUE( 0.0, 0.0, 1.0, 1.0 );
 
 pVertices[5].vPosition = D3DXVECTOR3( 2.0f, 2.0f, 0.0f );    
 pVertices[5].crDiffuse = D3DCOLOR_COLORVALUE( 0.0, 0.0, 1.0, 1.0 );
 
上面的代码分别设置了三条直线的向量和颜色值。然后调用下面函数显示:
 
m_pd3dDevice->DrawPrimitive( D3DPT_LINELIST, 0, m_nLineListCount );
第一个参数设置为D3DPT_LINELIST类型,就是直线列表的方式。
 
 3D游戏从入门到精通-15
 
3、             线带列表
在D3D里还提供线带列表显示,这种显示方式是把所有直线顺着顶点连接显示出来。如下图所示:
采用线带列表显示的方式,可以减少顶点占用内存空间,提高显示效率。下面这段代码就是显示6个顶点的直线。
 
m_nLineStripCount = 3;
 
HRESULT hr;
 
 // 创建顶点缓冲区。
 if( FAILED( hr = m_pd3dDevice->CreateVertexBuffer(
       m_nLineStripCount*2 * sizeof(VT_CAIPRIMITIVE),
       0, VT_CAIPRIMITIVE::dwFVF,
       D3DPOOL_MANAGED, &pVB, NULL ) ) )
 {
       //创建顶点缓冲区失败。
       return DXTRACE_ERR( "CreateVertexBuffer", hr );
 }
 
 //
 VT_CAIPRIMITIVE* pVertices;
 if( FAILED( hr = pVB->Lock( 0, 0, (VOID**)&pVertices, 0 ) ) )
 {
       //锁住顶点缓冲区。
       return DXTRACE_ERR( "Lock", hr );
 }
 
 pVertices[0].vPosition = D3DXVECTOR3( -6.0f, -2.0f, 2.0f );
 pVertices[0].crDiffuse = D3DCOLOR_COLORVALUE( 1.0, 0.0, 0.0, 1.0 );
 
 pVertices[1].vPosition = D3DXVECTOR3( -4.0f, 2.0f, 2.0f );   
 pVertices[1].crDiffuse = D3DCOLOR_COLORVALUE( 1.0, 0.0, 0.0, 1.0 );
 
 //
 pVertices[2].vPosition = D3DXVECTOR3( -2.0f, -2.0f, 2.0f );
 pVertices[2].crDiffuse = D3DCOLOR_COLORVALUE( 0.0, 1.0, 0.0, 1.0 );
 
 pVertices[3].vPosition = D3DXVECTOR3( 0.0f, 2.0f, 0.0f );    
 pVertices[3].crDiffuse = D3DCOLOR_COLORVALUE( 0.0, 1.0, 0.0, 1.0 );
 
 //
 pVertices[4].vPosition = D3DXVECTOR3( 2.0f, -2.0f, 2.0f );   
 pVertices[4].crDiffuse = D3DCOLOR_COLORVALUE( 0.0, 0.0, 1.0, 1.0 );
 
 pVertices[5].vPosition = D3DXVECTOR3( 4.0f, 2.0f, 2.0f );    
 pVertices[5].crDiffuse = D3DCOLOR_COLORVALUE( 0.0, 0.0, 1.0, 1.0 );
 
 //解锁顶点缓冲区。
 pVB->Unlock();
 
上面的代码是先创建合适的顶点缓冲区,然后设置6个顶点的值。由这6个顶点就可以连接成为5条直线。显示的代码如下:
 
m_pd3dDevice->SetStreamSource( 0, m_pvbLineStrip, 0, sizeof(VT_CAIPRIMITIVE) );
 m_pd3dDevice->SetFVF( VT_CAIPRIMITIVE::dwFVF );
 m_pd3dDevice->DrawPrimitive( D3DPT_LINESTRIP, 0, m_nLineStripCount*2 -1 );
上面的DrawPrimitive函数设置为D3DPT_LINESTRIP显示,就是线带方式显示。最后一个参数指明了它要显示多少条直线。
 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值