Сегодня у нас небольшая и простая тема: индексные буферы.

В прошлом уроке было упражнение, в котором вы должны были построить куб. Особо наблюдательные могли заметить, что для этого нужно было запихать в вершинный буфер координаты 36 вершин. Но, как вы возможно знаете, в кубе всего 8 вершин. Используя вершинный буфер пришлось определять 36. Представьте, сколько вершин нужно определять для моделей немногим сложнее куба!!?? Это ж рехнуться можно!

Для решения данной проблемы используется два буфера: вершинный и индексный. В вершинном буфере хранится непосредственно информация о вершинах: координаты, цвет и много чего другого, о чём нам ещё рано рассуждать. В индексном же буффере хранится, как бы это сказать, порядок (номера/индексы) вершин в треугольниках. Для создания индексного буфера предназначен метод IDirect3DDevice9::CreateIndexBuffer:

код на языке c++
HRESULT CreateIndexBuffer(
  UINT Length,
  DWORD Usage,
  D3DFORMAT Format,
  D3DPOOL Pool,
  IDirect3DIndexBuffer9** ppIndexBuffer,
  HANDLE* pSharedHandle
);

Length:
Длина буфера в байтах.

Usage:
Используется также как и в вершинном буфере.

Format:
Формат индексов, или по другому - размер каждого индекса. Для хранения индексов мы будем использовать тип unsigned short (16 бит), поэтому аргумент будет принимать значение - D3DFMT_INDEX16.

Pool
Используется также, как и в вершинном буфере.

ppIndexBuffer
Адрес указателя на индексный буфер.

pSharedHandle
Параметр используется только в Windows Vista. Всегда будем передавать NULL.

IDirect3DIndexBuffer9

Для индексных буферов по аналогии с IDirect3DVertexBuffer9 используется свой интерфейс - IDirect3DIndexBuffer9. Пока нас интересует только два метода (из трёх) этого интейрфейса:

код на языке c++
HRESULT Lock(
  UINT OffsetToLock,
  UINT SizeToLock,
  VOID ** ppbData,
  DWORD Flags
);

Как видите, данный метод идентичен соотвествующему из интерфейса IDirect3DVertexBuffer9. Поэтому остановимся только на третьем аргументе ppbData. В данный аргумент передаётся массив в котором хранится информация, а конкретно - индексы вершин из вершинного буфера.

Второй метод предназначен для размыканния буфера - Unlock, аргументов у него нет.

Ну, а теперь собственно сам код. Весь код связанный с индексным буфером я поместил рядом с вершинным, надеюсь, это поможет вам лучше разобраться.

Экземпляр интерфейса обзовём ib:

код на языке c++
IDirect3DVertexBuffer9* vb = NULL;
IDirect3DIndexBuffer9* ib = NULL;
Создаём буферы:

dev->CreateVertexBuffer( 4* sizeof(vertex), D3DUSAGE_WRITEONLY,
                         D3DFVF_XYZ|D3DFVF_DIFFUSE, D3DPOOL_DEFAULT,
                         &vb,NULL);

dev->CreateIndexBuffer( 6*sizeof(unsigned short),
                        D3DUSAGE_WRITEONLY, D3DFMT_INDEX16,
                        D3DPOOL_DEFAULT, &ib, NULL );

Далее, создаём данные для буферов: в первом - информация о вершинах, во втором - индексы этих вершин в треугольниках модели. Определение структуры vertex с прошлого выпуска не изменилось:

код на языке c++
vertex vertices[] =
{
  { 0, 0, 0, 0xff000000},
  { 0, 1, 0, 0xff000000},
  { 1, 0, 0, 0xff000000},
  { 1, 1, 0, 0xff000000},
};

unsigned short indexes[] = { 0, 1, 2, 1, 3, 2 };

Здесь определены четыре вершины в массиве vertices.

В массиве indexes хранятся индексы вершин из массива vertices, которые образуют треугольники. Первые три цифры - первый треугольик, следующие три цифры - второй и т.д.

В индексном буфере указываются номера элементов из вершинного буфера: 0, 1, 2, 1, 3, 2. Т.е. первый треугольник будет построен из элементов vertices[0], vertices[1], vertices[2]. Второй треугольник будет построен из вершин vertices[1], vertices[3], vertices[2].

Объявляем вспомогательные переменные:

код на языке c++
void* vb_vertices;
void* ib_indexes;
Заполняем буферы:

vb->Lock(0,sizeof(vertices),(void**)&vb_vertices,0);
memcpy(vb_vertices,vertices,sizeof(vertices));
vb->Unlock();

ib->Lock(0,sizeof(indexes),(void**)&ib_indexes,0);
memcpy(ib_indexes,indexes,sizeof(indexes));
ib->Unlock();

Насколько мы помним, дальше нужно указать гибкий формат вершин (FVF) и привязать вершинный буфер к потоку данных. Это выполняется двумя методами устройства: SetFVF и SetStreamSource. В этот раз нам понадобится ещё один метод - SetIndices, который говорит устройству (видеокарте) адрес индексного буфера. Единственный параметр данного метода - указатель на индексный буфер. Теперь, все три метода вместе:

код на языке c++
dev->SetStreamSource(0,vb,0,sizeof(vertex));
dev->SetFVF(D3DFVF_XYZ|D3DFVF_DIFFUSE);
dev->SetIndices( ib );

Вот в общем-то и всё, инициализация закончилась, можно переходить к выводу графики.

В прошлый раз мы использовали метод IDirect3DDevice9::DrawPrimitive. В случае использования индексный буферов, применяется другой метод:

код на языке c++
HRESULT DrawIndexedPrimitive(
  D3DPRIMITIVETYPE Type,
  INT BaseVertexIndex,
  UINT MinIndex,
  UINT NumVertices,
  UINT StartIndex,
  UINT PrimitiveCount
);

Type:
Передаём D3DPT_TRIANGLELIST. В одном из следующих уроков мы познакомимся и с другими типами примитивов.

BaseVertexIndex:
Смещение в вершинном буфере. Значение аргумента говорит, с какой вершины начнётся вывод.

MinIndex:
Минимальный индекс вершины.

NumVertices:
Количество вершин используемых для вывода.

StartIndex:
Первый индекс.

PrimitiveCount:
Количество примитивов.

Пока что на параметры BaseVertexIndex, MinIndex и StartIndex не обращайте внимания. Мы ещё разберёмся с ними.

Следующей строчкой нужно заменить вызов DrawPrimitive в основном цикле:

DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,4,0,2);
Четвёртый параметр - количество вершин применяемых для вывода графики, последний параметр - количество примитивов.