Программирование графики с использованием Direct3D

         

Функция CubeWin::CreateScene()


Сцена приложения Cube создается в функции CreateScene(), код которой представлен в листинге8.1.

Листинг 8.1. Функция CubeWin::CreateScene()

BOOL CubeWin::CreateScene() { // ------- СЕТКА -------- d3drm->CreateMesh(&mesh); mesh->AddGroup(24, 6, 4, vertorder, &group); mesh->SetVertices(group, 0, 24, vertexlist); mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(12), D3DVALUE(12), D3DVALUE(12)); //-------- ТЕКСТУРА ------ HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_WIN95TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); mesh->SetGroupTexture(group, texture); mesh->SetGroupMapping(group, D3DRMMAP_PERSPCORRECT); texture->Release(); texture = 0; //------- ФРЕЙМ -------- LPDIRECT3DRMFRAME frame; d3drm->CreateFrame(scene, &frame); frame->AddVisual(mesh); frame->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.04)); static CallbackData cbdata; cbdata.mesh = mesh; cbdata.group = group; frame->AddMoveCallback(UpdateCube, &cbdata); frame->Release(); frame = 0; // --------- СВЕТ -------- LPDIRECT3DRMLIGHT dlight; d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00), &dlight); LPDIRECT3DRMLIGHT alight; d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVALUE(0.50), D3DVALUE(0.50), D3DVALUE(0.50), &alight); LPDIRECT3DRMFRAME lightframe; d3drm->CreateFrame(scene, &lightframe); lightframe->SetOrientation(scene, D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1), D3DVALUE(0), D3DVALUE(1), D3DVALUE(0)); lightframe->AddLight(dlight); lightframe->AddLight(alight);

dlight->Release(); dlight = 0; alight->Release(); alight = 0; lightframe->Release(); lightframe = 0; //------ КАМЕРА ---------- d3drm->CreateFrame(scene, &camera); camera->SetPosition(scene, D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50)); d3drm->CreateViewport(device, camera, 0, 0, device->GetWidth(), device->GetHeight(), &viewport); return TRUE; }

Функция CreateScene() выполняет следующие действия:

  1. Создание сетки куба.
  2. Создание текстуры и ее наложение на сетку.
  3. Создание фрейма для сетки и установка функции обратного вызова.
  4. Создание двух источников света.
  5. Создание порта просмотра.

На первом этапе мы воспользуемся интерфейсом Direct3DRMMesh чтобы создать сетку куба. Давайте взглянем на код:

d3drm->CreateMesh(&mesh); mesh->AddGroup(24, 6, 4, vertorder, &group); mesh->SetVertices(group, 0, 24, vertexlist); mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(12), D3DVALUE(12), D3DVALUE(12));

Сперва для инициализации указателя mesh вызывается функция CreateMesh() интерфейса Direct3DRM. Обратите внимание, что ранее в тех приложениях, где использовался интерфейс Direct3DRMMesh, для создания сетки применялась функция CreateMesh() интерфейса Direct3DRMMeshBuilder, а не функция CreateMesh() интерфейса Direct3DRM. Создать сетку с помощью конструктора сеток очень легко, потому что можно использовать функцию Load() интерфейса Direct3DRMMeshBuilder чтобы загрузить сетку из файла. Поскольку мы используем функцию CreateMesh() интерфейса Direct3DRM, у нас будет создана пустая сетка.

Затем для инициализации сетки вызывается функция AddGroup() интерфейса Direct3DRMMesh. В результате будет создана группа граней сетки. Группой называется набор граней одной сетки, которыми можно управлять вместе как единой сущностью. Первым аргументом функции AddGroup() является количество вершин в группе. У нашего куба 24 вершины, поскольку он состоит из 6 граней у каждой из которых по 4 вершины. Второй аргумент функции AddGroup() задает количество граней в группе, а третий — количество вершин у каждой из граней. Четвертый аргумент функции AddGroup() — это массив индексов вершин. Определение массива vertorder, используемого в нашей программе в качестве четвертого аргумента, выглядит так:

unsigned vertorder[] = { 0,1,2,3,4,5,6,7,8,9,10,11, 12,13,14,15,16,17,18,19,20,21,22,23 };

Данный массив задает порядок вершин. В нашем случае индексы отсортированы в простейшем из возможных, последовательном порядке. Когда сетка создается с нуля, как это делаем мы, применение непоследовательного порядка вершин не дает особых преимуществ. Однако в данных сеток, экспортируемых из программ трехмерного моделирования, таких как 3D Studio, вершины вряд ли будут отсортированы по порядку номеров.

Пятый и последний аргумент функции AddGroup() это указатель на член данных group. Функция AddGroup() инициализирует эту переменную, записывая в нее идентификатор, который в последующих вызовах функций будет применяться для определения интересующей нас группы граней сетки. Если сетка содержит только одну группу граней (как в нашем случае), в качестве идентификатора группы используется ноль.

Функция AddGroup() добавляет грани к сетке и устанавливает порядок вершин, но все данные граней имеют устанавливаемые по умолчанию значения. Значения всех координат и нормалей новых вершин равны нулю. Новые грани окрашены в белый цвет и на них не наложены никакие текстуры. Чтобы присвоить начальные значения вершинам, необходимо воспользоваться функцией SetVertices():

mesh->SetVertices(group, 0, 24, vertexlist);

Функция SetVertices() присваивает значения координат, нормалей и позицию текстуры для одной или нескольких вершин группы граней сетки. Первый аргумент функции SetVertices() определяет модифицируемую группу граней сетки. Мы используем член данных group, который был инициализирован при вызове функции AddGroup(). Второй аргумент — это номер вершины с которой мы начнем изменения (индекс первой модифицируемой вершины). Третий аргумент — число модифицируемых вершин. Поскольку мы хотим задать значения для всех 24 вершин в группе граней сетки, используем значения 0 и 24. Четвертый аргумент является массивом структур D3DRMVERTEX в котором содержатся новые данные для вершин.

Перед тем, как взглянуть на массив vertexlist (используемый в качестве четвертого аргумента функции SetVertices()), давайте поговорим о структуре D3DRMVERTEX. Ее определение в Direct3D выглядит следующим образом:

typedef struct _D3DRMVERTEX { D3DVECTOR position; D3DVECTOR normal; D3DVALUE tu, tv; D3DCOLOR color; } D3DRMVERTEX;

Вектор position используется чтобы задать местоположение вершины. Вектор normal задает вектор нормали для вершины. Поля tu и tv определяют координаты текстуры. Если на сетку будет наложена текстура, эти два значения определяют какая именно точка текстуры совпадет с вершиной. И, наконец, поле color задает цвет вершины.

Чтобы в нашей программе определить сетку куба, необходимо предоставить массив структур D3DRMVERTEX. Для упрощения этой задачи мы воспользуемся следующим макросом:

#define VERTEX(px,py,pz,nx,ny,nz,tu,tv) \ { { D3DVALUE(px),D3DVALUE(py),D3DVALUE(pz) }, \ { D3DVALUE(nx),D3DVALUE(ny),D3DVALUE(nz), }, \ D3DVALUE(tu),D3DVALUE(tv),D3DCOLOR(0) }

Макрос получает восемь аргументов и создает из них одну структуру D3DRMVERTEX. Главная польза этого макроса в том, что он избавляет нас от необходимости загромождать объявление массива операциями приведения каждого поля структуры D3DRMVERTEX к типу D3DVALUE. Вот как выглядит определение массива вершин vertexlist, используемого в приложении Cube для инициализации сетки:

static D3DRMVERTEX vertexlist[]= { // левая грань VERTEX( 0,0,0, -1,0,0, 0,1 ), // вершина 0 VERTEX( 0,0,1, -1,0,0, 0,0 ), VERTEX( 0,1,1, -1,0,0, 1,0 ), VERTEX( 0,1,0, -1,0,0, 1,1 ), // правая грань VERTEX( 1,0,0, 1,0,0, 0,0 ), VERTEX( 1,1,0, 1,0,0, 1,0 ), VERTEX( 1,1,1, 1,0,0, 1,1 ), // вершина 6 VERTEX( 1,0,1, 1,0,0, 0,1 ), // передняя грань VERTEX( 0,0,0, 0,0,-1, 0,0 ), // вершина 8 VERTEX( 0,1,0, 0,0,-1, 1,0 ), VERTEX( 1,1,0, 0,0,-1, 1,1 ), VERTEX( 1,0,0, 0,0,-1, 0,1 ), // задняя грань VERTEX( 0,0,1, 0,0,1, 0,1 ), VERTEX( 1,0,1, 0,0,1, 0,0 ), VERTEX( 1,1,1, 0,0,1, 1,0 ), // вершина 14 VERTEX( 0,1,1, 0,0,1, 1,1 ), // верхняя грань VERTEX( 0,1,0, 0,1,0, 0,0 ), VERTEX( 0,1,1, 0,1,0, 1,0 ), VERTEX( 1,1,1, 0,1,0, 1,1 ), // вершина 18 VERTEX( 1,1,0, 0,1,0, 0,1 ), // нижняя грань VERTEX( 0,0,0, 0,-1,0, 0,0 ), // вершина 20 VERTEX( 1,0,0, 0,-1,0, 1,0 ), VERTEX( 1,0,1, 0,-1,0, 1,1 ), VERTEX( 0,0,1, 0,-1,0, 0,1 ), };

В коде выделено шесть групп по четыре вершины. Каждая группа из четырех вершин описывает одну из граней куба. Первые три аргумента в описании каждой из вершин задают ее координаты. Следующие три аргумента определяют нормаль к вершине. Обратите внимание, что нормали всех вершин одной грани одинаковы. Это обеспечивает четкий контраст между примыкающими друг к другу гранями. Последние два аргумента являются координатами текстуры.

Обратите внимание на комментарии, расположенные справа от описания шести из вершин. Таким образом отмечены те вершины, которые будут анимироваться.

Вернемся к коду создания сетки (этапу 1) в функции CreateScene(). Последние два вызова функций выглядят так:

mesh->Translate(D3DVALUE(-0.5), D3DVALUE(-0.5), D3DVALUE(-0.5)); mesh->Scale(D3DVALUE(12), D3DVALUE(12), D3DVALUE(12));

Функция Translate() применяется для настройки осей сетки. Куб создан таким образом, что в начале координат расположена одна из его вершин. Мы используем функцию Translate() для перемещения куба таким образом, чтобы с началом координат совпадал его центр. Функция Scale() используется чтобы увеличить куб в 12 раз. Обратите внимание, что порядок вызова этих двух функций очень важен. Если мы выполним операцию масштабирования перед операцией перемещения, начало координат не будет совпадать с центром куба.

На втором этапе своей работы функция CreateScene() создает текстуру и накладывает ее на созданную сетку:

HRSRC texture_id = FindResource(NULL, MAKEINTRESOURCE(IDR_WIN95TEXTURE), "TEXTURE"); LPDIRECT3DRMTEXTURE texture; d3drm->LoadTextureFromResource(texture_id, &texture); mesh->SetGroupTexture(group, texture); mesh->SetGroupMapping(group, D3DRMMAP_PERSPCORRECT); texture->Release(); texture = 0;

Текстура загружается из ресурсов программы функцией LoadTextureFromResource() интерфейса Direct3DRM. Затем вызывается функция SetGroupTexture() интерфейса Direct3DRMMesh чтобы связать текстуру с сеткой. Для разрешения перспективной коррекции используется функция SetGroupMapping() интерфейса Direct3DRMMesh. Обратите внимание, что обе эти функции требуют, чтобы в первом аргументе им был передан идентификатор группы граней сетки.

На третьем этапе создается фрейм для сетки:

LPDIRECT3DRMFRAME frame; d3drm->CreateFrame(scene, &frame); frame->AddVisual(mesh); frame->SetRotation(scene, D3DVALUE(0), D3DVALUE(1), D3DVALUE(0), D3DVALUE(.04)); static CallbackData cbdata; cbdata.mesh = mesh; cbdata.group = group; frame->AddMoveCallback(UpdateCube, &cbdata); frame->Release(); frame = 0;

Новый фрейм будет потомком фрейма scene и создается с помощью функции CreateFrame() интерфейса Direct3DRM. Сетка присоединяется к новому фрейму функцией AddVisual() интерфейса Direct3DRMFrame. С помощью функции SetRotation() новому фрейму назначается атрибут вращения. Этот атрибут является временным, поскольку присутствующая в приложении функция обратного вызова будет периодически изменять атрибут вращения фрейма сетки.

Далее объявляется статическая структура CallbackData. Эта структура используется в приложении Cube для передачи необходимых данных в функцию обратного вызова. Она объявлена статической потому что используется после того, как функция CreateScene() завершит свою работу, и объявленные в ней локальные переменные перестанут существовать.

В структуре сохраняются указатель на сетку и идентификатор группы граней сетки. Затем выполняется установка функции обратного вызова (UpdateCube()) с помощью функции AddMoveCallback() интерфейса Direct3DRMFrame. Указатель на структуру CallbackData передается функции AddMoveCallback() во втором аргументе.

На четвертом и пятом этапах работы функции CreateScene() выполняется создание источника света и порта просмотра. Мы не будем обсуждать эти действия здесь, поскольку они подробно рассмотрены в других главах и не имеют отношения к анимации вершин.



Содержание раздела