使用索引缓冲区绘制图形
当绘制一个比较复杂的图形时,需要使用许多相互邻接的三角形。如果为每个三角形准备三个顶点数据,显然有许多数据是重复的,这样会浪费大量的内存和系统带宽。为了解决这一问题,可以先创建一个顶点缓冲区,将不重复的顶点数据写入顶点缓冲区,然后创建一个顶点索引缓冲区(index
buffer),存放各个三角形的顶点索引信息,最后通过顶点索引和顶点数据共同完成图形绘制。
在Direct3D中一个顶点的索引只需要用一个16位或32位的整数表示,因此当多边形的顶点有较多重复使用时,使用索引数组通常能够比直接绘制顶点序列节省一部分内存空间和系统带宽。同时,Direct3D渲染流水线避免了对相同顶点进行重复计算,从而可以相应地提高图形程序的整体性能。
首先定义顶点结构和灵活顶点格式:
struct sCustomVertex
{
float x, y, z, rhw;
DWORD color;
};
#define D3DFVF_CUSTOM_VERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
接着创建顶点缓冲区和索引缓冲区,各顶点位置如下图所示:
void init_vertices()
{
sCustomVertex vertices[9];
vertices[0].x = 300.0f;
vertices[0].y = 250.0f;
vertices[0].z = 0.5f;
vertices[0].rhw = 1.0f;
vertices[0].color = 0xffff0000;
for(int i = 0; i < 8; i++)
{
vertices[i+1].x = (200 * sin(i * 3.14159f / 4.0f)) + 300;
vertices[i+1].y = -(200 * cos(i * 3.14159f / 4.0f)) + 250;
vertices[i+1].z = 0.5f;
vertices[i+1].rhw = 1.0f;
vertices[i+1].color = 0xff00ff00;
}
WORD indices[] = { 0,1,2, 0,2,3, 0,3,4, 0,4,5, 0,5,6, 0,6,7, 0,7,8, 0,8,1 };
// push vertex data into vertex buffer
g_device->CreateVertexBuffer(sizeof(vertices), 0, D3DFVF_CUSTOM_VERTEX, D3DPOOL_DEFAULT, &g_vertex_buffer, NULL);
void* ptr;
g_vertex_buffer->Lock(0, sizeof(vertices), (void**)&ptr, 0);
memcpy(ptr, vertices, sizeof(vertices));
g_vertex_buffer->Unlock();
// push vertex index into index buffer
g_device->CreateIndexBuffer(sizeof(indices), 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_index_buffer, NULL);
void* index_ptr;
g_index_buffer->Lock(0, sizeof(indices), (void**)&index_ptr, 0);
memcpy(index_ptr, indices, sizeof(indices));
g_index_buffer->Unlock();
}
在上面的代码段中,使用IDirect3DDevice9::CreateIndexBuffer()函数创建所需要的索引缓冲区,该函数的声明如下:
Creates an index buffer.
HRESULT CreateIndexBuffer(
UINT Length,
DWORD Usage,
D3DFORMAT Format,
D3DPOOL Pool,
IDirect3DIndexBuffer9** ppIndexBuffer,
HANDLE* pSharedHandle
);
Parameters
- Length
- [in] Size of the index buffer, in bytes.
- Usage
- [in] Usage can be 0, which indicates no usage
value. However, if usage is desired, use a combination of one or more
D3DUSAGE constants. It is good practice to match the usage parameter in
CreateIndexBuffer with the behavior flags in IDirect3D9::CreateDevice. For
more information, see Remarks.
- Format
- [in] Member of the D3DFORMAT enumerated type,
describing the format of the index buffer. For more information, see
Remarks. The valid settings are the following:
- D3DFMT_INDEX16
- Indices are 16 bits each.
- D3DFMT_INDEX32
- Indices are 32 bits each.
- Pool
- [in] Member of the D3DPOOL enumerated type,
describing a valid memory class into which to place the resource.
- ppIndexBuffer
- [out, retval] Address of a pointer to an
IDirect3DIndexBuffer9 interface, representing the created index buffer
resource.
- pSharedHandle
- [in] Reserved. Set this parameter to NULL.
Return Values
If the method succeeds, the return value is D3D_OK. If
the method fails, the return value can be one of the following:
D3DERR_INVALIDCALL, D3DERR_OUTOFVIDEOMEMORY, D3DXERR_INVALIDDATA, E_OUTOFMEMORY.
Remarks
Index buffers are memory resources used to hold
indices, they are similar to both surfaces and vertex buffers. The use of index
buffers enables Direct3D to avoid unnecessary data copying and to place the
buffer in the optimal memory type for the expected usage.
To use index buffers, create an index buffer, lock it,
fill it with indices, unlock it, pass it to IDirect3DDevice9::SetIndices, set up
the vertices, set up the vertex shader, and call
IDirect3DDevice9::DrawIndexedPrimitive for rendering.
The MaxVertexIndex member of the D3DCAPS9 structure
indicates the types of index buffers that are valid for rendering.
接着使用索引缓冲区绘制图形:
void render()
{
g_device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_X#050505, 1.0f, 0);
g_device->BeginScene();
g_device->SetRenderState(D3DRS_FILLMODE, g_fill_mode);
g_device->SetStreamSource(0, g_vertex_buffer, 0, sizeof(sCustomVertex));
g_device->SetFVF(D3DFVF_CUSTOM_VERTEX);
g_device->SetIndices(g_index_buffer);
g_device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 9, 0, 8);
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
}
函数IDirect3DDevice9::DrawIndexedPrimitive()使用当前设置的索引缓冲区绘制图形:
Based on indexing, renders the specified geometric
primitive into an array of vertices.
HRESULT DrawIndexedPrimitive(
D3DPRIMITIVETYPE Type,
INT BaseVertexIndex,
UINT MinIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount
);
Parameters
- Type
- [in] Member of the D3DPRIMITIVETYPE enumerated
type, describing the type of primitive to render. D3DPT_POINTLIST is not
supported with this method. See Remarks.
- BaseVertexIndex
- [in] Offset from the start of the vertex buffer to
the first vertex. See Scenario 4.
- MinIndex
- [in] Minimum vertex index for vertices used during
this call. This is a zero based index relative to BaseVertexIndex.
- NumVertices
- [in] Number of vertices used during this call. The
first vertex is located at index: BaseVertexIndex + MinIndex.
- StartIndex
- [in] Index of the first index to use when
accesssing the vertex buffer. Beginning at StartIndex to index vertices from
the vertex buffer.
- PrimitiveCount
- [in] Number of primitives to render. The number of
vertices used is a function of the primitive count and the primitive type.
The maximum number of primitives allowed is determined by checking the
MaxPrimitiveCount member of the D3DCAPS9 structure.
Return Values
If the method succeeds, the return value is D3D_OK. If
the method fails, the return value can be the following: D3DERR_INVALIDCALL.
Remarks
This method draws indexed primitives from the current
set of data input streams. MinIndex and all the indices in the index stream are
relative to the BaseVertexIndex.
The MinIndex and NumVertices parameters specify the
range of vertex indices used for each IDirect3DDevice9::DrawIndexedPrimitive
call. These are used to optimize vertex processing of indexed primitives by
processing a sequential range of vertices prior to indexing into these vertices.
It is invalid for any indices used during this call to reference any vertices
outside of this range.
IDirect3DDevice9::DrawIndexedPrimitive fails if
no index array is set.
The D3DPT_POINTLIST member of the D3DPRIMITIVETYPE
enumerated type is not supported and is not a valid type for this method.
When converting a legacy application to Direct3D 9, you
must add a call to either IDirect3DDevice9::SetFVF to use the fixed function
pipeline, or IDirect3DDevice9::SetVertexDeclaration to use a vertex shader
before you make any Draw calls.
完整源码如下:
#include <d3d9.h>
#include <math.h>
#define CLASS_NAME "GameApp"
#define release_com(p) do { if(p) { (p)->Release(); (p) = NULL; } } while(0)
IDirect3D9* g_d3d;
IDirect3DDevice9* g_device;
IDirect3DVertexBuffer9* g_vertex_buffer;
IDirect3DIndexBuffer9* g_index_buffer;
DWORD g_fill_mode = D3DFILL_SOLID;
struct sCustomVertex
{
float x, y, z, rhw;
DWORD color;
};
#define D3DFVF_CUSTOM_VERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
void init_vertices()
{
sCustomVertex vertices[9];
vertices[0].x = 300.0f;
vertices[0].y = 250.0f;
vertices[0].z = 0.5f;
vertices[0].rhw = 1.0f;
vertices[0].color = 0xffff0000;
for(int i = 0; i < 8; i++)
{
vertices[i+1].x = (200 * sin(i * 3.14159f / 4.0f)) + 300;
vertices[i+1].y = -(200 * cos(i * 3.14159f / 4.0f)) + 250;
vertices[i+1].z = 0.5f;
vertices[i+1].rhw = 1.0f;
vertices[i+1].color = 0xff00ff00;
}
WORD indices[] = { 0,1,2, 0,2,3, 0,3,4, 0,4,5, 0,5,6, 0,6,7, 0,7,8, 0,8,1 };
// push vertex data into vertex buffer
g_device->CreateVertexBuffer(sizeof(vertices), 0, D3DFVF_CUSTOM_VERTEX, D3DPOOL_DEFAULT, &g_vertex_buffer, NULL);
void* ptr;
g_vertex_buffer->Lock(0, sizeof(vertices), (void**)&ptr, 0);
memcpy(ptr, vertices, sizeof(vertices));
g_vertex_buffer->Unlock();
// push vertex index into index buffer
g_device->CreateIndexBuffer(sizeof(indices), 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_index_buffer, NULL);
void* index_ptr;
g_index_buffer->Lock(0, sizeof(indices), (void**)&index_ptr, 0);
memcpy(index_ptr, indices, sizeof(indices));
g_index_buffer->Unlock();
}
bool init_d3d(HWND hwnd)
{
g_d3d = Direct3DCreate9(D3D_SDK_VERSION);
if(g_d3d == NULL)
return false;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_device)))
{
return false;
}
init_vertices();
g_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
return true;
}
void cleanup()
{
release_com(g_index_buffer);
release_com(g_vertex_buffer);
release_com(g_device);
release_com(g_d3d);
}
void render()
{
g_device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(5, 5, 5), 1.0f, 0);
g_device->BeginScene();
g_device->SetRenderState(D3DRS_FILLMODE, g_fill_mode);
g_device->SetStreamSource(0, g_vertex_buffer, 0, sizeof(sCustomVertex));
g_device->SetFVF(D3DFVF_CUSTOM_VERTEX);
g_device->SetIndices(g_index_buffer);
g_device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 9, 0, 8);
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
}
LRESULT WINAPI WinProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_KEYDOWN:
switch(wParam)
{
case VK_SPACE:
g_fill_mode = (g_fill_mode == D3DFILL_WIREFRAME) ? D3DFILL_SOLID : D3DFILL_WIREFRAME;
break;
case VK_ESCAPE:
DestroyWindow(hwnd);
break;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR, INT)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_CLASSDC;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = inst;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = CLASS_NAME;
wc.hIconSm = NULL;
if(! RegisterClassEx(&wc))
return -1;
HWND hwnd = CreateWindow(CLASS_NAME, "Direct3D App", WS_OVERLAPPEDWINDOW, 200, 100, 600, 500,
NULL, NULL, wc.hInstance, NULL);
if(hwnd == NULL)
return -1;
if(init_d3d(hwnd))
{
ShowWindow(hwnd, SW_SHOWDEFAULT);
UpdateWindow(hwnd);
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
render();
}
}
cleanup();
UnregisterClass(CLASS_NAME, wc.hInstance);
return 0;
}
运行截图:
实体模式
线框模式
也可以只使用顶点缓冲区通过三角扇形图元来绘制,改动init_vertices()和render()即可,需要增加一个顶点使多边形封闭(即,使v9==v1)。
void init_vertices()
{
sCustomVertex vertices[10];
vertices[0].x = 300.0f;
vertices[0].y = 250.0f;
vertices[0].z = 0.5f;
vertices[0].rhw = 1.0f;
vertices[0].color = 0xffff0000;
for(int i = 0; i < 9; i++)
{
vertices[i+1].x = (200 * sin(i * 3.14159f / 4.0f)) + 300;
vertices[i+1].y = -(200 * cos(i * 3.14159f / 4.0f)) + 250;
vertices[i+1].z = 0.5f;
vertices[i+1].rhw = 1.0f;
vertices[i+1].color = 0xff00ff00;
}
// push vertex data into vertex buffer
g_device->CreateVertexBuffer(sizeof(vertices), 0, D3DFVF_CUSTOM_VERTEX, D3DPOOL_DEFAULT, &g_vertex_buffer, NULL);
void* ptr;
g_vertex_buffer->Lock(0, sizeof(vertices), (void**)&ptr, 0);
memcpy(ptr, vertices, sizeof(vertices));
g_vertex_buffer->Unlock();
}
void render()
{
g_device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_X#050505, 1.0f, 0);
g_device->BeginScene();
g_device->SetRenderState(D3DRS_FILLMODE, g_fill_mode);
g_device->SetStreamSource(0, g_vertex_buffer, 0, sizeof(sCustomVertex));
g_device->SetFVF(D3DFVF_CUSTOM_VERTEX);
g_device->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 8);
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
}