Привет! Представляю вашему вниманию перевод статьи «Creating A VR Audio/Visual Experience On the Web With A-Frame and Tone.js» автора Sean Sullivan.
A-Frame — это фреймворк для создания виртуальной реальности в вебе. Используя лишь ссылку, любой человек с VR-шлемом или поддерживающим VR смартфоном может погрузиться в 3D пространство. Tone.js — это JavaScript библиотека для создания звуков. Давайте взглянем, что будет, если их совместить.
Для начала, мы создадим окружение, с A-frame это очень просто. Используя лишь базовый HTML, мы можем создать целое 3D пространство, для этого нам нужен aframe-environment-component. Ниже приведена базовая разметка для наших целей.
<!DOCTYPE html>
<html>
<head>
<title>Basic Scene with Environment - A-Frame</title>
<meta name="description" content="Basic Scene with Environment - A-Frame">
<script src="https://aframe.io/releases/1.0.4/aframe.min.js"> </script>
<script src="https://unpkg.com/aframe-environment-component@1.1.0/dist/aframe-environment-component.min.js"></script>
</head>
<body>
<a-scene environment="preset: starry">
<a-camera>
<a-entity cursor="fuse: true; fuseTimeout: 500"
position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
material="color: black; shader: flat">
</a-entity>
</a-camera>
</a-scene>
</body>
</html>
Обратите внимание на элемент:
<a-entity cursor>
Он находится внутри нашей камеры. Чуть позже он позволит общаться с нашим синтезатором. Но перед тем, как приступить, стоит убедиться в корректной загрузке проекта.
Открывая страницу, вы должны увидеть трехмерное небо, звезды и сетку на земле. Все это было создано aframe-environment-component, когда мы задали окружение:
<a-scene environment=”preset: starry”>
При желании окружение можно поменять, достаточно добавить другой шаблон. На момент написания этой статьи существуют 16 разных шаблонов окружения на ваш выбор.
Мне нравится, что наш синтезатор находится в космосе, космос — это круто. Давайте сделаем так, чтобы наше окружение больше напоминало поверхность планеты.
Для начала мы удалим сетку и добавим текстуру земли, изменив:
<a-scene environment="preset: starry">
на
<a-scene environment="preset: starry; grid: none; groundTexture: walkernoise">
Запустив страницу сейчас, мы увидим, что наша планета все еще слишком темна, чтобы разглядеть что-то на земле. Исправим это, добавив источник света в нашу сцену.
<a-entity light="type: ambient; color: #CCC"></a-entity>
С ним сцена должна выглядеть примерно так:
Теперь, когда мы разобрались с окружением, давайте начнем разрабатывать синтезатор.
Создание компонента
A-Frame построен на entity-component-system. Она позволяет создавать компоненты и добавлять их к сущностям в нашей сцене.
Давайте создадим файл synth.js для нашего компонента.
AFRAME.registerComponent('synth', {
schema: {
// Описание свойств компонента.
},
init: function () {
// Действия при первом присоединении компонента.
},
update: function () {
// Действия при обновлении данных компонента.
},
remove: function () {
// Действия при отсоединении компонента или его сущности.
},
tick: function (time, timeDelta) {
// Действия при каждой итерации (тике) или кадре.
}
});
Как вы можете видеть, в A-Frame встроены методы жизненного цикла, это облегчает добавление интерактивности в наши WebVR проекты. База компонента готова, давайте взглянем на процесс создания синтезатора с Tone.js.
Tone.js
Tone.js — фреймворк, предназначенный для создания интерактивной музыки в браузере, является оболочкой для Web Audio API. Создание синтезатора с tone.js просто — достаточно лишь написать строку:
var synth = new Tone.Synth().toMaster()
Но мы создадим осциллятор и добавим несколько параметров для упрощения дальнейшей кастомизации:
const synth = new Tone.Synth({
volume: -15, // -15dB
oscillator: {
type: 'triangle' // тип осциллятора - волна "треугольник"
},
envelope: {
attack: 0.05, // атака - начало звука
release: 2 // релиз - затухание
}
}).toMaster()
Добавим этот код прямо поверх нашего компонента в файле synth.js. Теперь синтезатор у нас есть, но нам нужно предоставить нашему компоненту способ к нему обратиться. Помните <a-entity cursor>, который мы добавили к камере? У это курсора есть параметр fuse=«true». Это позволит нам следить за тем, как курсор взаимодействует с сущностями. Добавим EventListener к компоненту для fuse.
Мы создадим EventListener в методе init жизненного цикла и создадим новый метод с названием trigger, запускающий Tone.js.
...
init: function () {
// добавляем EventListener к нашему элементу для fuse
this.el.addEventListener('fusing', this.trigger.bind(this))
},
// создаем метод, запускающий tone.js
trigger: function () {
//tone.js функция, запускающая синтезатор с нашими параметрами
synth.triggerAttackRelease(this.data.note, this.data.duration)
},
...
Добавляем компонент синтезатора в сцену
Мы создали компонент, самое время добавить его в сцену A-Frame.
Для начала добавил Tone.js и компонент синтезатора в нашу разметку. Обратите внимание на порядок подключения файлов — synth.js загружается после Tone.js.
...
<script src="https://unpkg.com/tone@13.8.25/build/Tone.js"></script>
<script src="synth.js"></script>
</head>
...
Нам так же нужны несколько сущностей, к которым мы присоединим компонент. Добавим несколько стандартных фигур A-Frame для использования в нашей сцене.
<a-scene>
...
<a-box synth="note: E4" position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere synth="note: C4" position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder synth="note: G4" position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
...
Обратите внимание на атрибут synth. Это созданный нами компонент. ‘Synth’ — это имя, зарегистрированное нами с
AFRAME.registerComponent(‘synth’, {})
а “note” мы объявили в схеме компонента. Так же есть свойство “duration” — мы можем использовать его для изменения длины ноты. Например:
synth=«note: E4; duration: 8n»
сыграет 1/8 от целой ноты, а не стандартную 1/4.
Теперь открыв сцену в браузере мы увидим наши фигуры, а при наведении на них курсора должна проиграться нота с нашего компонента синтезатора.
Используем контроллер Oculus Go
Сейчас наша сцена работает так — курсор фиксирован в центре экрана. На VR шлемах это называется “зрительное” управление. Поворачивая голову, курсор будет перемещаться в направлении движения пользователя. Это абсолютно нормальный опыт и хорошо работает для многих проектов. Но что если мы хотим управлять синтезатором с помощью VR контроллера? Двигать руками вокруг и создавать музыку — звучит весело, поэтому давайте изменим нашу сцену, чтобы она использовала контроллер Oculus Go.
Для начала стоит добавить несколько сущностей в нашу сцену — контроллер и raycaster.
...
<a-entity oculus-go-controls>
<a-entity laser-controls raycaster="far: 200; interval: 100"></a-entity>
...
Тут мы имеем собственную сущность для управления Oculus Go, а также одну для raycaster, который будет запускаться каждые 100 миллисекунд.
Теперь давайте модифицируем компонент синтезатора для управления Oculus. Сделаем это, добавив raycaster в зависимости нашего компонента.
AFRAME.registerComponent('synth', {
dependencies: ['raycaster'],
...
Затем, в методе init поменяем EventListener — он должен отслеживать событие:
raycaster-intersection
init: function () {
this.el.addEventListener('raycaster-intersection', this.trigger.bind(this))
},
...
Запуск сцены в Oculus Go теперь должен показывать ваш контроллер — а лазерное управление должно запускать синтезатор, играющий ноты при наведении на фигуры.
Если вы хотите поближе ознакомиться с проектом, вы можете запустить его и посмотреть исходный код здесь — glitch.com/~space-synth-vr
В заключении
Теперь мы имеем простую сцену с VR синтезатором и есть огромное количество возможностей ее улучшить. Мы можем добавлять больше объектов для взаимодействия, больше синтезаторов и эффектов для компонента. Мы можем анимировать объекты, основываясь на каких-то событиях. По мере увеличения сцены, стоит думать о производительности. К счастью, в A-Frame много встроенных функций, способных помочь с этой проблемой.
Вот несколько полезных ссылок
Компоненты
Raycaster
Взаимодействие и контроль
Tone.js
Исходный код проекта
Спасибо за прочтение.
Автор: vasemkin