Анимация в 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!
Чтобы не пропустить новые уроки обязательно подпишись на телеграм канал!
Ну а код этого урока можно найти вот тут!