Преобразования (Direct3D 9)
Компонент Direct3D, перемещающий геометрию через фиксированный конвейер функциональной геометрии, называется модулем преобразования. Он находит модель и смотрящего в реальном мире, проецирует вершины для отображения на экране и обрезает вершины в окне просмотра. Модуль преобразования также выполняет вычисления освещения, чтобы определить рассеянные и зеркальные компоненты в каждой вершине.
Геометрический конвейер принимает вершины в качестве входных данных. Модуль преобразований применяет к вершинам мировое, видовое и проекционное преобразования, кадрирует результат и передает все в средство программной прорисовки.
В начале конвейера вершины модели объявлены относительно локальной системы координат. Локальная система координат — это локальное начало координат и ориентация. Такую ориентацию координат часто называют пространством модели, а отдельные координаты называются координатами модели.
Первый этап геометрического конвейера преобразует вершины модели из их локальной системы координат в систему координат, используемую всеми объектами в сцене. Процесс переориентации вершин называется преобразованием мира. Эту новую ориентацию обычно называют мировым пространством, и каждая вершина в мировом пространстве объявляется с помощью мировых координат.
На следующем этапе вершины, которые описывают ваш трехмерный мир, ориентируются по отношению к камере. То есть приложение выбирает точку зрения для сцены, а координаты мирового пространства перемещаются и поворачиваются вокруг обзора камеры, превращая мир в пространство камеры. Это преобразование представления.
Следующим этапом является преобразование проекции. В этой части конвейера объекты обычно масштабируются по отношению к их расстоянию от зрителя, чтобы придать сцене иллюзию глубины; Объекты close становятся больше удаленных объектов и т. д. С целью упрощения в этой документации пространство, в котором существуют вершины после проекционного преобразования, называется проекционным пространством. В некоторых книгах по графике проекционное пространство может называться постперспективным однородным пространством. Не все проекционные преобразования обеспечивают масштабирование размера объектов в сцене. Такая проекция иногда называется аффинной или ортогональной проекцией.
На последнем этапе конвейера все вершины, которые не будут видны на экране, удаляются, чтобы средство программной прорисовки не тратило время на вычисление цветов и затенения для того, что никто не увидит. Этот процесс называется кадрированием. После кадрирования оставшиеся вершины масштабируются в соответствии с параметрами окна просмотра и преобразуются в экранные координаты. Итоговые вершины, отображаемые на экране после растеризации стены, существуют в экранном пространстве.
Преобразования используются для преобразования геометрии объектов из одного пространства координат в другое. В Direct3D для 3D-преобразований используются матрицы. В этом разделе объясняется, как матрицы создают трехмерные преобразования, описаны некоторые распространенные способы использования преобразований, а также описано, как объединить матрицы для создания одной матрицы, охватывающей несколько преобразований.
- Преобразование мира (Direct3D 9) — преобразование из пространства модели в пространство мира
- Преобразование представления (Direct3D 9) — преобразование из мирового пространства в пространство просмотра
- Преобразование проекции (Direct3D 9) — преобразование из пространства представления в пространство проекции
Преобразования матрицы
В приложениях, работающих с 3D-графикой, с помощью геометрических преобразований можно:
- выражать местоположение одного объекта относительного другого объекта;
- поворачивать объекты и задавать их размер;
- менять позиции наблюдателя, направления и перспективы.
Любую точку (X,Y,Z) можно преобразовать в другую точку (X’, Y’, Z’) с помощью матрицы 4×4, как показано в следующем уравнении.

Примените следующие уравнения к (X, Y, Z) и матрице для получения точки (X’, Y’, Z’).

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

В C++ Direct3D объявляет матрицы как двумерный массив, используя структуру D3DMATRIX . В следующем примере показано, как инициализировать структуру D3DMATRIX в качестве однородной матрицы масштабирования.
// In this example, s is a variable of type float. D3DMATRIX scale = < s, 0.0f, 0.0f, 0.0f, 0.0f, s, 0.0f, 0.0f, 0.0f, 0.0f, s, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f >;
Перевод
Следующее уравнение параллельно переносит точку (X, Y, Z) в новую точку (X’, Y’, Z’).

Матрицу параллельного переноса можно вручную создать в C++. В следующем примере показан исходный код для функции, которая создает матрицу для параллельного переноса вершин.
D3DXMATRIX Translate(const float dx, const float dy, const float dz) < D3DXMATRIX ret; D3DXMatrixIdentity(&ret); ret(3, 0) = dx; ret(3, 1) = dy; ret(3, 2) = dz; return ret; >// End of Translate
Для удобства библиотека служебных программ D3DX предоставляет функцию D3DXMatrixTranslation .
Масштабирование
Следующее уравнение масштабирует точку (X, Y, Z) на произвольные значения в направлениях (X, Y, Z) для получения новой точки (X’, Y’, Z’).

Rotate
Описанные здесь преобразования предназначены для левовинтовых систем координат и, следовательно, могут отличаться от матриц преобразования, которые встречались вам где-либо еще.
Следующее уравнение поворачивает точку (X, Y, Z) вокруг оси X для получения новой точки (X’, Y’, Z’).

Следующее уравнение поворачивает точку вокруг оси Y.

Следующее уравнение поворачивает точку вокруг оси Z.

В этих примерах матриц греческой буквой тета обозначен угол поворота в радианах. Углы измеряются по часовой стрелке, если смотреть вдоль оси поворота в направлении начала координат.
В приложении C++ используйте функции D3DXMatrixRotationX, D3DXMatrixRotationY и D3DXMatrixRotationZ , предоставляемые библиотекой служебной программы D3DX, для создания матриц поворота. Ниже приведен код для функции D3DXMatrixRotationX .
D3DXMATRIX* WINAPI D3DXMatrixRotationX ( D3DXMATRIX *pOut, float angle ) < #if DBG if(!pOut) return NULL; #endif float sin, cos; sincosf(angle, &sin, &cos); // Determine sin and cos of angle pOut->_11 = 1.0f; pOut->_12 = 0.0f; pOut->_13 = 0.0f; pOut->_14 = 0.0f; pOut->_21 = 0.0f; pOut->_22 = cos; pOut->_23 = sin; pOut->_24 = 0.0f; pOut->_31 = 0.0f; pOut->_32 = -sin; pOut->_33 = cos; pOut->_34 = 0.0f; pOut->_41 = 0.0f; pOut->_42 = 0.0f; pOut->_43 = 0.0f; pOut->_44 = 1.0f; return pOut; >
Конкатенация матриц
Одним из преимуществ использования матриц является то, что эффекты двух или нескольких матриц можно объединить путем перемножения этих матриц. Это означает, что для поворота модели и последующего ее параллельного переноса в какое-либо место не нужно применять две матрицы. Вместо этого необходимо перемножить матрицы поворота и параллельного переноса для получения составной матрицы, содержащей все их эффекты. Этот процесс называется конкатенацией матриц и может описываться следующим уравнением.

В этом уравнении C — создаваемая составная матрица, а M₁ – Mₙ — отдельные матрицы. В большинстве случаев объединяют только две или три матрицы, однако на самом деле их количество не ограничено.
Используйте функцию D3DXMatrixMultiply для выполнения матричного умножения.
Порядок, в котором выполняется перемножение матриц, имеет решающее значение. Приведенная выше формула отражает правило конкатенации матриц «слева направо». Это значит, что визуальные эффекты матриц, используемых для создания составной матрицы, имеют место в порядке слева направо. В следующем примере показана типичная мировая матрица. Предположим, вы создаете мировую матрицу для классической летающей тарелки. Наверное, имеет смысл вращать летающую тарелку вокруг ее центра — оси Y модельного пространства — и параллельно переносить ее в какое-либо другое место в сцене. Для достижения этого эффекта необходимо сначала создать матрицу поворота, а затем умножить ее на матрицу параллельного переноса, как показано в следующем уравнении.

В этой формуле Ry — это матрица для поворота вокруг оси Y, а Tw — это параллельный перенос в некоторую точку в мировых координатах.
Порядок, в котором перемножаются матрицы, имеет значение, потому что, в отличие от умножения двух скалярных значений, умножение матриц не является коммутативным. Перемножение матриц в обратном порядке будет производить визуальный эффект параллельного переноса летающей тарелки в ее положение в мировом пространстве, а затем ее поворота вокруг мирового начала координат.
Вне зависимости от того, матрицу какого типа вы создаете, помните о правиле «слева направо», чтобы результаты всегда соответствовали ожидаемым.
Преобразование проекции (Direct3D 9)
Преобразование проекции можно рассматривать как управление внутренними данными камеры; это аналогично выбору объектива для камеры. Это самый сложный из всех трех типов преобразований. Это обсуждение преобразования проекции организовано в следующих разделах.
Обычно матрица проекции — это проекция масштаба и перспективы. Проекционное преобразование превращает видимое пространство (усеченную пирамиду) в кубовидную фигуру. Так как ближний конец видимого пространства меньше дальнего, это приводит к развертыванию объектов, которые находятся ближе к камере — так перспектива применяется к сцене.
В видимом пространстве расстояние между камерой и началом координат видимого пространства преобразования определяется произвольно как D, поэтому матрица проекции выглядит, как показано на следующем рисунке.

Матрица просмотра переносит камеру в начало координат, перемещаясь в направлении z на –D. Матрицы преобразования выглядит, как показано на следующем рисунке.

Умножение матрицы преобразования на матрицу проекции (T*P) дает составную матрицу проекции, как показано на следующем рисунке.

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

При преобразовании перспективы ограничения направлений x и y равны –1 и 1 соответственно. Ограничения по оси z равны 0 для передней плоскости и 1 для задней плоскости.
Эта матрица преобразует и масштабирует объекты на основе определенного расстояния между камерой и ближней плоскостью отсечения, но при этом не учитывается, что поле зрения (fov) и z-значения, создаваемые для объектов, на расстоянии могут быть практически идентичными, что усложняет сравнение глубины. Следующая матрица решает эти проблемы и корректирует вершины для учета пропорций окна просмотра, что хорошо подходит для проекции перспективы.

В этой матрице Zₙ — это z-значение ближней плоскости отсечения. Переменные w, h и Q имеют следующие значения. Обратите внимание, что fovw и fovₖ представляют горизонтальные и вертикальные поля зрения окна просмотра в радианах.

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

В этих формулах Zₙ представляет положение ближней плоскости отсечения, а переменные Vw и Vₕ представляют ширину и высоту окна просмотра в пространстве камеры.
Для приложения C++ эти два измерения напрямую соответствуют элементам Width и Height структуры D3DVIEWPORT9 .
Какую бы формулу вы ни выбрали, обязательно установите для Zₙ максимально возможное значение, так как z-значения, очень близкие к камере, не сильно отличаются. Это относительно усложняет сравнение глубины с помощью 16-разрядных z-буферов.
Как и в случае с преобразованиями мира и представления, вы вызываете метод IDirect3Device9::SetTransform , чтобы задать преобразование проекции.
Настройка матрицы проекции
Следующий пример функции ProjectionMatrix задает передние и задние отсеченные плоскости, а также горизонтальное и вертикальное поле угла зрения. Поля представления должны быть меньше, чем пи радианы.
D3DXMATRIX ProjectionMatrix(const float near_plane, // Distance to near clipping // plane const float far_plane, // Distance to far clipping // plane const float fov_horiz, // Horizontal field of view // angle, in radians const float fov_vert) // Vertical field of view // angle, in radians < float h, w, Q; w = (float)1/tan(fov_horiz*0.5); // 1/tan(x) == cot(x) h = (float)1/tan(fov_vert*0.5); // 1/tan(x) == cot(x) Q = far_plane/(far_plane - near_plane); D3DXMATRIX ret; ZeroMemory(&ret, sizeof(ret)); ret(0, 0) = w; ret(1, 1) = h; ret(2, 2) = Q; ret(3, 2) = -Q*near_plane; ret(2, 3) = 1; return ret; >// End of ProjectionMatrix
После создания матрицы задайте для нее значение IDirect3DDevice9::SetTransform , указав D3DTS_PROJECTION.
Служебная библиотека D3DX предоставляет следующие функции, которые помогут вам настроить матрицу проекции.
Матрица проекции с поддержкой W
Direct3D может использовать компонент w вершины, которая была преобразована абсолютной матрицей, а также матрицами представления и проекции для вычислений на основе глубины при использовании буфера глубины или эффекта тумана. Для таких вычислений требуется, чтобы матрица проекции нормализовала переменную w как эквивалентную координате z в абсолютном пространстве. Иными словами, если матрица проекции включает коэффициент (3,4), не равный 1, необходимо масштабировать все коэффициенты, инвертируя коэффициент (3,4) для получения правильной матрицы. Если не предоставить совместимую матрицу, эффекты тумана и буферизация глубины не будут правильно применены.
На следующем рисунке показана несоответствующая требованиям матрица проекции и та же матрица, масштабированная для применения тумана туман относительно зрителя.

В предыдущих матрицах предполагается, что все переменные не равны нулю. Дополнительные сведения о тумане относительно глаз см. в разделе Глубина относительного взгляда и Z. Сведения о буферизации глубины на основе W см. в разделе Буферы глубины (Direct3D 9).
Direct3D использует текущую матрицу проекции при расчетах глубины вершин на основе переменной w. В результате приложения должны установить соответствующую матрицу проекции для получения необходимых функций на основе компонента w, даже если они не используют Direct3D для преобразования.
Трехмерные преобразования над двухмерными битовыми картами
.png)
Программирование трехмерной графики — это в основном создание оптических иллюзий. Изображения визуализируются на плоском экране, состоящем из двухмерного массива пикселей, но эти объекты должны выглядеть так, будто у них его третье измерение с глубиной.
По-видимому, самый большой вклад в иллюзию трехмерности вносит заливка (shading) — искусство и наука окраски пикселей таким образом, чтобы поверхности напоминали реальные текстуры с освещением и тенями.
За всем этим, однако, лежит инфраструктура виртуальных объектов, описываемых трехмерными координатами. В конечном счете эти трехмерные координаты проецируются на двухмерное пространство, но до последнего этапа трехмерные координаты часто систематически модифицируются разными способами через преобразования (transforms). С математической точки зрения, преобразования — это операции матричной алгебры. Свободное владение этими трехмерными преобразованиями крайне важно для любого, кто хочет стать программистом трехмерной графики.
Недавно я исследовал несколько способов поддержки трехмерной графики в Direct2D — компоненте DirectX. Изучение трехмерной графики в относительной знакомом и комфортном Direct2D позволяет постепенно освоить концепции трехмерной графики до погружения в очень пугающие глубины Direct3D.
Битовые карты в трехмерной графике?
Один из нескольких способов, которыми трехмерная графика поддерживается в Direct2D, скрыт в последнем аргументе методов DrawBitmap, определенных в ID2D1DeviceContext. Этот аргумент позволяет применять трехмерное преобразование к двухмерной битовой карте. (Эта функциональность специфична для ID2D1DeviceContext. Она не поддерживается методами DrawBitmap, определенными в ID2D1RenderTarget или в других интерфейсах, производных от него.)
У методов DrawBitmap, определенных в ID2D1DeviceContext, последний аргумент имеет тип D2D1_MATRIX_4X4_F, который представляет матрицу преобразования 4×4, выполняющую трехмерное преобразование битовой карты при ее визуализации на экране:
void DrawBitmap(ID2D1Bitmap *bitmap, D2D1_RECT_F *destinationRectangle, FLOAT opacity, D2D1_INTERPOLATION_MODE interpolationMode, const D2D1_RECT_F *sourceRectangle, const D2D1_MATRIX_4X4_F *perspectiveTransform)
Вроде бы это единственное предназначение D2D1_MATRIX_4X4_F. Больше нигде в DirectX он не используется. В Direct3D-программировании для представления трехмерных преобразований вместо него применяется библиотека DirectX Math.
DD2D1_MATRIX_4X4_F — это typedef для D2D_MATRIX_4X4_F, который определен на рис. 1. Фактически это набор из 16 значений с плавающей точкой, упорядоченных в четыре строки с четырьмя столбцами. Вы можете сослаться на значение в третьей строки и втором столбце, используя поле _32, или получить то же значение как элемент массива с отсчетом индексов от нуля — m[2][1].
Рис. 1. Матрица трехмерного преобразования, применяемая к битовым картам
typedef struct D2D_MATRIX_4X4_F < union < struct < FLOAT _11, _12, _13, _14; FLOAT _21, _22, _23, _24; FLOAT _31, _32, _33, _34; FLOAT _41, _42, _43, _44; >; FLOAT m[4][4]; >; > D2D_MATRIX_4X4_F;
Однако, показывая вам математику преобразований, я буду ссылаться на элемент в третьей строке и втором столбце матрицы как m32. Всю матрицу можно представить в традиционной матричной нотации:
.png)
Трехмерное преобразование битовой карты в Direct2D лежит в основе аналогичного механизма в Windows Runtime, где оно предоставляется как структура Matrix3D и свойство Projection, определенные в UIElement. Эти два механизма столь похожи, что вы можете переносить свои знания одной среды в другую.
Линейное преобразование
Многие, впервые встречая трехмерные преобразования, спрашивают: почему матрица именно 4×4? Разве матрица 3×3 не была бы более подходящей для трехмерной графики?
Чтобы ответить на этот вопрос, исследуя трехмерное преобразование в DrawBitmap, я создал программу BitmapTransformExperiment, которую включил в код, сопутствующий этой статье. Данная программа содержит собственные элементы управления «наборные счетчики» (spinner controls), позволяющие выбирать значения для 16 элементов матрицы преобразования и наблюдать, как эта матрица влияет на отображение битовой карты (растрового изображения). Пример показан на рис. 2.
.png)
Рис. 2. Программа BitmapTransformExperiment
Для начальных экспериментов ограничьте свое внимание верхними тремя строками и тремя столбцами с левой стороны матрицы. Это дает матрицу 3×3, которая выполняет следующее преобразование:
.png)
Матрица 1×3 слева представляет трехмерную координату. Для битовой карты значение x меняется от 0 до ширины битовой карты, значение y — от 0 до высоты битовой карты и значение z равно 0.
Когда матрица 1×3 умножается на матрицу преобразования 3×3, стандартное перемножение матриц приводит к трансформации координат:
.png)
Матрица тождественности, или единичная матрица (identity matrix) (в ней диагональные элементы m11, m22 и m33 равны 1, а все остальные — 0), не дает никакого преобразования.
Поскольку я начинаю с плоской битовой карты, координата z равна 0, а значит, m31, m32 и m33 не влияют на результат. Когда преобразованная битовая карта визуализируется на экране, результат (x’, y’, z’) проецируется на плоскую двухмерную координатную систему, игнорируя координату z’, т. е. и в этом случае элементы m13, m23 и m33 не дают никакого эффекта. Вот почему третья строка и третий столбец затеняются в программе BitmapTransformExperiment. Вы можете задать значения для этих элементов матрицы, но они никак не скажутся на визуализации битовой карты.
Вы обнаружите, что m11 — это масштабный множитель по горизонтали со значением по умолчанию 1. Сделайте его больше или меньше, чтобы увеличить или уменьшить ширину битовой карты, или присвойте ему отрицательное значение, чтобы перевернуть битовую карту вокруг вертикальной оси. Аналогично m22 — масштабный множитель по вертикали.
Значение m21 является коэффициентом скоса по вертикали (vertical skewing factor): значения, отличные от 0, превращают прямоугольную битовую карту в параллелограмм, так как правый край смещается вверх или вниз. Аналогично m12 — коэффициент скоса по горизонтали.
Комбинация скоса по горизонтали и вертикали может привести к повороту. Чтобы повернуть битовую карту по часовой стрелке на конкретный угол, присвойте m11 и m22 косинус этого угла, m21 — его синус, а m12 — отрицательный синус. На рис. 2 показан поворот на 30 градусов.
В контексте трехмерной графики поворот, иллюстрируемый рис. 2, на самом деле является поротом вокруг оси Z, которая концептуально исходит из экрана. Для угла α матрица преобразования выглядит так:
.png)
Также можно повернуть битовую карту вокруг оси Y или X. Вращение вокруг оси Y не влияет на координату y, поэтому матрица преобразования является следующей:
.png)
Если вы поэкспериментируете с этим в программе BitmapTransformExperiment, то заметите, что эффект дает лишь значение m11. Присваивая его косинусу угла поворота, вы просто уменьшаете ширину визуализируемой битовой карты. Это уменьшение по ширине согласуется с поворотом вокруг оси Y.
Аналогично осуществляется поворот вокруг оси X:
.png)
В программе BitmapTransformExperiment это приводит к уменьшению высоты битовой карты.
Знаки двух коэффициентов наклона (синусов) в матрицах преобразования управляют направлением поворота. С концептуальной точки зрения, положительная ось Z считается исходящей из экрана, и вращение следует правилу левой руки: выровняйте большой палец левой руки по оси поворота и указывайте им в сторону положительных значений; кривая, описываемая остальными пальцами, задает направление поворота для положительных углов.
Тип трехмерного преобразования, представленный этой матрицей преобразования 3×3 известен как линейное преобразование. Это преобразование включает только константы, умножаемые на координаты x, y и z. Какие бы значения вы ни вводили в первые три строки и столбца матрицы преобразования, битовая карта никогда не превратится в нечто более экзотичное, чем параллелограмм; в трехмерных пространствах куб всегда преобразуется в параллелепипед.
Вы видели, как битовую карту можно масштабировать, наклонять и поворачивать, но на протяжении этих упражнений верхний левый угол битовой карты оставался зафиксированным в одной позиции. (Эта позиция определяется набором двухмерных преобразований в методе Render до вызова DrawBitmap.) Невозможность смещения верхнего левого угла битовой карты вытекает из математики линейного преобразования. В формуле этого преобразования нет ничего, что могло бы сдвинуть точку (0, 0, 0) в другую позицию. Тип преобразования, который делает это возможным, называется трансляцией (translation).
Трансляция
Чтобы понять, как добиться трансляции в трехмерном пространстве, давайте подумаем о двухмерных преобразованиях.
В двухмерном пространстве линейное преобразование осуществляется матрицей 2×2, и оно поддерживает масштабирование, наклон и вращение. Чтобы получить еще и трансляцию, предполагают, что двухмерная графика существует в трехмерном пространстве, но на двухмерной плоскости, где координата z всегда равна 1. Для подстройки под дополнительное измерение матрица двухмерного линейного преобразования расширяется в матрицу 3×3, но обычно последняя строка фиксирована:
.png)
Перемножение матриц дает эти формулы преобразования:
.png)
Элементы m31 и m32 являются множителями трансляции (translation factors). Секрет этого процесса в том, что трансляция в двух измерениях эквивалентна наклону в трех измерениях.
Аналогичный процесс используется в трехмерной графике: трехмерные координаты считаются расположенными в четырехмерном пространстве, где координата четвертого измерения равна 1. Но, пытаясь представить точку в системе четырехмерных координат, вы сталкиваетесь с небольшой и довольно смехотворной проблемой: трехмерная точка описывается как (x, y, z), и за z никаких букв нет; какую же букву использовать для четвертого измерения? Ближайшая доступная буква — w, поэтому координаты четырехмерной точки — (x, y, z, w).
Для включения дополнительного измерения матрица трехмерного линейного преобразования расширяется до 4×4:
.png)
Теперь формулы преобразования выглядят так:
.png)
Теперь у вас есть трансляция по осям X, Y и Z с помощью элементов m41, m42 и m43. Расчеты вроде бы происходят в четырехмерном пространстве, но на самом деле ограничены трехмерным сечением четырехмерного пространства, где координата w всегда равна 1.
Гомогенные координаты
Если вы поиграете со значениями m41 и m42 в BitmapTransformExperiment, то заметите, что они действительно приводят к горизонтальной и вертикальной трансляции.
А как насчет последней строки матрицы? Что будет, если не ограничивать эту строку нулями и единицами? Вот полная матрица преобразования 4×4, примененная к трехмерной точке:
.png)
Формулы для x’, y’ и z’ остаются теми же, а w’ теперь вычисляется так:
.png)
И это настоящая проблема. Ранее применялся небольшой трюк, чтобы использовать трехмерное сечение четырехмерного пространства, где координата w всегда равна 1. Но теперь координата по оси W больше не равна 1, и вы вышли за пределы трехмерного сечения. Вы заблудились в четырехмерном пространстве и должны вернуться к тому трехмерному сечению, где w = 1.
Однако нужды строить машину для путешествий по измерениям нет. К счастью, вы можете совершить скачок чисто математически, поделив все преобразованные координаты на w’:
.png)
Теперь w’ равно 1, и вы снова дома!
Но какой ценой? Теперь у вас есть деление в формулах преобразования, и легко увидеть, что в некоторых обстоятельствах делитель может оказаться равным 0. И вы получите бесконечные координаты.
Ну, может быть, это и к лучшему.
Когда немецкий математик Август Фердинанд Мёбиус (1790–1868) изобрел систему, которую я только что описал (назвав ее гомогенными, или проективными, координатами), одной из его целей было представить бесконечные координаты, используя конечные числа.
Матрицу с нулями и единицами в последней строке называют аффинным преобразованием (affine transform), подразумевая, что оно не дает бесконечные координаты, поэтому преобразование, способное привести к получению бесконечных координат, называют неаффинным (non-affine transform).
В трехмерной графике неаффинные преобразования чрезвычайно важны, потому что именно так достигается перспектива. Каждый знает, что в реальной жизни объекты, находящиеся от нас дальше, кажутся меньшими. В трехмерной графике вы добиваетесь того же эффекта с помощью делителя, который не является константой, равной 1.
Опробуйте это в BitmapTransformExperiment: если вы сделаете m14 небольшим положительным числом (а интересные результаты дают только малые значения), то значения x’ и y’ будут пропорционально уменьшаться по мере увеличения x. Затем сделайте m14 небольшим отрицательным числом, и более высокие значения x’ и y’ будут увеличиваться. Этот эффект в сочетании с ненулевым значением m12 показан на рис. 3. Визуализируемая битовая карта больше не будет параллелограммом, и перспектива предполагает, что правый край приближается к вашим глазам.
.png)
Рис. 3. Перспектива в BitmapTransformExperiment
Аналогично ненулевые значения m24 позволяют делать так, чтобы верх или низ битовой карты казался приближающимся к вам или, наоборот, отдаляющимся от вас. В программировании настоящей трехмерной графики обычно используется значение m34, так как оно дает возможность увеличивать или уменьшать размер объектов на основе их координат z — расстояния от глаз наблюдателя.
Когда трехмерное преобразование применяется к двухмерным объектам, значение m44 обычно оставляют равным 1, но оно может действовать как общим масштабный множитель. Кроме того, в программировании настоящей трехмерной графики m44 обычно задается нулевым, когда работают с перспективой, так как, с концептуальной точки зрения, камера находится в начале координат. Нулевое значение m44 работает, только если у трехмерных объектов нет нулевых координат z, но при операциях с двухмерными объектами координата z всегда равна 0.
Любой выпуклый четырехугольник
Применяя эту матрицу преобразования 4×4 к плоской битовой карте, вы используете лишь половину элементов матрицы. Но даже в таком случае на рис. 3 видно то, что нельзя сделать с помощью обычной двухмерной матрицы преобразования в Direct2D, в частности превратить прямоугольник в нечто иное, чем параллелограмм. И действительно, объекты преобразования, используемые в большей части Direct2D, называются D2D1_MATRIX_3X2_F и Matrix3x2F, подчеркивая недоступность третьей строки матрицы и невозможность выполнения неаффинных преобразований.
С помощью D2D1_MATRIX_4X4_F можно получить преобразование, которое превращает битовую карту в любой выпуклый четырехугольник, т. е. в произвольную четырехстороннюю фигуру с непересекающимися сторонами и внутренними углами в вершинах менее 180 градусов.
Если вы мне не верите, попробуйте поиграть с программой NonAffineStretch. Заметьте, что эта программа — модифицированная версия одноименной программы под Windows Runtime из главы 10 моей книги «Programming Windows, 6th Edition» (Microsoft Press, 2013).
Эта программа в действии показана на рис. 4. Вы можете использовать мышь и сенсорный ввод, чтобы перетаскивать зеленые точки в любые места на экране. Пока вы сохраняете фигуру выпуклым четырехугольником, преобразование 4×4 может быть получено на основе позиций точек. Это преобразование применяется при рисовании битовой карты (кроме того, в нижнем правом углу показывается текущая матрица). Задействовано только восемь значений; элементы в третьей строке и третьем столбце всегда содержат значения по умолчанию, а m44 всегда равен 1.
.png)
Рис. 4. Программа NonAffineStretch
Выполняемые при этом математическое операции довольно сложны, но алгоритм вы найдете в главе 10 моей книги.
Класс Matrix4x4F
Чтобы немного упростить работу со структурой D2D1_MATRIX_4X4_F, на ее основе в пространстве имен D2D1 создан класс Matrix4x4F. Этот класс определяет конструктор и оператор перемножения (который я использовал в алгоритме NonAffineStretch), а также несколько полезных статических методов для создания распространенных матриц преобразования. Например, метод Matrix4x4F::RotationZ принимает аргумент, который является углом в градусах, и возвращает матрицу, представляющую преобразование для поворота на этот угол вокруг оси Z:
.png)
Другие функции Matrix4x4F создают матрицы для поворота вокруг осей X и Y, а также для поворота вокруг произвольной оси, матрица которого гораздо сложнее.
У функции Matrix4x4F::PerspectiveProjection есть аргумент depth. Она возвращает такую матрицу:
.png)
Это означает, что формула преобразования для x’ следующая:
.png)
И аналогично для y’ и z’, т. е. всякий раз, когда координата z равна depth, делитель равен 0, и все координаты становятся бесконечными.
С концептуальной точки зрения, это означает, что вы смотрите на экран компьютера с расстояния в depth единиц, где единицы такие же, как и на самом экране, т. е. пиксели или аппаратно-независимые единицы. Если координата z какого-то графического объекта равна depth, он находится в depth единицах перед экраном — прямо в вашем глазу! Этот объект должен показаться вам очень большим — математически бесконечным.
Но постойте-ка: единственная цель структуры D2D1_MATRIX_4X4_F и класса Matrix4x4F — использование в вызовах DrawBitmap, а битовые карты всегда имеют нулевые координаты z. Тогда, как же m34 со значением –1/depth вообще на что-то влияет?
Если в вызове DrawBitmap используется сама матрица PerspectiveProjection, эффекта действительно никакого не будет. Но она предназначена для применения в сочетании с другими матричными преобразованиями. Матричные преобразования можно компоновать их перемножением. Хотя у исходной битовой карты нет координат z, и они игнорируются при рендеринге, координаты z определенно могут играть роль в компоновке преобразований.
Давайте рассмотрим пример. Программа RotatingText создает битовую карту с текстом «ROTATE» и шириной приблизительно в половину ширины экрана. Большая часть методов Update и Render приведена на рис. 5.
Рис. 5. Код из RotatingTextRenderer.cpp
void RotatingTextRenderer::Update(DX::StepTimer const& timer) < . // Начинаем с матрицы тождественности m_matrix = Matrix4x4F(); // Поворачиваем вокруг оси Y double seconds = timer.GetTotalSeconds(); float angle = 360 * float(fmod(seconds, 7) / 7); m_matrix = m_matrix * Matrix4x4F::RotationY(angle); // Применяем перспективу на основе ширины битовой карты D2D1_SIZE_F bitmapSize = m_bitmap->GetSize(); m_matrix = m_matrix * Matrix4x4F::PerspectiveProjection(bitmapSize.width); > void RotatingTextRenderer::Render() < . ID2D1DeviceContext* context = m_deviceResources->GetD2DDeviceContext(); Windows::Foundation::Size logicalSize = m_deviceResources->GetLogicalSize(); context->SaveDrawingState(m_stateBlock.Get()); context->BeginDraw(); context->Clear(ColorF(ColorF::DarkMagenta)); // Смещаем начало координат вверх по центру экрана Matrix3x2F centerTranslation = Matrix3x2F::Translation(logicalSize.Width / 2, 0); context->SetTransform(centerTranslation * m_deviceResources->GetOrientationTransform2D()); // Рисуем битовую карту context->DrawBitmap(m_bitmap.Get(), nullptr, 1.0f, D2D1_INTERPOLATION_MODE_LINEAR, nullptr, &m_matrix); . >
В методе Update метод Matrix4x4F::RotationY создает следующее преобразование:
.png)
Умножаем эту матрицу на показанную ранее (она была возвращена из метода Matrix4x4F::PerspectiveProjection) и получим:
.png)
Формулы преобразования выглядят так:
.png)
Здесь определенно включена перспектива, и результат можно посмотреть на рис. 6.
.png)
Рис. 6. Вывод программы RotatingText
Берегитесь: аргументу depth в Matrix4x4F::PerspectiveProjection присваивается ширина битовой карты, поэтому по мере того, как вращающаяся битовая карта разворачивается, она может задеть ваш нос.
Чарльз Петцольд (Charles Petzold) — давний «пишущий» редактор MSDN Magazine и автор книги «Programming Windows, 6th edition» (O’Reilly Media, 2012) о написании приложений для Windows 8. Его веб-сайт находится по адресу charlespetzold.com.
Выражаю благодарность за рецензирование статьи экспертам Microsoft Джиму Галасину (Jim Galasyn) и Майку Ричизу (Mike Riches).
Конвейер преобразования Direct3D
В этой статье содержится техническое объяснение для разработчиков приложений Direct3D о том, как задать параметры конвейера преобразования Direct3D путем прямого обращения с матрицами Direct3D.
- Обзор
- Конвейер преобразования
- Советы по использованию
Общие сведения
В Direct3D используется три преобразования для перевода координат трехмерной модели в координаты пикселей (пространство экрана). Эти преобразования — мировое преобразование, видовое преобразование и проекционное преобразование.
Преобразование мира управляет тем, как координаты модели преобразуются в мировые координаты. Преобразование мира может включать переводы, повороты и масштабирование, но оно не применяется к свету. Дополнительные сведения о работе с преобразованиями мира см. в разделе Преобразование мира.
Преобразование представления управляет переходом от мировых координат к «пространству камеры», определяя положение камеры в мире. Пример работы с преобразованиями представлений см. в разделе Преобразование представления.
Преобразование проекции изменяет геометрию из пространства камеры в «пространство клипа» и применяет искажение перспективы. Термин «пространство клипа» относится к тому, как геометрия обрезается в томе представления во время этого преобразования. Пример работы с преобразованиями проекции см. в разделе Преобразование проекции.
Наконец, геометрия в пространстве клипа преобразуется в пиксельные координаты (пространство экрана). Это преобразование управляется параметрами окна просмотра.
Обрезка и преобразование вершин должны происходить в однородном пространстве (проще говоря, в пространстве, в котором система координат включает четвертый элемент), но конечным результатом для большинства приложений должны быть неоднородные трехмерные (трехмерные) координаты, определенные в «пространстве экрана». Это означает, что входные вершины и том обрезки должны быть преобразованы в однородное пространство для выполнения обрезки, а затем переведены обратно в неоднородное пространство для отображения.
Три преобразования Direct3D — мир, представление и преобразование проекции — определяются матрицами Direct3D. Матрица Direct3D — это однородная матрица 4×4, определяемая структурой D3DMATRIX . Хотя матрицы Direct3D не являются стандартными объектами, они не представлены интерфейсом COM, их можно создавать и задавать так же, как и любой другой объект Direct3D. Дополнительные сведения о матрицах Direct3D см. в разделе Преобразования.
Конвейер преобразования
Если вершина в координате модели задается pm = (Xm, Ym, Zm, 1), то преобразования, показанные на следующем рисунке, применяются к координатам экрана вычислений Ps = (Xs, Ys, Zs, Ws).
Ниже приведены описания этапов, показанных на предыдущем рисунке:
-
Мировая матрица Mworld преобразует вершины из пространства модели в мировое пространство. Эта матрица задается следующим образом:
d3dDevice->SetTransform (D3DTRANSFORMSTATE_WORLD, matrix address)
d3dDevice->SetTransform (D3DTRANSFORMSTATE_VIEW, matrix address)
d3dDevice->SetTransform (D3DTRANSFORMSTATE_PROJECTION, matrix address)
Последний столбец матрицы проекции должен быть (0, 0, 1, 0) или (0, 0, a, 0) для правильных эффектов тумана и освещения; Форма (0, 0, 1, 0) является предпочтительной. Объем обрезки для всех точек Xp = (Xp, Yp, Zp, Wp) в пространстве проекции определяется следующим образом:
- Ширина окна sw-экрана в пространстве камеры в близкой плоскости обрезки
- Высота окна sh-screen в пространстве камеры в близкой плоскости обрезки
- Zn-расстояние до ближней плоскости обрезки вдоль осей Z в пространстве камеры
- Zf-расстояние до дальней плоскости обрезки вдоль осей Z в пространстве камеры
тогда матрицу проекции перспективы можно написать следующим образом:
где Mij являются членами Mproj.
Для ортогональной проекции мы имеем:
Direct3D предполагает, что матрица проекции перспективы имеет вид:
Если матрица проекции перспективы не имеет такой формы, будут присутствовать некоторые артефакты. Например, табличный туман не будет работать.
Cx, Cy - dvClipX, dvClipY from D3DVIEWPORT9 Cw, Ch - dvClipWidth, dvClipHeight from D3DVIEWPORT9 Zmin, Zmax - dvMinZ, dvMaxZ from D3DVIEWPORT9
Это преобразование может обеспечить повышенную точность и эквивалентно масштабированию и смещению тома обрезки. Соответствующая матрица Маклипа: Вершина преобразуется следующим образом:
dvClipWidth = 2 dvClipHeight = 2 dvClipX = -1 dvClipY = 1 dvMinZ = 0 dvMaxZ = 1
Если вы не хотите масштабировать том клипа, можно задать для параметров окна просмотра значения по умолчанию:
(Xc, Yc, Zc, Wc) = (Xp, Yp, Zp, Wp) * Mclip
dwX, dwY - viewport offsets in pixels from D3DVIEWPORT9 dwWidth, dwHeight - viewport width and height in pixels from D3DVIEWPORT9
Советы по использованию
Ниже приведены некоторые советы по использованию конвейера преобразования Direct3D.
- Последний столбец матриц мира и представления должен быть (0, 0, 0, 1), иначе освещение будет неправильным.
- Задайте параметры окна просмотра, чтобы создать матрицу Mclip для идентификации, если вы точно не понимаете, для чего она нужна.
dvClipWidth = 2 dvClipHeight = 2 dvClipX = -1 dvClipY = 1 dvMinZ = 0 dvMaxZ = 1