Реализация VR-пространства в браузере

Предлагаю начать с основ, а именно с фреймворка A-Frame, поскольку он упрощает работу на основе уже имеющихся знаний об HTML5 и JavaScript. Совместим ли он с WebXR? Да, в его текущей версии 1.0.4. 

Базовая “Hello World” в A-Frame:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello World A-Frame</title>
    <meta name="description" content="Hello World, WebXR! in A-Frame">
    <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
  </head>
  <body>
    <a-scene background="color: #FAFAFA">
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" shadow></a-sphere>
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" shadow></a-cylinder>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>
    </a-scene>
  </body>
</html>

Что же нам изначально даёт A-Frame? Пример Hello World. Это, конечно, замечательно, но хочется большего! Например, наличия вокруг геометрических фигур окружения и возможности перемещения по нему в VR. Давайте же это реализуем!

Наилучший способ добавить окружение  —  воспользоваться компонентом a-frame-environment. Его можно найти на GitHub.

Итак, добавляем его и получаем:

В коде это выглядит так:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello World A-Frame</title>
    <meta name="description" content="Hello World, WebXR! in A-Frame">
    <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/aframe-environment-component.min.js"></script>
  </head>
  <body>
    <a-scene background="color: #FAFAFA">
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" shadow></a-sphere>
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" shadow></a-cylinder>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>

<a-entity environment="preset: forest"></a-entity>

</a-scene>
  </body>
</html>

Но, кажется, мы потеряли плоскость и тени под фигурами? На самом деле они по-прежнему на месте, просто скрыты под окружением. Давайте это исправим, для чего перейдём в режим редактирования внешнего вида сцены “Inspector”, нажав ctrl-alt-i:

Обратите внимание, что теперь слева вверху вы можете выбрать плоскость и двигать её, перетаскивая вверх вглубь сцены зелёную стрелку в центре экрана. Координаты x, y и z плоскости будут отображаться в верхней правой части экрана, изменяясь соответствующим образом. Теперь можно аналогичным образом подстроить расположение сферы, куба и цилиндра в соответствии с поднятой плоскостью. Для этого просто выбирайте нужный объект слева вверху и также перемещайте.

Хорошо, идём дальше. Давайте изменим параметры окружения. Выберите <a-entity>, находящуюся сразу под <a-plane> там же в верхней левой части экрана:

Справа вы увидите меню ENVIRONMENT, а под ним параметры наполнения окружения dressing и цвета наполнения dressingColor. Для наполнения я выбрал mushrooms (грибы) и окрасил их в фиолетовый цвет, просто чтобы выделить. Теперь вы можете решить, что при выходе со страницы эти изменения сохранятся, но будете расстроены, потому что нет. 

Чтобы сохранить их, выберите <a-scene> справа вверху. Затем нажмите иконку “copy entity HTML to clipboard” справа от иконки gltf в верхнем правом углу (обведена красным). Вставьте содержимое буфера (clipboard) под HTML-кодом в редакторе, и у вас должно получиться что-то подобное:

Данные буфера обмена вставлены в редактор

Теперь обратите внимание на <a-scene>, все её сущности (<a-box>, <a-sphere>, <a-cylinder>, <a-plane>) присутствуют с изменёнными в Inspector координатами x, y, z. Скопируйте новые координаты вместо соответствующих координат сущностей выше. Также обратите внимание, что <a-entity environment=”preset: forest; active: true; seed: 8; skyType: gradient; skyColor: #24b59f; . . . буквально заполнена параметрами, которых для наших задач слишком много, поэтому от лишних лучше избавиться. Нам нужны лишь свойства dressing и dressingColor, которые мы изменяли ранее. Итак, изменив одни параметры и удалив другие, мы получим следующее:

Здесь у нас пропали тени, но мы это вскоре исправим. Заметьте, что теперь у нас есть всё из исходного “Hello World”, включая новое окружение с ландшафтом, небом и фиолетовыми грибами вокруг. Вот как это выглядит в коде:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello World A-Frame</title>
    <meta name="description" content="Hello World, WebXR! in A-Frame">
    <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>

<script src="https://unpkg.com/[email protected]/dist/aframe-environment-component.min.js"></script>

</head>
  <body>
    <a-scene background="color: #FAFAFA">
      <a-box position="-1 0.66921 -3" rotation="0 45 0" color="#4CC3D9" shadow="" material="" geometry=""></a-box>
      <a-sphere position="0 1.44508 -5" radius="1.25" color="#EF2D5E" shadow="" material="" geometry=""></a-sphere>
      <a-cylinder position="1 0.8993 -3" radius="0.5" height="1.5" color="#FFC65D" shadow="" material="" geometry=""></a-cylinder>
      <a-plane position="0 0.08958 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow="" material="" geometry=""></a-plane>

<a-entity environment="preset: forest; dressing: mushrooms; dressingColor: #6e1d8b;"></a-entity>

</a-scene>
  </body>
</html>

Весьма неплохо! Достаточно сжатый код для всего того, что мы получили. Так работает A-Frame. Теперь предлагаю заглянуть в раздел теней документации A-Frame (англ.):

Можете попробовать разобраться сами. У меня же получилось вот что:

Я получил тени на плоскости и сфере, добавив на сцену <a-entity light=…> и реализовав отбрасывание теней на сущность <a-plane>. Вот код:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello World A-Frame</title>
    <meta name="description" content="Hello World, WebXR! in A-Frame">

    <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/aframe-environment-component.min.js"></script>

</head>
  <body>
    <a-scene background="color: #FAFAFA">
      <a-box position="-1 0.66921 -3" rotation="0 45 0" color="#4CC3D9" shadow="" material="" geometry=""></a-box>
      <a-sphere position="0 1.44508 -5" radius="1.25" color="#EF2D5E" shadow="" material="" geometry=""></a-sphere>
      <a-cylinder position="1 0.8993 -3" radius="0.5" height="1.5" color="#FFC65D" shadow="" material="" geometry=""></a-cylinder>
      <a-plane position="0 0.08958 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow="recieve: true" material="" geometry=""></a-plane>

<a-entity environment="preset: forest; dressing: mushrooms; dressingColor: #6e1d8b;">
</a-entity><a-entity light="type:directional; castShadow:true;" position="1 1 1"></a-entity>

</a-scene>
  </body>
</html>

Если вам понадобится изменить направление отбрасывания теней, то для этого в Inspector нужно просто изменить позицию источника света. Это будет легко, учитывая, что вы в нем уже ориентируетесь. Свои настройки теней я пока больше менять не буду. Далее нам нужно реализовать остальное, а именно возможность перемещаться по пространству в VR. Для этого уже понадобится VR-гарнитура. Именно здесь и отсеивается большинство людей, потому что они либо не имеют нужного оборудования, либо не желают его приобретать. 

Итак, перемещение. Его можно реализовать несколькими способами. Загвоздка здесь в реализации таким образом, чтобы VR-устройства и контроллеры от разных поставщиков работали в разных версиях браузеров, а это уже серьёзная задача. Принимая во внимание постоянно выходящие на рынок новые VR-устройства, изменения в спецификациях браузеров и соответственно в базе вашего кода. Это как будто стараться сбить несколько движущихся целей одной стрелой. Поэтому не удивительно, что код время от времени может давать сбой. Так будет продолжаться, пока все не урегулируется, если это вообще произойдёт. Ну да хватит о плохом. 

Прежде чем мы начнём, все должно выглядеть примерно так:

Кстати, вам также следует знать о функции inspect браузера:

Inspect в консоли браузера Chrome

Для перехода на эту вкладку нажмите правую кнопку мыши и выберите Inspect. Далее выберите вкладку Console. Как видно на изображении выше, здесь отображается версия A-Frame, Three.js и другая информация, которая может пригодиться при отладке. Подробно эту вкладку я рассматривать не стану, но считаю, что вы должны о ней знать на случай возникновения ошибок в дальнейшем. Теперь закройте её, нажав X, и продолжим.

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

Перемещаться в VR можно несколькими способами. Я выбрал для этого аналоговый стик и телепортацию, для чего написал следующий код:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Advanced Hello World A-Frame</title>
    <meta name="description" content="Advanced Hello World, WebXR! in A-Frame"><!-- *** ЗАМЕНИТЕ СЛЕДУЮЩИЕ КОМПОНЕНТЫ НА КОМПОНЕНТЫ СО СВОЕГО СЕРВЕРА*** -->
    <script src="aframe-master/dist/aframe-v1.0.4.min.js"></script>
    <script src="aframe-environment-component-master/dist/aframe-environment-component.min.js"></script>
    <script src="aframe-extras-master/dist/aframe-extras.min.js"></script>
    <script src="aframe-teleport-controls-master/dist/aframe-teleport-controls.js"></script>
    <script src="superframe-master/components/thumb-controls/dist/aframe-thumb-controls-component.min.js"></script>
  </head>
  <body>
    <a-scene background="color: #FAFAFA"><!-- Basic movement and teleportation  -->
      <a-entity id="cameraRig" movement-controls="constrainToNavMesh: true;" navigator="cameraRig: #cameraRig; cameraHead: #head; collisionEntities: .collision; ignoreEntities: .clickable" position="0 0 0" rotation="0 0 0">
        <!-- camera -->
        <a-entity id="head" camera="active: true" position="0 1.6 0" look-controls="pointerLockEnabled: true; reverseMouseDrag: true" ></a-entity>
              <!-- Left Controller  -->
              <a-entity class="leftController" hand-controls="hand: left; handModelStyle: lowPoly; color: #15ACCF" tracked-controls vive-controls="hand: left" oculus-touch-controls="hand: left" windows-motion-controls="hand: left" teleport-controls="cameraRig: #cameraRig; teleportOrigin: #head; button: trigger; curveShootingSpeed: 18; landingMaxAngle: 60" visible="true"></a-entity>
              <!-- Right Controller  -->
              <a-entity class="rightController" hand-controls="hand: right; handModelStyle: lowPoly; color: #15ACCF" tracked-controls vive-controls="hand: right" oculus-touch-controls="hand: right" windows-motion-controls="hand: right" laser-controls raycaster="showLine: true; far: 10; interval: 0; objects: .clickable, a-link;" line="color: lawngreen; opacity: 0.5" visible="true"></a-entity>
      </a-entity><a-box position="-1 0.66921 -3" rotation="0 45 0" color="#4CC3D9" shadow="" material="" geometry=""></a-box>
      <a-sphere position="0 1.44508 -5" radius="1.25" color="#EF2D5E" shadow="" material="" geometry=""></a-sphere>
      <a-cylinder position="1 0.8993 -3" radius="0.5" height="1.5" color="#FFC65D" shadow="" material="" geometry=""></a-cylinder>
      <a-plane position="0 0.08958 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow="recieve: true" material="" geometry=""></a-plane><a-entity environment="preset: forest; dressing: mushrooms; dressingColor: #6e1d8b;"></a-entity><a-entity light="type:directional; castShadow:true;" position="1 1 1"></a-entity></a-scene>
  </body>
</html>

Вот рабочий пример на основе этого кода:

Вы могли заметить, что я заменил компоненты A-Frame в строчках <script src=…> на хранящиеся у меня на сервере. Причина в том, что A-Frame продолжает развиваться даже пока я пишу эту статью, и мне нужно было убедиться, что вы получите рабочую копию. Поэтому вам потребуется изменить эти инструкции самим на последнюю версию кода. Вот список использованных компонентов фреймворка и источники, где их можно взять (скопируйте на сервер и ссылайтесь на них напрямую):

Мой VR-блог по адресу https://rocketvirtual.com

Для размещения кода на сервере я использовал FTP. В качестве альтернативы можно также использовать Node.js, но объяснение этого момента выходит за рамки данной статьи и предполагает самостоятельное изучение. 

В итоге вы, скорее всего, предпочтёте использовать собственный сервер для размещения VR-кода. В этом случае для вас откроются дополнительные интересные возможности, такие как Magic Window, представляющее упрощённую альтернативу VR-устройствам и позволяющее демонстрировать VR-проекты большому числу пользователей. 

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Michael McAnally: How to create VR experiences for the browser