DEV/MATH[ V0.0.1-ALPHA ]//articles/rotations

О поворотах в играх по-простому

Всем привет! Меня зовут Григорий Дядиченко, я CTO & Founder White Label Games. Уже больше десяти лет занимаюсь разработкой на Unity, и за это время повороты успели побыть для меня и магией, и проклятием, и наконец просто инструментом — но через очень специфическую дорогу.

Сталкивались ли вы с ситуацией, когда персонаж в вашей игре на повороте начинает странно дёргаться? Или, скажем, плавная интерполяция между двумя ориентациями вдруг проходит «через всю комнату»? А может, вы открыли учебник по компьютерной графике, увидели формулу q · v · q⁻¹ со словами «и тут, конечно же, всё интуитивно понятно» — и захлопнули книжку?

Все эти симптомы — про одно и то же. Про повороты в трёхмерном пространстве и про то, как их хранить в памяти, чтобы не плакать. Конечно же, главный герой здесь — кватернионы. Но рассматривать их в вакууме, как делают в большинстве статей, мне кажется неправильным. Поэтому разберём всё семейство: углы Эйлера, матрицы, ось-угол, кватернионы. С плюсами и минусами, без выбора любимчика заранее.

И главное — поймём, откуда у кватернионов четыре числа. Не «возьмите как факт», а действительно поймём, через геометрию двух отражений. Заодно разберём, почему формула q · v · q⁻¹ — сэндвич, а не одно умножение, и почему поворот на 360° в кватернионном мире не равен исходному состоянию.

[больше интересного в телеграм]

Часть 1. Разминка: 2D, где всё ещё просто

Чтож, прежде чем разбирать кватернионы, давайте разогреемся. Поворот в 2D — это, по сути, одно число: угол. И это та редкая страница в статье, где «одно число» — действительно одно число, а не четыре с условиями нормировки.

Но даже здесь, в плоскости, у поворота уже два «лица». Можно описывать его матрицей. Можно — умножением на комплексное число. Два разных языка, один и тот же танец. Разберём.

Один угол — один поворот

Представьте, что вы крутите стрелку компаса. Положение стрелки полностью описывается одним числом — углом от севера. Никаких «трёх компонент с условиями ортогональности», никаких «четырёх чисел с нормировкой». Просто угол. По сути, это и есть поворот в 2D.

В программе вы храните одно число — θ — и можете его прибавлять, вычитать, интерполировать. Поворот на 30°, потом ещё на 60° — это просто 30 + 60 = 90. Никакой магии.

Способ первый: матрица 2×2

Когда нужно реально применить поворот к точке, появляется первая формула. Куда уехала точка (1, 0) после поворота на угол θ? В (cos θ, sin θ) — это видно из определения косинуса и синуса. А точка (0, 1)? В (−sin θ, cos θ) — повёрнутая на 90° вперёд. Эти два повёрнутых вектора и есть столбцы матрицы поворота:

R(θ)=(cosθsinθsinθcosθ)R(\theta) = \begin{pmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{pmatrix}

Применяете её к точке (x, y) — получаете повёрнутую (x cos θ − y sin θ, x sin θ + y cos θ). Готово.

Способ второй: комплексное число

Теперь странный фокус. Берём комплексное число z = x + iy — записываем нашу точку как одно «число». И умножаем его на другое комплексное число — e^{iθ}. Получаем точно ту же повёрнутую точку.

z=eiθz=(cosθ+isinθ)zz' = e^{i\theta} \cdot z = (\cos\theta + i\sin\theta) \cdot z

Почему так — отдельная история, формула Эйлера. Сейчас важнее, что работает: умножение на комплексное число с модулем 1 геометрически означает поворот. А складывание углов превращается в перемножение чисел: e^{iα} · e^{iβ} = e^{i(α+β)}.

Один результат, разные пути

[ DEMO 1.1 ]//

Две механики одного поворота

Покрутите слайдер. Точка слева ездит по кругу, точка справа — тоже. В обеих формулах внизу одинаковые числа — cos θ и sin θ, только в матрице они стоят по углам, а в комплексном числе — рядом, как Re и Im.

Это и есть главный сюжет Части 1: матрица и комплексное число — два языка про один и тот же поворот. Один многословный, другой компактный, оба правильные.

Запоминаем: «поворот в 2D — это число, которое умеет умножаться». В 3D эта роль перейдёт к кватернионам, и окажется, что параллель не случайная.

Часть 2. Три измерения, или где всё ломается

Итак, в 2D было хорошо. Один угол — один поворот. Складываем углы — складывается поворот. Никаких подвохов.

В 3D всё это перестаёт работать. И не из-за «недостатка усилий», а из-за того, что в трёхмерном пространстве у поворотов другая внутренняя структура. Сейчас покажу, что именно ломается.

Порядок имеет значение

Возьмите кубик в руки. Положите его на стол так, чтобы вы видели верхнюю грань. Теперь:

  1. Поверните на 90° вокруг оси X (грань, которая смотрела на вас, теперь смотрит вверх).
  2. Затем поверните на 90° вокруг оси Y (грань, которая смотрела вверх, теперь смотрит вправо).

Запомните результат. А теперь возьмите такой же кубик и сделайте те же два поворота, но в обратном порядке: сначала Y, потом X. Получится другая ориентация.

[ DEMO 2.1 ]//

Порядок имеет значение

В 2D такого не было. Поворот на 30°, потом на 60° — всегда даёт 90°, без разницы, что было первым. В математике это свойство называется коммутативностью: a + b = b + a. Сложение чисел коммутативно, умножение — тоже. А вот 3D-повороты — нет. И это не «исправляется хорошим подходом» — это фундаментально.

Три ручки не работают

Кажется логичным, что у поворота в 3D «три ручки»: yaw, pitch, roll — три угла поворота вокруг трёх осей. Берёшь три числа, крутишь — получаешь любую ориентацию. Удобно же.

Так и сделали — это называется углы Эйлера. Они работают. Но в них есть одна неприятность, которую сейчас невозможно увидеть, а в Части 3 разберём подробно: при определённых ориентациях две из трёх «ручек» начинают двигать кубик одинаково. Третья степень свободы исчезает. Это называется gimbal lock — и ситуация, когда персонаж дёргается на повороте, скорее всего, именно про это.

По сути, проблема такая: трёхмерное пространство ориентаций (математики называют его SO(3) — пространство всех 3D-поворотов. Имеет три степени свободы, но устроено топологически нетривиально (не как куб с тремя осями) — отсюда и гимбал-лок углов Эйлера, и необходимость кватернионов, чтобы покрыть его гладко. «SO» = «special orthogonal», «3» = размерность.) — это не куб с тремя осями. Это нечто покрученее. И попытка покрыть его тремя числами без вырождений — это как попытка нарисовать карту земного шара на одном плоском листе без разрывов и искажений. Не выйдет.

Поэтому существует зоопарк

Раз одного «правильного» способа нет, в практике сложилось несколько. Углы Эйлера — простые, но с гимбал-локом. Матрицы — универсальные, но громоздкие. Ось-угол — наглядные, но плохо интерполируются. Кватернионы — компактные и мощные, но нужна нетривиальная геометрия, чтобы понять, откуда они.

В Части 3 пройдёмся по каждому. С честными плюсами и минусами. Без объявления победителя — кватернионы получат своё место в Части 4, но до этого нужно увидеть, с чем мы их сравниваем.

Часть 3. Зоопарк: четыре способа повернуть кубик

Чтож, давайте знакомиться с зоопарком. Четыре представления ориентации. Каждое со своей механикой, своими плюсами и своими «но». Проходим по очереди, в конце сводная таблица.

3.1. Углы Эйлера: три слайдера и вечная драма

Идея: три последовательных поворота вокруг трёх осей. Yaw — рыскание, поворот вокруг вертикальной оси. Pitch — тангаж, наклон вверх-вниз. Roll — крен, поворот вокруг направления взгляда. Знакомо всем, кто хоть раз настраивал камеру в Unity или Unreal.

Хорош для: ввода от человека (понятные «ручки»), отображения в редакторе, сериализации в файл. Когда вам нужно показать художнику «поверни на 30 градусов влево», углы Эйлера — самый понятный способ.

Плох для: интерполяции (если не повезёт с углами — будет дёргаться), композиции (придётся переводить в матрицу), и главное — гимбал-лок.

[ DEMO 3.1 ]//

Три слайдера и сломанное управление

Покрутите слайдеры. На большинстве комбинаций всё хорошо. Но при pitch ≈ ±90° yaw и roll начинают крутить кубик вокруг одной и той же оси — степень свободы исчезла. Это и есть гимбал-лок — название из авиационных гироскопов, где три кольца карданного подвеса в определённых ориентациях схлопываются в одну плоскость.

Гимбал-лок не «исправляется добавлением четвёртой оси» (хотя в реальных гироскопах так и делают). Он неустраним внутри представления тремя углами. Это просто следствие того, что три числа не покрывают SO(3) — пространство всех 3D-поворотов. Имеет три степени свободы, но устроено топологически нетривиально (не как куб с тремя осями) — отсюда и гимбал-лок углов Эйлера, и необходимость кватернионов, чтобы покрыть его гладко. «SO» = «special orthogonal», «3» = размерность. гладко.

3.2. Матрицы 3×3: девять чисел и куда уехала ось

Идея: поворот как линейное преобразование. В 2D у нас была матрица 2×2:

R(θ)=(cosθsinθsinθcosθ)R(\theta) = \begin{pmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{pmatrix}

В 3D — матрица 3×3, девять чисел в табличке. Но не любых: они должны удовлетворять условиям ортогональности (столбцы попарно перпендикулярны и единичной длины, определитель = +1). Поворот вокруг каждой из трёх координатных осей выглядит так:

Rx(θ)=(1000cosθsinθ0sinθcosθ)R_x(\theta) = \begin{pmatrix} 1 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta \\ 0 & \sin\theta & \cos\theta \end{pmatrix}
Ry(θ)=(cosθ0sinθ010sinθ0cosθ)R_y(\theta) = \begin{pmatrix} \cos\theta & 0 & \sin\theta \\ 0 & 1 & 0 \\ -\sin\theta & 0 & \cos\theta \end{pmatrix}
Rz(θ)=(cosθsinθ0sinθcosθ0001)R_z(\theta) = \begin{pmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{pmatrix}

Заметьте: 2D-матрица — это в точности «угол» 3×3-матрицы R_z, с осью Z как осью вращения. То есть 2D-поворот — это просто 3D-поворот вокруг Z. Все остальные 3D-повороты — комбинации R_x, R_y, R_z через перемножение (порядок важен — это та же некоммутативность из Части 2).

Лайфхак для интуиции: столбцы матрицы — это куда поехали базисные векторы X, Y, Z после поворота. Первый столбец — куда уехала ось X. Второй — куда Y. Третий — куда Z. Это гораздо нагляднее, чем смотреть на матрицу как на «таблицу чисел».

[ DEMO 3.2 ]//

Матрица как тройка базисных векторов

Хорош для: применения к вершинам (v' = M · v — стандартная операция в шейдерах), композиции (M₁ · M₂ — поворот сначала на M₂, потом на M₁), универсальности (4×4 матрица заодно делает ещё и сдвиг — вся графика на этом).

Плох для: хранения (девять чисел вместо трёх — расточительно), интерполяции (как промежуточно поворачивать матрицы? отдельная боль), и численного дрейфа: после серии умножений матрица перестаёт быть точно ортогональной, нужна периодическая ре-ортогонализация.

3.3. Ось и угол: теорема Эйлера

Идея: теорема Эйлера говорит, что любой 3D-поворот, каким бы хитрым он ни был, эквивалентен повороту вокруг одной оси на один угол. Это, может быть, самое красивое утверждение во всей этой истории.

Хранится так: единичный вектор оси n = (nx, ny, nz) плюс угол θ. Итого четыре числа, но с одной связью (‖n‖ = 1), то есть три степени свободы. Никакого вырождения по углам, никакого гимбал-лока.

[ DEMO 3.3 ]//

Стрелка и угол

Хорош для: интуиции и отладки. Когда вы видите такое представление, вы понимаете поворот геометрически — где ось, насколько крутим. Полезно для дебага.

Плох для: интерполяции (как линейно интерполировать ось, чтобы получился осмысленный «промежуточный поворот»? тоже не очевидно), композиции (нужны хитрые формулы Родрига), и вырождения при θ = 0 (ось не определена — куда поворачивать, если поворот нулевой?).

3.4. Кватернионы: четыре загадочных числа

Идея: ну, сейчас я вам её толком и не расскажу. Это будет в Части 4.

Просто посмотрите. Четыре числа (w, x, y, z). Условие нормировки w² + x² + y² + z² = 1. Применяется к точке по формуле сэндвича q · v · q⁻¹. Откуда всё это берётся, пока неясно.

[ DEMO 3.4 ]//

Четыре загадочных числа

Хорош для: интерполяции (есть SLERP — гладкая интерполяция по дуге), композиции (просто перемножение), компактного хранения (четыре числа), численной устойчивости (легко нормализовать), отсутствия гимбал-лока. По сути, всё.

Плох для: интуиции. Никто не понимает, что такое i² = j² = k² = ijk = −1, пока не нарисует две плоскости-зеркала.

Вот это «всё на свете умеют, но никто не понимает» и есть та причина, по которой кватернионы в большинстве статей объясняют как магию. Часть 4 это исправляет.

Сводная таблица

ЭйлерМатрицаОсь-уголКватернион
Чисел3944
Интуитивендасреднеданет
Интерполируетсяплохоплохоплохоотлично
Композируетсячерез матрицудачерез кватернионда
Гимбал-локестьнетнетнет
Хранениедёшеводорогосреднедёшево
Численный дрейфнетестьмаломало

Вы смотрите на это и видите, как кватернионы выигрывают почти по всем строчкам, кроме интуитивности. Что и составляет загадку: «как такая мощная вещь может быть настолько непонятной?»

Собственно, в Части 4 разбираемся. Без магии.

Часть 4. Кватернионы по-настоящему

Вот мы и пришли. Часть, ради которой всё затевалось. Сейчас разберём, что такое кватернион геометрически, откуда у него четыре числа, что значат i, j, k, почему сэндвич, и зачем 720° равно нулю.

Если хотя бы один из этих вопросов вызывает дискомфорт — нормально. К концу части их не останется.

4.1. Поворот = два отражения

Это утверждение — главный гвоздь всей этой истории. Запомните его, остальное вырастает из него.

Любой поворот в 3D можно получить как композицию двух отражений в плоскостях. Угол между плоскостями — половина угла поворота. Ось поворота — линия пересечения плоскостей.

Возьмите два зеркала и поставьте их под углом α/2. Положите между ними объект и мысленно сделайте: отразите его в первом зеркале, потом во втором. Объект окажется повёрнутым на угол α вокруг оси, проходящей по линии пересечения зеркал.

[ DEMO 4.1 ]//

Два зеркала

Частные случаи:

  • Параллельные зеркала (угол между ними ) → нет поворота.
  • Перпендикулярные зеркала (угол 90°) → поворот на 180°.
  • Зеркала под углом 45° → поворот на 90°.

Это не просто красивое наблюдение. Это причина, по которой кватернионы устроены так, как устроены. Кватернион — это компактная запись пары зеркал. Скалярная часть w отвечает за угол между зеркалами. Векторная часть (x, y, z) — за направление линии пересечения зеркал (она же ось поворота). Половинный угол, который везде в формулах кватерниона возникает «откуда-то», — это просто угол между зеркалами, потому что два зеркала под α/2 дают поворот на α.

Половинный угол не магия. Это бухгалтерия двух отражений.

4.2. Почему именно четыре числа

Самый ответственный подраздел. Идём слоями — каждый следующий проясняет предыдущий.

Слой 1. Наивный счёт. У поворота в 3D три степени свободы: два числа на направление оси (углы широты и долготы) и одно на сам угол. Итого три. Кажется, трёх должно хватать.

Слой 2. Топологическая проблема. Пространство ориентаций (SO(3) — пространство всех 3D-поворотов. Имеет три степени свободы, но устроено топологически нетривиально (не как куб с тремя осями) — отсюда и гимбал-лок углов Эйлера, и необходимость кватернионов, чтобы покрыть его гладко. «SO» = «special orthogonal», «3» = размерность.) нельзя гладко покрыть тремя числами без вырождений. Никаким способом. Это не «нужно ещё подумать», это математически доказано.

Аналогия (намеренно спустимся на одну размерность вниз — на обычную 2-сферу): попробуйте покрыть земной шар координатной сеткой долгота/широта. На полюсах долгота вырождается — там «все направления одновременно», на полюсе долгота не определена. Любая попытка ввести две координаты на сферу оставит особые точки.

Тут полезно вспомнить Часть 1. Там мы выяснили, что 2D-поворот элегантнее всего описывается одним комплексным числом e^{iθ} — вместо двух координат (cos θ, sin θ). Любопытная параллель: 2-сферу тоже можно описать одним комплексным числом через стереографическую проекцию (это так называемая сфера Римана — сферу проецируют на плоскость комплексных чисел из одной точки, и каждой точке сферы кроме полюса проекции соответствует одно z = x + iy). И там, и там одно «специальное число, которое умеет умножаться» обходит проблему вырождения, с которой не справляются пары координат.

На SO(3) — пространство всех 3D-поворотов. Имеет три степени свободы, но устроено топологически нетривиально (не как куб с тремя осями) — отсюда и гимбал-лок углов Эйлера, и необходимость кватернионов, чтобы покрыть его гладко. «SO» = «special orthogonal», «3» = размерность. происходит то же самое, только размерность ещё выше. Гимбал-лок углов Эйлера — проявление этой топологической особенности. И правильным «специальным числом» для трёхмерных поворотов становится уже не комплексное (двух действительных компонент мало), а кватернион — прямое продолжение той же мысли, только с четырьмя компонентами вместо двух.

Кстати, даже обычную 2-сферу (продолжим аналогию) для гладкого описания обычно параметризуют тремя числами с одной связью: точка на ней — это (x, y, z) с условием x² + y² + z² = 1. Три числа, одна связь, две реальных степени свободы — ровно сколько и должно быть у точки на 2-сфере. SO(3) — пространство всех 3D-поворотов. Имеет три степени свободы, но устроено топологически нетривиально (не как куб с тремя осями) — отсюда и гимбал-лок углов Эйлера, и необходимость кватернионов, чтобы покрыть его гладко. «SO» = «special orthogonal», «3» = размерность. устроен сложнее, на размерность выше — и для него гладкое описание потребует уже четырёх чисел с одной связью. Этим как раз и окажется кватернион. Но сначала — промежуточная попытка.

Слой 3. Ось-угол как промежуточная попытка. Окей, давайте попробуем: три числа на ось (nx, ny, nz) плюс угол θ. Четыре числа с одной связью ‖n‖ = 1. Три степени свободы — уже хорошо. Но: при θ = 0 ось не определена (куда поворачивать, если не поворачиваем?). И интерполяция по такой паре — не очевидна. Не идеально.

Слой 4. Половинный угол. А что если взять не n и θ, а закодировать их хитрее? Скажем:

q=(cos(θ/2),  sin(θ/2)n)q = (\cos(\theta/2), \; \sin(\theta/2) \cdot \mathbf{n})

То есть w = cos(θ/2) — скаляр, а (x, y, z) = sin(θ/2) · n — вектор. Четыре числа. Условие w² + x² + y² + z² = 1 — единичная сфера в 4D, которую математики называют S³.

И вот тут происходит чудо. Половинный угол берётся не с потолка — он следует из тех самых двух отражений из 4.1: два зеркала под θ/2 дают поворот на θ. А кватернион — компактная запись пары зеркал. Поэтому половинка везде.

Заодно дважды-покрытие: q и −q дают один и тот же поворот. Это побочный эффект половинного угла (cos(θ/2 + π) = −cos(θ/2), sin(θ/2 + π) = −sin(θ/2)). И это не баг, а фича — именно дважды-покрытие позволяет сделать пространство кватернионов гладким (без особых точек), в отличие от ось-угла.

[ DEMO 4.2 ]//

Глобус ориентаций (стереографическая проекция S³)

Итого: четыре числа с одной связью → три степени свободы → дважды-покрывает SO(3) — пространство всех 3D-поворотов. Имеет три степени свободы, но устроено топологически нетривиально (не как куб с тремя осями) — отсюда и гимбал-лок углов Эйлера, и необходимость кватернионов, чтобы покрыть его гладко. «SO» = «special orthogonal», «3» = размерность. → нет вырождений → есть гладкая интерполяция. Победа.

4.3. i, j, k без мистики

Историю кватернионов обычно начинают с Гамильтона и его правил i² = j² = k² = ijk = −1. Это правда красиво, но без геометрии — непонятно, почему именно так.

С геометрией двух отражений всё встаёт на свои места.

i, j, k — это не «мнимые единицы трёх сортов». Это три ортогональные плоскости отражения. i — отражение от плоскости YZ. j — от плоскости XZ. k — от плоскости XY.

Что значит i² = −1? Применили отражение i дважды. Что получилось? Композиция двух отражений в одной и той же плоскости — это разворот на 360°. И в кватернионной алгебре 360° поворота — это −1 (потому что половинный угол: cos(360°/2) = cos(180°) = −1). Вот и i² = −1.

Что значит i · j? Композиция отражения в YZ и отражения в XZ. Эти плоскости перпендикулярны и пересекаются по оси Z. Композиция двух отражений в перпендикулярных плоскостях — поворот на 180° вокруг линии пересечения. То есть i · j = k (поворот на 180° вокруг Z, что в кватернионной записи и есть k).

И так далее. Все правила Гамильтона выводятся из геометрии композиции отражений. Никакой магии.

[ DEMO 4.3 ]//

Кватернион из двух зеркал

Отдельно — что значат скалярная и векторная части. У кватерниона q = (w, x, y, z) четыре компоненты:

  • w = cos(θ/2) — косинус половинного угла между зеркалами.
  • (x, y, z) = sin(θ/2) · n — синус половинного угла, помноженный на единичный вектор вдоль линии пересечения зеркал (он же ось поворота).

При θ = 0 (зеркала параллельны, нет поворота) → w = 1, (x, y, z) = (0, 0, 0) → единичный кватернион. При θ = 360° (полный оборот) → w = −1, (x, y, z) = (0, 0, 0) → минус-единичный. И вот это самое −1 — как раз то самое дважды-покрытие.

4.4. Формула q·v·q⁻¹ как сэндвич

Окей, кватернион определён. Как с его помощью повернуть точку?

Можно было бы наивно думать: q · v — поворачиваем вектор. Но эта операция работает не так. Если просто умножить q · v (где v — чистый вектор, то есть кватернион с w = 0), получится:

  1. Поворот на удвоенный угол (потому что в кватернионе сидит половинный угол, и одно умножение даёт лишь половину «целого» поворота).
  2. И ещё длина исказится.

Ни то, ни другое не годится.

Сэндвичq · v · q⁻¹ — это композиция двух операций: умножение слева на q, умножение справа на q⁻¹. Они вместе компенсируют искажение длины и удваивают эффект поворота — в результате получается ровно тот поворот на θ, который мы хотели.

Геометрически: q · v — это «применили половину поворота». · q⁻¹ — «применили ещё половину поворота, но с другой стороны, чтобы скомпенсировать длину». Два полуповорота в сумме дают полный поворот. Вот и сэндвич.

[ DEMO 4.4 ]//

Анатомия сэндвича

Если вы программист и вас бесит «почему нельзя одним умножением» — теперь понятно, почему. Потому что кватернион хранит половинный угол. И чтобы получить «целый» эффект, нужно дважды.

4.5. Plate trick — главный аттракцион

И теперь самая красивая часть. Помните, мы говорили: q и −q — один и тот же поворот? То есть в SO(3) — пространство всех 3D-поворотов. Имеет три степени свободы, но устроено топологически нетривиально (не как куб с тремя осями) — отсюда и гимбал-лок углов Эйлера, и необходимость кватернионов, чтобы покрыть его гладко. «SO» = «special orthogonal», «3» = размерность. есть «короткий путь» к ориентации и «длинный путь» через всю S³, и оба приходят туда же. Эта двойственность имеет физическое следствие.

Поворот на 360° ≠ исходное состояние. Поворот на 720° = исходное.

Это звучит безумно. Но это правда. И есть классический фокус, который это демонстрирует физически — он называется Dirac belt trick (фокус с ремнём Дирака).

Как проделать дома (нужен ремень, длинный шарф или собачий поводок):

  1. Возьмите ремень длиной хотя бы метр. Один конец прикрепите к чему-то неподвижному — зажмите дверью, привяжите к ручке шкафа, или попросите кого-то держать. Второй конец держите в правой руке, ремень натянут горизонтально.
  2. Держите свой конец плашмя (например, представьте, что в руке пряжка с надписью «вверх», и эта надпись должна оставаться сверху всё время).
  3. Первый оборот (0° → 360°): поверните ваш конец вокруг вертикальной оси на полный оборот, не наклоняя пряжку. Ремень между руками теперь закручен на один виток.
  4. Попытайтесь распутать: не вращая свой конец и не отрывая дальний, попытайтесь убрать виток. Можете обходить ремень с любой стороны, поднимать его над головой, опускать к ногам — виток никуда не денется. Один виток упрямо остаётся.
  5. Второй оборот (360° → 720°, в ту же сторону): продолжите крутить пряжку, ещё один полный оборот в ту же сторону. На ремне теперь два витка, выглядит ещё плотнее закрученным.
  6. А теперь распутайте: не вращая свой конец, проведите свой кулак через петлю. Конкретно: подведите вашу руку (со всё ещё горизонтальной пряжкой) под ремень, потом перенесите её над ремнём — то есть сделайте кулаком траекторию, которая проходит через витую часть. Один такой проход снимает сразу два витка, и ремень распрямляется. Пряжка осталась в той же ориентации, в которой была после двух оборотов.

Между 360° и 720° ваш конец крутился только вперёд, никаких разворотов назад. И в первом случае распутать было нельзя, а во втором — можно.

Почему 360° распутать нельзя, а 720° — можно? Каждое «провести конец ремня сквозь петлю» меняет число витков сразу на ±2 (пряжка прошла над и под ремнём — это два изменения сразу). Значит, чётность числа витков сохраняется при любых таких операциях. Один виток — нечётное число; ноль — чётное; перевести одно в другое никакими «проходами через петлю» нельзя. Два витка — уже чётное число, ноль достижим. Это и есть против на языке топологии: пути длиной стягиваются в точку, пути длиной — нет.

В демо ниже у нас не один ремень, а три ленты — топология та же. Кнопка «распутать ленты» доступна только на 720°, где число витков чётное. Анимация теперь делает это честно: каждая лента по очереди отрывается от своей точки крепления, проходит над кубиком и возвращается обратно, и за этот проход с неё снимаются сразу два витка. Это та самая операция «провести конец через петлю» из ремня. Кубик при этом не вращается — он стоит на месте всё распутывание.

[ DEMO 4.5 ]//

Чашка на ладони — plate trick

Это не подвох и не оптическая иллюзия. Это физическое проявление того, что в S³ путь от identity до identity длиной 360° (туда же, но через −q) — топологически нетривиален, а путь длиной 720° (через q обратно к identity) — тривиален.

В физике эта же штука называется спинорной геометрией. Электрон ведёт себя как объект на ремне: его «полный оборот» — это , а не . И это не случайность — это та же топология SO(3) — пространство всех 3D-поворотов. Имеет три степени свободы, но устроено топологически нетривиально (не как куб с тремя осями) — отсюда и гимбал-лок углов Эйлера, и необходимость кватернионов, чтобы покрыть его гладко. «SO» = «special orthogonal», «3» = размерность., что у нас в кватернионах. Если хочется глубже, копайте в сторону спиноров и групп Ли. Не для слабонервных.

4.6. SLERP и практика

Чтож, теории достаточно. Поговорим о применении.

Самая популярная операция с кватернионами — интерполяция между двумя ориентациями. Скажем, у вас камера в точке A смотрит туда-то, в точке B — сюда-то, и нужно за полсекунды плавно переехать. Три способа:

LERP — линейная интерполяция компонент. Тупо q(t) = (1−t) · q₁ + t · q₂. Дёшево. Но результат не нормирован, и идёт не по дуге, а по хорде сферы. Плюс, если q₁ · q₂ < 0 (то есть на S³ они «по разные стороны») — пойдёт длинным путём через всю сферу.

NLERP — LERP плюс нормализация. То же самое, но в конце делим на ‖q‖. Норма теперь правильная, но скорость по дуге неравномерная — ускоряется в середине.

SLERP — Spherical Linear intERPolation. Идёт по дуге большого круга на S³ с равномерной угловой скоростью. Формула:

slerp(q1,q2,t)=sin((1t)Ω)sinΩq1+sin(tΩ)sinΩq2\text{slerp}(q_1, q_2, t) = \frac{\sin((1-t)\Omega)}{\sin\Omega}\, q_1 + \frac{\sin(t\Omega)}{\sin\Omega}\, q_2

где Ω — угол между q₁ и q₂ на S³.

[ DEMO 4.6 ]//

Три способа интерполяции

На практике почти везде используется NLERP. SLERP идеален математически, но дороже (там тригонометрия), и для большинства геймдев-сценариев NLERP визуально неотличим. SLERP оставляйте для случаев, где вам действительно важна равномерная угловая скорость.

Типичные баги:

  • Не нормировали кватернион после серии умножений — медленно дрейфует от единичного, ориентации становятся скошенными. Лечится периодической нормализацией.
  • Не выбрали короткий путь. Если q₁ · q₂ < 0, нужно заменить q₂ на −q₂ перед интерполяцией. Иначе ваша камера сделает оборот через всю сцену вместо короткой дуги.
  • Перепутали порядок умножения. Кватернионы некоммутативны. q₁ · q₂ — это сначала q₂, потом q₁. Многих это сбивает.

Эпилог. Куда расти

Если вы дочитали досюда — поздравляю, вы теперь знаете о поворотах в 3D больше, чем 95% людей, которые ими пользуются. Серьёзно. Большинство геймдев-разработчиков работает с кватернионами как с волшебным ящиком: «крутит, не дёргается, и слава богу». А вы теперь понимаете, что внутри две плоскости-зеркала, половинный угол и дважды-покрытие SO(3) — пространство всех 3D-поворотов. Имеет три степени свободы, но устроено топологически нетривиально (не как куб с тремя осями) — отсюда и гимбал-лок углов Эйлера, и необходимость кватернионов, чтобы покрыть его гладко. «SO» = «special orthogonal», «3» = размерность.. Это уже другой уровень разговора с самим инструментом.

Куда копать дальше, если зацепило:

  • Двойные кватернионы (dual quaternions) — обобщение, которое за один объект объединяет и поворот, и сдвиг. В геймдеве используются для скиннинга мешей: классический weighted-кватернионный скиннинг даёт артефакты при сильных деформациях, dual quaternion skinning их убирает. Если делаете персонажей с гибкими руками — это ваше.

  • Геометрическая алгебра (она же Clifford algebra, и её прикладной кусок — роторы). Это обобщение «поворот через зеркала» на любую размерность пространства. Многие считают, что с этого надо начинать преподавать повороты, а кватернионы — частный случай для 3D. То, что мы делали с двумя зеркалами в Части 4, — частный случай одного оператора в GA. Очень красиво, но требует другого подхода к мышлению.

  • Спиноры — то же место, что и plate trick, но в физике. Если квантовая механика говорит, что у электрона спин-1/2 и его волновая функция меняет знак при повороте на 360°, а на 720° возвращается, — это ровно та же топология, что у нас в кватернионах. SU(2) и SO(3) — пространство всех 3D-поворотов. Имеет три степени свободы, но устроено топологически нетривиально (не как куб с тремя осями) — отсюда и гимбал-лок углов Эйлера, и необходимость кватернионов, чтобы покрыть его гладко. «SO» = «special orthogonal», «3» = размерность., та же история. Если интересно, копайте в сторону групп Ли и накрытий.

  • SLERP в продакшне. Если будете писать своё — гляньте, как сделано в Unity (Quaternion.Slerp) или Unreal (FQuat::Slerp): проверенные реализации с учётом всех численных подводных камней. Классическая статья, в которой SLERP и был сформулирован, — Кен Шумейкер, 1985 год, гуглится по «Shoemake quaternion curves».

И на этом всё. Надеюсь, статья была полезна — особенно если до неё кватернионы казались магией. Магии в графике, как и почти везде, на самом деле нет: есть только геометрия с хорошим костюмом.

[больше интересного в телеграм][другие статьи и квизы на dev-math.ru]
// нужна помощь в проекте?

Консультации и техническое сопровождение

Геймдев, графика, AR/VR и computer vision — с математикой как ядром. Разбор вашей задачи и предложение по формату — бесплатно.

Оставить заявку →