Создаем аудиовизуальный VR-опыт с применением A-Frame и Tone.js

в 9:15, , рубрики: javascript, web-разработка, WebVR, виртуальная реальность, Разработка веб-сайтов

Привет! Представляю вашему вниманию перевод статьи «Creating A VR Audio/Visual Experience On the Web With A-Frame and Tone.js» автора Sean Sullivan.

Firefox Reality в Oculus Go

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 теперь должен показывать ваш контроллер — а лазерное управление должно запускать синтезатор, играющий ноты при наведении на фигуры.

Oculus Go

Если вы хотите поближе ознакомиться с проектом, вы можете запустить его и посмотреть исходный код здесь — glitch.com/~space-synth-vr

В заключении

Теперь мы имеем простую сцену с VR синтезатором и есть огромное количество возможностей ее улучшить. Мы можем добавлять больше объектов для взаимодействия, больше синтезаторов и эффектов для компонента. Мы можем анимировать объекты, основываясь на каких-то событиях. По мере увеличения сцены, стоит думать о производительности. К счастью, в A-Frame много встроенных функций, способных помочь с этой проблемой.

Вот несколько полезных ссылок

Компоненты
Raycaster
Взаимодействие и контроль
Tone.js
Исходный код проекта

Спасибо за прочтение.

Автор: vasemkin

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js