/***********************************
*作者:蔡军生
*出处: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
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
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显示,就是线带方式显示。最后一个参数指明了它要显示多少条直线。