netlib.narod.ru< Назад | Оглавление | Далее >

Двухмерная графика в Direct3D

Теперь, когда вы знаете как сформулировать требования к интерфейсу и как сделать интерфейс простым и удобным, пришла пора узнать как отображать интерфейс на экране! Отбросьте опасения — в этом разделе я расскажу вам о том, как отображать графику интерфейса. Так как все новые графические карты построены на основе аппаратных ускорителей трехмерной графики, в рассматриваемых далее методах для отображения интерфейса будет использоваться трехмерная графика. Обратной стророной трехмерной визуализации является то, что вам не всегда требуется учитывать глубину при отображении объектов. Отдельные элементы интерфейса, такие как текст, выглядят лучше когда отображаются как двухмерные. Учитывая эти два требования, позвольте мне объяснить вам, как отображать трехмерную графику, которая будет выглядеть как двухмерная. Я покажу как можно использовать текстуры трехмерных объектов для имитации двухмерной графики.

Взгляните на рис. 6.8 и пристегните ремни — настало время писать код.


Рис. 6.8. Окно программы TitleScreen

Рис. 6.8. Окно программы TitleScreen


На рис. 6.8 показано окно, полученное в результате работы программы D3D_TitleScreen. Эта программа отображает стартовый экран воображаемой игры Battle Armor, который выглядит так, будто нарисован с использованием двухмерной графики. (Возможно однажды у меня появится время, чтобы действительно закончить работу над игрой Battle Armor, но до тех пор будем просто притворяться, что это реальная игра.) Итак, начнем! А сейчас загрузите проект в Visual C++, чтобы двигаться дальше.

Архитектура проекта D3D_TitleScreen

Проект содержит два уникальных файла: main.cpp и main.h. Остальные, включенные в проект файлы, — d3dfont.cpp, d3dutil.cpp и dxutil.cpp, — являются частью Microsoft DirectX 9.0 SDK. Вы заметили, что список короче чем раньше? Это из-за того, что данный пример не использует предоставляемый Microsoft каркас приложения DirectX. В данном проекте мы воспользуемся лишь несколькими вспомогательными файлами для выполнения действий не имеющих отношения к каркасу приложения.

Программе необходимы библиотеки d3d9.lib, dxguid.lib, d3dx9dt.lib, d3dxof.lib, comctl32.lib и winmm.lib.

Заголовочный файл Main.h

Есть только один заголовочный файл, который представляет для нас интерес — main.h. В этом заголовочном файле я выполняю следующие действия:

Ключевые типы данных Direct3D

В первых строках кода объявлены несколько ключевых переменных DirectX, необходимых для работы программы. Вот как выглядит этот фрагмент:

LPDIRECT3D9             g_pD3D           = NULL;
LPDIRECT3DDEVICE9       g_pd3dDevice     = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVBInterface   = NULL;
LPDIRECT3DTEXTURE9      g_pTexture[32];

Первая переменная относится к типу LPDIRECT3D9 и названа g_pD3D. Это указатель на интерфейс Direct3D. Без этого интерфейса программа не сможет выполнять никаких действий, относящихся к отображению трехмерной графики на экране. Это важный фрагмент головоломки, так что убедитесь, что помните о нем.

Следующая из объявленных переменных относится к типу LPDIRECT3DDEVICE9 и названа g_pd3dDevice. Она является указателем на устройство визуализации. Без устройства визуализации ваша программа будет выглядеть ужасно скучно. Эта переменная будет использоваться при настройке видеоадаптера, установке метода визуализации и отображении трехмерной графики. Учитывая, как много она делает, вам лучше хорошенько запомнить ее.

Следующая переменная, g_pVBInterface типа LPDIRECT3DVERTEXBUFFER9 используется для хранения необходимых примеру данных трехмерной геометрии. Буфер вершин хранит вершины, являющиеся основными кирпичиками из которых строятся трехмерные объекты. Чуть позже мы обсудим эту тему более подробно.

И, наконец, в коде объявлен массив текстур с именем g_pTexture[]. В нем хранится 32 элемента типа LPDIRECT3DTEXTURE9. Это всего лишь место хранения изображений, необходимых примеру. Благодаря вспомогательным библиотекам DirectX использовать текстуры очень удобно. Кроме того, они очень важны, так что уделите им достаточно внимания. Может быть вам интересно, почему я создал массив для 32 текстур. Для этого нет никаких причин. В программе используется только семь элементов массива, но всегда лучше предохраняться, чем извиняться!

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

int g_iXOffset = 0;
int g_iYOffset = 0;
int g_iWindowWidth = 640;
int g_iWindowHeight = 480;

Сначала перечислены две целочисленных переменных, g_iXOffset и g_iYOffset, которые применяются для хранения смещения клиентской области окна относительно координат окна. Я объясню их назначение позже, при обсуждении файла main.cpp. Сейчас просто примите их такими, как они есть, так же как смерть и налоги.

Следующие две целочисленных переменных, g_iWindowWidth и g_iWindowHeight, хранят и задают размеры окна, в котором программа отображает графику. Вы можете изменить их значения, если хотите уменьшить или увеличить изображение. Но подождите пока вностиь изменения. Вы должны сначала по крайней мере убедиться, что программа работает так, как задумывалось.

Структура данных настраиваемого формата вершин (FVF)

Перейдем дальше к структуре данных настраиваемого формата вершин. Она объявляет формат геометрических данных используемых в примере для трехмерной визуализации. Код выглядит следующим образом:

struct CUSTOMVERTEX
{
   D3DXVECTOR3 position;  // Местоположение
   D3DXVECTOR3 vecNorm;   // Нормаль
   FLOAT     tu, tv;      // Координаты текстуры
   FLOAT     tu2, tv2;    // Координаты текстуры
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX2)

DirectX использует структуру данных вершины при описании геометрии визуализируемых трехмерных объектов. Без такой структуры вы ничего не сможете отобразить на экране. Это очень важный аспект DirectX и краеугольный камень визуализации. Как видите, в описанном мной формате вершин, присутствует несколько ключевых элементов: сведения о местоположении, данные нормали и координаты текстуры.

Данные местоположения

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

Взгляните на рис. 6.9, чтобы увидеть образованный этими точками треугольник.


Рис. 6.9. Треугольник, образованный тремя точками в трехмерном пространстве

Рис. 6.9. Треугольник, образованный тремя точками в трехмерном пространстве


Как видно из рис. 6.9, местоположение каждой из вершин определяет геометрию образуемого в трехмерном пространстве объекта. Если изменить местоположение, то при визуализации изменится и геометрия объекта. Понятно, что для трехмерной визуализации это очень важно. Для хранения данных о местоположении я использую тип данных D3DXVECTOR3, поскольку он содержит элементы для хранения координат по осям X, Y и Z.

Данные нормали

Затем я объявляю данные, содержащие информацию о нормали грани. Нормали в трехмерной визуализации задают направление, в котором направлена грань. Эти данные необходимы для трехмерного освещения, поскольку аппаратура должна знать, как изображать освещение грани. Данные нормали хранятся в виде набора трех координат, так же, как и данные местоположения. Поэтому я снова использую значение типа D3DXVECTOR3. Чтобы увидеть нормаль, взгляните на рис. 6.10.


Рис. 6.10. Нормаль треугольной грани

Рис. 6.10. Нормаль треугольной грани


Обратите внимание, что на рис. 6.10 вектор нормали расположен под углом 90 градусов к грани. Это показывает, что нормали направлены от лицевой поверхности треугольника. Если вы хотите, чтобы лицевой считалась другая поверхность треугольника, просто измените вектор, чтобы он указывал в другом направлении. Большей частью эти данные относятся к случаю выполнения полутонового затенения, а не к плоскому затенению. Поскольку визуализация полутонового затенения базируется на данных нормалей, это весьма важная информация.

Данные текстуры

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

Direct3D представляет информацию о координатах текстуры в виде декартовых координат на плоскости. Другими словами координаты текстуры содержат два числа — одно для оси X и одно для оси Y. Главное отличие координат текстуры заключается в том, что диапазон допустимых значений для них ограничен. Они могут принимать значения в диапазоне от 0.0 до 1.0. Это объясняется тем, что указываются относительные координаты, а реальные будут зависеть от размера изображения текстуры. Возьмем текстуру размером 16 на 16 точек. Координаты текстуры (0.5, 0.5) будут соответствовать центру текстуры с реальными координатами точки (8, 8). В случае текстуры размером 32 на 32 точки те же самые координаты текстуры будут соответствовать центру текстуры с реальными координатами (16, 16). Эту концепцию иллюстрирует рис. 6.11.


Рис. 6.11. Координаты текстуры указываются относительно ее размера

Рис. 6.11. Координаты текстуры указываются относительно ее размера


На рис. 6.11 представлены две сетки. Сетка слева изображает текстуру размером 16 на 16 точек. Сетка справа изображает текстуру размером 32 на 32 точки. Под каждой сеткой изображена шкала координат текстуры с диапазоном значений от 0.0 до 1.0. Поскольку шкала связана с размером текстуры, размеры шкал совпадают с размерами сеток. Шкала справа в два раза больше, чем шкала слева, но диапазон значений на ней все тот же — от 0.0 до 1.0. Я понимаю, что говорю очевидные вещи, но они важны для понимания координат текстур.

Корме того, для каждой сетки я показываю диапазон координат точек. Меньшая сетка начинается с точки (0, 0) и заканчивается точкой (15, 15). Большая текстура начинается с точки (0, 0) и простирается до точки (31, 31). Здесь нет ничего особенного; числа просто обозначают координаты точек.

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

Определение формата вершины

Теперь, когда структура данных вершины описана, настало время создать определение вершины для последующего использования. В инструкции #define установлены флаги формата вершины. Этот набор флагов сообщает системе визуализации, какие данные следует ожидать в структуре данных вершины. Вам следует быть очень внимательными и перечислять флаги в том порядке, в котором данные располагаются в структуре.

В данной программе я использую три флага: D3DFVF_XYZ, D3DFVF_NORMAL и D3DFVF_TEX2. Флаг D3DFVF_XYZ указывает данные местоположения. Флаг D3DFVF_NORMAL указывает данные нормали. Флаг D3DFVF_TEX2 указывает наличие двух наборов координат текстур. Если вам нужна дополнительная информация о других доступных флагах, обратитесь к документации DirectX.

Прототипы функций

Теперь вы закончили знакомство со структурой данных для настраиваемого формата вершин. Следующий блок кода задает прототипы используемых в программе функций. Вот как он выглядит:

void vDrawInterfaceObject(int iXPos, int iYPos, float fXSize, float fYSize,
                          int iTexture);
void vInitInterfaceObjects(void);
LRESULT WINAPI fnMessageProcessor(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
HRESULT InitD3D(HWND hWnd);
void vRender(void);
void vCleanup(void);

Я объясню назначение каждой из перечисленных функций в следующих разделах этой главы.

Файл программы Main.cpp

Следующий уникальный файл, main.cpp, содержит основную функциональность программы. Он следует стандартной структуре программы, которую я описал в главе 2.

Функция WinMain()

Первой представляющей интерес функцией является WinMain(). Как и в любой программе для Windows, она является главной точкой входа в код. Вот как выглядит ее листинг:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,
                    int iCmdShow)
{
   HWND         hWnd;
   MSG          msg;
   WNDCLASSEX   wndclass;
   RECT         rcWindowClient;
   // Инициализация класса окна
   wndclass.cbSize        = sizeof(wndclass);
   wndclass.style         = CS_HREDRAW | CS_VREDRAW;
   wndclass.lpfnWndProc   = fnMessageProcessor;
   wndclass.cbClsExtra    = 0;
   wndclass.cbWndExtra    = 0;
   wndclass.hInstance     = hInstance;
   wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
   wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
   wndclass.lpszMenuName  = NULL;
   wndclass.lpszClassName = "Title Demo";
   wndclass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
   // Регистрация класса окна
   if(RegisterClassEx(&wndclass) == NULL) {
      // Выход из программы при сбое
      exit(1);
   }
   // Создание окна
   hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
                "Title Demo", "D3D_TitleScreen",
                WS_OVERLAPPEDWINDOW, 0, 0,
                g_iWindowWidth, g_iWindowHeight,
                NULL, NULL, hInstance, NULL);
   // Отображение окна
   ShowWindow(hWnd, iCmdShow);
   // Получение размеров клиентской области
   GetClientRect(hWnd, &rcWindowClient);
   // Вычисление смещения визуализации на основе размеров клиентской области
   g_iXOffset = (g_iWindowWidth - (rcWindowClient.right - rcWindowClient.left));
   g_iYOffset = (g_iWindowHeight - (rcWindowClient.bottom - rcWindowClient.top));
   // Изменение размеров окна, чтобы они соответствовали желаемому разрешению
   SetWindowPos(hWnd, NULL, 0, 0,
          g_iWindowWidth + g_iXOffset,  // Ширина
          g_iWindowHeight + g_iYOffset, // Высота
          NULL);
   // Очистка структуры сообщения
   ZeroMemory(&msg, sizeof(msg));
   // Инициализация Direct3D
      if(SUCCEEDED(InitD3D(hWnd))) {
         // Инициализация виртуального буфера для отображения четырехугольников
         vInitInterfaceObjects();
         // Вход в цикл сообщений
         while(msg.message != WM_QUIT) {
         if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
         }
         else {
            // Визуализация сцены
            vRender();
         }
      }
   }
   // Освобождение ресурсов и выход из приложения
   vCleanup();
   UnregisterClass("Title Demo", wndclass.hInstance);
   return 0;
}

Чтобы отделить код, относящийся к Direct3D от «стандартного» кода программы для Windows, взгляните на рис. 6.12.


Рис. 6.12. Структура вызовов других функций в функции WinMain()

Рис. 6.12. Структура вызовов других функций в функции WinMain()


На рис. 6.12 показан список функций, которые я использую в WinMain(). Они приведены в порядке их исполнения. Обратите внимание, что большинство вызовов не относятся к Direct3D. Это объясняется тем, что функциия WinMain() содержит в основном код инициализации, а не код выполнения или визуализации.

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

Вычисление смещения клиентской области

Вы должны помнить, что у функции CreateWindowEx() есть параметры, задающие размер создаваемого окна. Эти параметры позволяют создать окно необходимого размера. Проблема заключается в том, что при этом не учитывается наличие у окна заголовка. В результате, если у вашего окна есть заголовок, вы получите меньше пространства для визуализации, чем рассчитывали. Взгляните на рис. 6.13.


Рис. 6.13. Окно размером 640 x 480

Рис. 6.13. Окно размером 640 x 480


На рис. 6.13 показано созданное мной окно шириной 640 точек и высотой 480 точек. Обратите внимание, что высота заголовка равна 24 точкам. В результате высота видимой области визуализации будет составлять только 456 точек, что представляет проблему как для художника, так и для программиста. Поскольку все ваши вычисления для визуализации графического интерфейса пользователя базируются на размерах экрана, проблема должна быть решена. Функция GetClientRect() — ваш выход.

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

Для примера, изображенного на рис. 6.13 формулы работают следующим образом:

Ширина окна остается равной 640 точкам, а высота становится равной 504 точкам. Теперь размеры области визуализации в окне достаточны для корректного отображения изображений размером 640 на 480 точек. На рис. 6.14 показано новое окно и его размеры.


Рис. 6.14. Измененное окно размером 640 x 504

Рис. 6.14. Измененное окно размером 640 x 504


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

Перемещайтесь по коду вниз, пока не увидите вызов функции InitD3D(). Найдя его, перейдите к коду, реализующему функцию.

Функция InitD3D()

Функция InitD3D() занимается трудной задачей создания среды визуализации Direct3D. Используемый в этом примере код выглядит очень простым, если его сравнивать с полноценной процедуой инициализации Direct3D. Например, я не потрудился перечислить доступные видеоадаптеры и устройства. Код просто настраивает среду выполнения и надеется, что все будет хорошо. Если у вас инициализация экрана не выполняется, можно попробовать изменить код. Это должно сработать, поскольку основная часть кода инициализации взята из DirectX SDK. Хватит предостережений, давайте взглянем на код функции:

HRESULT InitD3D(HWND hWnd)
{
   D3DPRESENT_PARAMETERS   d3dpp;
   D3DXMATRIX              matproj, matview;
   D3DDISPLAYMODE          d3ddm;

   // Создание объекта D3D
   if(NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)))
      return E_FAIL;

   // Получение видеорежима, используемого рабочим столом, чтобы мы могли 
   // установить такой же формат для вторичного буфера
   if(FAILED(g_pD3D->GetAdapterDisplayMode(
                            D3DADAPTER_DEFAULT, &d3ddm)))
      return E_FAIL;

   // Создание вторичного буфера и установка формата
   ZeroMemory(&d3dpp, sizeof(d3dpp));
   d3dpp.Windowed                = TRUE;
   d3dpp.SwapEffect              = D3DSWAPEFFECT_DISCARD;
   d3dpp.BackBufferFormat        = d3ddm.Format;
   d3dpp.EnableAutoDepthStencil  = FALSE;

   // Создание D3DDevice
   if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,
                    DS3DDEVTYPE_HAL, hWnd,
                    3DCREATE_HARDWARE_VERTEXPROCESSING,
                    &d3dpp, &g_pd3dDevice)))
   {
      return E_FAIL;
   }

   // Установка двухмерного представления и состояния визуализации
   D3DXMatrixIdentity(&matview);
   g_pd3dDevice->SetTransform(D3DTS_VIEW, &matview);

   // Установка ортогональной проекции, т.е двухмерная графика в трехменом пространстве
   D3DXMatrixOrthoLH(&matproj, (float)g_iWindowWidth, (float)g_iWindowHeight, 0, 1);
   // Задание матрицы проецирования
   g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matproj);
   // Выключение отбраковки
   g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
   // Выключение освещения
   g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
   // Выключение Z-буфера
   g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
   g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);

   return S_OK;
}

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


Рис. 6.15. Ход выполнения функции InitD3D()

Рис. 6.15. Ход выполнения функции InitD3D()


Первым интересным моментом, который можно заметить на рисунке, является вызов единственной функции верхнего уровня с именем Direct3DCreate9(). После этого вызова все оставшиеся функции относятся к указателю на объект Direct3D с именем g_pD3D. Он обрабатывает все остальные, относящиеся к трехмерной графике вызовы функций, которые будут сделаны во время жизненного цикла программы.

Создание объекта Direct3D

Функция Direct3DCreate9() является самой важной в Direct3D, поскольку без нее ничего не сможет произойти. Это самая первая функция, которую вы вызываете в процессе настройки среды визуализации. Главной задачей функции является создание объекта IDirect3D9. Вот как выглядит ее прототип:

IDirect3D9 *Direct3DCreate9(
   UINT SDKVersion
);

Вы должны полюбить его за простоту. Единственный параметр, являющийся беззнаковым целым числом, указывает используемую вами версию DirectX SDK. Для этого параметра всегда следует использовать значение D3D_SDK_VERSION.

Параметр предназначен для проверки кода программы на отсутствие тривиальных ошибок. Фактически, выполняется проверка переданного номера версии и его сравнение с номером версии, хранящимся в заголовочных файлах DirectX. Если номера версий не совпадают, код знает, что что-то в установленном DirectX вызывает подозрения. Не заморачивайте себе этим голову; лучше всего оставьте эту функцию в покое и используйте рекомендованное значение.

ПРИМЕЧАНИЕ
Если вы добавите к вашей системе новые видеокарты или мониторы после вызова функции Direct3DCreate9(), объект IDirect3D9 не будет знать об этих устройствах. Чтобы новые устройства были перечислены в списке, необходимо заново создать объект.
Настройка параметров отображения

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


Рис. 6.16. Двойная буферизация в действии

Рис. 6.16. Двойная буферизация в действии


На рис. 6.16 изображены два буфера — первичный (FB) и вторичный (BB). Оба буфера содержат графические данные. В первичном буфере находится изображение, которое в данный момент отображается на экране пользователя, а вторичный буфер хранит изображение, которое будет показано следующим. Когда наступает время показа следующего изображения, вторичный бувер меняется местами с первичным, либо содержимое вторичного буфера копируется в первичный буфер. В результате на экране появляется новое изображение.

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

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

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

HRESULT GetAdapterDisplayMode(
   UINT Adapter,
   D3DDISPLAYMODE *pMode
);

У функции есть два параметра, Adapter и pMode. Параметр Adapter должен содержать порядковый номер используемой видеокарты. Поскольку в компьютере могут быть установлены несколько видеокарт, важно указать правильное значение параметра. Проще всего ограничиться поддержкой одного монитора, задав значение параметра равным D3DADAPTER_DEFAULT. В этом случае система будет использовать первичный видеоадаптер, который в системах с одним монитором является единственным устройством отображения.

Второй параметр, pMode, вызывает больше вопросов, поскольку является указателем на структуру данных D3DDISPLAYMODE. После завершения работы функции эта структура данных будет содержать информацию о текущем видеорежиме. На повестку дня выностися вопрос: «Как выглядит структура данных D3DDISPLAYMODE?» А вот и ответ на него:

typedef struct _D3DDISPLAYMODE {
   UINT Width;
   UINT Height;
   UINT RefreshRate;
   D3DFORMAT Format;
} D3DDISPLAYMODE;

Первый член структуры, Width, хранит ширину экрана в пикселях.

Второй член структуры, Height, хранит высоту экрана.

Третий член структуры, RefreshRate, содержит частоту кадров текущего видеорежима. Если его значение равно 0, значит установлено значение частоты по умолчанию.

Четвертое значение, Format, значительно сложнее, чем предыдущие. Эта переменная является перечислением типа D3DFORMAT и может содержать различные значения, описывающие формат поверхности для текущего видеорежима. Существует слишком много доступных значений, чтобы перечислять их здесь, так что за дополнительной информацией я рекомендую обращаться к документации DirectX SDK.

Теперь нам известен формат вторичного буфера и настало время инициализировать структуру параметров отображения. Следующий блок кода присваивает значения элементам структуры данных D3DPRESENT_PARAMETERS. Данная структура содержит значения различных параметров, необходимые для инициализации системы визуализации. Вот как выглядит ее описание:

typedef struct _D3DPRESENT_PARAMETERS_ {
   UINT BackBufferWidth;
   UINT BackBufferHeight;
   D3DFORMAT BackBufferFormat;
   UINT BackBufferCount;
   D3DMULTISAMPLE_TYPE MultiSampleType;
   DWORD MultiSampleQuality;
   D3DSWAPEFFECT SwapEffect;
   HWND hDeviceWindow;
   BOOL Windowed;
   BOOL EnableAutoDepthStencil;
   D3DFORMAT AutoDepthStencilFormat;
   DWORD Flags;
   UINT FullScreen_RefreshRateInHz;
   UINT PresentationInterval;
} D3DPRESENT_PARAMETERS;

Первые два элемента структуры, BackBufferWidth и BackBufferHeight, достаточно просты; они просто хранят размеры буфера.

Следующий член данных, BackBufferFormat, содержит используемый для вторичного буфера формат поверхности. Здесь мы воспользуемся тем самым форматом, который получили ранее при вызове функции GetAdapterDisplayMode().

Затем мы задаем тип множественной выборки, хранящийся в переменной MultiSampleType. Эта переменная имеет тип D3DMULTISAMPLE_TYPE. Ух ты — еще одно перечисление! Давайте обратимся к таблице 6.1, чтобы разобраться в элементах этого нового перечисления и их назначении.

Таблица 6.1. Типы множественной выборки

Значение Описание
D3DMULTISAMPLE_NONE Множественная выборка отсутствует.
D3DMULTISAMPLE_NONMASKABLE Разрешает использование значений уровня качества.
D3DMULTISAMPLE_2_SAMPLES Доступно две выборки.
D3DMULTISAMPLE_3_SAMPLES Доступно три выборки.
D3DMULTISAMPLE_4_SAMPLES Доступно четыре выборки.
D3DMULTISAMPLE_5_SAMPLES Доступно пять выборок.
D3DMULTISAMPLE_6_SAMPLES Доступно шесть выборок.
D3DMULTISAMPLE_7_SAMPLES Доступно семь выборок.
D3DMULTISAMPLE_8_SAMPLES Доступно восемь выборок.
D3DMULTISAMPLE_9_SAMPLES Доступно девять выборок.
D3DMULTISAMPLE_10_SAMPLES Доступно десять выборок.
D3DMULTISAMPLE_11_SAMPLES Доступно одиннадцать выборок.
D3DMULTISAMPLE_12_SAMPLES Доступно двенадцать выборок.
D3DMULTISAMPLE_13_SAMPLES Доступно тринадцать выборок.
D3DMULTISAMPLE_14_SAMPLES Доступно четырнадцать выборок.
D3DMULTISAMPLE_15_SAMPLES Доступно пятнадцать выборок.
D3DMULTISAMPLE_16_SAMPLES Доступно шестнадцать выборок.
D3DMULTISAMPLE_FORCE_DWORD Не используется.


Множественная выборка необходима для визуализации с включенным сглаживанием. Не знаете что такое сглаживание? Взгляните на рис. 6.17, чтобы увидеть его в действии.


Рис. 6.17. Сглаживание

Рис. 6.17. Сглаживание


На рис. 6.17 изображены две линии. Линия, изображенная слева, имеет ступенчатые края. У линии справа края сглажены. Обратите внимание, что линия справа выглядит более гладкой, а линия слева — более зазубренной. Это достигается благодаря использованию более светлых серых пикселей по краям линии. Если ваша видеокарта поддерживает сглаживание, вы можете включить его, чтобы графика в играх, поддерживающих эту технологию, выглядела более гладкой. Если видеокарта не поддерживает данную технологию, возможно пора подумать о ее замене.

ПРИМЕЧАНИЕ
Запомните, что множественная выборка корректно работает только в том случае, если значение параметра, определяющего режим работы цепочки переключения экранных буферов равно D3DSWAPEFFECT_DISCARD.

Вернемся к представленным параметрам. Следующий параметр в списке называется MultiSampleQuality и имеет тип DWORD. Как вы, возможно, догадались, этот параметр устанавливает качество множественной выборки. Его значение может находиться в диапазоне от нуля до максимально возможного уровня, возвращаемого функцией IDirect3D9::CheckDeviceMultiSampleType(), минус один.

Далее расположен параметр SwapEffect. Он сообщает системе визуализации о том, каким образом во время визуализации будет выполняться переключение экранных буферов. Параметр может принимать одно из значений перечисления D3DSWAPEFFECT. Значения этого перечисления и их описание приведены в таблице 6.2.

Таблица 6.2. Режимы переключения экранных буферов

Значение Описание
D3DSWAPEFFECT_DISCARD Разрешает драйверу видеокарты самому выбрать наиболее эффективный метод переключения. Этот метод обычно быстрее, чем другие. Единственная проблема этого метода заключается в том, что он негарантирует сохранность вторичного буфера и его содержимого. Так что при использовании этого метода вы не можете полагаться на неизменность содержимого вторичного буфера. Вы должны использовать этот метод, если значение параметра, определяющего тип множественной выборки, отличается от D3DMULTISAMPLE_NONE.
D3DSWAPEFFECT_FLIP Использует циклическую схему экранных буферов. Буферы перемещаются по кругу в замкнутой кольцевой очереди. Метод позволяет добиться гладкой визуализации, если значение параметра, определяющего интервал переключения не равно D3DPRESENT_INTERVAL_IMMEDIATE.
D3DSWAPEFFECT_COPY Этот метод может использоваться только в том случае, если у вас есть один вторичный буфер. Кроме того, этот метод гарантирует неизменность изображения во вторичном буфере. Недостатком метода является то, что в оконном режиме он может вызвать нарущения целостности графики. Это вызвано тем, что изображение выводится во время обратного хода кадровой развертки монитора. Не используйте этот метод, если ваша программа работает в оконном режиме и вам необходима гладкая графика.
D3DSWAPEFFECT_FORCE_DWORD Не используется


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

Следующий член данных, Windowed, указывает будет ли использоваться оконный режим, или программа будет работать в полноэкранном режиме. Установите значение TRUE для оконного режима и FALSE — для полноэкранной визуализации.

Затем расположен флаг EnableAutoDepthStencil. Он сообщает системе визуализации будет ли она управлять буфером глубины или нет. Если вы присвоите данному параметру значение TRUE, Direct3D будет управлять буфером глубины за вашу программу.

Следующий член структуры, названный AutoDepthStencilFormat, устанавливает используемый формат буфера глубины. Он используется только в том случае, если значение параметра EnableAutoDepthStencil равно TRUE. Если вы используете автоматическую установку буфера глубины, убедитесь, что в этом параметре вы указали корректный формат.

Следующий член данных в списке носит неопределенное имя Flags. Вы не слишком любите такие прямолинейные имена как это? Значения для этого параметра перечислены в таблице 6.3.

Таблица 6.3. Флаги режима отображения

Значение Описание
D3DPRESENTFLAG_LOCKABLE_BACKBUFFER Разрешает приложению блокировать вторичный буфер.
D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL Разрешает системе освобождать z-буфер после каждого отображения данных буфера. Это может увеличить быстродействие, если драйвер видеокарты поддерживает данную возможность.
D3DPRESENTFLAG_DEVICECLIP Область визуализации в оконном режиме будет обрезаться. Работает только в Windows 2000 и Windows XP.
D3DPRESENTFLAG_FORCEGDIBLT Для копирования изображений будет использоваться GDI. Работает только в Windows 2000 и Windows XP.
D3DPRESENTFLAG_VIDEO Сообщает драйверу видеокарты, что вторичный буфер содержит видеоданные.


Следующий член данных называется FullScreen_RefreshRateInHz. Его назначение достаточно очевидно: он задает частоту с которой видеокарта будет обновлять изображение на экране монитора. Для визуализации в оконном режиме здесь следует указать значение D3DPRESENT_RATE_DEFAULT; в этом случае будет установлена частота кадров по умолчанию данной видеокарты.

Ах, вот и последний элемент параметров отображения! Он называется PresentationInterval. Эта переменная устанавливает максимальную частоту, с которой будет отображаться вторичный буфер. Оконные приложения требуют, чтобы значение этого параметра было D3DPRESENT_INTERVAL_IMMEDIATE. Для полноэкранной визуализации можно использовать значение D3DPRESENT_INTERVAL_DEFAULT или одно из значений, перечисленных в таблице 6.4.

Таблица 6.4. Интервалы отображения

Значение Описание
D3DPRESENT_INTERVAL_DEFAULT Система сама выбирает частоту смены кадров. В оконном режиме используется частота по умолчанию.
D3DPRESENT_INTERVAL_ONE Перед отображением графики система ждет один период кадровой развертки.
D3DPRESENT_INTERVAL_TWO Перед отображением графики система ждет два периода кадровой развертки.
D3DPRESENT_INTERVAL_THREE Перед отображением графики система ждет три периода кадровой развертки.
D3DPRESENT_INTERVAL_FOUR Перед отображением графики система ждет четыре периода кадровой развертки.
D3DPRESENT_INTERVAL_IMMEDIATE Система не ожидает завершения кадровой развертки перед выводом изображения. Этот метод может вызвать несогласованность графики.

Создание устройства трехмерной визуализации

Вы уже установили параметры отображения, так что настало время создать объект устройства IDirect3DDevice9. Он является основой для всех относящихся к визуализации вызовов и поэтому исключительно важен. Без этого маленького объекта вы ничего не сможете отобразить на экране. Для его создания применяется функция IDirect3D9::CreateDevice(). Вот как выглядит ее прототип:

HRESULT CreateDevice(
   UINT Adapter,
   D3DDEVTYPE DeviceType,
   HWND hFocusWindow,
   DWORD BehaviorFlags,
   D3DPRESENT_PARAMETERS *pPresentationParameters,
   IDirect3DDevice9 **ppReturnedDeviceInterface
);

Первый параметр, Adapter, содержит порядковый номер используемой видеокарты. Для большинства систем с одним монитором можно использовать значение D3DADAPTER_DEFAULT. Этот параметр работает точно так же как и первый параметр функции GetAdapterDisplayMode(), который я описал ранее.

Второй параметр, DeviceType, является перечислением, задающим тип устройства. Доступные типы перечислены в таблице 6.5. В данной программе я использую значение D3DDEVTYPE_HAL. Из-за этого программа может не работать с видеокартами, не поддерживающими аппаратное ускорение. В этом случае для того, чтобы программа запустилась попробуйте изменить значение этого параметра на D3DDEVTYPE_REF.

Таблица 6.5. Типы устройств

Значение Константа Описание
1 D3DDEVTYPE_HAL Визуализация выполняется аппаратурой видеокарты. Этот метод позволяет использовать преимущества доступных методов аппаратного ускорения.
2 D3DDEVTYPE_REF Direct3D всю визуализацию выполняет программно. Это плохой выбор, если видеокарта поддерживает аппаратное ускорение.
3 D3DDEVTYPE_SW Используется программное устройство визуализации, эмулирующее аппаратуру.


Третий параметр, hFocusWindow, задает окно, которому будет принадлежать фокус системы визуализации DirectX. Для полноэкранного режима это должно быть окно самого верхнего уровня. В рассматриваемом примере я использую дескриптор окна, созданного в функции WinMain(). Это общепринятая практика для приложений, работающих в оконном режиме.

Четвертый параметр, BehaviorFlags, задает один или несколько флагов, определяющих параметры создаваемого устройства отображения. Доступные флаги перечислены в таблице 6.6.

Таблица 6.6. Флаги, задающие поведение устройства

Значение Описание
D3DCREATE_FPU_PRESERVE Приложение требует вычислений с плавающей точкой двойной точности.
D3DCREATE_MULTITHREADED Direct3D будет работать в безопасном многопоточном режиме. Этот режим приводит к значительному падению производительности и должен применяться только в случае крайней необходимости.
D3DCREATE_PUREDEVICE Указывает, что устройство не поддерживает вызовы Get для использования в блоках состояния. Кроме того, устройство не будет поддерживать обработку вершин. Лично я никогда не использую этот флаг.
D3DCREATE_HARDWARE_VERTEXPROCESSING Обработка вершин производится аппаратурой видеокарты. Этот флаг используется в рассматриваемой программе. Если приложение на вашем компьютере не работает, попробуйте заменить его на следующее значение из спсика.
D3DCREATE_SOFTWARE_VERTEXPROCESSING Обработка вершин производится программно. Это значительно медленнее, чем аппаратная обработка.
D3DCREATE_MIXED_VERTEXPROCESSING Обработка вершин производится как аппаратурой, так и программно. Этот вариант может работать медленнее, чем чисто аппаратная обработка.
D3DCREATE_DISABLE_DRIVER_MANAGEMENT Устраняет драйвер видеокарты от управления ресурсами. Если флаг установлен, всеми ресурсами управляет Direct3D.
D3DCREATE_ADAPTERGROUP_DEVICE Используется, если одновременно работают несколько видеокарт.
D3DCREATE_MANAGED Передает вопросы управления памятью в ведение устройства.


Пятый параметр, pPresentationParameters, задает параметры отображения для устройства. В рассматриваемом примере я использую указатель на инициализированную ранее структуру.

Последний параметр, ppReturnedDeviceInterface, содержит адрес указателя, который после завершения работы функции будет представлять созданное устройство. В этом параметре я передаю глобальный указатель на интерфейс IDirect3DDevice9 с именем g_pd3dDevice.

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

Настройка среды визуализации

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

D3DXMATRIX *D3DXMatrixOrthoLH(
   D3DXMATRIX *pOut,
   FLOAT w,
   FLOAT h,
   FLOAT zn,
   FLOAT zf
);

Первый параметр, pOut, хранит указатель на объект D3DXMATRIX, используемый для хранения полученного результата.

Второй параметр, w, задает ширину области просмотра.

Третий параметр, h, задает высоту области просмотра.

Четвертый параметр, zn, задает минимальное значение по оси Z. Все объекты, расположенные ближе к камере, чем указанное значение, не отображаются.

Пятый параметр, zf, задает максимальное значение по оси Z. Объекты, расположенные дальше от камеры не отображаются.

Взгляните на фрагмент моего кода, который выполняет эту операцию:

D3DXMatrixOrthoLH(&matproj, (float)g_iWindowWidth, (float)g_iWindowHeight, 0, 1);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matproj);

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

Проекционная матрица является основой и готова к использованию. Последним шагом в настройке проекции для двухмерной визуализации является вызов функции SetTransform() с параметром D3DTS_PROJECTION. Он активирует проекционную матрицу и открывает путь к двухмерной визуализации.

Есть еще несколько параметров визуализации, которые необходимо установить, прежде чем мы перейдем непосредственно к отображению графики. Во-первых надо выключить отбраковку. Это делается путем присваивания переменной состояния визуализации с именем D3DRS_CULLMODE значения D3DCULL_NONE. Эта установка указывает системе визуализации, что необходимо показывать обратные стороны треугольников. Мы не будем использовать это состояние визуализации, но оно облегчит вам жизнь при вращении треугольников.

Следующей настройкой состояния визуализации является отключение аппаратного освещения для сцены. Этот шаг не является необходимым; я добавил его только чтобы упростить данный пример. Для выключения освещения переменной состояния визуализации с именем D3DRS_LIGHTING присваивается значение FALSE.

Затем я отключаю Z-буфер, присваивая переменной состояния визуализации D3DRS_ZENABLE значение FALSE. Я выключаю z-буферизацию для того, чтобы никакие фрагменты моей двухмерной картинки не были удалены во время визуализации.

Последним шагом настройки состояний визуализации является отключение записи в z-буфер. Для этого переменной состояния визуализации D3DRS_ZWRITEENABLE присваивается значение FALSE. Я делаю это чтобы предотвратить изменение z-буфера операциями двухмерной визуализации. Это очень важно, когда в одной сцене совместно используются двухмерные и трехмерные элементы. Вы же не хотите, чтобы двухмерные элементы интерфейса изменяли буфер глубины, используемый вашими трехмерными объектами.

На этом настройка среды визуализации закончена. Пришло время функции vInitInterfaceObjects().

Функция vInitInterfaceObjects()

К данному моменту вы создали окно, проинициализировали Direct3D, создали проекционную матрицу для двухмерного отображения и установили различные переменные состояния визуализации. Функция vInitInterfaceObjects() содержит последний фрагмент головоломки, завершающий общую картину инициализации. Она создает геометрические объекты, необходимые для трехмерной визуализации и загружает двухмерные текстуры для отображения на экране. Взгляните на рис. 6.18, где показан геометрический объект, создаваемый в этой функции.


Рис. 6.18. Полоса треугольников, используемая для двухмерной визуализации

Рис. 6.18. Полоса треугольников, используемая для двухмерной визуализации


На рис. 6.18 вы видите четырехугольник, созданный из двух полигонов. У фигуры четыре вершины, соединенные линиями, образующими четырехугольник. Вы можете заметить, что две грани изображены пунктирной линией. Эти грани выделены по той причине, что мы используем для описания объекта полосу треугольников. Полоса треугольников требует меньшего объема памяти, чем список треугольников и зачастую быстрее отображается, поскольку центральному процессору и графическому процессору приходится выполнять меньше работы. За дополнительной информацией я рекомендую обратиться к справочной системе DirectX SDK.

Создание буфера вершин

Чтобы создать объект для визуализации необходимо сначала создать буфер вершин, который будет хранить данные, описывающие геометрию объекта. Эту задачу выполняет функция IDirect3DDevice9::CreateVertexBuffer(). Она достаточно прямолинейна, поскольку лишь создает буфер вершин для геометрических операций DirectX. Вот как выглядит прототип этой функции:

HRESULT CreateVertexBuffer(
   UINT Length,
   DWORD Usage,
   DWORD FVF,
   D3DPOOL Pool,
   IDirect3DVertexBuffer9 **ppVertexBuffer,
   HANDLE* pHandle
);

Первый параметр, Length, содержит размер создаваемого буфера вершин. Это очень важный параметр, поскольку вам придется дальше работать с буфером вершин запрошенного размера.

Второй параметр, Usage, содержит одну или несколько констант типа D3DUSAGE. Доступные значения перечислены в таблице 6.7.

Таблица 6.7. Значения D3DUSAGE

Значение Описание
D3DUSAGE_DYNAMIC Буфер вершин требует динамического использования памяти. Это позволяет драйверу видеокарты управлять буфером для оптимизации скорости визуализации. Если данный флаг отсутствует, буден создан статический буфер. Совместно с этим флагом нельзя использовать флаг параметров пула D3DPOOL_MANAGED
D3DUSAGE_AUTOGENMIPMAP Для буфера автоматически генерируются mip-текстуры. Этот метод требует больше памяти, но благодаря его использованию достигается более высокое качество визуализации
D3DUSAGE_DEPTHSTENCIL Буфер является буфером глубины или буфером трафарета. Совместно с этим флагом можно использовать только флаг параметров пула D3DPOOL_DEFAULT
D3DUSAGE_RENDERTARGET Буфер является целевым буфером визуализации. Совместно с этим флагом должен использоваться флаг параметров пула D3DPOOL_DEFAULT


В рассматриваемой программе я присваиваю параметру значение NULL. В результате будет создан статический буфер вершин.

Третий параметр, FVF, содержит комбинацию флагов D3DFVF. Он сообщает системе, какая информация будет содержаться в данных каждой вершины. Вы можете предпочесть не использовать формат FVF для буфера вершин. В этом случае присвойте данному параметру значение NULL. За дополнительной информацией о флагах D3DFVF обращайтесь к документации DirectX SDK.

Четвертый параметр, Pool, сообщает системе, какой класс памяти следует использовать. Так как данные вершин занимают память, они должны ее где-то получить. Значения D3DPOOL указывают доступные возможности. Все значения перечислены в таблице 6.8.

Таблица 6.8. Значения D3DPOOL

Значение Описание
D3DPOOL_DEFAULT Разрешает системе выделять память в наиболее подходящем пуле данных. Данный метод требует освобождения выделенных ресурсов перед сбросом устройства Direct3D
D3DPOOL_MANAGED Разрешает системе в случае необходимости копировать ресурсы из системной памяти в память устройства. Это позволяет выполнять сброс устройства без предварительного принудительного освобождения управляемой памяти
D3DPOOL_SYSTEMMEM Требует, чтобы система хранила ресурсы вне памяти устройства. Это не самый эффективный способ для тех систем, где есть аппаратные ускорители трехмерной графики. Созданные ресурсы не требуется освобождать перед сбросом устройства
D3DPOOL_SCRATCH Ресурсы данного типа недоступны для устройства Direct3D. Тем не менее их можно создавать, блокировать и копировать
D3DPOOL_FORCE_DWORD Не используется


В программе для настройки пула памяти я использую значение D3DPOOL_DEFAULT. В данной ситуации это простейший способ действий.

Пятый параметр, ppVertexBuffer, должен содержать адрес указателя IDirect3DVertexBuffer9. После завершения функции он будет указывать на созданный буфер вершин. В коде я использую глобальный буфер вершин с именем g_pVBInterface.

Последний параметр, pHandle, в данное время не используется, так что не стоит о нем беспокоиться. Разве это не здорово?

Блокировка буфера вершин

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

HRESULT Lock(
   UINT OffsetToLock,
   UINT SizeToLock,
   VOID **ppbData,
   DWORD Flags
);

К счастью, параметры этой функции не слишком сложны. Первый из них, OffsetToLock, задает номер байта в буфере вершин с которого начинается блокировка. Он очень полезен, когда надо отредактировать часть буфера вершин не блокируя его весь. Если вы не хотите задавать смещение, укажите значение 0.

Второй параметр, SizeToLock, указывает количество байт, блокируемых в буфере вершин. Если вы хотите заблокировать весь буфер, укажите значение 0.

Третий параметр, ppbData, является указателем на блокируемый буфер вершин. Ничего себе имечко у него!

Четвертый параметр, Flags, определяет параметры блокировки. В рассматриваемом примере никакие флаги не используются, но я все равно приведу описание доступных флагов в таблице 6.9.

Таблица 6.9. Значения D3DLOCK

Значение Описание
D3DLOCK_DISCARD Приложение переписывает значения пользуясь только операциями записи. Этот метод применяется когда используются динамические данные, такие как динамические текстуры, и буферы вершин
D3DLOCK_NO_DIRTY_UPDATE Оберегает систему от изменения грязных областей данных. Флаг используется достаточно редко
D3DLOCK_NOSYSLOCK Предотвращает остановку действий системы, таких как перемещение курсора мыши, на время блокировки. Рекомендуется использовать, когда выполняется длительная по времени блокировка.
D3DLOCK_READONLY Буфер доступен только для чтения
D3DLOCK_NOOVERWRITE Система немедленно возвращается из функции блокировки, поскольку приложение обязуется не перезаписывать содержимое буфера. Этот вариант работает быстрее, чем блокировка только для чтения


Как видите, в коде программы я использовал принятые по умолчанию параметры блокировки.

Заполнение буфера вершин данными

Итак, у вас в руках есть свежесозданный буфер вершин, но в нем нет никаких осмысленных данных. Что же делать программисту? Я скажу что вам требуется сделать: создайте необходимые данные вершин и поместите их в буфер!

Взгляните на следующий фрагмент кода в котором содержатся используемые программой данные вершин:

pVertices[0].position  = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
pVertices[0].tu        = 0.0f;
pVertices[0].tv        = 1.0f;
pVertices[0].tu2       = 0.0f;
pVertices[0].tv2       = 1.0f;
pVertices[0].vecNorm   = D3DXVECTOR3(0.0f,0.0f,1.0f);
pVertices[1].position  = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
pVertices[1].tu        = 0.0f;
pVertices[1].tv        = 0.0f;
pVertices[1].tu2       = 0.0f;
pVertices[1].tv2       = 0.0f;
pVertices[1].vecNorm   = D3DXVECTOR3(0.0f,0.0f,1.0f);
pVertices[2].position  = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
pVertices[2].tu        = 1.0f;
pVertices[2].tv        = 1.0f;
pVertices[2].tu2       = 1.0f;
pVertices[2].tv2       = 1.0f;
pVertices[2].vecNorm   = D3DXVECTOR3(0.0f,0.0f,1.0f);
pVertices[3].position  = D3DXVECTOR3(1.0f, 1.0f, 0.0f);
pVertices[3].tu        = 1.0f;
pVertices[3].tv        = 0.0f;
pVertices[3].tu2       = 1.0f;
pVertices[3].tv2       = 0.0f;
pVertices[3].vecNorm   = D3DXVECTOR3(0.0f,0.0f,1.0f);

Я знаю, что необученному взгляду эти данные почти ничего не говорят. Черт побери, даже для обученного взгляда они похожи на мигрень. Вы еще не забыли рис. 6.18? Его обновленная версия приведена на рис. 6.19.


Рис. 6.19. Геометрия, используемая для двухмерной визуализации в трехмерном пространстве

Рис. 6.19. Геометрия, используемая для двухмерной визуализации в трехмерном пространстве


На рис. 6.19 представлены четыре пронумерованных вершины. Первой вершине присвоен номер 0, а последней — номер 3. Номера на рис. 6.19 соответствуют позициям в массиве вершин из приведенного выше кода. Находящиеся рядом с каждой из вершин координаты показывают вам где в трехмерном пространстве расположена каждая вершина.

Проследуйте от вершины 0 к вершине 3 и обратите внимание, что форма пути напоминает букву Z, положенную на бок. Поскольку я использую полосу треугольников, система сама дополнит оставшиеся грани, чтобы образовался квадрат. Обратите внимание также на то, что на рисунке отмечена базовая точка изображения. Это очень важный момент, поскольку все последующие операции трансформации должны знать какая точка квадрата является базовой.

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

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

Данные вершин помещены на свои места, так что можно идти дальше и разблокировать буфер вершин. Это делает простой вызов функции IDirect3DVertexBuffer9::Unlock(). Вам надо только вызвать функцию разблокировки для буфера вершин, и вы завершите его редактирование.

Загрузка текстур

Буфер вершин полностью наряжен, но пока нет места куда он мог бы пойти. Как насчет нескольких текстур? Оставшаяся часть кода инициализации осуществляет загрузку текстур, необходимых программе TitleScreen. Для загрузки текстур я использую функцию D3DXCreateTextureFromFile() предоставляемую вспомогательной библиотекой DirectX. Вот как выглядит ее прототип:

HRESULT D3DXCreateTextureFromFile(
   LPDIRECT3DDEVICE9 pDevice,
   LPCSTR pSrcFile,
   LPDIRECT3DTEXTURE9 *ppTexture
);

Первый параметр, pDevice, является указателем на устройство Direct3D, которое вы используете для визуализации. Я передаю в этом параметре созданный ранее глобальный указатель на устройство.

Второй параметр, pSrcFile, содержит имя загружаемого файла. Функция может загружать файлы различных типов, включая JPEG, TGA, BMP и PCX. В коде программы я передаю в этом параметре имена различных файлов с текстурами. Если у вас есть желание поэкспериментировать с различными типами графических файлов, вы можете изменить приведенные имена.

Третий параметр, ppTexture, является адресом указателя на объект IDirect3DTexture9, который будет хранить загруженную текстуру. Как видите в рассматриваемом примере я передаю в этом параметре глобальный массив текстур с именем g_pTexture.

Вот и все, что я хотел сказать о загрузке текстур. Как видите, стоит только привыкнуть и эта работа оказывается очень легкой.

Функция vRender()

Вы готовы к визуализации? Думаю, да. Следуйте за мной к функции vRender(). Вот как выглядит ее код:

void vRender(void)
{
   // Очистка вторичного буфера
   g_pd3dDevice->Clear(0, NULL,
             D3DCLEAR_TARGET,
             D3DCOLOR_XRGB(0,0,0),
             1.0f, 0);
   // Начало создания сцены
   g_pd3dDevice->BeginScene();

   // Рисование титульного экрана
   vDrawInterfaceObject(0, 0, 256.0f, 256.0f, 0);
   vDrawInterfaceObject(256, 0, 256.0f, 256.0f, 1);
   vDrawInterfaceObject(512, 0, 256.0f, 256.0f, 2);
   vDrawInterfaceObject(0, 256, 256.0f, 256.0f, 3);
   vDrawInterfaceObject(256, 256, 256.0f, 256.0f, 4);
   vDrawInterfaceObject(512, 256, 256.0f, 256.0f, 5);

   // Логотип
   vDrawInterfaceObject(192, 64, 256.0f, 256.0f, 6);

   // Конец создания сцены
   g_pd3dDevice->EndScene();

   // Вывод содержимого вторичного буфера на экран
   g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
}

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

Функция vDrawInterfaceObject()

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

void vDrawInterfaceObject(
   int iXPos,
   int iYPos,
   float fXSize,
   float fYSize,
   int iTexture
);

Первые два параметра, iXPos и iYPos, задают местоположение текстуры на экране. В отличие от вызовов, относящихся к трехмерной графике, здесь следует указывать координаты в двухмерном пространстве экрана.

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

Последний параметр, iTexture, является индексом в глобальном массиве текстур. Он определяет какая исменно текстура будет отображена.

Теперь взгляните на код этого бриллианта из мира функций:

void vDrawInterfaceObject(int iXPos, int iYPos, float fXSize, float fYSize,
                           int iTexture)
{
   D3DXMATRIX   matWorld, matRotation;
   D3DXMATRIX   matTranslation, matScale;
   float        fXPos, fYPos;

   // Установка начальных значений местоположения, 
   // масштабирования и вращения
   D3DXMatrixIdentity(&matTranslation);
   // Масштабирование спрайта
   D3DXMatrixScaling(&matScale, fXSize, fYSize, 1.0f);
   D3DXMatrixMultiply(&matTranslation, &matTranslation, &matScale);
   // Поворот спрайта
   D3DXMatrixRotationZ(&matRotation, 0.0f);
   D3DXMatrixMultiply(&matWorld, &matTranslation, &matRotation);
   // Вычисление местоположения на экране
   fXPos = (float)(-(g_iWindowWidth / 2) + iXPos);
   fYPos = (float)(-(g_iWindowHeight / 2) - iYPos + fYSize - g_iYOffset);
   // Перемещение спрайта
   matWorld._41 = fXPos;  // X
   matWorld._42 = fYPos;  // Y
   // Установка матрицы
   g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);
   g_pd3dDevice->SetTexture(0, g_pTexture[iTexture]);
   g_pd3dDevice->SetStreamSource(0, g_pVBInterface, 0, sizeof(CUSTOMVERTEX));
   g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
   g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
   // Разыменовывание текстуры
   g_pd3dDevice->SetTexture(0, NULL);
}

В начале кода создается матрица для трехмерного объекта, содержащая значения по умолчанию. Это достигается путем вызова функции D3DXMatrixIdentity(). Эта вспомогательная функция предоставляется DirectX SDK и удобна для создания матрицы по умолчанию. Она обнуляет за вас все значения в матрице, за исключением тех, которые расположены на главной диагонали (им она присваивает значение 1). Это эквивалент стирания старых записей со школьной доски.

В следующем блоке кода выполняется создание матрицы масштабирования. Она предназначена для масштабирования трехмерного квадрата таким образом, чтобы его размеры совпадали с размером текстуры. Поскольку глубина в нашем случае не используется, коэффициет масштабирования по оси Z задан равным 1.0. Готовая матрица масштабирования умножается на матрицу преобразования.

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

По умолчанию матрица описывает геомертию трехмерного пространства. Поскольку мы выполняем операции с двухмерной поверхностью дисплея, необходимо вычислить координаты объекта в экранных пикселах. Делая это я учитываю высоту и ширину экрана. Как можно увидеть в коде примера, я делю размеры экрана на два и использую их в формуле вместе с желаемыми координатами объекта, чтобы вычислить его местположение на экране. Концепцию трехмерных координат в пространстве экрана поясняет рис. 6.20.


Рис. 6.20. Отображение текстуры в трехмерном пространстве на экране

Рис. 6.20. Отображение текстуры в трехмерном пространстве на экране


На рис. 6.20, точка с координатами (0,0) соответствует центру экрана. Это коренным образом отличается от традиционной двухмерной визуализации. В традиционной двухмерной среде изображенной на рис 6.20 точке в центре экрана соответствуют координаты (400,300). Так как вы имеете дело с трехмерным миром, вам необходимо компенсировать это различие. Для этого и пригодился код о котором я только что говорил. Он преобразует трехмерную геометрию для отображения в двухмерном пространстве экрана.

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

Теперь матрица содержит данные о масштабировании, вращении и перемещении, необходимые для визуализации. Матрицу следует активировать, для чего необходимо вызвать функцию IDirect3DDevice9::SetTransform(). Я вызываю эту функцию, которая меняет матрицу D3DTS_WORLD на матрицу, созданную ранее для отображения растра. В результате активируется новая матрица.

Затем я активирую требуемую текстуру. Для этой цели применяется вызов функции IDirect3DDevice9::SetTexture(), которой в качестве параметра передается указатель на ту текстуру, которую мы хотим отобразить.

Чтобы при визуализации использовался созданный ранее в программе буфер вершин, я должен активировать его вызвав функцию IDirect3DDevice9::SetStreamSource(). Эта функция активирует указанный буфер вершин, после чего он будет использован в качестве потокового источника для последующих вызовов функций визуализации.

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

И последней — но от этого она не становится менее важной — я вызываю функцию IDirect3DDevice9::DrawPrimitive(). Она является самым сердцем визуализации и выполняет всю работу, необходимую для отображения трехмерных объектов. Поскольку для изображения квадрата использовалась полоса треугольников, в первом параметре функции я указываю тип данных D3DPT_TRIANGLESTRIP.

И, наконец, я разыменовываю текстуру, присваивая активной текстуре значение NULL.

Закончив разбирать функцию рисования элементов интерфейса, вернемся назад к функции vRender() и посмотрим, каким образом в ней реализовано рисование интерфейса. Ниже я еще раз привел отвечающий за это фрагмент кода:

vDrawInterfaceObject(0, 0, 256.0f, 256.0f, 0);
vDrawInterfaceObject(256, 0, 256.0f, 256.0f, 1);
vDrawInterfaceObject(512, 0, 256.0f, 256.0f, 2);
vDrawInterfaceObject(0, 256, 256.0f, 256.0f, 3);
vDrawInterfaceObject(256, 256, 256.0f, 256.0f, 4);
vDrawInterfaceObject(512, 256, 256.0f, 256.0f, 5);
vDrawInterfaceObject(192, 64, 256.0f, 256.0f, 6);

Чтобы лучше представить себе результат этих вызовов функций, взгляните на рис. 6.21.


Рис. 6.21. Расположение текстур на титульном экране

Рис. 6.21. Расположение текстур на титульном экране


На рис. 6.21 изображен экран обрамленный тонкой линией. Кроме того там изображены шесть текстур, расположенных в виде сетки. Эти шесть текстур полностью заполняют экран. Поскольку ширина и высота каждой текстуры равна 256 точкам, их общий размер превышает размеры экрана ширина которого равна 640 точкам, а высота — 480 точкам. Такие размеры текстур необходимы для того, чтобы они соответствовали требованиям оборудования, которое поддерживает только текстуры с размером, равным степени двойки. Большинство современных видеокарт могут работать только с текстурами размером 2 x 2, 4 x 4, 8 x 8, 16 x 16, 32 x 32, 64 x 64, 128 x 128, 256 x 256 и т.д. точек, поэтому и требуется данное действие. Лучший способ учесть эти требования — создать экран в требуемом разрешении, а затем разбить его на блоки размером 256 x 256 точек. Конечно, в результате увеличится объем использукмой памяти, но это можно проигнорировать.

Единственная текстура, которая не рисуется при отображении сетки — это логотип игры. Поскольку он находится в центре экрана, для него следует задать слегка отличные координаты. Попробуйте изменить координаты текстур в коде и посмотрите что получится. Вы можете даже добавить к титульному экрану несколько своих тестур, чтобы обрести необходимые навыки.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz