Опыт изучения OpenGL — Часть 7 — Пространственные преобразования (продолжение)

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

Смена базиса

Для начала поговорим переходе от одной системы координат к другой. В 3-хмерном евклидовом пространстве существует т. н. базис, который состоит из трех векторов. Базис — это набор векторов, такой, что любой вектор можно представить в виде линейной комбинации базисных векторов. Коэффициенты в этой линейной комбинации называются координатами. Пусть есть две системы координат K и K’. У одной будет один базис, у другой — другой. Кроме того, начало координат одной системы отлично от начала координат другой. Соответственно, одна и та же точка P в одной системе будет иметь одни координаты, а в другой — другие. Задача — найти преобразование (матрицу преобразования), которое переводит вектор из одной системы координат в другую. Вычисление матрицы перехода из одной системы в другую показано на рис. 1.

Рис. 1 — Переход их одной системы координат в другую.

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

Рис. 2 — Вращение вокруг оси Z.

Model Space -> World Space

Каждая модель, как уже говорилось в предыдущей заметке, создается в с своем координатном пространстве — пространстве модели — model space. Наша задача — поместить модель в мир (world space), где она будет перемещаться и вращаться (см. рис. 3). Т. е. зная координаты вершин модели в model space, найти их значения в world space. Как задается позиция модели в world space? — При помощи вектора в world space (на рис. 3 это вектор t). Как задается ориентация модели в world space? — При помощи трех векторов: forward, right и up, которые обычно совпадают с координатными осями model space — Z, X и Y соответственно. Нам должны быть известны координаты этих трех векторов в world space. Иными словами, мы знаем, как ориентированы координатные оси model space по отношению к world space. Тогда матрица, которая переводит вершины модели из model space в world space, находится легко.

Рис. 3 — Model-to-World Matrix.

Последовательность преобразований

Итак, чтобы перевести вершины модели из model space в world space, надо умножить координаты вершин на некую матрицу. Если это единичная матрица (identity matrix), то модель находится в начале координат world space и ее ориентация (задаваемая тремя векторами forward, right и up) совпадает с ориентацией осей world space. Умножение на матрицу перемещает и/или переориентирует модель. Скажем, умножение на матрицу трансляции перемещает модель параллельно самой себе на заданный вектор перемещения. Умножение на матрицу поворота — поворачивает модель на заданный угол. На рис. 4 показано, что матрицу model-to-world-matrix можно представить как произведение матрицы поворота на матрицу трансляции. В общем, перемещение модели в world space в процессе игры можно представить как последовательность умножений на различные матрицы. Причем результат в общем случае зависит от порядка, в котором выполняется умножение матриц. Точно так же, результат преобразований зависит от порядка, в котором они выполняются.

Рис. 4 — Последовательность преобразований.

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

Еще один вопрос, который кажется мне похожим на вопрос выбора взаимного расположения матрицы и вектора при перемножении — это вопрос как хранить матрицу в памяти. Память компьютера «линейна», т. е. множество адресов байтов в памяти имеет размерность 1. Существует два варианта хранения матриц в памяти. Row-major: в соседних ячейках памяти располагаются элементы одной строки. Column-major: в соседних ячейках памяти располагаются элементы одного столбца (рис. 5). Если выбрать вариант row-major, то будет легко «вытащить» из памяти, в которой хранится матрица, отдельную строку и производить над ней какие-то операции. Если выбрать column-major, то легко будет вытащить из памяти отдельный столбец. Во второй строке на рисунке 5 показано, что умножение матрицы и вектора в случае row-major можно выразить через скалярные произведения вектора на строки матрицы, а в случае column-major — как сумму произведений координат вектора на столбцы матрицы. Таким образом, оба варианта допустимы. Какой из них выбрать — наверное дело вкуса. Но выбрав один вариант, надо строго ему следовать.

Рис. 5 — Row-major & Column-major order.

Способы представления поворота: углы Эйлера, кватернионы и прочее

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

  • Yaw — рысканье — вращение вокруг вертикальной оси (up-axis)
  • Pitch — тангаж — вращение вокруг горизонтальной оси, которая смотрит направо (right-axis)
  • Roll — крен — вращение вокруг горизонтальной оси, которая смотрит прямо (forward-axis)

Поскольку три угла — это 3 числа, а матрица поворота — это как минимум 9 чисел, представление поворота в виде углов Эйлера экономит память. Однако есть проблемы. Например, как определить в виде углов Эйлера результат двух последовательных поворотов (тоже задаваемых двумя тройками углов Эйлера)? Что касается проблемы gimbal lock, которая постоянно упоминается в связи с углами Эйлера, то она пока осталась для меня не ясна. Еще один вариант представления поворота — кватернионы — четырехмерные векторы со своими специфическими правилами сложения. Они подробно рассмотрены в [Gregory, 4.4 Quaternions]. Кватернион состоит из четырех чисел, занимая примерно в два раза меньше памяти, чем матрица 3×3. Проблем со сложением поворотов для кватернионов вроде как нет. Вообще, сравнение различных представлений поворотов можно найти в [Gregory, 4.5 Comparison of Rotational Representations]. Я же пока предпочитаю матрицы как наиболее понятные мне объекты.

Правые и левые системы координат. Векторное произведение и псевдовекторы.

На рисунке 3 показано, что для вычисления матрицы перехода из model space в world space нужно знать координаты векторов, задающих направление осей model space относительно world space. На самом деле достаточно знать направления двух осей — направление третьей можно вычислить через векторное произведение двух других. Что такое векторное произведение? Ответ — на рисунке 6, где векторное произведение представлено как определитель матрицы, в верхней строке которой стоят базисные векторы (орты) координатного пространства, а в других двух строках — координаты перемножаемых векторов.

Рис. 6 — Векторное произведение.

Векторное произведение — это вектор, который направлен перпендикулярно перемножаемым векторам. Но таких направлений два. В свое время на уроках физики меня учили, что направление векторного произведения определяется «правилом правого винта», оно же «правило буравчика», оно же «правило правой руки». Но оказывается, направление векторного произведения зависит от ориентации осей системы координат… Или не даже не от системы координат, а от ориентации (handedness) пространства, под коей понимается выбор нами тройки векторов и объявления ее «правой». В общем, я буду считать, что выбранная нами ориентация пространства совпадает с ориентацией выбранной системы координат. Итак, если координатные оси образуют правовинтовую систему (рис. 6), то направление векторного произведения определяется правилом правой руки, если левовинтовую — то правилом левой руки (вот это-то и оказалось для меня новостью). Практически это означает, что если у вас есть world space и model space, и если вам известны направления двух осей model space относительно world space (т. е. вам известны координаты двух ортов model space и эти координаты заданы в координатной системе world space), то направление третьей оси можно посчитать, используя формулу на рис. 6. Причем ориентация (handedness) получившихся трех осей model space будет совпадать с ориентацией координатных осей world space.

Я, когда писал свой API, предполагал, что у меня координатная система world space левая. Ось X смотрит вправо, ось Y — вверх, а ось Z — вглубь экрана. Если смотреть вдоль оси Z, движение от оси X к оси Y по кратчайшему пути пойдет против часовой стрелки. Таким образом оси образуют левую тройку векторов. Что это за собой влечет? Я вижу пока только одну вещь: если model space для какой-либо модели окажется правой, а не левой, то мне для правильного отображения такой модели на экране придется применить к ней преобразование отражения (reflection).

Далее мне стало интересно, изменились ли бы какие-то функции моего API, если бы я выбрал не левую, а правую систему координат world space. И я нашел одно изменение — должна была бы измениться матрица перспективной проекции — на рисунке 7 показано, как именно. Ну и еще изменились бы настройки depth test.

Рис. 7 — Как матрица перспективной проекции зависит от ориентации (handedness) системы координат.

И наконец, как пишут в умных книжках, векторное произведение — это псевдовектор. Псевдовектор ведет себя так же как вектор при выполнении преобразования поворота, трансляции и прочих, за исключением преобразования отражения (в 3-хмерном пространстве отражение производится относительно плоскости). Обычный вектор «просто отражается». Псевдовектор тоже «просто отражается» и еще меняет направление на противоположное. Банальный пример приведен на рисунке 8. Векторы i и j никак не изменяются при отражении относительно плоскости, которая параллельна той плоскости, в которой они сами лежат. А вот их векторное при зеркальном отражении ведет себя иначе, как это видно на рисунке.

Рис. 8 — Псевдовекторы.

Термины

Хотел упомянуть два термина, которые, наверное, полезно знать. Не знал, куда прилепить, поэтому напишу здесь.

Афинные преобразования — преобразования при которых параллельные прямые переходят в параллельные прямые, пересекающиеся — в пересекающиеся, а скрещивающиеся — в скрещивающиеся. Афинное преобразование необязательно сохраняет длины (т. е. размеры объектов) — например преобразование масштабирования (scale transformation). Также афинное преобразование необязательно сохраняет углы между векторами — например преобразование сдвига (shear transformation). Таким образом, к афинным относятся преобрвазования: поворота, отражения, масштабирования, сдвига.

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *