Базовые понятия 3D-графики
- Во многом перекликаются со школьным курсом геометрии.
Содержание
- Базовые понятия 3D-графики
- Вершина (Vertice)
- Ребро (Edge)
- Полигон
- Грань (Face)
- Плоскость (Plane)
- Меш (сетка, mesh)
- Материал
- Система координат (Coordinate system)
- Создание объектов (Constructing objects)
- Работа с камерами
- Векторы
- Длина (величина, magnitude) вектора
- Сложение векторов (Vector addition)
- Вычитание векторов (Vector substraction)
- Умножение векторов на скалярное число (Vector multiplication by scalar)
- Нормализация вектора (Vector normalization)
- Скалярное сложение векторов. Нахождение длины суммы векторов (Vector Dot Product). Теорема косинусов.
- Перпендикуляр к двум векторам (Vector Cross Product)
- Матрицы (Matrices)
- Трансформации (Transformations)
- Комбинирование трансформаций
- Трансформации объектов сцены перед рендерингом
- Источники
Вершина (Vertice)
- Наименьшая единица изображения в графическом пространстве.
- Представляет собой точку с координатами, размещённую в 2D или 3D пространстве.
- Графический чип (GPU) на видеокарте "видит" только вершины, образующие треугольники.
- набросать (plot) точки самостоятельно;
- загрузить их из готового файла 3D-модели (в котором сохранены все точки, составляющие модель).
- Пространство экрана (Screen space; с использованием трансформированных координат);
- Пространство модели (Model space; с использованием нетрансформированных координат);
- Пространство мира (World space; также с использованием нетрансформированных координат).
Вершины, размещённые в мировом пространстве, имеют финальные координаты, используемые при рендеринге объекта. К примеру, представь самого себя в виде меша. Твои суставы представляют собой вершины, которые изначально определены в локальном пространстве, т.к. за точку отсчёта при этом взят условный локальный центр координат, расположенный по центру твоей груди.
При передвижении по дому (который представляет собой мировое пространство), координаты твоих суставов также перемещаются относительно мира, но (вцелом) остаются неподвижны относительно твоего тела (см. Рис.3).
Опорная точка вершины (Origin of Vertice)
Любая точка имеет т.н. опорную точку (origin).1 Она невидима, но именно относительно неё выполняется вращение вершины-обладательницы. Более того, опорные точки есть у каждого объекта сцены. При перемещении (=трансляции) объекта в другое место, его опорная точка остаётся на прежних координатах. Поэтому, если объект переместить и затем вращать, то вращаться он будет по окружности, центром которой является его опорная точка, находящаяся от него на некотором удалении. Раньше это часто вызывало трудности у начинающих программеров: вершины и объекты перемещались, а их опорные точки - нет. Но с приходом DirectX проблема была решена легко и изящно.Все объекты состоят из вершин. Фокус в том, чтобы оставлять "оригинал" объекта неизменным (в памяти). Вместо этого все манипуляции выполняются с его копиями (инстансами). Оставляя объект-источник в точке, совпадающей с его опорной точкой, его можно вращать относительно его собственного локального центра координат, что предотвратит перемещение объекта в 3D-сцене (там, где это не нужно).
Так как же переместить объект, не сдвинув его с места? С помощью математических матриц. Матрица (в DirectX; в математике размер матриц ничем не ограничен) представляет собой таблицу чисел размером 4х4 элемента. Эта таблица отображает текущее положение объекта в 3D-пространстве. У каждого объекта в 3D-сцене (и игре) есть своя собственная матрица. Матрицы позволяют манипулировать объектами сцены независимо друг от друга. Можно даже манипулировать всей сценой целиком, при этом не затрагивая отдельные её объекты. К примеру допустим, мы работаем над игрой жанра автогонки (racing) и в ней есть автомобили (модели), которые ездят по трассе в форме овала (трек). Ты стремишься к тому, чтобы каждый автомобиль выглядел наиболее реалистично. Поэтому каждый объект автомобиля можно перемещать и вращать независимо от других. В определённый момент мы добавляем код, который "разбивает" машины при их столкновении. Также мы стремимся, чтобы автомобили были расположены строго горизонтально относительно плоскости трека, что подразумевает подсчёт угла трека и положение всех четырёх углов автомобиля.
Представь, какие возможности появятся в игре, когда мы организуем содержание в объектах субобъектов, каждый из которых имеет свою собственную опорную точку (origin), которая всюду следует за опорной точкой объекта-родителя. В этом случае можно разместить субобъекты относительно объекта-родителя и заставить их вращаться. Конечно, речь идёт о колёсах. Колёса всё время двигаются вместе с самой машиной (вместе с ней они перемещаются, вращаются, изменяются в размерах), но, вдобавок, сами по себе могут вращаться по горизонтальной оси и по вертикальной (в случае поворота автомобиля вправо и влево ).
Ребро (Edge)
- Это отрезок (линия), соединяющий две вершины.
Полигон
- Обычно это плоский треугольник, состоящий из трёх вершин (и трёх граней).
Любопытный факт
Исключением в своё время стала игровая консоль Sega Saturn, увидевшая свет в 1994 году, которая "понимала" только четырёхугольные полигоны. Поначалу это представляли как фичу, но на деле такой нестандартный полигон обернулся головной болью для разработчиков. Т.к. абсолютно все системы до этого оперировали только треугольниками.
Вершины, рёбра и полигоны можно представить в виде настольной игры "соедини точки", где точки - вершины, а соединяющие их линии - рёбра.
Грань (Face)
Один и более полигонов образуют грань.Плоскость (Plane)
- Применяется в математике и 3D-моделировании для классифицирования (=объединения в множество) точек в 3D-пространстве.
- В игрокодинге применяется для отсечения объектов, не попадающих в зону видимости, а также для определения столкновений (например со стенами).
typedef struct D3DXPLANE { FLOAT a; FLOAT b; FLOAT c; FLOAT d; }D3DXPLANE;
Создание плоскости на основе трёх точек
- Самый простой способ.
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). Этого также достаточно для построения плоскости.Нормаль (он же вектор нормали) - это единичный вектор (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) плоскостью.
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-объект (модель; обычно неанимируемую, т.к. по умолчанию она пуста внутри).
Пересечение меша лучом (Rays Intersecting Meshes)
- Можно определить м помощью функции D3DXIntersect.
HRESULT WINAPI D3DXIntersect ( LPD3DXBASEMESH pMesh, // Меш, с которым определяем пересечение луча const D3DXVECTOR3 *pRayPos, // Позиция начальной точки луча const D3DXVECTOR3 *pRayDir, // Вектор направления луча BOOL *pHit, // Пересекает - ставим TRUE. Если нет - ставим FALSE. DWORD *pFaceIndex, // В случае пересечения указываем, какой именно полигон пересёк луч. FLOAT *pU, // Указатель на координату U барицентрического центра попадания. FLOAT *pV, // Указатель на координату V барицентрического центра попадания. FLOAT *pDist, // Расстояние от начала луча до ПЕРВОГО пересечения меша. LPD3DXBUFFER *ppAllHits, // Если интересует только первое пересечение меша, ставим здесь NULL. // В противном случае передаём здесь указатель на массив ID3DXBuffer, // который заполним точками попадания, каждая из которых будет описана // структурой D3DXINTERSECTINFO. DWORD *pCountOfHits // Кол-во пересечений. Обычно здесь ставят NULL. // Если здесь указать предварительно созданную переменную типа DWORD, // то функция запишет в неё кол-во пересечений. );
Функция возвращает булево значение, указывающее, было ли пересечение вообще. Также возвращается расстояние от начала луча до пересечения, а также массив, хранящий все точки пересечения. Структура D3DXINTERSECTINFO:
typedef struct _D3DXINTERSECTINFO { DWORD FaceIndex; // Какой именно полигон пересёк луч. FLOAT U, // Координата U барицентрического центра попадания. FLOAT V, // Координата V барицентрического центра попадания. FLOAT Dist // Расстояние от начала луча до данного пересечения меша. }D3DXINTERSECTINFO, *LPD3DXINTERSECTINFO
Вот пример:
D3DXVector3 RayPos(0, 0, 0); D3DXVector3 RayDir(0, 0, 1.0f); BOOL bHit = false; DWORD FaceIndex = 0; FLOAT pU = 0; FLOAT pV = 0; FLOAT pDist = 0; D3DXVECTOR3 IntersectPoint(0, 0, 0); D3DXIntersect(Mesh, &RayPos, &RayDir, &bHit, &FaceIndex, &pU, &pV, &pDist, NULL, NULL); if(bHit) { IntersectPoint = RayPos + (RayDir * pDist); }
Материал
Используется для более реалистичного вида 3D-объекта (меша), заполняя пустые полигоны меша определёнными цветами.Представлен комбинацией цветовых компонентов, либо растровым изображением (текстура, bitmap), которое "натягивается" на поверхность полигона перед рендерингом.
Система координат (Coordinate system)
Всю 3D-сцену можно представить в виде математической (координатной) сетки (grid) с тремя осями (X, Y, Z). Ты, должно быть, ранее сталкивался с 2Р-координатами при работе с растровыми изображениями. Они имеют ширину (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).
В обоих видах данной коорд. системы координаты x и y вычисляются точно также, как это делается в "плоской" двухмерной системе координат (положительные координаты по оси x откладываются в правую сторону от нулевой координаты, по оси y -вверх). Когда эта двухмерная система переходит в 3D, добавляется третье измерение, которое измеряется по оси z. В леворучной координатной системе ось z направлена в сторону, противоположную наблюдателю, а в праворучной -направлена на него (См. Рис.2).
Абсолютно всё в 3D-пространтсве измеряется в этих координатных системах.
Создание объектов (Constructing objects)
Создание любого объекта начинается с вершин. Каждая вершина имеет координаты x, y и z. Создавать объекты можно:- программно (генерируя с помощью кода);
- в 3D-редакторах (загружая готовые файлы моделей с жёсткого диска).
Примитивы Direct3D. Списки (Lists), полосы (Strips) и вентиляторы (Fans)
- Являются объектами сцены, генерируемыми программно.
В зависимости от наличия/отсутствия у близлежащих полигонов общих вершин выделяют 3 вида полигональных примитивов:
- Треугольный список (Triangle list)
- Треугольная полоса (Triangle strip)
- Треугольный вентилятор (Triangle fan)
Порядок указания вершин (Vertex ordering)
- Важен, т.к. от него зависит определение т.н. "лицевой" стороны грани (т.е. которую видит зритель).
Обрати внимание
В Direct3D (версия 9 и выше) систему координат можно изменять на праворучную. В этом случае для корректного определения лицевой стороны грани вершины указываются против часовой стрелки.
Обрати внимание
Начиная с версии 8, для отрисовки 2D-объектов (отрезки, пиксели) DirectX использует исключительно Direct3D (в составе DirectX Graphics). И эти объекты представлены здесь в качестве 3D-объектов, хотя на самом деле таковыми не являются. С точки зрения Direct3D, пиксели являются полигонами с одной вершиной, а отрезки - с двумя. В Direct3D отрезки и пиксели создаются с помощью треугольных списков (Triangle lists).
Обрати внимание
В большинстве 3D-движков и в Direct3D обратная сторона грани (по умолчанию) не рендерится и не выводится на экран. Такой метод оптимизации называется отсечением обратной стороны грани (backface culling).
Окрашивание полигонов
Как только ты определил группу полигонов или создал целый меш, можно переходить к окрашиванию объекта. В Direct3D есть две простые техники окрашивания полигонов:Материал (Material)
- обычно представляет собой окрашивание поверхности сплошной заливкой определённого (статичного) цвета. Материал состоит из нескольких компонентов:
- цвет рассеяного света (diffuse)
- цвет окружающего освещения (ambient)
- цвет отражённого света (specular)
Часто diffuse и ambient объединяют в один компонент diffuse, представляющий собой собственный цвет объекта.
Текстурирование (Texture mapping)
Включает в себя окрашивание полигона (или меша) определённым рисунком. Текстурные карты - это обычные изображения, которые обычно загружаются из файлов растровых изображений (bitmaps). Эти изображения при нанесении на полигон либо растягиваются на всю его площадь, либо образуют повторяющиеся изображения (паттерны).Работа с камерами
Самая распространённая проблема в 3D-игрокодинге - это отсутствие сцены на экране при том, что код выглядит вполне работоспособным. Обычно это случается при отсутствии объекта камеры и/или трансформации вида (view transformation). Первым делом необходимо установить на сцене камеру с перспективным видом (perspective camera) и "показать" через неё тестовый полигон или группу полигонов.Другая распространённая проблема связана с позиционированием камеры в пространстве 3D-сцены. Камера может оказаться расположенной слишком близко к объекту. Как результат нужный объект не будет виден на сцене. Камера может "улететь" за пределы сцены. Самое просто решение - отодвинуть камеру от начала координат (например переместить на величину -100 по оси Z) и убедиться, что матрица цели (target matrix; координаты точки направления "взгляда" камеры) указывает на начало координат.
Следующий шаг - проверить условия освещения сцены. Часто включают DirectX-освещение по умолчанию, забыв разместить на сцене источники света. Без них сцена почти всегда будет очень тёмной.
Векторы
Выше ты познакомился с принципом перемещения примитива в пространстве путём применения к нему трансформации трансляции. Например, перемещение треугольника из точки (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)
Вообще их бывает два вида:- алгебраическая сумма векторов,
- геометрическое сложение (вектор-результат, правило треугольника).
В Direct3D для алгебраического сложения векторов есть специальные функции D3DXVec2Add и 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);
Умножение векторов на скалярное число (Vector multiplication by scalar)
...выполняется путём перемножения на это число каждой из его (вектора) координат.Нормализация вектора (Vector normalization)
...в математике означает приведение вектора к единичному виду, т.е. приравнивание его величины к единице. Длина единичного вектора всегда равна 1. Такой вектор часто называют единичным (unit vector).Нормализацию выполняют для получения вектора, указывающего направление без учёта его длины.
В Direct3D есть немало функций, которые требуют нормализованные векторы в качестве вводных параметров. В Direct3D для нормализации вектора есть функция D3DXVec3Normalize.
Скалярное сложение векторов. Нахождение длины суммы векторов (Vector Dot Product). Теорема косинусов.
Подробнее по данной теме читаем здесь: https://function-x.ru/vectors_cosinus.html .Один из математических способов нахождения длины суммы векторов выглядит так:
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)
- Представляют собой таблицы чисел. (См. Рис.7)
Матрицы широко применяются в математике. В Direct3D матрицей называется таблица данных размером 4Х4. Это двумерный массив. Если в матрице один столбец или одна строка, то такие матрицы также называют векторами (о них читай ниже). Матрица идентичности (identity matrix), она же единичная матрица (unit matrix) - сильно абстрактное понятие. К примеру если мы перемножим любое другое число на 1, то в результате всегда получим исходное число (5 * 1 = 5). Произведением произвольной матрицы и единичной матрицы является та же самая исходная произвольная матрица. Единичная матрица очень широко применяется как сама по себе, так и совместно с другими матрицами.
Отдельные поля матрицы (в Direct3D) структурированы по строкам и столбцам вида 4х4. Это позволяет выполнять внутри одной матрицы множество различных преобразований (трансформаций, transformations). Трансформация происходит при сложении, вычитании, перемножении или делении одной матрицы на другую. Именно эти операции заставляют 3D-объекты двигаться, вращаться и изменять свои размеры. Для хранения матриц:
- XNA предоставляет структуру Matrix;
- в DirectX есть структура D3DXMATRIX.
Каждое число матрицы представляет собой компонент (англ. 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
Обрати внимание
В Direct3D координату элемента принято предварять символом нижнего подчёркивания (_).
Direct3D-код (C++) объявления матрицы и присвоения значений некоторым из её элементов может выглядеть так:
D3DXMATRIX Matrix; Matrix._11 = 5.0f; Matrix._31 = 7.0f; Matrix._43 = 9.0f;
Сложение матриц (Matrix Addition)
- Схоже с алгебраическим сложением векторов.
- Суммировать можно только матрицы одного размера.
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)
- Часто данную операцию называют конкатенацией матриц (matrix concatenation).
- Подробнее об этом читаем здесь: https://studwork.org/spravochnik/matematika/matricy/umnojenie-matric.
- Можно перемножать матрицы разных размеров. Но...
Обрати внимание
Матрицу 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);
Единичная матрица (Identity Matrix)
- Представлена на Рис.8 .
- В мире матриц является примерно тем же, чем является число 1 в пространстве скалярных чисел.
- Это как будто только что проинициализированная матричная переменная.
- При умножении любой матрицы на матрицу идентичности в результате получим исходную матрицу без изменений.
- В случае трансформирования группы полигонов путём перемножения на единичную матрицу получим ту же самую группу полигонов без изменений и на той же позиции.
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.
D3DXMATRIX *D3DXMatrixInverse ( D3DXMATRIX *pOut // Указатель на матрицу-результат FLOAT *pDeterminant, // Детерминант CONST D3DXMATRIX *pM // Указатель на исходную матрицу );
Вот пример:
D3DXMATRIX Result; D3DXMATRIX Matrix1; D3DXMatrixInverse(&Result, NULL, &Matrix1);
Трансформации (Transformations)
После того, как определена модель (или просто набор полигонов), её размещают в 3D-пространстве на определённой позиции. При этом объект не просто стоит, но и способен перемещаться, вращаться и изменять свои размеры. Это даёт возможность, единожды загрузив модель, создать из неё несколько её копий (объектов), зафиксированных в различных позициях (см. Рис.8).Такие преобразования объекта называют трансформациями и выполняют путём применения к объекту математических матриц преобразования (transformation matrices). В игрокодинге существуют 3 основных вида преобразований объекта:
Трансляция (Translation; изменение положения в пространстве)
- Означает обычное перемещение объектов в 3D-пространстве.
- Её можно закодировать в матрице (См. Рис.9; в Direct3D принцип кодирования несколько иной).
В 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 есть константа D3DX_PI, в которой хранится значение числа пи.
В Direct3D есть пара макросов для конвертации градусов в радианы и обратно:
- D3DXToRadian
#define D3DXToRadian(degree) ((degree)*(D3DX_PI/180.0f))
Пример:
FLOAT AnglelnRadians = D3DXToRadian(4 5);
- 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 // Угол поворота в радианах. );
Угол поворота задаётся по часовой стрелке, если смотреть вдоль положительного направления оси из (гипотетической) точки начала координат.
Масштабирование (Scaling)
- Также можно закодировать в матрице, указав значение масштаба (scaling factor) по каждой из трёх осей X, Y и Z (см. Рис.11).
В 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);
В данном примере размер объекта изменится непропорционально.
Комбинирование трансформаций
- Производится путём перемножения соответствующих матриц.
Но здесь важно, в каком порядке это производится. В отличие от числовых множителей, когда от перестановки множителей произведение не изменяется, при перемножении матриц их порядок имеет ключевое значение. Т.е. Матрица 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;
Трансформации объектов сцены перед рендерингом
Каждый объект перед выводом на экран проходит следующие трансформации (в порядке их указания здесь; см. Рис.12, 13):
Мировая трансформация (World transformation)
- Перемещает объект по всему "миру" (здесь - 3D-сцене).
- Применяется для конвертации локальных (local) координат объекта в мировые (world).
- Заставляет объекты сцены перемещаться, вращаться и изменять свои размеры.
Трансформация вида (View transformation)
- Ориентирует объекты сцены в 3D-пространстве относительно положения зрителя (= виртуальной камеры, viewer).
- Представляет собой своеобразный "объектив камеры", который определяет, что именно игрок видит на экране.
- Выполняет перемещение виртуальной камеры в 3D-пространстве.
Трансформация проекции (Projection transformation)
- "Сплющивает" 3D-изображение в 2D-картинку, выводимую на экран монитора.
- Финальный этап подготовки к рендерингу.
- Результат её выполнения - плоское 2D-изображение сцены, выводимое на экран.
К объекту в данный момент времени может быть применена одна или несколько трансформаций одновременно. К примеру, нам необходимо только транслировать (=переместить) объект, для чего достаточно обновить (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
Последние изменения страницы Вторник 09 / Август, 2022 20:14:40 MSK
Последние комментарии wiki