2.Структура визуализации Графическое приложение реального времени (например, компьютерная игра), рассчитывает и показывает пользователю (обычно на мониторе) последовательность кадров (см. рис. 1). Рис.1. Формирование кадров и последующий их показ на экране монитора. Скорость обновления кадров характеризуется величиной, которая называется FPS (frames per second – количество кадров в секунду). Эта же величина обычно используется как характеристика быстродействия приложения.
Чтобы создать у пользователя ощущение мгновенного отклика на ввод, в приложениях реального времени эта величина составляет от 20 кадров в секунду и выше. Однако в приложениях с быстро развивающимися процессами (например, игра Quake III Arena) требуется более высокий FPS, чтобы создать комфортные условия для пользователя.
Для показа изображения на мониторе используется так называемая идеология двойной буферизации (double buffering). В то время пока происходит построение текущего буфера кадра (back buffer), на экране монитора показывается содержимое предыдущего буфера кадра (front buffer). По окончанию рендеринга1 буфера меняются местами (или содержимое текущего буфера копируется в показываемый) – таким образом на экране монитора появляется сформированное изображение (см. рис. 2).
Процесс формирования одного кадра обычно разбивается на три этапа:
Обработка ввода. На этом этапе производится обработка ввода: нажатия клавиатуры, движения мышки и джойстика и т.д.
Обновление мира (update). Отображаемый виртуальный мир (далее сцена) живет по своим законам. Чтобы визуализировать текущее состояние этого мира, следует производить расчет новых положений объектов этого мира, их свойства (например, цвет) и т.д. Такие расчеты производятся на этапе обновления.
Рендеринг. На этом шаге производится рендеринг сцены в буфер кадра с последующим отображением буфера кадра на мониторе. Именно этот этап будет находиться под нашим пристальным вниманием на протяжении всего данного руководства.
3.Введение в библиотеку Перед тем, как начать более детальное рассмотрение библиотеки, мы должны понять, как устроен Direct3D 9.0с API.
Во-первых, следует отметить, что Direct3D – объектно-ориентированная библиотека, в которой все классы отнаследованы от одного базового класса IUnknown. Это сделано для того, чтобы использовать идеологию счетчика ссылок (reference counter). В результате, создание любых объектов библиотеки осуществляется методом-фабрикой (factory function) другого объекта, а освобождение ссылки на объект производится вызовом IUnknown::Release().
Единственный объект, создаваемый более-менее напрямую, это IDirect3D9 – базовый объект библиотеки. Он создается вызовом Direct3DCreate9().
IDirect3D9 *pD3D9 = Direct3DCreate9(D3D_SDK_VERSION);
С помощью этого объекта (метод IDirect3D9::CreateDevice()) создается объект-устройство IDirect3DDevice9, который отвечает за создание всех остальных объектов. Большинство функций, описанных в настоящем руководстве, являются методами этого класса. Для них в дальнейшем мы будем опускать название класса.
Создание устройства – достаточно нетривиальный процесс, так как требуется заполнить большое количество параметров, смысл которых не вполне ясен. Рассмотрим его на примере следующего кода.
// Первым делом следует заполнить структуру D3DPRESENT_PARAMETERS
D3DPRESENT_PARAMETERS ppParams; // Начнем с того, что проинициализируем ее 0-и, чтобы незаданные параметры
// не оказались случайными
memset(&ppParams, 0, sizeof(D3DPRESENT_PARAMETERS)); // Выберем между оконным и полноэкранным режимом в пользу первого
ppParams.Windowed = TRUE; // “Магическая” константа, задающая кол-во буферов
ppParams.BackBufferCount = 1; // Еще одна “магическая” константа, задающая сглаживание
ppParams.MultiSampleType = D3DMULTISAMPLE_NONE; // И еще одна “магическая” константа, позволяющая выбрать между
// копированием и обменом back и front буфером
ppParams.SwapEffect = D3DSWAPEFFECT_DISCARD; // Устанавливая этот флаг в TRUE, мы говорим методу, что необходимо
// создать буфер глубины (depth или z buffer)
ppParams.EnableAutoDepthStencil = TRUE; // Здесь мы задаем формат этого буфера. Можно трактовать как очередную
// “магическую” константу
ppParams.AutoDepthStencilFormat = D3DFMT_D24S8; // Требуется передать HANDLE окна
ppParams.hDeviceWindow = hWnd; // Ширина и высота буфера, в который мы будем все рендерить
ppParams.BackBufferWidth = params.nWidth;
ppParams.BackBufferHeight = params.nHeight; // Формат этого буфера. Пока что тоже можно воспринимать, как “магическую”
// константу. Обсуждение понятия формат производится в разделе 6.1.
ppParams.BackBufferFormat = D3DFMT_X8R8G8B8; // Создаем устройство
IDirect3DDevice9 *pD3D9Device = NULL;
HRESULT hRes = pD3D9->CreateDevice(
0, // Номер устройства. Обычно 0.
D3DDEVTYPE_HAL, // Тип устройства. Должен быть _HAL
ppParams.hDeviceWindow, // Окно, в которое осуществляется вывод
// Флаги, задающие поведение устройства. Можно смело воспринимать
// как “магическую” константу.
D3DCREATE_HARDWARE_VERTEXPROCESSING,
&ppParams, // Указатель, на заполненную выше структуру
&pD3D9Device); // Возвращаемый указатель на устройство // Проверка на успешность выполнения метода
if (hRes != D3D_OK)
{
// Действия, в случае ошибки
} Если IDirect3D9::CreateDevice() вернул D3D_OK, то pD3D9Device будет содержать указатель на устройство, с которым мы далее будем работать (не забудьте освободить его перед выходом из приложения).
Почти все методы библиотеки, возвращают код ошибки HRESULT, который в идеале следует так или иначе обрабатывать. Однако в реальной жизни это имеет смысл делать далеко не со всеми методами. Первыми в этом списке, очевидно, идут методы-фабрики, создающие объекты. Следует продумать сценарий, по которому будет производиться обработка отказа при создании объекта. Понятно, что для устройства и других критичных объектов это будет выход из программы. Однако в остальных случаях лучше продолжать работу программы, не используя проблемный объект.
Теперь, создав устройство, можно посмотреть из каких вызовов API состоит простейший шаг рендеринга.
class SomeClass
{
IDirect3DDevice9 *m_pDev; void render()
{
// Производим очистку буферов2 перед тем, как начать рендерить
// следующий кадр
m_pDev->Clear(0, NULL, // Производим очистку всего буфера
D3DCLEAR_TARGET | // Очищаем буфер кадра
D3DCLEAR_ZBUFFER | // Очищаем z-buffer
D3DCLEAR_STENCIL, // Очищаем stencil-buffer
0xFF007F00, // Очищаем буфер кадра темно-зеленым3
1.0f, // Очищаем z-buffer самым большим значением
0); // Очищаем stencil нулем // Установка окна вывода в back buffer на (10, 20) -> (330, 260)
// Следует знать, что по умолчанию окно выставляется в полный back
// buffer, что достаточно для большинства лаботаторных работ.
D3DVIEWPORT9 viewPort;
viewPort.X = 10;
viewPort.Y = 20;
viewPort.Width = 320;
viewPort.Height = 240;
viewPort.MinZ = 0.0f; // “Магические”
viewPort.MaxZ = 1.0f; // константы
m_pDev->SetViewport(&viewPort); // Все вызовы рендеринга объектов должны произодиться после
// BeginScene() и до EndScene().
m_pDev->BeginScene(); // Здесь производится установка настроек вывода и рендеринг объектов
... // Конец рендеринга
m_pDev->EndScene(); // Вызов этого метода осуществляет обмен или копирование back и front
// буферов
m_pDev->Present(NULL, NULL, NULL, NULL);
}
}
|