Смешивание анимаций: crossFadeTo и weight

Механизм анимации в Three.js основан на объекте AnimationMixer, который управляет воспроизведением анимационных клипов (AnimationClip) через экземпляры AnimationAction. Каждый клип содержит набор ключевых кадров, а AnimationAction отвечает за конкретное проигрывание этого клипа: запуск, остановку, изменение скорости, зацикливание и смешивание с другими действиями.

При работе со сложными сценами часто требуется плавный переход между анимациями. Например:

  • переход персонажа из состояния ожидания в ходьбу;
  • смена ходьбы на бег;
  • переход из движения в прыжок.

Мгновенная смена анимации создаёт резкий визуальный скачок, поскольку положение костей модели изменяется без интерполяции. Для устранения этого эффекта используется смешивание анимаций (animation blending).

Основными инструментами для такого перехода являются:

  • метод crossFadeTo()
  • параметр weight

Они позволяют постепенно уменьшать влияние одной анимации и увеличивать влияние другой.


Принцип смешивания анимаций

Каждый объект AnimationAction имеет параметр веса (weight). Этот параметр определяет, насколько сильно анимация влияет на итоговую позу объекта.

Диапазон значений:

0.0 — анимация не влияет на модель
1.0 — анимация полностью влияет на модель

При наличии нескольких одновременно воспроизводимых анимаций их трансформации смешиваются пропорционально весам.

Пример:

Анимация Weight
Idle 0.7
Walk 0.3

Итоговая поза будет комбинацией двух анимаций, где 70% позы формируется анимацией ожидания и 30% — ходьбой.


AnimationMixer и AnimationAction

Перед использованием смешивания необходимо создать миксер анимации.

const mixer = new THREE.AnimationMixer(model);

Затем из анимационных клипов создаются действия:

const idleAction = mixer.clipAction(idleClip);
const walkAction = mixer.clipAction(walkClip);
const runAction = mixer.clipAction(runClip);

Каждый AnimationAction может воспроизводиться независимо:

idleAction.play();

Однако в реальных сценах одновременно могут работать несколько действий с разными весами.


Управление весом анимации (weight)

Вес можно устанавливать напрямую.

walkAction.setEffectiveWeight(1.0);
idleAction.setEffectiveWeight(0.0);

Также существует свойство:

action.weight

Однако предпочтительнее использовать методы:

setEffectiveWeight()
getEffectiveWeight()

Они учитывают дополнительные модификаторы (например, fade).

Пример изменения веса

idleAction.setEffectiveWeight(1);
walkAction.setEffectiveWeight(0);

После этого модель полностью находится в состоянии ожидания.

Постепенное изменение:

walkAction.setEffectiveWeight(0.5);
idleAction.setEffectiveWeight(0.5);

Теперь итоговая поза является смешанной.


Плавные переходы между анимациями

Ручное изменение веса требует постоянного обновления значений, что неудобно. Для автоматизации используется система fading, реализованная внутри AnimationAction.

Существует несколько методов:

fadeIn()
fadeOut()
crossFadeTo()
crossFadeFrom()

Наиболее распространённый вариант — crossFadeTo.


Метод crossFadeTo

Метод crossFadeTo() выполняет перекрёстное затухание (cross-fade) между двумя анимациями.

Сигнатура метода:

action.crossFadeTo(targetAction, duration, warp)

Параметры:

Параметр Описание
targetAction анимация, к которой выполняется переход
duration длительность перехода (в секундах)
warp синхронизация скорости анимаций

Во время перехода:

  • текущая анимация постепенно уменьшает weight до 0
  • целевая анимация увеличивает weight до 1

Пример перехода Idle → Walk

idleAction.play();
walkAction.play();

idleAction.crossFadeTo(walkAction, 0.5, false);

Процесс:

  1. idleAction.weight уменьшается
  2. walkAction.weight увеличивается
  3. переход длится 0.5 секунды

Итог — плавная смена состояния персонажа.


Полный пример переключения анимаций

function fadeToAction(action, duration) {

    previousAction = activeAction;
    activeAction = action;

    if (previousAction !== activeAction) {

        previousAction.fadeOut(duration);

    }

    activeAction
        .reset()
        .setEffectiveTimeScale(1)
        .setEffectiveWeight(1)
        .fadeIn(duration)
        .play();

}

Использование:

fadeToAction(walkAction, 0.5);
fadeToAction(runAction, 0.5);

Такой подход используется во многих демо Three.js.


Использование crossFadeFrom

Метод crossFadeFrom() работает аналогично, но вызывается на целевой анимации.

runAction.crossFadeFrom(walkAction, 0.5, false);

Внутренне происходит:

walk → fadeOut
run → fadeIn

Оба метода приводят к одинаковому результату.


Синхронизация скорости анимаций (warp)

Параметр warp используется для синхронизации времени анимаций.

crossFadeTo(targetAction, duration, true)

Если длина клипов различается, система автоматически изменит timeScale, чтобы фазы движений совпали.

Пример:

Анимация Длительность
Walk 1.2 сек
Run 0.7 сек

При включённом warp система скорректирует скорость проигрывания.

Это особенно важно для:

  • шагов
  • циклов движения
  • синхронизации ног

Одновременное смешивание нескольких анимаций

Three.js поддерживает слоистую систему анимаций.

Например:

Анимация Назначение
Walk движение
Shoot стрельба
LookAround поворот головы

Каждая из них может иметь собственный weight.

Пример:

Walk weight = 1
Shoot weight = 0.4
LookAround weight = 0.2

В результате:

  • тело выполняет ходьбу
  • руки частично участвуют в стрельбе
  • голова слегка вращается

Такой подход широко применяется в игровых движках.


Обновление AnimationMixer

Для работы системы анимации необходимо обновлять миксер каждый кадр.

const delta = clock.getDelta();
mixer.update(delta);

Метод update():

  • изменяет время анимаций
  • пересчитывает веса
  • применяет смешивание

Без вызова update() анимации не будут воспроизводиться.


Типичная архитектура управления анимациями

В проектах обычно используется контроллер состояний персонажа.

Пример структуры:

Idle
Walk
Run
Jump
Attack

Каждое состояние соответствует отдельному AnimationAction.

Переключение:

Idle → Walk
Walk → Run
Run → Jump
Jump → Idle

Переход выполняется через crossFadeTo.


Оптимизация системы смешивания

При работе с большим количеством персонажей важно учитывать производительность.

Основные рекомендации:

Ограничение активных анимаций

Не следует одновременно воспроизводить десятки действий.

Обычно достаточно:

2–3 активных action

Использование reset()

Перед повторным запуском анимации полезно сбросить её состояние.

action.reset();

Это возвращает анимацию к первому кадру.

Отключение ненужных action

Если анимация больше не используется:

action.stop();

Распространённые ошибки

Резкая смена анимации

Ошибка:

walkAction.stop();
runAction.play();

Результат — скачок позы.

Правильный вариант:

walkAction.crossFadeTo(runAction, 0.3, false);

Не запущена целевая анимация

Иногда забывают вызвать play().

Неправильно:

idleAction.crossFadeTo(walkAction, 0.5);

Правильно:

walkAction.play();
idleAction.crossFadeTo(walkAction, 0.5);

Отсутствие mixer.update

Без обновления миксера:

crossFade не работает
анимации не воспроизводятся

Необходим код в цикле рендера:

mixer.update(clock.getDelta());

Пример полной системы переключения

let activeAction;
let previousAction;

function setAction(newAction) {

    previousAction = activeAction;
    activeAction = newAction;

    if (previousAction !== activeAction) {

        previousAction.fadeOut(0.5);

        activeAction
            .reset()
            .setEffectiveWeight(1)
            .fadeIn(0.5)
            .play();

    }

}

Использование:

setAction(idleAction);
setAction(walkAction);
setAction(runAction);

Каждый вызов создаёт плавный переход.


Роль crossFadeTo в анимационных системах

Метод crossFadeTo() является ключевым инструментом при построении:

  • игровых персонажей
  • симуляций движения
  • интерактивных моделей
  • VR/AR сцен

Он обеспечивает:

  • естественные переходы поз
  • отсутствие визуальных скачков
  • гибкое управление несколькими анимациями

В сочетании с управлением весами (weight) система анимации Three.js позволяет реализовывать сложные анимационные графы, сопоставимые с возможностями игровых движков.