Введение в программирование трехмерных игр с DX9

         

Чайник, описанный в его собственной, локальной системе координат



Рисунок 2.8. Чайник, описанный в его собственной, локальной системе координат



Форматы вершин



2.1.1. Форматы вершин

Приведенное выше определение вершин верно с математической точки зрения, но в контексте Direct3D является неполным. Это вызвано тем, что в Direct3D у вершины могут быть дополнительные свойства, помимо ее местоположения. Например, вершине может быть назначен цвет или с ней может быть связана нормаль (цвет будет обсуждаться в главе4, а нормали — в главе 5). Direct3D обладает значительной гибкостью и позволяет нам конструировать собственные форматы вершин; другими словами, он позволяет нам указать, какая информация будет содержаться в данных вершины.

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

struct ColorVertex { float _x, _y, _z; // местоположение DWORD _color; // цвет };

struct NormalTexVertex { float _x, _y, _z; // местоположение float _nx, _ny, _nz; // вектор нормали float _u, _v; // координаты текстуры };

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

#define FVF_COLOR (D3DFVF_XYZ | D3DFVF_DIFFUSE)

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

#define FVF_NORMAL_TEX (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1)

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

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

Полный список доступных флагов формата вершин вы найдете в документации по ключевому слову D3DFVF.



Конвейер визуализации


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





Индексы



2.1.3. Индексы

Очень часто образующие трехмерный объект треугольники имеют общие вершины, как, например в прямоугольнике, изображенном на Рисунок  2.4. Хотя в примере с прямоугольником дублируются всего две вершины, по мере роста детализированности и сложности модели число таких вершин быстро растет. Например, у изображенного на Рисунок  2.5 куба восемь уникальных вершин, но в списке треугольников, образующих куб каждая из этих вершин встречается по несколько раз.



с помощью сетки из треугольных


Трехмерные объекты представляются с помощью сетки из треугольных ячеек — списка треугольников, описывающих форму и контуры объекта.
Моделью виртуальной камеры является усеченная пирамида. Пространство внутри приамиды — это то, что «видит» камера.
Трехмерные объекты описываются каждый в своем локальном пространстве, а затем все переносятся в общее, мировое пространство. Чтобы упростить проекцию, отбрасывание невидимых граней и другие операции, объекты переносятся в пространство вида, в котором камера расположена в начале координат и направлена вдоль положительного направления оси Z. После преобразования в пространство вида выполняется проекция объектов в окно проекции. Преобразование порта просмотра переносит геометрию из окна проекции в область порта просмотра. И, в самом конце, на этапе растеризации вычисляется цвет каждого пикселя итогового двухмерного изображения.

Компоненты пирамиды видимого пространства



Рисунок 2.15. Компоненты пирамиды видимого пространства

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

форматноеСоотношение = ширинаЭкрана / высотаЭкрана

Матрица проекции устанавливается с помощью метода IDirect3DDevice9::SetTransform, в котором указан тип преобразования D3DTS_PROJECTION. В приведенном ниже примере создается матрица проекции на основании усеченной пирамиды видимого пространства с углом поля зрения в 90 градусов, передней плоскостью, расположенной на расстоянии 1 единицы и задней плоскостью, расположенной на расстоянии в 1000 единиц.

D3DXMATRIX proj; D3DXMatrixPerspectiveFovLH( &proj, PI * 0.5f, (float)width / (float)height, 1.0, 1000.0f); Device->SetTransform(D3DTS_PROJECTION, &proj);

ПРИМЕЧАНИЕ

Заинтересованные читатели могут подробнее узнать о проекции во втором издании книги Алана Ватта «3D Computer Graphics».

Конвейер визуализации


Как только мы описали геометрию трехмерной сцены и установили виртуальную камеру, перед нами встает задача формирования двухмерного представления этой сцены на мониторе. Последовательность действий, которые должны быть выполнены для решения этой задачи называется конвейером визуализации (rendering pipeline). На Рисунок 2.7 представлена упрощенная схема этого конвейера и в последующих разделах мы подробнее обсудим каждый его этап.



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



Рисунок 2.2. Ландшафт, представленный с помощью сетки из треугольников

Точка, в которой встречаются две грани полигона называется вершиной (vertex). Чтобы описать треугольник, мы задаем местоположение трех точек, являющихся его вершинами (Рисунок  2.3.). Чтобы описать объект, мы задаем составляющие его треугольники.



Левое изображение показывает несколько



Рисунок 2.1. Левое изображение показывает несколько объектов, образующих трехмерную сцену и нацеленную на них камеру. Справа показано двухмерное изображение, созданное на основании того, что «видит» камера.


Цели

Узнать, как в Direct3D представляются трехмерные объекты.

Изучить моделирование виртуальной камеры.

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



Локальное пространство



2.3.1. Локальное пространство

Локальное пространство (local space) или пространство моделирования (modeling space)— это та система координат, в которой мы описываем объект в виде списка треугольных граней. Локальное пространство полезно потому что оно упрощает процесс моделирования. Создавать модель в ее собственной, локальной системе координат проще чем встраивать ее непосредственно в сцену. Локальное пространство позволяет нам создавать модели не заботясь об их расположении, размере или ориентации относительно других объектов сцены.



Мировое пространство



2.3.2. Мировое пространство

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



Несколько трехмерных объектов, описанных в единой мировой системе координат



Рисунок 2.9. Несколько трехмерных объектов, описанных в единой мировой системе координат

<
Мировое преобразование представляется с помощью матрицы и устанавливается в Direct3D с помощью метода IDirect3DDevice9::SetTransform, где в качестве вида преобразования указано D3DTS_WORLD. Предположим, мы хотим поместить куб в точку (–3,2, 6) мирового пространства, а сферу — в точку (5, 0, –2). Для этого следует написать:

// Создаем матрицу мирового преобразования для куба, // которая содержит только перемещение D3DXMATRIX cubeWorldMatrix; D3DXMatrixTranslation(&cubeWorldMatrix, -3.0f, 2.0f, 6.0f);

// Создаем матрицу мирового преобразования для сферы, // которая содержит только перемещение D3DXMATRIX sphereWorldMatrix; D3DXMatrixTranslation(&sphereWorldMatrix, 5.0f, 0.0f, -2.0f);

// Устанавливаем преобразование для куба Device->SetTransform(D3DTS_WORLD, &cubeWorldMatrix); drawCube(); // рисуем куб

// Теперь, поскольку сфера использует другую матрицу мирового // преобразования, мы должны изменить мировое преобразование для сферы. // Если не сделать этого, сфера будет рисоваться с использованием // предыдущей матрицы мирового преобразования, которая предназначалась // для куба. Device->SetTransform(D3DTS_WORLD, &sphereWorldMatrix); drawSphere(); // рисуем сферу

Это очень упрощенный пример, поскольку обычно объекты приходится не только перемещать, но и вращать и масштабировать, но он показывает как работает мировое преобразование.


Объект с фронтальными и обратными полигонами



Рисунок 2.11. Объект с фронтальными и обратными полигонами

Исследовав Рисунок  2.11 мы увидим, что фронтальные полигоны скрывают находящиеся за ними обратные полигоны. Direct3D может извлечь из этого пользу отбросив (исключив из дальнейшей обработки) обратные полигоны; этот процесс называется удалением невидимых граней (backface culling). На Рисунок  2.12 показан тот же самый объект, но уже после удаления невидимых граней. Камера будет все равно показывать ту же самую сцену, поскольку обратные грани скрыты и в любом случае их нельзя увидеть.



Освещение



2.3.5. Освещение

Источники света описываются в мировом пространстве, но потом преобразуются в пространство вида при соответствующем преобразовании сцены. В пространстве вида источники света применяются для освещения объектов сцены, что позволяет получить более реалистичный вид. Работа с освещением в фиксированном конвейере визуализации подробно рассматривается в главе 5. Позднее, в части IV, мы реализуем собственную схему освещения с использованием программируемого конвейера.



Отсечение



2.3.6. Отсечение

Теперь нам необходимо отбросить геометрию, которая находится вне видимого пространства; этот процесс называется отсечением (clipping). Есть три варианта размещения треугольной грани относительно усеченной пирамиды видимого пространства:

Полностью внутри— Если треугольник полностью находится внутри области видимого пространства, он переходит на следующий этап.

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

Частично внутри (частично снаружи) — если внутри пирамиды видимого пространства находится только часть треугольника, то он разбивается на две части. Часть, которая находится внутри пирамиды видимого пространства остается, а часть, которая находится снаружи — отбрасывается.

Все три рассмотренных варианта изображены на Рисунок  2.13.



Отсечение геометрии, находящейся вне видимого пространства



Рисунок 2.13. Отсечение геометрии, находящейся вне видимого пространства



Представление моделей


Сценой (scene) называется набор объектов или моделей. Объект представляется с помощью сетки с треугольными ячейками (triangle mesh), как показано на Рисунок 2.2. Отдельные треугольники сетки — это строительные блоки с помощью которых мы моделируем объекты. Чтобы сослаться на треугольник сетки мы будем использовать следующие взаимозаменяемые термины: полигон, примитив и ячейка сетки. (Треугольники являются примитивами, но Direct3D поддерживает еще два вида примитивов: линии и точки. Однако, поскольку линии и точки не слишком полезны для моделирования трехмерных твердых объектов, мы опустим обсуждение этих примитивов. О некоторых применениях точек мы поговорим в главе 14.)

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

Device->SetRenderState(D3DRS_CULLMODE, Value);

где Value может принимать одно из следующих значений:

D3DCULL_NONE — Удаление обратных граней выключено.

D3DCULL_CW — Отбрасываются треугольники с порядком обхода вершин по часовой стрелке.

D3DCULL_CCW — Отбрасываются треугольники с порядком обхода вершин против часовой стрелки. Это значение по умолчанию.


Составленный из треугольников куб



Рисунок 2.5. Составленный из треугольников куб



Преобразование из мирового пространства



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


Чтобы вычислить матрицу преобразования вида можно воспользоваться следующей функцией библиотеки D3DX:

D3DXMATRIX *D3DXMatrixLookAtLH( D3DXMATRIX* pOut, // указатель на возвращаемую матрицу преобразования CONST D3DXVECTOR3* pEye, // местоположение камеры в сцене CONST D3DXVECTOR3* pAt, // точка, на которую направлена камера CONST D3DXVECTOR3* pUp // вектор, задающий направление вверх – (0, 1, 0) );

Параметр pEye задает точку пространства, в которой располагается камера. Параметр pAt задает ту точку сцены, на которую направлена камера. Параметр pUp — это вектор, который задает направление «вверх» для нашей сцены. Почти всегда это вектор, совпадающий с осью Y — (0, 1, 0).

Предположим, мы разместили камеру в точке (5, 3, –10) и направили ее на начало системы координат нашей сцены (0, 0, 0). Чтобы создать матрицу преобразования пространства вида, надо написать:

D3DXVECTOR3 position(5.0f, 3.0f, –10.0f); D3DXVECTOR3 targetPoint(0.0f, 0.0f, 0.0f); D3DXVECTOR3 worldUp(0.0f, 1.0f, 0.0f);

D3DXMATRIX V; D3DXMatrixLookAtLH(&V, &position, &targetPoint, &worldUp);

Преобразование пространства вида устанавливается с помощью метода IDirect3DDevice9::SetTransform, у которого в качестве типа преобразования указано D3DTS_VIEW:

Device->SetTransform(D3DTS_VIEW, &V);



Преобразование порта просмотра



2.3.8. Преобразование порта просмотра

Преобразование порта просмотра отвечает за преобразование координат из окна проекции в прямоугольную область экрана, которая называется портом просмотра (viewport). Для игр портом просмотра обычно является весь экран. Однако, если приложение работает в окнонном режиме, портом просмотра является часть экрана или клиентская область. Прямоугольник порта просмотра описывается относительно содержащего его окна и задается в оконных координатах (Рисунок  2.16).

Vertex rect[6] = {v0, v1, v2, // треугольник 0 v0, v2, v3}; // треугольник 1

ПРИМЕЧАНИЕ



Прямоугольник порта просмотра



Рисунок 2.16. Прямоугольник порта просмотра



Прямоугольник, образованный из двух треугольников



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

Порядок, в котором задаются вершины треугольника очень важен и называется порядком обхода (winding order). Подробнее об этом мы поговорим в разделе2.3.4.
<
В Direct3D порт просмотра представляется структурой D3DVIEWPORT9. Ее объявление выглядит так:

typedef struct _D3DVIEWPORT9 { DWORD X; DWORD Y; DWORD Width; DWORD Height; DWORD MinZ; DWORD MaxZ; } D3DVIEWPORT9;

Первые четыре члена данных описывают прямоугольник порта просмотра относительно содержащего его окна. Переменная MinZ задает минимальное значение буфера глубины, а переменная MaxZ— максимальное значение буфера глубины. Direct3D использует значения буфера глубины в диапазоне от нуля до единицы, поэтому переменным MinZ и MaxZ следует присваивать эти значения, если только вы не хотите реализовать какие-нибудь спецэффекты.

После инициализации структуры D3DVIEWPORT9, мы устанавливаем порт просмотра Direct3D следующим образом:

D3DVIEWPORT9 vp = { 0, 0, 640, 480, 0, 1 }; Device->SetViewport(&vp);

Direct3D выполняет преобразование порта просмотра за нас автоматически, но для справки мы приведем матрицу, описывающую это преобразование. Переменные в ней соответствуют одноименным членам данных структуры D3DVIEWPORT9.


Проекция



2.3.7. Проекция

Для пространства вида остается задача получения двухмерного представления трехмерной сцены. Процесс перехода от n-мерного пространства к (n–1)-мерному называется проекцией (projection). Существует множество способов выполнить проекцию, но нас интересует один частный случай, называемый перспективной проекцией (perspective projection). Перспективная проекция выполняется таким образом, что объекты, расположенные дальше от камеры выглядят меньше, чем объекты того же размера, находящиеся ближе к камере. Этот тип проекции позволяет представить трехмерную сцену в виде двухмерного изображения. На Рисунок 2.14 показана точка в трехмерном пространстве, проецируемая в на плоскость проекции с использованием перспективной проекции.



Проекция точки в трехмерном пространстве в окно проекции



Рисунок 2.14. Проекция точки в трехмерном пространстве в окно проекции

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

D3DXMATRIX *D3DXMatrixPerspectiveFovLH( D3DXMATRIX* pOut, // возвращает матрицу проекции FLOAT fovY, // вертикальный угол поля зрения в радианах FLOAT Aspect, // форматное соотношение = ширина / высота FLOAT zn, // расстояние до передней полскости FLOAT zf // расстояние до задней плоскости );



Пространство вида



2.3.3. Пространство вида

В мировом пространстве геометрия объектов и камера описаны относительно одной мировой системы координат, как показано на Рисунок  2.10. Однако, проекция и другие операции станут более трудными и менее эффективными, если камера не занимает определенное местоположение и не ориентирована требуемым образом. Чтобы упростить вычисления мы перемещаем камеру в начало координат и поворачиваем ее таким образом, чтобы она была направлена вдоль положительного направления оси Z. Вместе с камерой перемещаются и поворачиваются и все образующие сцену объекты, так что вид сцены остается неизменным. Данное преобразование называется преобразованием пространства вида (view space transformation), и после него говорят, что объекты расположены в пространстве вида (view space).



Растеризация



2.3.9. Растеризация

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



Растеризация треугольника на экране



Рисунок 2.17. Растеризация треугольника на экране

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



Сцена после отбрасывания обратных граней



Рисунок 2.12. Сцена после отбрасывания обратных граней

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

ПРИМЕЧАНИЕ

Обратите внимание, что мы говорим «в пространстве вида». Это вызвано тем, что при повороте треугольника на 180 градусов порядок обхода вершин у него меняется на противоположный. Следовательно, треугольник у которого порядок обхода вершин в локальном пространстве был по часовой стрелке, может сменить порядок обхода вершин в пространстве вида, поскольку в процессе преобразований выполнялось вращение объектов.
<
Для решения этой проблемы мы добавим концепцию индексов (indices). Она действует следующим образом: мы создаем список вершин и список индексов. В списке вершин перечисляются все уникальные вершины, а список индексов содержит последовательность номеров (индексов) вершин из списка вершин, показывающую как объединяются вершины для формирования треугольников. Для примера с прямоугольником список вершин мог бы выглядеть так:

Vertex vertexList[4] = {v0, v1, v2, v3};

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

WORD indexList[6] = {0, 1, 2, // треугольник 0 0, 2, 3}; // треугольник 1

Если облечь код в слова, определение массива indexList говорит, что треугольник 0 образован нулевым (vertexList[0]), первым (vertexList[1]) и вторым (vertexList[2]) элементами списка вершин, а треугольник 1 образован нулевым (vertexList[0]), вторым (vertexList[2]) и третьим (vertexList[3]) элементами списка вершин.


Треугольник, заданный тремя вершинами



Рисунок 2.3. Треугольник, заданный тремя вершинами



Треугольники



2.1.2. Треугольники

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



Удаление невидимых поверхностей



2.3.4. Удаление невидимых поверхностей

У полигона есть две стороны; одну из них мы будем называть лицевой (front), а другую— обратной (back). Обычно обратные стороны полигонов никогда не видны. Это происходит из-за того, что большинство объектов сцены являются сплошными объемными телами, такими как ящики, цилиндры, цистерны, персонажи и т.д. и камера никогда не попадает внутрь занимаемого объектом пространства. Поэтому камера никогда не может увидеть обратные стороны полигонов. Это очень важно знать, потому что если мы дадим возможность видеть обратные стороны полигонов, удаление невидимых поверхностей не будет работать.

На Рисунок  2.11 показан объект в пространстве вида, лицевая сторона каждой грани которого помечена стрелкой. Полигон, лицевая сторона которого обращена к камере, называется фронтальным полигоном (front facing polygon), а полигон, лицевая сторона которого обращена от камеры, называется обратным полигоном (back facing polygon).



Упрощенная схема конвейера визуализации



Рисунок 2.7. Упрощенная схема конвейера визуализации


Несколько этапов конвейера выполняют преобразование из одной системы координат в другую. Эти преобразования выполняются с помощью матриц. Direct3D выполняет вычисления преобразований за нас. Это полезно, потому что преобразования могут выполняться аппаратурой, если ваша видеокарта поддерживает аппаратную обработку преобразований. Если мы используем для преобразований Direct3D, нам надо только предоставить матрицу преобразования, которая описывает преобразования, необходимые для перехода от одной системы координат к другой. Мы задаем матрицу с помощью метода IDirect3DDevice->SetTransform. Он получает параметр, описывающий тип преобразования и указатель на матрицу преобразования. Например, на Рисунок  2.7, для преобразования, необходимого для перехода от локального пространства к мировому, мы должны написать:

Device->SetTransform(D3DTS_WORLD, &worldMatrix);

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



Усеченная пирамида, определяющая область пространства, которую «видит» камера



Рисунок 2.6. Усеченная пирамида, определяющая область пространства, которую «видит» камера


Область видимого пространства преставляет собой усеченную пирамиду (frustum) и определяется углами поля зрения, передней и задней плоскостями. Причины использования усеченной пирамиды станут ясны, если принять во внимание, что экран на котором отображается сцена — прямоугольный. Объекты, которые не находятся внутри заданного пространства невидимы и должны быть исключены из процесса дальнейшей обработки. Процесс исключения таких данных называется отсечением (clipping).

Окно проекции (projection window) — это двухмерная область на которую проецируются находящиеся внутри области видимого пространства трехмерные объекты для создания двухмерного изображения, представляющего трехмерную сцену. Важно помнить, что мы определяем окно проекции таким образом, что координаты его правого верхнего угла будут (1, 1), а координаты левого нижнего угла — (–1, –1).

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



Виртуальная камера


Камера определяет какую часть мира может видеть зритель и, следовательно, для какой части мира нам надо создавать ее двухмерное изображение. Камера позиционируется и ориентируется в пространстве и определяет видимую облать пространства. Схема используемой нами модели камеры показана на Рисунок 2.6.