在Direct3D中,实体模型中的一个点可能被多个三角形所共用,如果对每个三角形都构造三个顶点,则会浪费很多的系统资源。而如果我们将重复使用的顶点数据只保留一份,则需要有一份表来记录每个图元都使用了哪些点,而这份表就是索引缓冲区。通过使用索引缓冲区可以减少原来顶点缓冲区的顶点数量,且将顶点数据和顶点顺序分开存储,顶点数据仍放在顶点缓冲区中,索引缓冲区则按图元的格式,存放顶点的索引顺序。

我们先看以下代码:

//********************************************************************
//      Filename: Main.cpp
//        Author: Chinafish
//      Modifier: Chinafish
//       Created: 2011-09-21 10:43
//       Updated: 2011-09-21 10:43
//            QQ: 149200849
//           MSN: china_fish@msn.com
//       Purpose: Index buffer.
//====================================================================
//  Copyright(C) 2004-2011 by Chinafish. All Rights Reserved.
//********************************************************************

#define STRICT
#define WIN32_LEAN_AND_MEAN
#include "windows.h"

// Link D3D
#include "d3d9.h"
#include "d3dx9.h"
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")

#define Release_Safe(p){if(p!=NULL) p->Release();}

#define WINDOW_TITLE	"www.csinx.org - Index Buffer (DX9+VC9)"
#define WINDOW_WIDTH	800
#define WINDOW_HEIGHT	600

//-----------------------------------------------------------------------------
// GLOBALS
//-----------------------------------------------------------------------------
HWND					g_hWnd			= NULL;
LPDIRECT3D9				g_pD3D			= NULL;
LPDIRECT3DDEVICE9		g_pd3dDevice	= NULL;
D3DPRESENT_PARAMETERS	g_d3dpp;
LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer			= NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVertexBufferIndexed	= NULL;
LPDIRECT3DINDEXBUFFER9	g_pIndexBuffer			= NULL;

BOOL					g_bUseIndexBuffer		= FALSE;
int						g_nNumVertexIndexed		= 0;

#define D3DFVF_MY_VERTEX ( D3DFVF_XYZRHW | D3DFVF_DIFFUSE )

struct MyVertex
{
	float x, y, z, rhw;	// Position of vertex in screen coordinates
	DWORD color;		// Color of vertex
};

//-----------------------------------------------------------------------------
// PROTOTYPES
//-----------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void InitD3D();
void ShutDown(void);
void Render(void); 

//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: The application's entry point
//-----------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASSEX	winClass;
	MSG			uMsg;
	memset(&uMsg,0,sizeof(uMsg));

	winClass.lpszClassName	= "CLASS_INDEXBUFFER";
	winClass.cbSize			= sizeof(WNDCLASSEX);
	winClass.style			= CS_HREDRAW | CS_VREDRAW;
	winClass.lpfnWndProc	= WindowProc;
	winClass.hInstance		= hInstance;
	winClass.hIcon			= LoadIcon(hInstance, (LPCTSTR)NULL);
	winClass.hIconSm		= LoadIcon(hInstance, (LPCTSTR)NULL);
	winClass.hCursor		= LoadCursor(NULL, IDC_ARROW);
	winClass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
	winClass.lpszMenuName	= NULL;
	winClass.cbClsExtra		= 0;
	winClass.cbWndExtra		= 0;

	if(!RegisterClassEx(&winClass))
		return E_FAIL;

	g_hWnd = CreateWindowEx( NULL, "CLASS_INDEXBUFFER",
		WINDOW_TITLE,
		WS_EX_TOPMOST, 0, 0,
		WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL,
		hInstance, NULL );

	if( g_hWnd == NULL )
		return E_FAIL;

	// Adjust window
	RECT rect = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT };
	AdjustWindowRect( &rect, GetWindowLong( g_hWnd, GWL_STYLE ), FALSE );
	SetWindowPos( g_hWnd, 0, 0, 0, rect.right - rect.left, rect.bottom - rect.top,
		SWP_NOZORDER | SWP_NOMOVE );

	ShowWindow( g_hWnd, nCmdShow );
	UpdateWindow( g_hWnd );

	// Init or reset D3D
	InitD3D();

	while(uMsg.message != WM_QUIT)
	{
		if(PeekMessage( &uMsg, NULL, 0, 0, PM_REMOVE ))
		{
			TranslateMessage( &uMsg );
			DispatchMessage( &uMsg );
		}
		else
		{
			// Render a frame
			Render();
		}
	}

	ShutDown();

	UnregisterClass( "CLASS_INDEXBUFFER", winClass.hInstance );

	return uMsg.wParam;
}

//-----------------------------------------------------------------------------
// Name: WindowProc()
// Desc: The window's message handler
//-----------------------------------------------------------------------------
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch( msg )
	{
	case WM_KEYDOWN:
		{
			switch( wParam )
			{
			case VK_ESCAPE:
				PostQuitMessage(0);
				break;
			case VK_SPACE:
				g_bUseIndexBuffer = !g_bUseIndexBuffer;
				break;
			}
		}
		break;
	case WM_CLOSE:
		{
			PostQuitMessage(0);
		}
	case WM_DESTROY:
		{
			PostQuitMessage(0);
		}
		break;
	default:
		{
			return DefWindowProc( hWnd, msg, wParam, lParam );
		}
		break;
	}

	return 0;
}

//-----------------------------------------------------------------------------
// Name: InitD3D()
// Desc: This function will only be called once during the application's
//       initialization phase. Therefore, it can't contain any resources that
//       need to be restored every time the Direct3D device is lost or the
//       window is resized.
//-----------------------------------------------------------------------------
void InitD3D()
{
	// Create D3D
	g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
	if( !g_pD3D )
	{
		MessageBox( NULL, "Direct3DCreate9() - Failed!", "Error", NULL );
		return;
	}

	D3DDISPLAYMODE d3ddm;
	g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm );

	ZeroMemory( &g_d3dpp, sizeof( g_d3dpp ) );
	g_d3dpp.Windowed				= TRUE;
	g_d3dpp.SwapEffect				= D3DSWAPEFFECT_DISCARD;
	g_d3dpp.BackBufferFormat		= d3ddm.Format;
	g_d3dpp.PresentationInterval	= D3DPRESENT_INTERVAL_IMMEDIATE;

	g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
		g_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,
		&g_d3dpp, &g_pd3dDevice );

	if( !g_pd3dDevice )
	{
		return;
	}

	//
	// Initialize vertices
	//

	float fAngle = D3DX_PI/180;
	float fX1[6],fY1[6],fX2[6],fY2[6];
	float fXLeft = WINDOW_WIDTH/2;
	float fTopEX = WINDOW_HEIGHT/2;
	float fR1 = fXLeft*WINDOW_HEIGHT/WINDOW_WIDTH;
	float fR2 = fR1*sin( 18*fAngle )/cos( 36*fAngle );
	for(int i=0;i<5;i++)
	{
		fX2[i] = fXLeft + fR1*cos( (90+i*72)*fAngle );
		fY2[i] = fTopEX - fR1*sin( (90+i*72)*fAngle );
		fX1[i] = fXLeft + fR2*cos( (54+i*72)*fAngle );
		fY1[i] = fTopEX - fR2*sin( (54+i*72)*fAngle );
	}

	MyVertex vertexData[] =
	{
		// 20
		{ fXLeft, fTopEX, 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fX1[0], fY1[0], 0.0f, 1.0f, D3DCOLOR_XRGB(255, 255, 255) },
		{ fX1[1], fY1[1], 0.0f, 1.0f, D3DCOLOR_XRGB(255, 255, 255) },
		{ fX2[0], fY2[0], 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fXLeft, fTopEX, 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fX1[1], fY1[1], 0.0f, 1.0f, D3DCOLOR_XRGB(255, 255, 255) },
		{ fX1[2], fY1[2], 0.0f, 1.0f, D3DCOLOR_XRGB(  0, 255,   0) },
		{ fX2[1], fY2[1], 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fXLeft, fTopEX, 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fX1[2], fY1[2], 0.0f, 1.0f, D3DCOLOR_XRGB(  0, 255,   0) },
		{ fX1[3], fY1[3], 0.0f, 1.0f, D3DCOLOR_XRGB(  0,   0, 255) },
		{ fX2[2], fY2[2], 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fXLeft, fTopEX, 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fX1[3], fY1[3], 0.0f, 1.0f, D3DCOLOR_XRGB(  0,   0, 255) },
		{ fX1[4], fY1[4], 0.0f, 1.0f, D3DCOLOR_XRGB(  0, 255,   0) },
		{ fX2[3], fY2[3], 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fXLeft, fTopEX, 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fX1[4], fY1[4], 0.0f, 1.0f, D3DCOLOR_XRGB(  0, 255,   0) },
		{ fX1[0], fY1[0], 0.0f, 1.0f, D3DCOLOR_XRGB(255, 255, 255) },
		{ fX2[4], fY2[4], 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) }
	};

	MyVertex vertexData_Indexed[] =
	{
		// 11
		{ fX2[0], fY2[0], 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fX2[1], fY2[1], 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fX1[1], fY1[1], 0.0f, 1.0f, D3DCOLOR_XRGB(255, 255, 255) },
		{ fX1[0], fY1[0], 0.0f, 1.0f, D3DCOLOR_XRGB(255, 255, 255) },
		{ fX2[4], fY2[4], 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fXLeft, fTopEX, 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fX1[2], fY1[2], 0.0f, 1.0f, D3DCOLOR_XRGB(  0,   0, 255) }, // 绿与原蓝色区别
		{ fX1[4], fY1[4], 0.0f, 1.0f, D3DCOLOR_XRGB(  0,   0, 255) }, // 绿与原蓝色区别
		{ fX1[3], fY1[3], 0.0f, 1.0f, D3DCOLOR_XRGB(  0, 255,   0) }, // 蓝与原绿色区别
		{ fX2[2], fY2[2], 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) },
		{ fX2[3], fY2[3], 0.0f, 1.0f, D3DCOLOR_XRGB(255,   0,   0) }
	};

	WORD indexedData[] =
	{
		5, 3, 2,  0,		// 上
		5, 2, 6,  1,		// 左上
		5, 6, 8,  9,		// 左下
		5, 8, 7, 10,		// 右下
		5, 7, 3,  4,		// 右上
	};

	// Create the vertex buffer. Here we are allocating enough memory
	// (from the default pool) to hold all our 3 custom vertices. We also
	// specify the FVF, so the vertex buffer knows what data it contains.
	g_pd3dDevice->CreateVertexBuffer( sizeof( vertexData ), 0, D3DFVF_MY_VERTEX,
		D3DPOOL_DEFAULT, &g_pVertexBuffer,
		NULL );

	// Now we fill the vertex buffer. To do this, we need to Lock() the VB to
	// gain access to the vertices. This mechanism is required becuase vertex
	// buffers may be in device memory.
	MyVertex *pVertices = NULL;
	g_pVertexBuffer->Lock( 0, sizeof( vertexData ), (void**)&pVertices, 0 );
	memcpy( pVertices, vertexData, sizeof( vertexData ) );
	g_pVertexBuffer->Unlock();

	// uniqued vertex
	g_nNumVertexIndexed = sizeof( vertexData_Indexed )/sizeof( MyVertex );
	g_pd3dDevice->CreateVertexBuffer( sizeof( vertexData_Indexed ), 0, D3DFVF_MY_VERTEX,
		D3DPOOL_DEFAULT, &g_pVertexBufferIndexed,
		NULL );

	g_pVertexBufferIndexed->Lock( 0, sizeof( vertexData_Indexed ), (void**)&pVertices, 0 );
	memcpy( pVertices, vertexData_Indexed, sizeof( vertexData_Indexed ) );
	g_pVertexBufferIndexed->Unlock();

	//
	// Create an index buffer to use with our indexed vertex buffer...
	//

	g_pd3dDevice->CreateIndexBuffer( sizeof( indexedData ),
		D3DUSAGE_WRITEONLY,
		D3DFMT_INDEX16,
		D3DPOOL_DEFAULT,
		&g_pIndexBuffer,
		NULL );

	WORD *pIndices = NULL;
	g_pIndexBuffer->Lock( 0, sizeof( indexedData ), (void**)&pIndices, 0 );
	memcpy( pIndices, indexedData, sizeof( indexedData ) );
	g_pIndexBuffer->Unlock();


	g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );
}

//-----------------------------------------------------------------------------
// Name: ShutDown()
// Desc: Release all
//-----------------------------------------------------------------------------
void ShutDown(void)
{
	Release_Safe( g_pVertexBuffer );
	Release_Safe( g_pVertexBufferIndexed );
	Release_Safe( g_pIndexBuffer );
	Release_Safe( g_pd3dDevice );
	Release_Safe( g_pD3D );
}

//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Render something you want
//-----------------------------------------------------------------------------
void Render(void)
{
	// Clear screen with D3DCOLOR_XRGB(80, 0, 80)
	g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(80, 0, 80), 1.0f, 0 );

	g_pd3dDevice->BeginScene();

	// Render geometry here...
	g_pd3dDevice->SetFVF( D3DFVF_MY_VERTEX );
	if( g_bUseIndexBuffer )
	{
		// indexed
		g_pd3dDevice->SetStreamSource( 0, g_pVertexBufferIndexed, 0, sizeof( MyVertex ) );
		g_pd3dDevice->SetIndices( g_pIndexBuffer );
		g_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLESTRIP, 0, 0, g_nNumVertexIndexed,  0, 2 );
		g_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLESTRIP, 0, 0, g_nNumVertexIndexed,  4, 2 );
		g_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLESTRIP, 0, 0, g_nNumVertexIndexed,  8, 2 );
		g_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLESTRIP, 0, 0, g_nNumVertexIndexed, 12, 2 );
		g_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLESTRIP, 0, 0, g_nNumVertexIndexed, 16, 2 );
	}
	else
	{
		g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof( MyVertex ) );
		g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  0, 2 );
		g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  4, 2 );
		g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  8, 2 );
		g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 12, 2 );
		g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 16, 2 );
	}

	g_pd3dDevice->EndScene();

	g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
				

以上代码我们使用了两种渲染方法,通过空格键切换,为了便于查看两种渲染方式有三个顶点颜色是不相同的。默认使用没有索引的渲染方式,先定义每个图元的顶点数据,包括位置和颜色。 通过调用 IDirect3DDevice9::CreateVertexBuffer 方法创建了一个包含重复顶点数据的顶点缓冲区和一个无重复顶点数据的顶点缓冲区,通过调用 IDirect3DDevice9::CreateIndexBuffer 方法创建索引缓冲区。在渲染时通过调用 IDirect3DDevice9::SetStreamSource 方法将顶点缓冲区绑定到设备数据流上。在不使用索引缓冲区时通过调用 IDirect3DDevice9::DrawPrimitive 方法按照当前指定的顶点格式渲染无索引的顶点序列,最后显示出来。

在使用索引缓冲区时先通过调用 IDirect3DDevice9::SetIndices方法设置索引数据,再通过调用 IDirect3DDevice9::DrawIndexedPrimitive 方法按照当前指定的顶点格式渲染有索引的顶点序列,最后显示出来。

下图为本例代码执行效果:

[返回目录][我有话说]