Выбрать главу

Координаты вершин пирамиды в пространстве можно записать следующим образом (не обращай пока внимание на последние 3 параметра каждой вершины):

float a=6.0;

#define vertA {-a/2, a/2, 0.0f, 0xffffffff, 0.0f, 1.0f,}

#define vertB {-a/2, -a/2, 0.0f, 0xffffffff, 0.0f, 0.0f,}

#define vertC {a/2, -a/2, 0.0f, 0xffffffff, 1.0f, 0.0f,}

#define vertD {a/2, a/2, 0.0f, 0xffffffff, 1.0f, 1.0f,}

#define vertS {0.0f, 0.0f, (float)(a/sqrt(2)), 0xffffffff, 0.5f, 0.5f,}

В D3D трехмерную модель можно задать различными способами. В нашем случае будем использовать TRIANGLELIST для которого фигура задается последовательностью треугольников. Когда задается треугольник, Direct3D сам определяет его лицевую и тыльную стороны по порядку следования вершин в массиве. При рендеринге, D3D автоматически "выбраковывает" тыльные стороны треугольников. Это заметно повышает скорость работы приложения. Но необходимо указать D3D в каком именно порядке задаются вершины лицевой стороны треугольников. Это делается с помощью той самой волшебной функции SetRenderState().

Итак, чтобы описать culling (выбраковку) тыльных сторон треугольников, вершины которых расположены в массиве по часовой стрелке (clockwise), необходимо написать следующее:

g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);

Для хранения вершин в D3D используются Vertex Buffer'ы (в дальнейшем, VB. Не путать с Visual Basic'ом :)). В зависимости от конкретной программы, VB'ы могут быть разных форматов. Например, если требуется написать программу, которая рисует на экране набор одноцветных точек, то для задания любой из точек требуется три числа, содержащих ее координаты в пространстве. Если точки должны отличаться по цвету, вводим четвертый параметр — цвет точки. Вроде бы все просто… Единственная сложность — мы как-то должны "сообщить" D3D в каком именно формате хранятся вершины в массиве, чтобы в процессе рендеринга не возникло путаницы. Впервые это нужно сделать в момент создания VB, затем перед рендерингом. Формат задается в виде комбинации флагов D3DFVF_*, полный список которых приведен в документации к D3D8. Нам же понадобятся лишь 3 флага:

#define D3DFVF_MYVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

D3DFVF_XYZ — вершина задается тремя координатами в пространстве (а может задаваться и четырьмя — при D3DFVF_XYZRHW)

D3DFVF_DIFFUSE — вершина содержит цвет, который влияет на рассеяние света

D3DFVF_TEX1 -—вершина содержит две текстурные координаты

Т.к. запись

D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1

интерпретируется компилятором в точности, как и

D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_DIFFUSE

значит порядок расположения данных в памяти в этом месте программы не задается. На программиста накладываются обязательства следовать схеме расположения данных, приведенной в руководстве D3D8 (раздел "About Vertex Formats").

Ну, надеюсь с этим все ясно. Теперь нужно занести вершины всех полигонов пирамиды в память. Для этого создаем массив из вершин и заполняем его данными:

MYVERTEX Vertices[] = {

 vertS, vertA, vertD,

 vertS, vertB, vertA,

 vertS, vertC, vertB,

 vertS, vertD, vertC,

};

Следующий шаг — нужно создать буфер вершин (VB) требуемого размера и формата. Пирамида будет отображена на экране так, что ее нижнего основания не будет видно, значит можно обойтись лишь 4-мя полигонами вместо 6-ти. Здесь я следовал правилу, которое прочитал в руководстве DX: "Remember, the fastest polygons are the ones you don't draw" (что в переводе означает: "Помни, наиболее быстрые полигоны — это те, которые ты не рисуешь"). Создание VB производится функцией CreateVertexBuffer:

HRESULT CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer8** ppVertexBuffer);

Length — длина VB в байтах

Usage — дополнительная информация о VB, которую D3D использует для создания оптимального VB

FVF — формат вершин, которые будут храниться в VB

Pool — в какой памяти создавать VB (можно создать его как в видеопамяти, так и в RAM)

ppVertexBuffer — адрес переменной, которая будет содержать указатель на созданный VB

Всего для хранения полигонов пирамиды используется 4*3*sizeof(MYVERTEX) байт (4 полигона, по 3 вершины в каждом).

if (FAILED(g_pD3DDevice->CreateVertexBuffer(4*3*sizeof(MYVERTEX), 0, D3DFVF_MYVERTEX, D3DPOOL_DEFAULT, &g_pVB))) {

 return FALSE;

}

Остается заполнить буфер вершинами. Для операций заполнения в DX (не только в D3D) используется пара команд Lock() и Unlock(). Команда Lock() возвращает адрес памяти, по которому расположен первый байт буфера. При этом вся память, отведенная под буфер как бы "запирается", и становится недоступной для других приложений. Операция "отпирания" памяти производится командой Unlock(). После запирания памяти, скопируем данные с помощью Си'шной функции memcpy().

VOID* pVertices;

if (FAILED(g_pVB->Lock(0, sizeof(Vertices), (BYTE**)&pVertices, 0))) return FALSE;

memcpy(pVertices, Vertices, sizeof(Vertices));

g_pVB->Unlock();

Функция DoMatrices()

Я считаю, что это — самая сложная для понимания функция. Разговор о матрицах выходит за формат данной статьи, т.к. это очень обширная тема. Советую почитать статьи JM'а по этому поводу (скажу по секрету — он фанат матриц ;o)). Но вкратце, я все равно расскажу о матрицах :-)