В этой статье я кратко расскажу, как именно преобразуются координаты точек при повороте камеры в 3D играх, css-преобразованиях и вообще везде, где есть какие-то вращения камеры или предметов в пространстве. По совместительству это будет кратким введением в линейную алгебру: читатель узнает, что такое (на самом деле) вектор, скалярное произведение и, наконец, матрица поворота.
Задача. Предположим, что у нас есть двумерная картинка, как ниже (домик).
![](https://habrastorage.org/r/w1560/files/845/568/356/845568356a554d78b9f0a30e4f5841ae.png)
Предположим также, что мы смотрим на домик из начала координат в направлении ОХ. Теперь мы повернулись на некоторый угол против часовой стрелки. Вопрос: как будет выглядеть для нас домик? Интуитивно понятно, что результат будет примерно такой, как на картинке ниже.
![](https://habrastorage.org/r/w1560/files/ab4/374/2ab/ab43742abaef4a2788450996631fb35a.png)
Но как рассчитать результат? И, что хуже всего, как это делать в трехмерном пространстве? Если, например, мы вращаем камеру очень хитро: сначала вдоль оси ОZ, потом ОX, потом OY?
Ответы на эти вопросы даст статья ниже. Вначале я расскажу, как представлять домик в виде цифр (то есть расскажу о векторах), потом—о том, что такое углы между векторами (то есть о скалярном произведении) и наконец—о том, как вращать камеру (о матрице поворота).
Давайте подумаем о векторах. То есть о стрелках с длиной и направлением.
![](https://habrastorage.org/r/w1560/files/52f/dd1/bc2/52fdd1bc2b0e454da116f6a3cd236355.png)
В приложении к компьютерной графике—каждая такая стрелка задает точку в пространстве.
![](https://habrastorage.org/r/w1560/files/c77/29b/731/c7729b731bf243c48881dfa44b7f8c1b.png)
Их-то мы и хотим научиться вращать. Потому что когда мы повернем все стрелки на картинке выше, мы повернем домик. Давайте представим, что все, что мы умеем делать с этими стрелками—это складывать и домножать на число. Как в школе: чтоб сложить два вектора, надо провести линию от начала первого вектора до конца второго.
![](https://habrastorage.org/r/w1560/files/aab/050/b02/aab050b02ea840cfbcc5fbf956ad0199.png)
Пока мы сосредоточимся на двумерных векторах. Для начала, нам хотелось бы научиться как-то записывать эти векторы через числа, потому что каждый раз рисовать их на бумаге в виде стрелок не очень удобно.
Научимся мы это делать, вспомнив о том, что любой вектор можно представить как сумму некоторых специальных векторов (ОХ и ОY), возможно, домноженных на определенные коэффициенты. Эти специальные векторы называются базисными, а коэффициенты—ни что иное, как координаты нашего вектора. Если мы будем обозначать базисные векторы через
(здесь i—это индекс вектора, он равен либо 1, либо 2), рассматриваемый вектор как
, а координаты последнего как
, то получим формулу
(1)![](https://habrastorage.org/r/w1560/files/b41/1ef/eab/b411efeab9254ae9a03a756e0f081379.png)
Можно показать, что эти координаты будут единственными для данного базиса.
![](https://habrastorage.org/r/w1560/files/b45/2fb/f27/b452fbf279844eb88f0442324b7182ad.png)
Польза от процедуры приписывания координат нашим стрелочкам очевидна—раньше надо было постоянно рисовать стрелку, чтоб описать вектор, а теперь достаточно просто написать два числа—координаты этого вектора. Например, мы можем условиться писать вот так:
. Или так:
. Тогда
, а
. Замечательно.
В настоящей математике процедура несколько другая. Вначале описываются свойства, которым должны удовлетворять какие угодно объекты, чтобы мы называли их векторами. Они очень естественные. Например, векторы должны поддерживать операцию сложения (двух векторов) и домножения на число. Сумма двух векторов не должна зависеть от порядка слагаемых. Сумма трех векторов не должна зависеть от того, в каком порядке мы их складываем попарно. И т. д. Полный список есть на википедии.
Если наши какие угодно штуки удовлетворяют этим свойствам, то эти штуки можно называть векторами (это такой duck typing). А все множество этих штук—векторным или линейным пространством. Свойства выше называют аксиомами, и из них выводят все остальные свойства векторов (или линейного пространства—отсюда и название «линейная алгебра»). Например, можно вывести, что среди векторов будут существовать такие особые, базисные векторы, через которые можно выразить любой вектор по формуле (1) и то, что это разложение на координаты будет единственным для данного базиса. Очень быстро можно показать, что наши стрелочки как раз удовлетворяют этим аксиомам. Аксиоматический подход удобен, потому что если мы столкнемся с какими-то другим объектами, которые удовлетворяют аксиомам, то к ним сразу можно применить все результаты нашей теории. Кроме того, так мы избегаем определений стрелочек на пальцах в начале теории.
Можно легко показать (из тех самых аксиом), что при сложении векторов их соответствующие координаты будут складываться, а при домножении вектора на число все координаты домножаются на это же число. Теперь, чтоб сложить два вектора, как на картинке ниже, мы можем не рисовать их (и не вести линию от начала первого вектора до конца второго), а писать, например,
.
![](https://habrastorage.org/r/w1560/files/aab/050/b02/aab050b02ea840cfbcc5fbf956ad0199.png)
Давайте теперь введем специальную функцию от двух произвольных векторов a и b, которую будем называть скалярным произведением. Мы будем обозначать его вот так:
. Эти модные скобочки по-научному называются бра- и кет- векторы. Никакой пользы от них пока нет, но выглядит здорово—кроме того, это обозначение все-таки имеет глубокий смысл, особенно если вдаваться в математические подробности или квантовую механику.
В духе нашего аксиоматического подхода мы лишь потребуем, чтоб скалярное произведение удовлетворяло нескольким аксиомам. Если вместо первого вектора взять сумму векторов типа
, то мы хотим, чтоб
. Здесь греческие буквы—множители, x и y—вектора. Еще мы хотим, чтоб если такую сумму подставить вместо второго вектора, то можно сделать такие же преобразования (скалярное произведение суммы векторов тоже оказывается суммой скалярных произведений, а множители также выносятся за скобки). Кроме того, мы хотим, чтоб значения
всегда были неотрицательными. Наконец, мы хотим, чтоб
равнялось нулю тогда и только тогда, когда сам вектор
нулевой. Ах да, и еще, чтоб
.
Если мы добавим к наших стрелочкам на бумаге линейку и транспортир, то этим аксиомам будет удовлетворять функция, известная со школы:
, где длины векторов меряются линейкой, угол между векторами
—транспортиром.
Если же линейки и транспортира у нас нет, то из скалярного произведения можно определить длину вектора—
—и угол между векторами
:
. Конечно же, угол зависит от способа определения скалярного произведения.
Давайте посмотрим, как выражается скалярное произведение через отдельные координаты векторов. Предположим, что у нас есть два вектора a и b, которые выглядят вот так:
,
. Тогда
.
Выглядит не очень. Чтоб сделать жизнь лучше, мы будем дальше работать только с особыми системами координат. Мы выберем только те системы координат, у которых базисные векторы имеют единичную длину и перпендикулярны между собой. Другими словами,
(2a)
, если ![](https://habrastorage.org/r/w1560/files/bfe/cd2/de2/bfecd2de2a454a17a71ca2864623539b.png)
(2b)
, если ![](https://habrastorage.org/r/w1560/files/e79/8c9/b02/e798c9b02d214a448cf83c3cbaef40f1.png)
Такие векторы называются ортонормированными. Выражение для скалярного произведения в ортонормированных системах координат преображается до неузнаваемости:
(3)![](https://habrastorage.org/r/w1560/files/005/9e2/ed6/0059e2ed64874c40a53ed07541c14a2b.png)
Все системы координат в этой статье предполагаются ортонормированными. Удивительно, но из нашего построения следует, что результат формулы (3) не зависит от того, какой именно ортонормированный базис для координат выбрать.
Внимательно посмотрев на картинку «Разложение вектора на координаты» (она приведена еще раз после этого абзаца), можно заподозрить, что координата вектора—это не что иное, как его проекция на соответствующий базисный вектор. То есть значение скалярного произведения исходного вектора с одним из базисных векторов:
(4)![](https://habrastorage.org/r/w1560/files/e9a/ea3/f00/e9aea3f00baf4008b2247eaaeb20f4be.png)
![](https://habrastorage.org/r/w1560/files/b45/2fb/f27/b452fbf279844eb88f0442324b7182ad.png)
Действительно, например,
. Кажется, что это тавтология, потому что координаты базисных векторов в своем же базисе всегда будут (1, 0) и (0, 1). Но ведь мы можем взять другие базисные векторы, и выразить их через старый базис. Например, новый ортонормированный базис может выглядеть в старом базисе как
и
. И тогда мы можем определить, например, первую координату вектора
в новом базисе по формуле (4) как
.
Дотошный читатель скажет «но ведь формулу (3) можно использовать в качестве определения скалярного произведения, и тогда нам не надо никакой ортонормированности базисных векторов». И будет прав в том, что формула (3) может работать как одно из определений скалярного произведения. Но здесь есть тонкий момент: тогда нам надо показать, что при изменении системы координат эта же формула, но с координатами векторов a и b из другого базиса, даст такое же число. А это будет только в том случае, если все базисы ортонормированны. Это можно будет показать, прочитав следующий раздел.
Давайте выясним, как меняются координаты векторов, если мы меняем всю систему координат. Зачем нам вообще менять систему координат? Если немного подумать, то станет ясно, что поворот системы координат эквивалентен повороту камеры в 3D или 2D-моделировании (смотрите ниже чуть модифицированный рисунок с домиком). Так что научиться вращать систему координат—это как раз то, что нам надо.
![](https://habrastorage.org/r/w1560/files/bb8/d52/3d9/bb8d523d9d724acfa67bba04f177c546.png)
Давайте обозначим i-ую координату вектора а в новой системе координат как
, а новые базисные векторы как
. Кроме того, обозначим j-ую координату СТАРОГО базисного вектора i в НОВОМ базисе как
. Наконец, обозначим i-ую координату НОВОГО базисного вектора j в СТАРОМ базисе как
. Теперь мы можем выразить исходный вектор и старые базисные векторы через новые базисные векторы. А именно
(5)
и
(6)![](https://habrastorage.org/r/w1560/files/438/d89/fa6/438d89fa62c04010a9dbf57a33e50176.png)
Кроме того, можно выразить новые базисные векторы через старые:
(7)![](https://habrastorage.org/r/w1560/files/c52/34a/be8/c5234abe89ae450c88c405ce24298d4f.png)
Некоторые из этих разложений изображены на картинке ниже.
![](https://habrastorage.org/r/w1560/files/a85/7d3/fa7/a857d3fa754c41fc92d0669b0137cf1e.png)
Теперь мы просто перепишем формулу (1) через векторы нового базиса:
![](https://habrastorage.org/r/w1560/files/c8a/add/11c/c8aadd11c6854f8eb8c59a16b47009d5.png)
Если сравнить это с формулой (5), и вспомнить, что координаты векторов определены однозначно, то можно заметить, что
(8)
и ![](https://habrastorage.org/r/w1560/files/6a7/425/96e/6a742596e40c4b2cb486d4f24c23eb62.png)
Удивительно, как эти формула похожи на уравнения (3)! Они выглядят как скалярное произведение вектора
с определенными векторами (ниже мы покажем, что это не случайно).
Если записать уравнения (9) одной формулой, то получится
(9)![](https://habrastorage.org/r/w1560/files/e29/93a/58f/e2993a58fb544494b7d1bce9d69c23eb.png)
Эту формулу можно вывести по-другому, объединив (4), (1) и (7):
. Если вспомнить свойства скалярного произведения, то эта формула распадается на четыре суммы. Если же теперь вспомнить о том, что наши базисные векторы ортонормированны, то получаем
.
Эта формула выглядит не совсем так, как формула (9): вместо
здесь стоят
. Это не ошибка—просто
. То есть j-ая координата СТАРОГО базисного вектора i в НОВОМ базисе
всегда равна i-ой координате НОВОГО базисного вектора j в СТАРОМ базисе,
. Это станет очевидно, если попытаться выразить эти координаты через формулу (4):
(10)
, ![](https://habrastorage.org/r/w1560/files/021/1f4/b11/0211f4b116104938a1426a062ac3c0e3.png)
А скалярное произведение, как мы помним, не зависит от порядка произведения векторов.
Это станет еще более очевидно, если вспомнить о том, что эти скалярные произведения—это углы между разными (единичными) базисными векторами. Эти углы, разумеется, не зависят от того, откладывать ли их по или против часовой стрелки.
Если вы знакомы с матрицами, то формулу (9) (две формулы) можно переписать в матричном виде
(11)![](https://habrastorage.org/r/w1560/files/bbc/cba/888/bbccba888c264a6cb9ea21b4d193b1ce.png)
Что это вообще все значит?! Как обычно, здесь нет никакой магии—просто мы договариваемся, что домножение этой таблички из чисел слева на вектор справа вычисляется как формула (9). То есть каждую строчку таблички мы домножаем на столбец справа (как будто бы мы совершаем скалярное произведение двух векторов), и результаты записываем друг под другом, тоже получая столбец.
Можно, кстати, распространить это правило на перемножение двух табличек: договоримся домножать каждую строчку первой таблички на каждый столбец второй, и результаты тоже записывать в табличку: перемножение первой строчки с третьим столбцом запишем в первую строчку и третий столбец. Формула (11) тогда становится частным случаем этого правила. Схематически это все изображено на рисунке ниже.
![](https://habrastorage.org/r/w1560/files/20c/fbb/187/20cfbb18759c4124aee9abba9ca8308f.png)
Таблички из чисел, снабженные таким правилом домножения на векторы и другие таблички, будем называть матрицами.
Правило перемножения матриц выглядит еще более естественно, если узнать, что скалярное произведение, как мы договорились его обозначать,
, на самом предполагает, что вектор a—это строка, а вектор b—столбец. До этого мы договорились записывать векторы столбцами, и вектор-строка a на самом деле—это не просто перевернутый вектор a, а объект специального двойственного векторного пространства. Но в случае ортонормированных базисов координаты исходного и двойственного векторов совпадают (то есть а-строка и а-столбец одинаковы), так что эти детали не влияют на наше изложение. Все нормально. Кроме того, с другой стороны, формула (3) для скалярного произведения—это частный случай перемножения матриц.
Вообще, можно показать, что домножение на матрицы соответствует определенным трансформациям векторов. Вместо «трансформации» принято говорить операторы. А домножение на матрицу является линейным оператором. Кроме того, для любого линейного оператора существует одна и только одна матрица оператора. О том, что это такое, можно почитать на википедии. Если описывать это здесь, то статья никогда не закончится.
Итак, как мы выяснили, поворот системы координат эквивалентен повороту камеры в 3D или 2D-моделировании. Поэтому матрица из формулы (11) называется матрицей поворота. Формулу (11) можно интерпретировать не как замену системы координат, а как описание оператора поворота.
Пока не совсем понятно, как вычислять эту матрицу. Это несложно, если обратиться к формулам (10) и вспомнить, что косинус угла между единичными векторами равен их скалярному произведению. Тогда, например,
, где
—угол вращения системы координат (камеры). Он по определению положительный, если вращение происходит против часовой стрелки, и отрицательный при вращении по часовой стрелке (смотрите рисунок чуть ниже—он уже был, но теперь снова актуален). Если немного повозиться с геометрией или тригонометрией, то можно выяснить, что вся матрица поворота выглядит вот так:
(12a)![](https://habrastorage.org/r/w1560/files/d60/685/90d/d6068590dc394cf2baaf566fbf755344.png)
Если заглянуть в википедию, то формула там выглядит немного по-другому:
(12b)![](https://habrastorage.org/r/w1560/files/32b/542/629/32b54262965e42c6ba52d303a09ca556.png)
Это все потому, что
в ней—это угол вращения самих векторов (или наших объектов типа домика). Он тоже считается положительным, если вращение происходит против часовой стрелки. Но вращение камеры противоположно вращению объектов, то есть угол вращения камеры равен углу вращения объектов, но взятому с обратным знаком.
Снова актуальный рисунок:
![](https://habrastorage.org/r/w1560/files/bb8/d52/3d9/bb8d523d9d724acfa67bba04f177c546.png)
Эти матрицы—очень удобные штуки. Их можно перемножать и складывать, притом не только по две, но и по три и по четыре. Матрицы можно обозначать большими буквами. Например, Т—обычное обозначение для матриц поворота (она, безусловно, зависит от того, как поворачивать систему или камеру). Тогда формула (11) перейдет в
.
У матриц есть единичная матрица—в том смысле, что любой вектор, будучи домноженным на нее слева, остается самим собой. И любая матрица тоже. Единичная матрица выглядит как таблица, вся заполненная нулями, только на диагонали стоят единицы. Такая матрица соответствует повороту на ноль градусов. То есть отсутствию поворота. Она выглядит так:
(13)![](https://habrastorage.org/r/w1560/files/7a4/5b0/63d/7a45b063db0e44e4a4a249c0ac918f53.png)
Если немного посчитать или подумать, то перемножение матриц поворота соответствует нескольким поворотам, совершенным один за другим (но в другом порядке—справа налево). Так что если в вашей программе происходит несколько заранее известных поворотов подряд, то не спешите их применять для всех точек в вашем трехмерном мире. Лучше перемножьте матрицы, получите общую матрицу поворота, и уже примените ее.
Для многих матриц можно найти такие, что при домножении первой на вторую получается единичная матрица. Эти новые матрицы называются обратными, и для T они обозначаются как
. То есть
.
Если еще немного подумать, то такая матрица
должна соответствовать обратному для Т повороту—такому, который нейтрализует Т. Это уже очень удобно. Если научиться вычислять обратные матрицы, можно легко вращать камеру обратно, при необходимости.
Если подумать совсем не немного, и даже немного посчитать, то для матриц поворота, в силу того, что они обладают специальной структурой (см. формулу (10)), очень легко получить обратные матрицы: достаточно просто повернуть матрицу вдоль диагонали из левого верхнего в правый нижний угол (той самой диагонали, вдоль которой стоят единицы в единичной матрице). Эта операция называется транспонированием. Она намного (намного-намного) быстрее, чем поиск обратной матрицы в общем случае.
Наконец, перейдем к трехмерному пространству. Все формулы преобразуются тривиально—в векторах оказывается три координаты, в скалярном произведении—три суммы и т. п.
Сложность возникает только с матрицей поворота. Интуитивно почти очевидно, что любой поворот представим в виде последовательности трех поворотов (вдоль OX, OY, OZ) (впрочем, надо слегка потрудиться, чтоб это показать)—так что этими тремя углами поворота можно задать любое вращение. Три матрицы, соответствующие этим поворотам, можно перемножить, и получить общую матрицу для любого трехмерного поворота (с тремя параметрами—углами поворотов относительно координатных осей). Ее вид можно найти на википедии.
Можно показать, что вместо трех углов любой трехмерный поворот можно задать вектором, вокруг которого происходит поворот, и углом, на который мы вращаем камеру вдоль этого вектора (положим, против часовой стрелки, если смотреть с конца вектора). Как ни странно (а точнее, разумеется), этот способ тоже требует три числа. Поскольку длина вектора нам не важна, мы можем сделать его единичной длины. Тогда, чтоб его задать, нам потребуется только два числа (например, два угла—скажем, относительно OX и OY). К этим двум числам добавляется угол, на который мы будем совершать вращение относительно вектора. Формулу для матрицы поворота с этими параметрами тоже можно найти на википедии.
На этом все, спасибо за внимание.
P.S. В процессе подготовки статьи выяснилось, что формулы выглядят немного размытыми, хоть и сохранены вроде бы с 600 dpi в png. Видимо, так неудачно Inkscape сохраняет маленькие png. За это я дико извиняюсь, но сил переделывать у меня нет.
P.P.S. Хоть картинки и залиты на habrastorage, но иногда некоторые не отображаются. Видимо, какие-то проблемы с habrastorage. Попробуйте просто перезагрузите страничку
Введение
Задача. Предположим, что у нас есть двумерная картинка, как ниже (домик).
![](https://habrastorage.org/files/845/568/356/845568356a554d78b9f0a30e4f5841ae.png)
Предположим также, что мы смотрим на домик из начала координат в направлении ОХ. Теперь мы повернулись на некоторый угол против часовой стрелки. Вопрос: как будет выглядеть для нас домик? Интуитивно понятно, что результат будет примерно такой, как на картинке ниже.
![](https://habrastorage.org/files/ab4/374/2ab/ab43742abaef4a2788450996631fb35a.png)
Но как рассчитать результат? И, что хуже всего, как это делать в трехмерном пространстве? Если, например, мы вращаем камеру очень хитро: сначала вдоль оси ОZ, потом ОX, потом OY?
Ответы на эти вопросы даст статья ниже. Вначале я расскажу, как представлять домик в виде цифр (то есть расскажу о векторах), потом—о том, что такое углы между векторами (то есть о скалярном произведении) и наконец—о том, как вращать камеру (о матрице поворота).
Координаты вектора
Давайте подумаем о векторах. То есть о стрелках с длиной и направлением.
![](https://habrastorage.org/files/52f/dd1/bc2/52fdd1bc2b0e454da116f6a3cd236355.png)
В приложении к компьютерной графике—каждая такая стрелка задает точку в пространстве.
![](https://habrastorage.org/files/c77/29b/731/c7729b731bf243c48881dfa44b7f8c1b.png)
Их-то мы и хотим научиться вращать. Потому что когда мы повернем все стрелки на картинке выше, мы повернем домик. Давайте представим, что все, что мы умеем делать с этими стрелками—это складывать и домножать на число. Как в школе: чтоб сложить два вектора, надо провести линию от начала первого вектора до конца второго.
![](https://habrastorage.org/files/aab/050/b02/aab050b02ea840cfbcc5fbf956ad0199.png)
Пока мы сосредоточимся на двумерных векторах. Для начала, нам хотелось бы научиться как-то записывать эти векторы через числа, потому что каждый раз рисовать их на бумаге в виде стрелок не очень удобно.
Научимся мы это делать, вспомнив о том, что любой вектор можно представить как сумму некоторых специальных векторов (ОХ и ОY), возможно, домноженных на определенные коэффициенты. Эти специальные векторы называются базисными, а коэффициенты—ни что иное, как координаты нашего вектора. Если мы будем обозначать базисные векторы через
![](https://habrastorage.org/files/8ec/e79/b89/8ece79b898cb4f17a8ff0a8e8ccaee25.png)
![](https://habrastorage.org/files/8f4/7ee/751/8f47ee751f52451c86a5add78f57a65c.png)
![](https://habrastorage.org/files/62f/6f8/9db/62f6f89dbd8843d9bdd3a4dd29f59b96.png)
(1)
![](https://habrastorage.org/files/b41/1ef/eab/b411efeab9254ae9a03a756e0f081379.png)
Можно показать, что эти координаты будут единственными для данного базиса.
![](https://habrastorage.org/files/b45/2fb/f27/b452fbf279844eb88f0442324b7182ad.png)
Польза от процедуры приписывания координат нашим стрелочкам очевидна—раньше надо было постоянно рисовать стрелку, чтоб описать вектор, а теперь достаточно просто написать два числа—координаты этого вектора. Например, мы можем условиться писать вот так:
![](https://habrastorage.org/files/5d9/f99/469/5d9f99469dbe421386c0123997c51872.png)
![](https://habrastorage.org/files/928/ea5/7f0/928ea57f0c8c479f8f8659a445510b8e.png)
![](https://habrastorage.org/files/961/2a7/11a/9612a711a84d4dca8e5f3ade5cc19cb4.png)
![](https://habrastorage.org/files/553/fd6/e12/553fd6e1235444aca5e2efad7d4f2401.png)
В настоящей математике процедура несколько другая. Вначале описываются свойства, которым должны удовлетворять какие угодно объекты, чтобы мы называли их векторами. Они очень естественные. Например, векторы должны поддерживать операцию сложения (двух векторов) и домножения на число. Сумма двух векторов не должна зависеть от порядка слагаемых. Сумма трех векторов не должна зависеть от того, в каком порядке мы их складываем попарно. И т. д. Полный список есть на википедии.
Если наши какие угодно штуки удовлетворяют этим свойствам, то эти штуки можно называть векторами (это такой duck typing). А все множество этих штук—векторным или линейным пространством. Свойства выше называют аксиомами, и из них выводят все остальные свойства векторов (или линейного пространства—отсюда и название «линейная алгебра»). Например, можно вывести, что среди векторов будут существовать такие особые, базисные векторы, через которые можно выразить любой вектор по формуле (1) и то, что это разложение на координаты будет единственным для данного базиса. Очень быстро можно показать, что наши стрелочки как раз удовлетворяют этим аксиомам. Аксиоматический подход удобен, потому что если мы столкнемся с какими-то другим объектами, которые удовлетворяют аксиомам, то к ним сразу можно применить все результаты нашей теории. Кроме того, так мы избегаем определений стрелочек на пальцах в начале теории.
Можно легко показать (из тех самых аксиом), что при сложении векторов их соответствующие координаты будут складываться, а при домножении вектора на число все координаты домножаются на это же число. Теперь, чтоб сложить два вектора, как на картинке ниже, мы можем не рисовать их (и не вести линию от начала первого вектора до конца второго), а писать, например,
![](https://habrastorage.org/files/7f7/286/885/7f72868859c04bf29a5d0d95fe6c5480.png)
![](https://habrastorage.org/files/aab/050/b02/aab050b02ea840cfbcc5fbf956ad0199.png)
Скалярное произведение
Давайте теперь введем специальную функцию от двух произвольных векторов a и b, которую будем называть скалярным произведением. Мы будем обозначать его вот так:
![](https://habrastorage.org/files/9ec/09f/e01/9ec09fe01ebf47ebb75b334013245e71.png)
В духе нашего аксиоматического подхода мы лишь потребуем, чтоб скалярное произведение удовлетворяло нескольким аксиомам. Если вместо первого вектора взять сумму векторов типа
![](https://habrastorage.org/files/e6a/9f2/5eb/e6a9f25eb9d34da9897a7db7aa3df36a.png)
![](https://habrastorage.org/files/c3e/534/10b/c3e53410bd5943939c6aacd7e5e8ef03.png)
![](https://habrastorage.org/files/af9/f1e/660/af9f1e6607dd4ae581402d0d529d3651.png)
![](https://habrastorage.org/files/af9/f1e/660/af9f1e6607dd4ae581402d0d529d3651.png)
![](https://habrastorage.org/files/8f4/7ee/751/8f47ee751f52451c86a5add78f57a65c.png)
![](https://habrastorage.org/files/ca2/6f7/b71/ca26f7b712844fda9ba37f76207d9fac.png)
Если мы добавим к наших стрелочкам на бумаге линейку и транспортир, то этим аксиомам будет удовлетворять функция, известная со школы:
![](https://habrastorage.org/files/a2e/273/c21/a2e273c21e424e08ab0247a34ab69d7d.png)
![](https://habrastorage.org/files/bce/bc5/701/bcebc57011054d5f804dbe9092534e2e.png)
Если же линейки и транспортира у нас нет, то из скалярного произведения можно определить длину вектора—
![](https://habrastorage.org/files/843/7ba/e59/8437bae597b3455a8f84699cfb9f1771.png)
![](https://habrastorage.org/files/bce/bc5/701/bcebc57011054d5f804dbe9092534e2e.png)
![](https://habrastorage.org/files/496/329/cb7/496329cb70884fbcbbd197c9d9c99b4b.png)
Давайте посмотрим, как выражается скалярное произведение через отдельные координаты векторов. Предположим, что у нас есть два вектора a и b, которые выглядят вот так:
![](https://habrastorage.org/files/a97/43f/eec/a9743feecbbb41e28fabd8fd94904069.png)
![](https://habrastorage.org/files/ebb/aa8/515/ebbaa8515a8c4fcd8a82898b8bdb2227.png)
![](https://habrastorage.org/files/d1b/755/dce/d1b755dce0c94d3fa4348d94ae401562.png)
Выглядит не очень. Чтоб сделать жизнь лучше, мы будем дальше работать только с особыми системами координат. Мы выберем только те системы координат, у которых базисные векторы имеют единичную длину и перпендикулярны между собой. Другими словами,
(2a)
![](https://habrastorage.org/files/8df/8c6/659/8df8c66590ea455aac29fe6292d492d0.png)
![](https://habrastorage.org/files/bfe/cd2/de2/bfecd2de2a454a17a71ca2864623539b.png)
(2b)
![](https://habrastorage.org/files/7dd/4b9/021/7dd4b902145048af85689d4d5ce304d2.png)
![](https://habrastorage.org/files/e79/8c9/b02/e798c9b02d214a448cf83c3cbaef40f1.png)
Такие векторы называются ортонормированными. Выражение для скалярного произведения в ортонормированных системах координат преображается до неузнаваемости:
(3)
![](https://habrastorage.org/files/005/9e2/ed6/0059e2ed64874c40a53ed07541c14a2b.png)
Все системы координат в этой статье предполагаются ортонормированными. Удивительно, но из нашего построения следует, что результат формулы (3) не зависит от того, какой именно ортонормированный базис для координат выбрать.
Внимательно посмотрев на картинку «Разложение вектора на координаты» (она приведена еще раз после этого абзаца), можно заподозрить, что координата вектора—это не что иное, как его проекция на соответствующий базисный вектор. То есть значение скалярного произведения исходного вектора с одним из базисных векторов:
(4)
![](https://habrastorage.org/files/e9a/ea3/f00/e9aea3f00baf4008b2247eaaeb20f4be.png)
![](https://habrastorage.org/files/b45/2fb/f27/b452fbf279844eb88f0442324b7182ad.png)
Действительно, например,
![](https://habrastorage.org/files/e90/7fe/404/e907fe404b854e599e9a73dbfc61bd2a.png)
![](https://habrastorage.org/files/5dc/f36/910/5dcf369106a5487c8792adf86ddacda1.png)
![](https://habrastorage.org/files/1c9/efb/763/1c9efb763bda43e89cf74081fee66945.png)
![](https://habrastorage.org/files/8f4/7ee/751/8f47ee751f52451c86a5add78f57a65c.png)
![](https://habrastorage.org/files/c7a/0a7/4b6/c7a0a74b65794ed18423271557801fa9.png)
Дотошный читатель скажет «но ведь формулу (3) можно использовать в качестве определения скалярного произведения, и тогда нам не надо никакой ортонормированности базисных векторов». И будет прав в том, что формула (3) может работать как одно из определений скалярного произведения. Но здесь есть тонкий момент: тогда нам надо показать, что при изменении системы координат эта же формула, но с координатами векторов a и b из другого базиса, даст такое же число. А это будет только в том случае, если все базисы ортонормированны. Это можно будет показать, прочитав следующий раздел.
Поворот системы координат
Давайте выясним, как меняются координаты векторов, если мы меняем всю систему координат. Зачем нам вообще менять систему координат? Если немного подумать, то станет ясно, что поворот системы координат эквивалентен повороту камеры в 3D или 2D-моделировании (смотрите ниже чуть модифицированный рисунок с домиком). Так что научиться вращать систему координат—это как раз то, что нам надо.
![](https://habrastorage.org/files/bb8/d52/3d9/bb8d523d9d724acfa67bba04f177c546.png)
Давайте обозначим i-ую координату вектора а в новой системе координат как
![](https://habrastorage.org/files/5fb/cfe/f4a/5fbcfef4a9bd42b18fbaff7424557445.png)
![](https://habrastorage.org/files/399/36b/6b2/39936b6b22604edcafca0830e2cc3dc4.png)
![](https://habrastorage.org/files/edb/08c/ab1/edb08cab1a794bf39dcb34d1fb33fc28.png)
![](https://habrastorage.org/files/be2/9be/355/be29be355ca445d680a21d1d0d200431.png)
(5)
![](https://habrastorage.org/files/d32/063/026/d32063026bc34c2080e46d749d3070a7.png)
(6)
![](https://habrastorage.org/files/438/d89/fa6/438d89fa62c04010a9dbf57a33e50176.png)
Кроме того, можно выразить новые базисные векторы через старые:
(7)
![](https://habrastorage.org/files/c52/34a/be8/c5234abe89ae450c88c405ce24298d4f.png)
Некоторые из этих разложений изображены на картинке ниже.
![](https://habrastorage.org/files/a85/7d3/fa7/a857d3fa754c41fc92d0669b0137cf1e.png)
Теперь мы просто перепишем формулу (1) через векторы нового базиса:
![](https://habrastorage.org/files/c8a/add/11c/c8aadd11c6854f8eb8c59a16b47009d5.png)
Если сравнить это с формулой (5), и вспомнить, что координаты векторов определены однозначно, то можно заметить, что
(8)
![](https://habrastorage.org/files/f88/f32/f82/f88f32f827ca4a94bd0dc791605d3e79.png)
![](https://habrastorage.org/files/6a7/425/96e/6a742596e40c4b2cb486d4f24c23eb62.png)
Удивительно, как эти формула похожи на уравнения (3)! Они выглядят как скалярное произведение вектора
![](https://habrastorage.org/files/8f4/7ee/751/8f47ee751f52451c86a5add78f57a65c.png)
Если записать уравнения (9) одной формулой, то получится
(9)
![](https://habrastorage.org/files/e29/93a/58f/e2993a58fb544494b7d1bce9d69c23eb.png)
Эту формулу можно вывести по-другому, объединив (4), (1) и (7):
![](https://habrastorage.org/files/7c1/128/483/7c1128483c8640598b83359e28b01f28.png)
![](https://habrastorage.org/files/d78/157/cd3/d78157cd301147a68052fa89d8713588.png)
Эта формула выглядит не совсем так, как формула (9): вместо
![](https://habrastorage.org/files/961/fd7/3a9/961fd73a99f24ea88a2a75abdd3eaa55.png)
![](https://habrastorage.org/files/24f/8ab/cf3/24f8abcf3ac140de99bd55df11fadaf1.png)
![](https://habrastorage.org/files/4a2/b67/0f5/4a2b670f5071479fb42cfa00f30059ce.png)
![](https://habrastorage.org/files/ad5/dec/c8e/ad5decc8ed5549d2a6721dd377a93a1d.png)
![](https://habrastorage.org/files/684/f74/faf/684f74fafd6f4a23b7a5ba7e0eb2d419.png)
(10)
![](https://habrastorage.org/files/bce/b39/103/bceb3910302e4ebd9318ea783c3ca265.png)
![](https://habrastorage.org/files/021/1f4/b11/0211f4b116104938a1426a062ac3c0e3.png)
А скалярное произведение, как мы помним, не зависит от порядка произведения векторов.
Это станет еще более очевидно, если вспомнить о том, что эти скалярные произведения—это углы между разными (единичными) базисными векторами. Эти углы, разумеется, не зависят от того, откладывать ли их по или против часовой стрелки.
Матрица поворота
Если вы знакомы с матрицами, то формулу (9) (две формулы) можно переписать в матричном виде
(11)
![](https://habrastorage.org/files/bbc/cba/888/bbccba888c264a6cb9ea21b4d193b1ce.png)
Что это вообще все значит?! Как обычно, здесь нет никакой магии—просто мы договариваемся, что домножение этой таблички из чисел слева на вектор справа вычисляется как формула (9). То есть каждую строчку таблички мы домножаем на столбец справа (как будто бы мы совершаем скалярное произведение двух векторов), и результаты записываем друг под другом, тоже получая столбец.
Можно, кстати, распространить это правило на перемножение двух табличек: договоримся домножать каждую строчку первой таблички на каждый столбец второй, и результаты тоже записывать в табличку: перемножение первой строчки с третьим столбцом запишем в первую строчку и третий столбец. Формула (11) тогда становится частным случаем этого правила. Схематически это все изображено на рисунке ниже.
![](https://habrastorage.org/files/20c/fbb/187/20cfbb18759c4124aee9abba9ca8308f.png)
Таблички из чисел, снабженные таким правилом домножения на векторы и другие таблички, будем называть матрицами.
Правило перемножения матриц выглядит еще более естественно, если узнать, что скалярное произведение, как мы договорились его обозначать,
![](https://habrastorage.org/files/6a6/472/b81/6a6472b8182f457eb05b3f66ad1748e3.png)
Вообще, можно показать, что домножение на матрицы соответствует определенным трансформациям векторов. Вместо «трансформации» принято говорить операторы. А домножение на матрицу является линейным оператором. Кроме того, для любого линейного оператора существует одна и только одна матрица оператора. О том, что это такое, можно почитать на википедии. Если описывать это здесь, то статья никогда не закончится.
Итак, как мы выяснили, поворот системы координат эквивалентен повороту камеры в 3D или 2D-моделировании. Поэтому матрица из формулы (11) называется матрицей поворота. Формулу (11) можно интерпретировать не как замену системы координат, а как описание оператора поворота.
Пока не совсем понятно, как вычислять эту матрицу. Это несложно, если обратиться к формулам (10) и вспомнить, что косинус угла между единичными векторами равен их скалярному произведению. Тогда, например,
![](https://habrastorage.org/files/654/862/d04/654862d04ea14bce9d6a6f2ca69d0371.png)
![](https://habrastorage.org/files/628/1fb/ee2/6281fbee232a4f7faf2e00f7b523b72f.png)
(12a)
![](https://habrastorage.org/files/d60/685/90d/d6068590dc394cf2baaf566fbf755344.png)
Если заглянуть в википедию, то формула там выглядит немного по-другому:
(12b)
![](https://habrastorage.org/files/32b/542/629/32b54262965e42c6ba52d303a09ca556.png)
Это все потому, что
![](https://habrastorage.org/files/628/1fb/ee2/6281fbee232a4f7faf2e00f7b523b72f.png)
Снова актуальный рисунок:
![](https://habrastorage.org/files/bb8/d52/3d9/bb8d523d9d724acfa67bba04f177c546.png)
Приятные мелочи
Эти матрицы—очень удобные штуки. Их можно перемножать и складывать, притом не только по две, но и по три и по четыре. Матрицы можно обозначать большими буквами. Например, Т—обычное обозначение для матриц поворота (она, безусловно, зависит от того, как поворачивать систему или камеру). Тогда формула (11) перейдет в
![](https://habrastorage.org/files/253/32f/2a2/25332f2a289843eea7eb8fb40c96e344.png)
У матриц есть единичная матрица—в том смысле, что любой вектор, будучи домноженным на нее слева, остается самим собой. И любая матрица тоже. Единичная матрица выглядит как таблица, вся заполненная нулями, только на диагонали стоят единицы. Такая матрица соответствует повороту на ноль градусов. То есть отсутствию поворота. Она выглядит так:
(13)
![](https://habrastorage.org/files/7a4/5b0/63d/7a45b063db0e44e4a4a249c0ac918f53.png)
Если немного посчитать или подумать, то перемножение матриц поворота соответствует нескольким поворотам, совершенным один за другим (но в другом порядке—справа налево). Так что если в вашей программе происходит несколько заранее известных поворотов подряд, то не спешите их применять для всех точек в вашем трехмерном мире. Лучше перемножьте матрицы, получите общую матрицу поворота, и уже примените ее.
Для многих матриц можно найти такие, что при домножении первой на вторую получается единичная матрица. Эти новые матрицы называются обратными, и для T они обозначаются как
![](https://habrastorage.org/files/f49/888/233/f4988823392d4ec893e256f25836cd39.png)
![](https://habrastorage.org/files/885/fa5/8e2/885fa58e26064c299ff27cad81d3a439.png)
Если еще немного подумать, то такая матрица
![](https://habrastorage.org/files/21b/2ae/c50/21b2aec50bab4870821b19ce67ea02fb.png)
Если подумать совсем не немного, и даже немного посчитать, то для матриц поворота, в силу того, что они обладают специальной структурой (см. формулу (10)), очень легко получить обратные матрицы: достаточно просто повернуть матрицу вдоль диагонали из левого верхнего в правый нижний угол (той самой диагонали, вдоль которой стоят единицы в единичной матрице). Эта операция называется транспонированием. Она намного (намного-намного) быстрее, чем поиск обратной матрицы в общем случае.
Трехмерное пространство
Наконец, перейдем к трехмерному пространству. Все формулы преобразуются тривиально—в векторах оказывается три координаты, в скалярном произведении—три суммы и т. п.
Сложность возникает только с матрицей поворота. Интуитивно почти очевидно, что любой поворот представим в виде последовательности трех поворотов (вдоль OX, OY, OZ) (впрочем, надо слегка потрудиться, чтоб это показать)—так что этими тремя углами поворота можно задать любое вращение. Три матрицы, соответствующие этим поворотам, можно перемножить, и получить общую матрицу для любого трехмерного поворота (с тремя параметрами—углами поворотов относительно координатных осей). Ее вид можно найти на википедии.
Можно показать, что вместо трех углов любой трехмерный поворот можно задать вектором, вокруг которого происходит поворот, и углом, на который мы вращаем камеру вдоль этого вектора (положим, против часовой стрелки, если смотреть с конца вектора). Как ни странно (а точнее, разумеется), этот способ тоже требует три числа. Поскольку длина вектора нам не важна, мы можем сделать его единичной длины. Тогда, чтоб его задать, нам потребуется только два числа (например, два угла—скажем, относительно OX и OY). К этим двум числам добавляется угол, на который мы будем совершать вращение относительно вектора. Формулу для матрицы поворота с этими параметрами тоже можно найти на википедии.
На этом все, спасибо за внимание.
P.S. В процессе подготовки статьи выяснилось, что формулы выглядят немного размытыми, хоть и сохранены вроде бы с 600 dpi в png. Видимо, так неудачно Inkscape сохраняет маленькие png. За это я дико извиняюсь, но сил переделывать у меня нет.
P.P.S. Хоть картинки и залиты на habrastorage, но иногда некоторые не отображаются. Видимо, какие-то проблемы с habrastorage. Попробуйте просто перезагрузите страничку