Загрузка...
 
Печать

Базовые понятия 3D-графики

  • Во многом перекликаются со школьным курсом геометрии.


Содержание

Рис. 1 Вершины, рёбра и грани - основные элементы для создания 3D-объектов
Рис. 1 Вершины, рёбра и грани - основные элементы для создания 3D-объектов

Вершина (Vertice)

  • Наименьшая единица изображения в графическом пространстве.
  • Представляет собой точку с координатами, размещённую в 2D или 3D пространстве.
  • Графический чип (GPU) на видеокарте "видит" только вершины, образующие треугольники.

Абсолютно всё в 3D-игре представлено в виде 3D-точек (См. Рис.1). Задача видеокарты - заполнить пустое пространство между тремя вершинами (полигона).
Существует два основных способа вывода 3D-объектов на экран:

  • набросать (plot) точки самостоятельно;
  • загрузить их из готового файла 3D-модели (в котором сохранены все точки, составляющие модель).

Какие бы координаты не были присвоены вершине, они будут рассматриваться в трёх разных пространствах:

  • Пространство экрана (Screen space; с использованием трансформированных координат);

Применяется для создания вершин в координатах экрана.

  • Пространство модели (Model space; с использованием нетрансформированных координат);

Также её называют локальное пространство (Local space). Представляет собой координаты, отсчитываемые от условного центра (начала координат) модели.

  • Пространство мира (World space; также с использованием нетрансформированных координат).

Перед рендерингом вершины, размещённые в локальном пространстве, конвертируются в мировое пространство. Во время рендеринга координаты, размещённые в мировом пространстве, снова конвертируются в координаты экранного пространства (Screen space).
Вершины, размещённые в мировом пространстве, имеют финальные координаты, используемые при рендеринге объекта. К примеру, представь самого себя в виде меша. Твои суставы представляют собой вершины, которые изначально определены в локальном пространстве, т.к. за точку отсчёта при этом взят условный локальный центр координат, расположенный по центру твоей груди.
При передвижении по дому (который представляет собой мировое пространство), координаты твоих суставов также перемещаются относительно мира, но (вцелом) остаются неподвижны относительно твоего тела (см. Рис.3).

Опорная точка вершины (Origin of Vertice)

Любая точка имеет т.н. опорную точку (origin).1 Она невидима, но именно относительно неё выполняется вращение вершины-обладательницы. Более того, опорные точки есть у каждого объекта сцены. При перемещении (=трансляции) объекта в другое место, его опорная точка остаётся на прежних координатах. Поэтому, если объект переместить и затем вращать, то вращаться он будет по окружности, центром которой является его опорная точка, находящаяся от него на некотором удалении. Раньше это часто вызывало трудности у начинающих программеров: вершины и объекты перемещались, а их опорные точки - нет. Но с приходом DirectX проблема была решена легко и изящно.
Все объекты состоят из вершин. Фокус в том, чтобы оставлять "оригинал" объекта неизменным (в памяти). Вместо этого все манипуляции выполняются с его копиями (инстансами). Оставляя объект-источник в точке, совпадающей с его опорной точкой, его можно вращать относительно его собственного локального центра координат, что предотвратит перемещение объекта в 3D-сцене (там, где это не нужно).
Так как же переместить объект, не сдвинув его с места? С помощью математических матриц. Матрица (в DirectX; в математике размер матриц ничем не ограничен) представляет собой таблицу чисел размером 4х4 элемента. Эта таблица отображает текущее положение объекта в 3D-пространстве. У каждого объекта в 3D-сцене (и игре) есть своя собственная матрица. Матрицы позволяют манипулировать объектами сцены независимо друг от друга. Можно даже манипулировать всей сценой целиком, при этом не затрагивая отдельные её объекты. К примеру допустим, мы работаем над игрой жанра автогонки (racing) и в ней есть автомобили (модели), которые ездят по трассе в форме овала (трек). Ты стремишься к тому, чтобы каждый автомобиль выглядел наиболее реалистично. Поэтому каждый объект автомобиля можно перемещать и вращать независимо от других. В определённый момент мы добавляем код, который "разбивает" машины при их столкновении. Также мы стремимся, чтобы автомобили были расположены строго горизонтально относительно плоскости трека, что подразумевает подсчёт угла трека и положение всех четырёх углов автомобиля.
Представь, какие возможности появятся в игре, когда мы организуем содержание в объектах субобъектов, каждый из которых имеет свою собственную опорную точку (origin), которая всюду следует за опорной точкой объекта-родителя. В этом случае можно разместить субобъекты относительно объекта-родителя и заставить их вращаться. Конечно, речь идёт о колёсах. Колёса всё время двигаются вместе с самой машиной (вместе с ней они перемещаются, вращаются, изменяются в размерах), но, вдобавок, сами по себе могут вращаться по горизонтальной оси и по вертикальной (в случае поворота автомобиля вправо и влево).

Ребро (Edge)

  • Это отрезок (линия), соединяющий две вершины.

Полигон

  • Обычно это плоский треугольник, состоящий из трёх вершин (и трёх граней).

Всё, что рисуется в Direct3D состоит из полигонов. В теории полигоны могут состоять из 4, 5, 6 и даже более вершин. Но на практике почти повсеместно (и в Direct3D) применяют треугольные полигоны.

Закрыть
noteЛюбопытный факт

Исключением в своё время стала игровая консоль Sega Saturn, увидевшая свет в 1994 году, которая "понимала" только четырёхугольные полигоны. Поначалу это представляли как фичу, но на деле такой нестандартный полигон обернулся головной болью для разработчиков. Т.к. абсолютно все системы до этого оперировали только треугольниками.


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

Грань (Face)

Один и более полигонов образуют грань.

Плоскость (Plane)

  • Применяется в математике и 3D-моделировании для классифицирования (=объединения в множество) точек в 3D-пространстве.
  • В игрокодинге применяется для отсечения объектов, не попадающих в зону видимости, а также для определения столкновений (например со стенами).

Представь себе ровный лист бумаги, размещённый в 3D-пространстве произвольным образом. А теперь представь, что длина и ширина этого листа бесконечны, а толщина пренебрежимо мала (т.е. условно равна 0). Это и будет плоскость.
В математике плоскость – это поверхность, которая полностью содержит каждую прямую, соединяющую любые ее точки (Источник: https://microexcel.ru/ploskost(external link)). Любая точка 3D-пространства может быть расположена перед плоскостью, за плоскостью, либо на плоскости (= принадлежать плоскости).
В Direct3D для хранения плоскости есть специальная структура D3DXPLANE:

typedef struct D3DXPLANE
{
 FLOAT a;
 FLOAT b;
 FLOAT c;
 FLOAT d;
}D3DXPLANE;

Создание плоскости на основе трёх точек

  • Самый простой способ.

3 точки образую треугольник. 3 точки также однозначно определяют плоскость, при условии что все они принадлежат данной плоскости (= лежат на ней). Их достаточно, чтобы определить положение плоскости в 3D-пространстве.
В Direct3D для создания плоскости на основе трёх точек применяют функцию D3DXPlaneFromPoints:

D3DXPLANE *WINAPI D3DXPlaneFromPoints
(
 D3DXPLANE *pOut,  // Результирующая структура, хранящая плоскость
 CONST D3DXVECTOR3 *pV1,
 CONST D3DXVECTOR3 *pV2,
 CONST D3DXVECTOR3 *pV3
);

Здесь для создания плоскости нужны координаты всех трёх точек (хранящиеся в структурах D3DXVECTOR3) + указатель на создаваемую плоскость.
Пример:

D3DXPLANE Plane;
D3DXVECTOR3 V1(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 V2(1.0f, 0.0f, 0.0f);
D3DXVECTOR3 V3(1.0f, 0.0f, 1.0f);

D3DXPlaneFromPoints(&Plane, &V1, &V2, &V3);

Создание плоскости на основе точки и нормали

Допустим, у нас нет трёх точек (либо их координаты неизвестны), но есть одна + вектор нормали (normal vector). Этого также достаточно для построения плоскости.
Нормаль (он же вектор нормали(external link)) - это единичный вектор (unit vector; т.е. его длина всегда равна 1), перпендикулярный какой либо плоскости или грани. Про нормаль обычно говорят, что данная нормаль (принадлежит) этой грани. Вектор нормали указывает начальную точку и направление. На основе одной точки и нормали можно определить плоскость в 3D-пространстве.
В Direct3D для создания плоскости на основе точки и нормали применяют функцию D3DXPlaneFromPointNormal:

D3DXPLANE *WINAPI D3DXPlaneFromPointNormal
(
 D3DXPLANE *pOut,  // Результирующая структура, хранящая плоскость
 CONST D3DXVECTOR3 *pPoint,  // Точка
 CONST D3DXVECTOR3 *pNormal,  // Нормаль
);

Как видим, вектор нормали тоже задаётся структурой D3DXVECTOR3.
Пример:

D3DXPLANE Plane;
D3DXVECTOR3 Point(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 Normal(0.0f, 1.0f, 0.0f);

D3DXPlaneFromPointNormal(&Plane, &Point, &Normal);

Положение точки относительно плоскости (Classifying Points in 3D Using Planes)

Как только плоскость построена (любым способом) начинаем проверять произвольные точки 3D-пространства на предмет их принадлежности данной плоскости. Здесь возможны 3 варианта (относительно леворучной картезианской системы координат):

  • Точка расположена впереди (in front) плоскости.
  • Точка расположена на плоскости.
  • Точка расположена за (behind) плоскостью.

В Direct3D для проверки принадлежности точки данной плоскости применяют функцию D3DXPlaneDotCoord:

D3DXPLANE *WINAPI D3DXPlaneDotCoord
(
 CONST D3DXPLANE *pP,  // Указатель на предварительно созданную структуру плоскости
 CONST D3DXVECTOR3 *pV  // Проверяемая точка
);

Возвращаемое значение - скалярное число. Возможные варианты:

  • Если возвращаемое значение равно 0, то данные точка и плоскость копланарны (coplanar). Т.е. точка лежит на плоскости.
  • Если возвращаемое значение больше 0, то данная точка расположена спереди плоскости.
  • Если возвращаемое значение меньше 0, то данная точка расположена за плоскостью.

Пример:

D3DXVECTOR3 Point(0.0f, 0.0f, 0.0f);

FLOAT result = D3DXPlaneDotCoord(&Plane, &Point);
if(result == 0)  // Точка на плоскости
if(result > 0)  // Точка спереди плоскости
if(result < 0)  // Точка за плоскостью

Пересечение плоскости прямой (Plane & Line Intersection)

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

D3DXPLANE *WINAPI D3DXPlaneIntersectLine
(
 D3DXVECTOR3 *pOut,  // Результирующая точка пересечения (если есть)
 CONST D3DXPLANE *pP,
 CONST D3DXVECTOR3 *pV1,
 CONST D3DXVECTOR3 *pV2
);

Функция возвратит NULL, если прямая и плоскость не имеют общих точек (то есть параллельны друг другу).
Пример:

D3DXVECTOR3 StartPoint(-5.0f, -5.0f, -7.0f);
D3DXVECTOR3 EndPoint(3.0f, 5.0f, 7.0f);
D3DXVECTOR3 Result;

D3DXPlaneIntersectLine(&Result, &Plane, &StartPoint, &EndPoint);

if(Result)
{
 // Возвращаемое значение - либо координаты точки пересечения, либо NULL.
}

Меш (сетка, mesh)

  • Состоит из одной и более граней.
  • Заключает в себе 3D-объект (модель; обычно неанимируемую, т.к. по умолчанию она пуста внутри).

Материал

  • Используется для более реалистичного вида 3D-объекта (меша), заполняя пустые полигоны меша определёнными цветами.
  • Представлен комбинацией цветовых компонентов, либо растровым изображением (текстура, bitmap), которое "натягивается" на поверхность полигона перед рендерингом.

Рис.2 Различия между 2-мя типами Картезианской системы координат
Рис.2 Различия между 2-мя типами Картезианской системы координат

Система координат (Coordinate system)

Всю 3D-сцену можно представить в виде математической (координатной) сетки (grid) с тремя осями (X, Y, Z). Ты, должно быть, ранее сталкивался с 2D-координатами при работе с растровыми изображениями. Они имеют ширину (width) и высоту (height), измеряемые в пикселах. Горизонтальная ось измерения рассматривается как ось X, вертикальная - как ось Y. Точкой отсчёта в такой системе является левый верхний угол изображения. В Direc3D 2D (двухмерные) координаты рассматриваются как трансформированные координаты (transformed coordinates), т.к. они представляют собой финальные (после всех преобразований) координаты, используемые для вывода объектов на экран. В трёхмерном пространстве (3D space) к началу двух осей добавляется третья - z.
Исходя из своего названия, Direct3D производит рендеринг 3D-объектов. Все эти объекты имеют свою позицию в 3D-пространстве. Для определения местоположения любого 3D-объекта используют набор координат - x, y, z.2 Такая система координат называется Картезианской и подразделяется на 2 типа:

  • направление оси z определяется по правилу правой руки (праворучная, right-handed);
  • направление оси z определяется по правилу левой руки (леворучная, left-handed).

Direct3D для представления объектов в 3D-пространстве использует леворучную Картезианскую систему координат.
В обоих видах данной коорд. системы координаты x и y вычисляются точно также, как это делается в "плоской" двухмерной системе координат (положительные координаты по оси x откладываются в правую сторону от нулевой координаты, по оси y - вверх). Когда эта двухмерная система переходит в 3D, добавляется третье измерение, которое измеряется по оси z. В леворучной координатной системе ось z направлена в сторону, противоположную наблюдателю, а в праворучной - направлена на него (См. Рис.2).
Абсолютно всё в 3D-пространтсве измеряется в этих координатных системах.

Рис.3 Локальное и мировое пространства
Рис.3 Локальное и мировое пространства

Создание объектов (Constructing objects)

Создание любого объекта начинается с вершин. Каждая вершина имеет координаты x, y и z.
Создавать объекты можно:

  • программно (генерируя с помощью кода);
  • в 3D-редакторах (загружая готовые файлы моделей с жёсткого диска).

В DirectX (помимо вершин, рёбер и граней) есть базовые примитивы (цилиндр, пирамида, тор, чайник и др.), которые можно генерировать программно. Обычно это делают только при обучении или тестировании (например работы шейдеров).
Игровые объекты, как правило, создаются в программах 3D-редакторах.

Примитивы Direct3D. Списки (Lists), полосы (Strips) и вентиляторы (Fans)

  • Являются объектами сцены, генерируемыми программно.

После выбора типа координат для прорисовки объекта (в локальном, экранном или мировом пространстве), мы размещаем вершины (нумеруя их по порядку, в котором они размещаются). Затем эти вершины объединяются в группы по 3 штуки, образуя треугольные полигональные грани (polygon faces).

Рис. 4 Треугольный список (list) не имеет общих вершин с другими полигонами. Объединяя полигоны в треугольные полосы (strips) и вентиляторы (fans), мы сокращаем число вершин, тем самым экономя ресурсы и увеличивая производительность.
Рис. 4 Треугольный список (list) не имеет общих вершин с другими полигонами. Объединяя полигоны в треугольные полосы (strips) и вентиляторы (fans), мы сокращаем число вершин, тем самым экономя ресурсы и увеличивая производительность.


В зависимости от наличия/отсутствия у близлежащих полигонов общих вершин выделяют 3 вида полигональных примитивов:

  • Треугольный список (Triangle list)

- набор вершин, не имеющий общих вершин с другими полигонами.

  • Треугольная полоса (Triangle strip)

- набор (треугольных) граней с общими вершинами, расположенными так, что каждый полигон имеет одно общее ребро с соседним.

  • Треугольный вентилятор (Triangle fan)

...образуется когда несколько граней имеют одну общую вершину. Почти также, как и у реального вентилятора (см. Рис.4).

Порядок указания вершин (Vertex ordering)

  • Важен, т.к. от него зависит определение т.н. "лицевой" стороны грани (т.е. которую видит зритель).

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

Закрыть
noteОбрати внимание

В Direct3D (версия 9 и выше) систему координат можно изменять на праворучную. В этом случае для корректного определения лицевой стороны грани вершины указываются против часовой стрелки.

Закрыть
noteОбрати внимание

Начиная с версии 8, для отрисовки 2D-объектов (отрезки, пиксели) DirectX использует исключительно Direct3D (в составе DirectX Graphics). И эти объекты представлены здесь в качестве 3D-объектов, хотя на самом деле таковыми не являются. С точки зрения Direct3D, пиксели являются полигонами с одной вершиной, а отрезки - с двумя. В Direct3D отрезки и пиксели создаются с помощью треугольных списков (Triangle lists).

Закрыть
noteОбрати внимание

В большинстве 3D-движков и в Direct3D обратная сторона грани (по умолчанию) не рендерится и не выводится на экран. Такой метод оптимизации называется отсечением обратной стороны грани (backface culling).

Окрашивание полигонов

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

Материал (Material)

  • обычно представляет собой окрашивание поверхности сплошной заливкой определённого (статичного) цвета.

Материал состоит из нескольких компонентов:

  • цвет рассеяного света (diffuse)
  • цвет окружающего освещения (ambient)
  • цвет отражённого света (specular)

- это цвет блика (highlight), когда близко расположенный источник света освещает объект.
Часто diffuse и ambient объединяют в один компонент diffuse, представляющий собой собственный цвет объекта.

Текстурирование (Texture mapping)

Включает в себя окрашивание полигона (или меша) определённым рисунком. Текстурные карты - это обычные изображения, которые обычно загружаются из файлов растровых изображений (bitmaps). Эти изображения при нанесении на полигон либо растягиваются на всю его площадь, либо образуют повторяющиеся изображения (паттерны).

Работа с камерами

Самая распространённая проблема в 3D-игрокодинге - это отсутствие сцены на экране при том, что код выглядит вполне работоспособным. Обычно это случается при отсутствии объекта камеры и/или трансформации вида (view transformation).
Первым делом необходимо установить на сцене камеру с перспективным видом (perspective camera) и "показать" через неё тестовый полигон или группу полигонов.
Другая распространённая проблема связана с позиционированием камеры в пространстве 3D-сцены. Камера может оказаться расположенной слишком близко к объекту. Как результат нужный объект не будет виден на сцене. Камера может "улететь" за пределы сцены. Самое просто решение - отодвинуть камеру от начала координат (например переместить на величину -100 по оси Z) и убедиться, что матрица цели (target matrix; координаты точки направления "взгляда" камеры) указывает на начало координат.
Следующий шаг - проверить условия освещения сцены. Часто включают DirectX-освещение по умолчанию, забыв разместить на сцене источники света. Без них сцена почти всегда будет очень тёмной.

Рис.5 Длина вектора равна корню суммы разностей квадратов соответствующих координат его начала и конца. Источник: https://slidetodoc.com/finding-the-magnitude-of-a-vector-a-vector
Рис.5 Длина вектора равна корню суммы разностей квадратов соответствующих координат его начала и конца. Источник: https://slidetodoc.com/finding-the-magnitude-of-a-vector-a-vector

Векторы

Выше ты познакомился с принципом перемещения примитива в пространстве путём применения к нему трансформации трансляции. Например, перемещение треугольника из точки (5, 5, 3) в (10, 7, 6). Такой расклад допустим, когда заранее знаешь координаты точки назначения. Допустим, надо переместить монстра в игре.3 Мы знаем, что монстр может перемещаться в определённом направлении и для того, чтобы прибыть в конечную точку, ему понадобится 5 минут. Двигаясь с определённой скоростью, он будет перемещаться всё ближе и ближе к конечной точке. В этой ситуации указание начальной и конечной точек движения неэффективно. Куда лучше сразу задать вектор направления и скорость.
Векторы используются в игрокодинге практически повсеместно (например для создания пути движения игровых персонажей).
Векторы во многом схожи с вершинами. Они могут иметь как 2D так и 3D-координаты. Основные различия в том, что вектор дополнительно имеет направление и размер (длину). Вершины лишь отмечают определённую позицию в пространстве. Например, ты стоишь в точке (5, 3) и дан вектор (2, 3). Это не значит, что данный вектор командует тебе идти в точку (2,3). А значит, что необходимо переместиться на 2 единицы по оси X и на 3 единицы по оси Y от начальной позиции. В результате попадём в точку (вершину) (7, 6). Таким образом векторы задают (в том числе) направление движения.
В DirectX для хранения координат векторов служат специальные структуры D3DXVECTOR2 и D3DXVECTOR3. Часто эти структуры используют "не по назначению" и хранят в них координаты вершин и даже цвета.
Векторы также могут иметь негативные координаты, например (-2, -3). В этом случае будем двигаться влево и вниз по координатной сетке.

Длина (величина, magnitude) вектора

Расстояние от начальной до конечной точки вектора (длина) называется его величиной (magnitude). Для обозначения этой величины в математике букву вектора заключают в две прямые линии (знак модуля ||; см. Рис.5). Формула вычисления длины вектора схожа с формулой из теоремы Пифагора.
У Direct3D есть свои собственные функции для подсчёта величины вектора: D3DXVec2Lenght и D3DXVec3Lenght. В обоих функциях в качестве единственного вводного параметра указывается указатель на вектор.

Сложение векторов (Vector addition)

Вообще их бывает два вида:

  • алгебраическая сумма векторов,
  • геометрическое сложение (вектор-результат, правило треугольника).

В случае, когда есть несколько векторов, соединённых вместе и образующие зигзагообразную ломаную линию, их можно сложить для получения кратчайшего пути от начальной до конечной точки. Для алгебраического сложения всех векторов необходимо просто сложить все их соответствующие координаты (x, y, z).
У Direct3D для алгебраического сложения векторов есть специальные функции D3DXVec2Add и D3DXVec3Add. Определение выглядит так:

Функция D3DXVec3Add
D3DXVECTOR3 *D3DXVec3Add
{
 D3DXVECTOR3 *pOut,
 CONST D3DXVECTOR3 *pV1,
 CONST D3DXVECTOR3 *pV2,
}

Здесь *pOut - указатель на результирующий вектор. Остальные два - слагаемые векторы.
Пример применения:

D3DXVECTOR3 Vec1(0, 0, 0);
D3DXVECTOR3 Vec2(5, 5, 5);
D3DXVECTOR3 ResultVec(0, 0, 0);

// ResultVec(5, 5, 5)
D3DXVec3Add(&ResultVec, &Vec1, &Vec2);

Вычитание векторов (Vector substraction)

Также бывает алгебраическим и геометрическим (см. Рис.6). Перед вычитанием векторы приводят к одному началу (при необходимости) и затем из соответствующих координат конца вычитают координаты начала.
У Direct3D для алгебраического вычитания векторов есть специальные функции D3DXVec2Substract и D3DXVec3Substract. Вот пример применения функции D3DXVec3Substract:

D3DXVECTOR3 Vec1(5, 5, 5);
D3DXVECTOR3 Vec2(1, 3, 5);
D3DXVECTOR3 ResultVec(0, 0, 0);

// ResultVec(4, 2, 0)
D3DXVec3Substract(&ResultVec, &Vec1, &Vec2);
Рис.6 Вычитание векторов. Источник: https://vectorified.com
Рис.6 Вычитание векторов. Источник: https://vectorified.com

Умножение векторов на скалярное число (Vector multiplication by scalar)

...выполняется путём перемножения на это число каждой из его (вектора) координат.

Нормализация вектора (Vector normalization)

...в математике означает приведение вектора к единичному виду, т.е. приравнивание его величины к единице. Длина единичного вектора всегда равна 1. Такой вектор часто называют единичным (unit vector).
Нормализацию выполняют в следующих целях:

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

В Direct3D для нормализации вектора есть функция D3DXVec3Normalize.

Скалярное сложение векторов. Нахождение длины суммы векторов (Vector Dot Product). Теорема косинусов.

Подробнее по данной теме читаем здесь: https://function-x.ru/vectors_cosinus.html(external link)
Один из математических способов нахождения длины суммы векторов выглядит так:

D3DXVECTOR3 VectorA(1.0f, 1.0f, 1.0f);
D3DXVECTOR3 VectorB(2.0f, 5.0f, 7.0f);

FLOAT DotProduct = (VectorA.x * VectorB.x) + (VectorA.y + VectorB.y) + (VectorA.z * VectorB.z);

В Direct3D для нормализации вектора есть функция D3DXVec2Dot и D3DXVec3Dot.
По скалярному числу, возвращаемому обеими этими функциями, можно кое-что узнать об угле между ними.

  • Если DotProduct - отрицательное число, то угол между векторами меньше 90 градусов (acute).
  • Если DotProduct больше 0, то угол между векторами больше 90 градусов (obtuse).
  • Если DotProduct равен 0, то угол между векторами равен 90 градусов (perpendicular).

Для нахождения точного значения угла между двумя векторами применяют следующй фрагмент:

FLOAT Angle = acos(D3DXVec3Dot(&Vec1, &Vec2) / (D3DXVec3Length(&Vec1)*D3DXVec3Length(&Vec2));

Перпендикуляр к двум векторам (Vector Cross Product)

  • Является перпендикуляром к плоскости, образованной двумя другими векторами.
  • Его ещё называют vector product.
  • При вычислении имеет значение порядок указания исходных векторов.
  • В математике обозначается большой буквой X.
  • Вычисляется по следующей формуле:
D3DXVECTOR3 v;
D3DXVECTOR3 pV1;
D3DXVECTOR3 pV2;

v.x = pV1.y * pV2.z - pV1.z * pV2.y;
v.y = pV1.z * pV2.x - pV1.x * pV2.z;
v.z = pV1.x * pV2.y - pV1.y * pV2.x;

В Direct3D для вычисления перендикуляра к двум векторам есть функция D3DXVec3Cross. Вот её определение:

D3DXVECTOR3 *D3DXVec3Cross
(
 D3DXVECTOR3 *pOut;
 CONST D3DXVECTOR3 *pV1;
 CONST D3DXVECTOR3 *pV2;
)

Матрицы (Matrices)

Начальные сведения о них читаем здесь http://www.mathprofi.ru/deistviya_s_matricami.html(external link).
Матрицы широко применяются в математике. В Direct3D матрицей называется таблица данных размером 4Х4. Это двумерный массив. Если в матрице один столбец или одна строка, то такие матрицы также называют векторами (о них читай ниже).
Матрица идентичности (identity matrix), она же единичная матрица (unit matrix) - сильно абстрактное понятие. К примеру если мы перемножим любое другое число на 1, то в результате всегда получим исходное число (5 * 1 = 5). Произведением произвольной матрицы и единичной матрицы является та же самая исходная произвольная матрица. Единичная матрица очень широко применяется как сама по себе, так и совместно с другими матрицами.
Отдельные поля матрицы (в Direct3D) структурированы по строкам и столбцам вида 4х4. Это позволяет выполнять внутри одной матрицы множество различных преобразований (трансформаций, transformations). Трансформация происходит при сложении, вычитании, перемножении или делении одной матрицы на другую.1 Именно эти операции заставляют 3D-объекты двигаться, вращаться и изменять свои размеры.
Для хранения матриц:

  • XNA предоставляет структуру Matrix;
  • в DirectX есть структура D3DXMATRIX.

Для 3D-трансформаций применяются матрицы размером 4х4. Для 2D - 3х3.
Каждое число матрицы представляет собой компонент (англ. component; в русских учебниках - элемент). Таким образом матрица 3х3 содержит 9 элементов. Матрица 4х4 - 16 элементов. В свете того, что элементы внутри матрицы расположены по строкам и столбцам, к каждому элементу можно обратиться, указав его позицию в матрице (grid index). Например: (1,1) или (3,3). Порядковые координаты элементов матрицы присваиваются по принципу "сначала построчно, потом - по столбцам":

_11, _12, _13, _14
_21, _22, _23, _24
_31, _32, _33, _34
_41, _42, _43, _44
Закрыть
noteОбрати внимание

В Direct3D принято предварять координату элемента символом нижнего подчёркивания (_).

Direct3D-код (C++) объявления матрицы и присвоения значений некоторым из её элементов может выглядеть так:

D3DXMATRIX Matrix;
Matrix._11 = 5.0f;
Matrix._31 = 7.0f;
Matrix._43 = 9.0f;

Сложение матриц (Matrix Addition)

  • Схоже с алгебраическим сложением векторов.
  • Суммировать можно только матрицы одного размера.

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

D3DXMATRIX Result;
D3DXMATRIX Matrix1;
D3DXMATRIX Matrix2;

Result = Matrix1 + Matrix2;

Вычитание матриц (Matrix Substraction)

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

D3DXMATRIX Result;
D3DXMATRIX Matrix1;
D3DXMATRIX Matrix2;

Result = Matrix1 - Matrix2;

Умножение матрицы на число (Matrix Multiplication by Scalar)

Любая матрица может быть умножена на скалярное число. Для этого просто перемножаем на это число каждый её элемент.
В Direct3D умножение матрицы на число выглядит так:

D3DXMATRIX Result;
D3DXMATRIX Matrix1;
FLOAT Scalar = 3.5f;

Result = Matrix1 * Scalar;

Перемножение матриц (Matrix by Matrix Multiplication)

Закрыть
noteОбрати внимание

Матрицу P можно умножить на матрицу K только в том случае, если число столбцов матрицы P равняется числу строк матрицы K. Сюда же относятся матрицы одинакового размера (наиболее часто встречаются в игрокодинге). Матрицы, для которых данное условие не выполняется, умножать нельзя.

Если кратко, то умножение (годных по размеру) матриц осуществляется путем умножения строки на столбец. Находятся произведения первого элемента строки и первого элемента столбца, второго элемента строки и второго элемента столбца и т.д. Затем полученные произведения суммируются.
В Direct3D для перемножения двух матриц применяется функция D3DXMatrixMultiply:

D3DXMATRIX *WINAPI D3DXMatrixMultiply
(
 D3DXMATRIX *pOut,  // Матрица-результат
 CONST D3DXMATRIX *pM1,  // Матрица-множитель
 CONST D3DXMATRIX *pM2  // Матрица-множитель
)

Пример применения:

D3DXMATRIX Result;
D3DXMATRIX Matrix1;
D3DXMATRIX Matrix2;

D3DXMatrixMultiply(&Result, &Matrix1, &Matrix2);

Рис.7 Единичная матрица (Identity Matrix)
Рис.7 Единичная матрица (Identity Matrix)

Единичная матрица (Identity Matrix)

  • Представлена на Рис.7 .
  • В мире матриц является примерно тем же, чем является число 1 в пространстве скалярных чисел.
  • Это как будто только что проинициализированная матричная переменная.
  • При умножении любой матрицы на матрицу идентичности в результате получим исходную матрицу без изменений.
  • В случае трансформирования группы полигонов путём перемножения на единичную матрицу получим ту же самую группу полигонов без изменений и на той же позиции.

Поначалу она кажется не очень полезной штукой. Но важно помнить, что при создании матрицы средствами Direct3D она не инициализируется. Поэтому в идеале все вновь создаваемые матрицы желательно приводить к виду единичной матрицы. В Direct3D для этой цели есть специальная функция D3DXMatrixIdentity:

D3DXMATRIX *D3DXMatrixIdentity(D3DXMATRIX *pOut);

С помощью функции D3DXMatrixIsIdentity можно проверить, является ли данная матрица единичной:

D3DXMATRIX Result;
D3DXMatrixIdentity(&Result);

if(D3DXMatrixIsIdentity(&Result))
 MessageBox(NULL, "Is Identity Matrix", "", MB_OK);

Обратная матрица (Inverse Matrix)

  • Если любую матрицу умножить на обратную ей матрицу в результате получится единичная матрица (Identity Matrix).
  • Если детерминант матрицы является нулем, то обратную к ней матрицу получить нельзя.
  • В том случае, если обратная матрица может существовать, то она будет единственной.
  • Подробнее об этом читаем здесь: https://studwork.org/spravochnik/matematika/matricy/obratnaya-matrica(external link).

В Direct3D для вычисления матрицы, обратной данной, применяется функция D3DXMatrixInverse:

D3DXMATRIX *D3DXMatrixInverse
(
 D3DXMATRIX *pOut  // Указатель на матрицу-результат
 FLOAT *pDeterminant,  // Детерминант
 CONST D3DXMATRIX *pM  // Указатель на исходную матрицу
);

Вот пример:

D3DXMATRIX Result;
D3DXMATRIX Matrix1;

D3DXMatrixInverse(&Result, NULL, &Matrix1);

Рис.8 При размещении 3D-объектов в их локальном пространстве, их также можно размещать в мировом пространстве 3D-сцены.
Рис.8 При размещении 3D-объектов в их локальном пространстве, их также можно размещать в мировом пространстве 3D-сцены.

Трансформации (Transformations)

После того, как определена модель (или просто набор полигонов), её размещают в 3D-пространстве на определённой позиции. При этом объект не просто стоит, но и способен перемещаться, вращаться и изменять свои размеры. Это даёт возможность, единожды загрузив модель, создать из неё несколько её копий (объектов), зафиксированных в различных позициях (см. Рис.8).
Такие преобразования объекта называют трансформациями и выполняют путём применения к объекту математических матриц преобразования (transformation matrices).
В игрокодинге существуют 3 основных вида преобразований объекта:

Рис.9 Кодирование координат трансляции в матрице
Рис.9 Кодирование координат трансляции в матрице

Трансляция (Translation; изменение положения в пространстве)

  • Означает обычное перемещение объектов в 3D-пространстве.

Мы транслируем объект из одной точки в другую путём корректного перемещения каждой точки объекта.

  • Её можно закодировать в матрице (см. Рис.9; в Direct3D принцип кодирования несколько иной).

На месте букв X, Y и Z подставляются координаты смещения. После этого берут группу полигонов и применяют к ней матрицу, заполненную актуальными значениями.
В Direct3D для создания матрицы трансляции применяется функция D3DXMatrixTranslation:

D3DXMATRIX *WINAPI D3DXMatrixTranslation
(
 D3DXMATRIX *pOut  // Указатель на матрицу-результат
 FLOAT x,  // Смещение по оси X.
 FLOAT y,  // Смещение по оси Y.
 FLOAT z  // Смещение по оси Z.
);

Вот пример:

D3DXMATRIX Result;
D3DXVECTOR3 Vector1(5.0f, 7.0f, 9.0f);

D3DXMatrixTranslation(&Result, Vector1.x, Vector1.y, Vector1.z);

Вращение (Rotation)

Вращение объекта может производиться по одной или нескольким осям (X, Y, Z). Перемещая точки объекта соответствующим образом в 3D-пространстве мы можем заставить наш объект крутиться словно волчок. В матрице можно закодировать вращение 3D-объекта.
Для построения матрицы вращения в Direct3D есть 3 функции (по одной на каждую ось):

  • D3DXMatrixRotationX,
  • D3DXMatrixRotationY,
  • D3DXMatrixRotationZ.

Эти и многие другие Direct3D-функции принимают в качестве вводного параметра угол поворота в радианах(external link), а не в градусах. 1 радиан равен прибл. 57,3 град. Подробнее об этом см. здесь: https://ru.wiktionary.org/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:Degree-Radian_Conversion.svg(external link).

Закрыть
noteОбрати внимание

С понятием радиан тесно связано понятие числа пи(external link). В Direct3D есть константа D3DX_PI, в которой хранится значение числа пи.

В Direct3D есть пара макросов для конвертации градусов в радианы и обратно:

  • D3DXToRadian
#define D3DXToRadian(degree) ((degree)*(D3DX_PI/180.0f))

Пример:

FLOAT AngleInRadians = D3DXToRadian(45);
  • D3DXToDegree
#define D3DXToDegree(radian) ((radian)*(180.0f/D3DX_PI))

Пример:

FLOAT AngleToDegrees = D3DXToDegree(D3DX_PI/2);


Так вот. Каждая из трёх функций вращения принимает два параметра:

  • указатель на исходную матрицу (туда же запишется результат),
  • угол поворота.
D3DXMATRIX *WINAPI D3DXMatrixRotationX(D3DXMATRIX *pOut, FLOAT Angle);
D3DXMATRIX *WINAPI D3DXMatrixRotationY(D3DXMATRIX *pOut, FLOAT Angle);
D3DXMATRIX *WINAPI D3DXMatrixRotationZ(D3DXMATRIX *pOut, FLOAT Angle);

Пример:

D3DXMATRIX Result;
D3DXVECTOR3 FLOAT Angle = D3DXToRadian(45);

D3DXMatrixRotationX(&Result, Angle);

Но и это ещё не всё. В Direct3D также есть функция D3DXMatrixRotationAxis, позволяющая вращать объект по абсолютно произвольной оси, направление которой задано вектором:

D3DXMATRIX *WINAPI D3DXMatrixRotationAxis
(
 D3DXMATRIX *pOut,  // Указатель на исх. матрицу. Сюда же запишется результат.
 CONST D3DXVECTOR3 *pV,  // Вектор, указывающий направление произвольной оси вращения.
 FLOAT Angle  // Угол поворота в радианах.
);

Угол поворота задаётся по часовой стрелке, если смотреть вдоль положительного направления оси из (гипотетической) точки начала координат.

Рис.10 Кодирование масштабирования по каждой из трёх осей
Рис.10 Кодирование масштабирования по каждой из трёх осей

Масштабирование (Scaling)

  • Также можно закодировать в матрице, указав значение масштаба (scaling factor) по каждой из трёх осей X, Y и Z (см. Рис.10).

Масштабирование объекта делает объекты больше или меньше. Это достигается за счёт изменения расстояний между точками объекта. Так при значении (scaling factor) 1 размер остаётся тем же самым. При значении 2 - объект увеличивается в два раза. При значении 0,5 - объект уменьшается вдвое.
В Direct3D для матричного масштабирования объектов применяют функцию D3DXMatrixScaling:

D3DXMATRIX *WINAPI D3DXMatrixScaling
(
 D3DXMATRIX *pOut,  // Результирующая матрица
 FLOAT sx,  // Фактор масштабирования по оси X
 FLOAT sy,  // Фактор масштабирования по оси Y
 FLOAT sz,  // Фактор масштабирования по оси Z
);

Пример:

D3DXMATRIX Result;

D3DXMatrixScaling(&Result, 2.0f, 1.0f, 0.5f);

В данном примере размер объекта изменится непропорционально.

Комбинирование трансформаций

  • Производится путём перемножения соответствующих матриц.

Мы можем применять к объекту одну, две, либо все три трансформации одновременно путём перемножения (multiplying) соответствующих матриц друг на друга. Другими словами одна матрица может заключать в себе все три трансформации (трансляцию, вращение и масштабирование). Процесс объединения матриц называется конкатенацией (concatenation) матриц.
Но здесь важно, в каком порядке это производится. В отличие от числовых множителей, когда от перестановки множителей произведение не изменяется, при перемножении матриц их порядок имеет ключевое значение. Т.е. Матрица A * Матрица B != (не равно) Матрица B * Матрица A.
То есть все приведённые выше трансформации применяются к объекту в определённой последовательности, в случае изменения которой можно получить совсем непредсказуемый результат. Почти всегда (для получения корректного результата рендеринга) они выполняются именно в том порядке, в котором мы их перечислили выше.
В Direct3D для перемножения (=объединения) двух матриц в одну есть функция D3DXMatrixMultiply. Её прототип содержится в D3dx9math.h и выглядит так:

D3DXMATRIX* D3DXMatrixMultiply
(
  D3DXMATRIX *pOut,  // Матрица-результат
  const D3DXMATRIX *pM1,  // Матрица-множитель №1
  const D3DXMATRIX *pM2  // Матрица-множитель №2
);

Как вариант, можно объявить все операнды в виде матриц и просто записать их в виде уравнения:

D3DXMATRIX MatTranslation;
D3DXMATRIX MatRotation;
D3DXMATRIX MatCombined;

MatCombined = MatTranslation * MatRotation;

Рис.13 Трансформация вершин перед рендерингом. Источник: http://www.directxtutorial.com/Lesson.aspx?lessonid=9-4-5
Рис.13 Трансформация вершин перед рендерингом. Источник: http://www.directxtutorial.com/Lesson.aspx?lessonid=9-4-5

Трансформации объектов сцены перед рендерингом

Каждый объект перед выводом на экран проходит следующие трансформации (в порядке их указания здесь; см. Рис.13):

Мировая трансформация (World transformation)

  • Перемещает объект по всему "миру" (здесь - 3D-сцене).
  • Применяется для конвертации локальных (local) координат объекта в мировые (world).
  • Заставляет объекты сцены перемещаться, вращаться и изменять свои размеры.

Здесь выполняется изменение масштаба объекта (scaling), его вращение (rotation) по осям X, Y и Z (иногда по всем сразу) и трансляция. Причём по стандартам DirectX их следует выполнять именно в таком порядке.

Трансформация вида (View transformation)

  • Ориентирует объекты сцены в 3D-пространстве относительно положения зрителя (=виртуальной камеры, viewer).
  • Представляет собой своеобразный "объектив камеры", который определяет, что именно игрок видит на экране.
  • Выполняет перемещение виртуальной камеры в 3D-пространстве.

При этом мировые координаты объекта конвертируются в координаты вида (view coordinates). Камера может быть спозиционирована в любой точке "мира" (= 3D-сцены).

Трансформация проекции (Projection transformation)

  • "Сплющивает" 3D-изображение в 2D-картинку, выводимую на экран монитора.
  • Финальный этап подготовки к рендерингу.
  • Результат её выполнения - плоское 2D-изображение сцены, выводимое на экран.

Работает примерно так же как линзы камеры. Здесь можно изменить увеличение (zoom), сделать изображение широкоугольным, или наоборот, сузить панораму + можно добавить ряд эффектов. Например т.н. искажение "рыбий глаз" (Fish eye).

К объекту в данный момент времени может быть применена одна или несколько трансформаций одновременно. К примеру, нам необходимо только транслировать (=переместить) объект, для чего достаточно обновить (update) мировую матрицу объекта (object's world matrix). Затем нам может понадобиться повернуть объект под одной из осей, для чего применим к нему трансформацию вращения. Обычным делом является масштабирование (изменение размера объекта) после его загрузки из 3D-редактора в игровой мир. Здесь применяется трансформация масштабирования. Часто все эти трансформации применяют одновременно путём перемножения их соответствующих матриц.

Источники:


1. Harbour J.S. Beginning Game Programming. - Thomson Course Technology. 2005
2. Young V. Programming a Multiplayer FPS in DirectX 9.0. - Charles River Media. 2005
3. Thorn A. DirectX 9 graphics: the definitive guide to Direct3D. - Wordware Publishing. 2005

Contributors to this page: slymentat .
Последнее изменение страницы Среда 26 / Январь, 2022 16:48:15 MSK автор slymentat.

Помочь проекту

yoomoney.ru (бывший Яндекс-деньги): 410011791055108