Мы продолжаем цикл статей про математику и смежные дисциплины в компьютерной графике по курсу Александра Паничева, ведущего разработчика логики в UNIGINE. В этот раз поговорим о функциях плавности, которые используются в анимации (и не только). Представим ситуацию, что у нас есть машинка и ее нужно анимировать, чтобы она ездила от одного края экрана до другого и обратно.  Дизайнер: «Надо бы анимировать эту машинку!»     А теперь представим абстрактного программиста-джуна, который ничего не понимает и решает просто использовать функцию lerp (линейную интерполяцию). Получается нереалистично (1-я анимация ниже), и это замечает Дизайнер, который говорит программисту, что машинка должна в начале разгоняться, а в конце замедляться (2-я анимация ниже): То есть нужно нелинейное движение (как это обычно и происходит в жизни). При этом, в следующий раз Дизайнер вообще может сказать, что она должна начать ускоряться с 5-й секунды или ехать не более 3 секунд… Можно, конечно, использовать какие-нибудь функции из физики: С ускорением машины все просто, но как применить эти функции, чтобы машина замедлялась ближе к концу и не проехала дальше нужного? Опытный программист сразу вспомнит про кривые. Например, про так называемую кривую Безье: В интерактивных приложениях мы часто сталкиваемся с двумя видами кривых Безье: квадратичной и кубической. Если воспринимать параметр t за нормализованное время (от 0 до 1), p0 — начальная позиция автомобиля, p2 — конечное, а возвращаемое значение — за текущую позицию автомобиля для текущего времени t, то становится понятно, что, управляя параметром p1 (так называемой контрольной точкой или касательной) мы можем двигать автомобиль либо линейно (p1 между p0 и p2), либо с ускорением (p1 ближе к p0), либо с торможением (p1 ближе к p2). А как сделать плавное ускорение в начале и плавное торможение в конце? Здесь у нас уже есть целых две контрольных точки! Первое для первой половины анимации и второе для второй половины. Можно вызвать bezier_cubic(0, 0, 1, 1, t) и увидеть, что анимация у нас стала той, какую мы хотели видеть. Ну... Почти. С одномерной кубической Безье невозможно контролировать степень искажения, нельзя сделать ускорение и/или торможение еще более сильным. Что делать? Можно сделать поверх текущей кривой вторую, которая будет искажать t (таким образом и работают кривые в катсцен-редакторах)... Но посмотрите на сложность функции: здесь используется 12 умножений, 3 сложения, 1 вычитание и 5 аргументов на входе. Нельзя ли как-то... попроще? И чтобы контроля было больше. Чтобы можно было сильнее изгибать кривую. Конечно можно! Ведь для этого и существуют «функции плавности»! Такого рода анимации, как описанные выше на примере с машинкой, называют Duration-based. У нас есть начало, есть конец и есть продолжительность. Линейное движение — это использование функции lerp(), она же линейная интерполяция. Она есть в любых игровых движках. Нелинейное движение — это использование easing-функций (или функций плавности). Они уже встречаются в движках заметно реже. Например, в движке Unity есть сторонний плагин iTween — он уже много лет в топе по скачиваниям. Помимо разработчиков игровых движков, функциями плавности пользуются веб-дизайнеры и аниматоры. Функции плавности придумал Роберт Пеннер — математик, физик и Flash-разработчик. Его раздражало, что во Flash сглаженная интерполяция работала либо на ускорение (Ease от -100 до 0, In), либо на торможение (Ease от 0 до 100, Out). Прямо, как с квадратичной кривой Безье. Из-за этого для создания того примера с разгоняющейся в начале и замедляющейся в конце машинкой приходилось использовать дополнительные ключевые кадры. Всего Роберт изобрел 30 функций: Кажется, что много, но их можно разделить на 3 категории: In (ускоряющие), Out (затухающие) и InOut (ускорение с затуханием в конце): Рассмотрим самую первую функцию easeInQuad. Выглядит она так: t — это нормализованное время от 0 до 1 (прямо, как в lerp). Выходящее значение — полученная кривая, также в пределах от 0 до 1 (но есть и исключения, например easeOutElastic). Как можно понять, Quad тут написано не просто так: аргумент t мы возводим в квадрат. Последующие три функции Cubic, Quart, Quint делают, соответственно, похожую вещь. Если линейная интерполяция в данном случае — это return t, то остальные варианты — это различные степени t: 3-я, 4-ая, 5-ая. Это и дает изгиб линии — чем выше степень, тем сильнее: Следующие — Sine, Expo и Circ — это синус/косинус, возведение в степень и движение по окружности соответственно. Последняя группа — самые необычные и часто используемые: Elastic (пружина), InBack / OutBack (уход за границу значений от 0 до 1), Bounce (скачки мяча и т.п.). Функции плавности — это необязательно только положение или смещение. Это также может быть масштаб, поворот, изменение цвета и даже искажение времени — все, что имеет движение можно помножить на эти функции плавности. На примере анимации движения, вращения и масштабирования easeInQuad() выглядит так: Out-вариант данной функции выглядит так: Результат: А это уже совмещение двух предыдущих графиков: Результат: Как можно увидеть ниже, квадратичная функция (во 2-й степени) — самая плавная, но и самая медленная. А функция в 5-й степени — самая быстрая, но и наименее плавная: А вот как выглядят результаты функций с косинусом, возведением в степень и движением по окружности: Результат: Результат: Результат: Функции плавности Роберта Пеннера подразумевают, что входной аргумент нормализован (имеет значение от 0 до 1). Выходное значение так же в этих же пределах (почти всегда). Поэтому их часто используют вместе с функцией изменения диапазона: Указываем диапазон, например, от 5 до 10. Если взять число ровно посередине — 7,5, то после изменения диапазона на от 1 до 3 число тоже будет ровно посередине — 2. Допустим, нам нужна анимация от 5-ой до 7-ой секунды. В этот период надо распахнуть дверь с 0 до 90 градусов: Функции плавности часто используются в различных интерфейсах, вроде сайтов или мобильных приложений. Изначально Роберт Пеннер придумал 30 функций, но позднее разработчикам стало очевидным, что не помешало бы еще что-то. Например, в некоторых ситуациях требуется замедление анимации посередине, а максимальная скорость на краях — так появилась OutIn (вместо привычной InOut): В результате получаем анимацию, похожую на эффект замедления времени (как в «Матрице»): Такое часто бывает, когда учитывается пользовательский ввод: в интерфейсах приложений или в играх. Например, у нас есть объект, который плавно и постоянно должен двигаться до курсора мыши: Или скроллинг: Плавный драг-н-дроп: Плавная слежка за объектом камерой: В таких случаях применяются анимации, основанные на физике (physics-based). Сделать свою! На самом деле это не так страшно как кажется, потому что кода придется написать ненамного больше, чем было в этом уроке. И как раз в следующем уроке мы и поговорим про создание контролируемых нелинейных анимаций через формулы из физики!