Three.js
March 23, 2022

Анимация в three.js

Перед тем как начать:

В предыдущем уроке мы освоили с вами вращение объектов и возможность группировки нескольких объектов для их общей трансформации. Если ты это пропустил, то советую вернуться!

Введение

Мы с вами успешно создали сцену, которая отрисовывается один раз в конце работы кода. В целом, это уже своего рода прогресс, но чаще всего нам бы хотелось оживить наши творения, поэтому сегодня мы разберемся с тем, как добавить анимацию!

В three.js анимация - это что-то вроде покадровой съемки.

Двигаем объект -> делаем рендер сцены -> еще немного двигаем объект -> снова рендерим сцену. Поэтому чем сильнее вы двигаете объект между рендерами, тем быстрее он будет двигаться на итоговой анимации.

Экран, на который вы смотрите, работает с определенной частотой. Мы называем это частотой кадров. Частота кадров в основном зависит от экрана, но у самого компьютера есть ограничения. Большинство экранов работают со скоростью 60 кадров в секунду. Если вы посчитаете, это означает примерно один кадр каждые 16мс. Но некоторые экраны могут работать намного быстрее, и когда компьютер испытывает трудности с обработкой данных, он будет работать медленнее.

Для анимации мы будем реализовывать функцию, которая будет перемещать объекты и выполнять рендеринг в каждом кадре независимо от частоты кадров.

Интересно:

Кстати, чтобы получить такую функцию в нативном JavaScript можно использовать метод window.requestAnimationFrame(...).

Подготовка

Перед тем как начать давайте убедимся, что у нас с вами одинаковые стартовые картинки (я перекрасила кубик, чтобы он лучше выглядел на скринах).

Код на на начало урока живет тут.

в

Анимация при помощи requestAnimationFrame

Изначально функция requestAnimationFrame не создана для того, чтобы выполнять код для каждого фрейма, но если мы напишем функцию, которая будет внутри себя вновь вызывать метод requestAnimationFrame, и передавать в качестве параметра саму себя, то это будет именно то что нам нужно.

Сказала, наверно, сложно, так что давайте посмотрим лучше на примере.

Создадим функцию helloFrame и вызовем ее один раз. А внутри этой функции вызовем метод window.requestAnimationFrame(...) для того, чтобы вызвать функцию helloFrame в следующем фрейме.

const helloFrame = () =>
{
    console.log('helloFrame');

    window.requestAnimationFrame(tick);
};

helloFrame();

Вот и все, теперь у нас есть бесконечный цикл!

Если вы откроете консоль, то увидите, что helloFrame вызывается для каждого фрейма.

Сейчас мы можем переместить функцию, которая отрисовывает нашу сцену renderer.render(...) вовнутрь такой функции, а еще добавить в эту функцию немного трансформации нашего куба:

/**
 * Анимация
 */
const animateLoop = () =>
{
    // Трансформируем объект
    mesh.rotation.y += 0.01;

    // Отрисовываем
    renderer.render(scene, camera);

    // Вызываем функцию анимации в следующем фрейме
    window.requestAnimationFrame(animateLoop);
};

animateLoop();

Вот мы и получили нашу первую анимацию в three.js .

Адаптируемся к частоте кадров

На самом деле тут кроется некоторая проблема, если вы запустите ваш код на компьютере с высокой частотой кадров, куб будет вращаться быстрее, а если частота кадров будет низкой - то куб будет вращаться медленней.

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

Для того чтобы измерять время в ванильном JavaScript можно использовать:

const time = Date.now()

Это время будет соответствовать тому сколько прошло с 1го Января 1970г (начало времени для Unix). В JavaScript это время хранится в миллисекундах.

Дальше все что нам нужно сделать, это сопоставить текущее время с временем на момент предыдущего тика, после сопоставления мы получим разницу во времени и сможем использовать ее при анимации:

let time = Date.now();

/**
 * Анимация
 */
const animateLoop = () =>
{
    // Время
    const currentTime = Date.now();
    const deltaTime = currentTime - time;
    time = currentTime;
    
    // Трансформируем объект
    mesh.rotation.y += 0.01 * deltaTime;

    // Отрисовываем
    renderer.render(scene, camera);

    // Вызываем функцию анимации в следующем фрейме
    window.requestAnimationFrame(animateLoop);
};

animateLoop();

Куб начал вращаться быстрее, а все из-за того что deltaTime примерно равна 16, если частота кадров компьютера 60fps.

Теперь, когда мы основываем нашу анимацию на том, сколько времени было потрачено с момента последнего кадра, эта скорость анимации будет одинаковой на каждом экране и на всех компьютерах, независимо от частоты кадров.

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

Хотя решение с использованием расчета времени и не особо сложное, но в библиотеке three.js уже есть готовое решение этой проблемы, называется оно Clock.

Clock - возьмет на себя все расчеты связанные со временем. Нам нужно просто создать объект класса Clock и воспользоваться встроенными методами по типу getElapsedTime(). Этот метод вернет нам то количество времени, которое прошло с момента создания объекта.

Давайте переделаем предыдущий пример с использованием Clock:

const clock = new THREE.Clock();

/**
 * Анимация
 */
const animateLoop = () =>
{
    const elapsedTime = clock.getElapsedTime();
    
    // Трансформируем объект
    mesh.rotation.y = elapsedTime;

    // Отрисовываем
    renderer.render(scene, camera);

    // Вызываем функцию анимации в следующем фрейме
    window.requestAnimationFrame(animateLoop);
};

animateLoop();

Кстати, можно попробовать использовать это значение и для изменения свойства position, при комбинировании с математическими функциями по типу Math.sin(...) и Math.cos(...) получится прекрасный результат.

const clock = new THREE.Clock();

/**
 * Анимация
 */
const animateLoop = () =>
{
    const elapsedTime = clock.getElapsedTime();

    // Передвижение объекта
    mesh.position.x = Math.cos(elapsedTime);
    mesh.position.y = Math.sin(elapsedTime);

    // Отрисовываем
    renderer.render(scene, camera);

    // Вызываем функцию анимации в следующем фрейме
    window.requestAnimationFrame(animateLoop);
};

animateLoop();

Ну и очевидно, что подобную технику можно использовать для анимации любого объекта, который унаследован от Object3D , например, камеру:

const clock = new THREE.Clock();

/**
 * Анимация
 */
const animateLoop = () =>
{
    const elapsedTime = clock.getElapsedTime();
    
    // Трансформируем объект
    camera.position.x = Math.cos(elapsedTime);
    camera.position.y = Math.sin(elapsedTime);
    camera.lookAt(mesh.position);

    // Отрисовываем
    renderer.render(scene, camera);

    // Вызываем функцию анимации в следующем фрейме
    window.requestAnimationFrame(animateLoop);
};

animateLoop();

На этом на сегодня все, надеюсь вам было интересно и вы узнали для себя что-то новое из мира three.js!

Чтобы не пропустить новые уроки обязательно подпишись на телеграм канал!

Ну а код этого урока можно найти вот тут!

Камеры в three.js ->