Отображение трехмерных блоков

Отображение трехмерных блоков! Оооо, звучит угрожающе, не так ли? Хотя название темы звучит пугающе, в действительности она почти ничем не отличается от рассмотренного в этой главе ранее отображения двухмерных блоков. Действительно, в каждом, использующем двухмерную графику примере из этой главы, применялась трехмерная визуализация. Главное видимое отличие заключается в том, что «настоящие» трехмерные программы не используют ортогональную проекцию.
Проект, содержащий код программы называется D3DFrame_3DTiles. Вы найдете его в сопроводительных файлах. Загрузите его и следуйте дальше.

Архитектура проекта D3DFrame_3DTiles
Проект содержит два уникальных файла: main.cpp и main.h. Остальные включенные в проект файлы, — d3dapp.cpp, d3denumeration.cpp, d3dfile.cpp, d3dfont.cpp, d3dsettings.cpp, d3dutil.cpp и dxutil.cpp, — являются частью Microsoft DirectX 9.0 SDK.

Обратите внимание, что название одного из файлов выделено полужирным шрифтом. Файл d3dfile.cpp отсутствовал ранее и был добавлен только к этому проекту. Его назначение — помогать загружать файлы формата .x, содержащие информацию о трехмерных объектах. Он необходим для загрузки трехмерных моделей, созданных в таких пакетах визуального моделирования, как Maya, 3D Studio MAX и MilkShape. Если хотите, вы можете написать собственный загрузчик моделей, но встроенные функции, предоставляемые файлом d3dfile.cpp сделают вашу жизнь намного проще.

Заголовочный файл Main.h
В коде заголовочного файла проекта отображения трехмерной графики изменений немного, но все они существенные. Ниже приведен весь код заголовочного файла.

#define STRICT
#include
#include
#include
#include
#include
#include
#include
#include "DXUtil.h"
#include "D3DEnumeration.h"
#include "D3DSettings.h"
#include "D3DApp.h"
#include "D3DFont.h"
#include "D3DFile.h"
#include "D3DUtil.h"

int g_iNumTiles = 2;

// Формат вершин для трехмерных блоков
struct D3DVERTEX
{
D3DXVECTOR3 p;
D3DXVECTOR3 n;
FLOAT tu, tv;

static const DWORD FVF;
};
const DWORD D3DVERTEX::FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;

class CD3DFramework : public CD3DApplication
{
CD3DFont* m_pStatsFont;
TCHAR m_strFont[LF_FACESIZE];
DWORD m_dwFontSize;
// Данные трехмерных объектов
CD3DMesh* m_pObject[32];
// Массив целых чисел для хранения блочной карты
int m_iTileMap[100];
short m_shTileMapWidth;
short m_shTileMapHeight;
// Размеры окна
short m_shWindowWidth;
short m_shWindowHeight;

protected:
HRESULT OneTimeSceneInit();
HRESULT InitDeviceObjects();
HRESULT RestoreDeviceObjects();
HRESULT InvalidateDeviceObjects();
HRESULT DeleteDeviceObjects();
HRESULT Render();
HRESULT FrameMove();
HRESULT FinalCleanup();
HRESULT CreateD3DXTextMesh(LPD3DXMESH* ppMesh, TCHAR* pstrFont, DWORD dwSize);

public:
LRESULT MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
CD3DFramework();
};

Первое изменение находится в разделе включаемых файлов. Файлу d3dfile.cpp требуется заголовочный файл d3dfile.h. Поскольку наша программа использует функции из файла d3dfile.cpp, заголовочный файл должен быть также включен в исходный код.

Следующий фрагмент, который представляет интерес — объявление переменной g_iNumTiles расположенное сразу после перечисления включаемых файлов. Данная переменная содержит количество загружаемых программой трехмерных моделей. Очень важно отслеживать это число, поскольку программа должна знать, сколькими моделями ей управлять. Если вы решите добавить к рассматриваемому примеру еще модели, убедитесь, что значение переменной также соответствующим образом увеличено.

Следующее изменение находится в структуре данных D3DVERTEX. Ее формат слегка отличается от использованного ранее. Это сделано для поддержки формата вершин трехмерных моделей. Для такой поддержки необходима переменная FVF типа DWORD. Это единственное отличие от использованного ранее формата вершин.

Спустимся ниже к определению класса. Здесь добавлена новая переменная с именем m_pObject. Это массив значений типа CD3DMesh. Объект CD3DMesh предоставляется библиотекой d3dfile.cpp. Это ваше окно в мир загрузки и отображения трехмерных моделей. Я произвольным образом установил размер массива равным 32. Это указывает, что максимальное количество загруженных блоков будет равно 32. Не стоит волноваться, — если захотите, вы в дальнейшем всегда сможете изменить это число.

Файл программы Main.cpp
Теперь откройте файл main.cpp, чтобы увидеть код, используемый в данном примере. Ниже приведен первый фрагмент кода, который представляет для нас интерес:

HRESULT CD3DFramework::OneTimeSceneInit()
{
int i;
m_pStatsFont = new CD3DFont(_T("Arial"), 8, NULL);
if(m_pStatsFont == NULL)
return E_FAIL;
// Выделение памяти для блоков
for(i = 0; i < g_iNumTiles; i++) {
m_pObject[i] = new CD3DMesh();
}
// Заполнение карты блоками с кодом 0
memset(m_iTileMap, 0, (m_shTileMapWidth * m_shTileMapHeight) * sizeof(int));
// Случайное размещение скал на траве
// Инициализация генератора случайных чисел
srand(timeGetTime());
for(i = 0; i < 100; i++) {
if(rand() % 5 == 3)
m_iTileMap[i] = 1;
else
m_iTileMap[i] = 0;
}
return S_OK;
}

Новые действия начинаются в первом цикле for. В нем выделяется память для трехмерных объектов (блоков). Для этой цели используется оператор new. Смотрите, разве это не просто?

Далее расположен еще один цикл for предназначенный для случайного размещения блоков на карте. В данном примере используются всего два типа блоков, так что код должен всего лишь выбрать одни из этих двух блоков и поместить его на карту. В данном случае 1 — это блок с изображением горы, а 0 — блок с изображением травы.

Загрузка трехмерных моделей
Мы выделили память для объектов и инициализировали блочную карту. Пришло время загрузить трехмерные модели, содержащие данные для трехмерных блоков. Вот код, делающий это:

HRESULT CD3DFramework::InitDeviceObjects()
{
HRESULT hr;
char szFileName[512];
// Инициализация шрифта
if(FAILED(hr = m_pStatsFont->InitDeviceObjects(m_pd3dDevice)))
return hr;
// Загрузка информации трехмерных блоков
for(int i = 0; i < g_iNumTiles; i++) {
// Создаем имя файла
sprintf(szFileName, "ground_tile%d.x", i+1);
// Загружаем сетку
if(FAILED(m_pObject[i]->Create(m_pd3dDevice, _T(szFileName))))
return D3DAPPERR_MEDIANOTFOUND;
// Устанавливаем тип вершин
m_pObject[i]->SetFVF(m_pd3dDevice, D3DVERTEX::FVF);
}
return S_OK;
}

Цикл в этой функции начинается с создания имени загружаемого файла с данными трехмерного объекта. Как только имя готово к использованию, код вызывает функцию Create(), принадлежащую объекту класса CD3DMesh. Вот прототип функции CD3DMesh::Create():

HRESULT Create(
LPDIRECT3DDEVICE9 pd3dDevice,
TCHAR* strFilename)

Первый параметр, pd3dDevice, является указателем на используемое в приложении трехмерное устройство. Я передаю в этом параметре указатель m_pd3dDevice, который инициализируется в коде каркаса приложения.

Следующий параметр, strFilename, является именем файла, содержащего загружаемый в память объект. Здесь я передаю созданное ранее имя файла.

Теперь, когда объект загружен в память, укажем формат вершин для объекта. Выполняйте эти действия когда хотите управлять форматом вершин, используемым при визуализации объекта. Если вам это не нужно, нет никаких причин использовать SetFVF(). Данный вызов добавлен только чтобы обеспечить дополнительный контроль.

Точечный источник света
Переместимся ниже к функции CD3DFramework::RestoreDeviceObjects(), чтобы увидеть сделанные мной незначительные изменения ее кода. Отличий совсем немного; главное из них то, что в этом примере я использую новый тип источника света. Вот код для нового точечного источника света:

ZeroMemory(&d3dLight, sizeof(D3DLIGHT9));
d3dLight.Type = D3DLIGHT_POINT;
d3dLight.Diffuse.r = 1.0f;
d3dLight.Diffuse.g = 1.0f;
d3dLight.Diffuse.b = 1.0f;
d3dLight.Position.x = 0.0f;
d3dLight.Position.y = -20.0f;
d3dLight.Position.z = 20.0f;
d3dLight.Attenuation0 = 1.0f;
d3dLight.Attenuation1 = 0.0f;
d3dLight.Range = 100.0f;

В предыдущих примерах программ использовался направленный источник света. DirectGraphics предлагает для использования и другие типы освещения, такие как зональное освещение (прожектор) и точечный источник света. В рассматриваемом примере я применяю точечный источник света.

Значение Type указывает системе визуализации тип источника света. В данном примере я задаю значение D3DLIGHT_POINT. Оно указывает, что система визуализации должна ожидать и использовать параметры для точечного источника света.

Структура Diffuse задает цвет источника света. Она содержит три компонента — красный, зеленый и синий. Значения каждого компонента должны находиться в диапазоне от 0.0 до 1.0. Значение 0.0 соответствует полному отсутствию данного цвета, а значение 1.0 — его максимальной интенсивности. Поскольку для каждого из цветов я указал значение 1.0, в результате будет получен белый свет максимальной интенсивности. Если вы не знакомы с освещением трехмерных сцен, я предлагаю вам поиграть с параметрами, чтобы увидеть, какое влияние они оказывают на сцену.

Структура Position содержит местоположение источника света в трехмерном пространстве. Я разместил его в точке с координатами (0.0, –20.0, 20.0).

Значение Attentuation0 определяет, как интенсивность света будет изменяться с увеличением расстояния. Это значение устанавливает константу, с которой начнется изменение интенсивности. Значение Attenuation1 устанавливает следующую константу, используемую для изменения интенсивности. Задав масштабирование в диапазоне от 1.0 до 0.0, я указываю, что с увеличением расстояния интенсивность света должна уменьшаться.

Значение Range указывает расстояние на котором источник света перестает оказывать влияние на объекты. В нашем примере источник света не освещает предметы, которые удалены от него более, чем на 100 единиц.

Визуализация трехмерных моделей
Готовы ли вы к визуализации трехмерных блоков? Я готов! Вот новый и улучшенный код визуализации трехмерных блоков:

HRESULT CD3DFramework::Render()
{
D3DXMATRIX matTranslation;
int iX, iY;
int iCurTile;
float fXPos;
float fYPos;

// Очистка порта просмотра
m_pd3dDevice->Clear(0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0L);

// Начало создания сцены
if(SUCCEEDED(m_pd3dDevice->BeginScene()))
{
for(iY = 0; iY < 10; iY++) {
// Горизонтали
for(iX = 0; iX < 10; iX++) {
// Вычисляем, какой блок отображать
iCurTile = m_iTileMap[iX + (iY * m_shTileMapWidth)];
// Вычисляем местоположение блока
fXPos = (-5.0f * iX) + 22.5f;
fYPos = (-5.0f * iY) + 32.5f;
// Устанавливаем позицию блока
D3DXMatrixTranslation(&matTranslation, fXPos, fYPos, 0.0f);
m_pd3dDevice->SetTransform(D3DTS_WORLD, &matTranslation);
// Отображаем блок
m_pObject[iCurTile]->Render(m_pd3dDevice);
}
}

// Показываем частоту кадров
m_pStatsFont->DrawText(2, 0, D3DCOLOR_ARGB(255,255,255,0), m_strFrameStats);
// Показываем сведения о видеокарте
m_pStatsFont->DrawText(2, 20, D3DCOLOR_ARGB(255,255,255,0), m_strDeviceStats);

// Завершаем создание сцены
m_pd3dDevice->EndScene();
}

return S_OK;
}

Обратите внимание, насколько этот код похож на предыдущие примеры. Визуализация трехмерных моделей на самом деле не так уж и сложна. В рассматриваемом примере присутствует обычный набор ключевых фрагментов. Есть внешний цикл для визуализации блоков вдоль оси Y и внутренний цикл для визуализации блоков вдоль оси X. Местоположение блока вычисляется обычным способом с небольшим изменением координат. Главное отличие — вызов функции D3DXMatrixTranslation().

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

Теперь объект находится в требуемой позиции, и для его отображения следует вызвать функцию Render(). Функция Render() принадлежит объекту CD3DMesh и выполняет за вас всю необходимую работу. Вам надо только передать указатель на трехмерное устройство, и объект сделает все остальное. Разве не круто?

Теперь вы знаете как отображать трехмерные блоки. Получен ответ на древний вопрос! Я признаю, что еще многое требуется изучить, но вы уже на пути к созданию полностью трехмерной стратегической игры с блочной графикой.