От автора перевода: в предыдущей статье мы обсудили базовые концепции A-Frame. Чтобы продолжить цикл, я хотел создать урок, который бы иллюстрировал основные возможности A-Frame, но понял, что лучше сделать перевод статьи с официального сайта, которая, по-моему мнению написана велеколепно, и повторять подобную статью просто нет всякого смысла.
Давайте сделаем базовую сцену в A-Frame, чтобы понять как фреймворк работает. Нам потребуется начальное понимание HTML. В ходе этого урока мы выучим:
- как добавлять 3D объекты с помощью примитивов;
- как трансформировать объекты в 3-х мерном пространстве с помощью, перемещений, поворотов и масштабирования;
- как добавить окружение;
- как добавить текстуры;
- как добавить базовую интерактивность с помощью событий и анимации;
- как добавить текст.
Поиграть с кодом можно тут
Начнем с HTML
<html>
<head>
<script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
</a-scene>
</body>
</html>
Мы подключаем A-Frame через <script> тег, добавленный в раздел <head>. Код библиотеки находится на CDN. Подключение A-Frame должно быть добавлено до <a-scene> так как A-Frame регистрирует пользовательские HTML элементы, которые должны быть определены до того как <a-scene> будет добавлен в DOM, иначе сцена останется пустой.
Далее, мы добавляем тег <a-scene> в тело документа <body>. <a-scene> это элемент с помощью которого происходит управление сценой. Добавляя элементы в <a-scene> мы добавляем их на фактическую 3D сцену. <a-scene> отвечает за все что необходимо: устанавливает WebGL, <canvas>, камеру, свет, renderer (отрисовщик), а так же и все что связано с поддержкой WebVR на таких платформах как HTC Vive, Oculus Rift, Samsung GearVR, Oculus Go и Google Cardboards.
Добавляем объекты
Используя <a-scene>, мы добавляем 3D объекты c помощью стандартных примитивов A-Frame, например <a-box>. Мы можем использовать <a-box> просто как обычный HTML элемент, добавляя к нему атрибуты для кастомизации. Вот еще несколько стандартных примитивов доступных в A-Frame: <a-cylinder>, <a-plane>, <a-sphere>.
В следующем примере кода мы добавим цветной <a-box>. Больше атрибутов для <a-box> можно найти тут.
Необходимо заметить, что примитивы в A-Frame являются всего лишь обертками для некоторых компонентов. Это удобно, но нужно понимать что под <a-box> кроется <a-entity> с geometry и material компонентами:
<a-entity id="box" geometry="primitive: box" material="color: red"></a-entity>
В любом случае, так как камера и куб, добавленные на сцену, находятся в одной и той же точке 0,0,0, мы не сможем увидеть куб до тех пор, пока не поменяем его позицию. Сделать это достаточно просто: нужно всего лишь поменять атрибут position, чтобы переместить наш куб в пространстве.
Трансформации объекта в 3D
Давайте сначала поговорим о 3D пространстве. A-Frame использует правую (right-handed) систему координат. Направление камеры по умолчанию: положительная X-ось уходит вправо, положительная Y-ось уходит вверх и положительная Z-ось уходит назад от экрана в направлении нас:
A-Frame измеряет дистанцию в метрах (а не в пикселях как в React360) так как WebVR API возвращает данные позиции и позы в метрах. Когда вы проектируете ВР сцену очень важно рассматривать именно реальные размеры объектов. Куб с высотой в 10 метров может выглядеть нормально на экранах наших компьютеров, но он будет слишком массивным при погружении в виртуальную реальность.
Единицы вращения в A-Frame — это градусы (не радианы как в Three.js), поэтому они автоматически будут пересчитаны в радианы при попадании в Three.js. Для определения положительного направления вращения используется правило правой руки. Направьте палец правой руки вдоль положительного направления любой оси, тогда направление в котором наши пальцы согнуты и будут положительным направлением вращения.
Чтобы переместить, вращать или масштабировать наш куб, мы может изменить соответственно position, rotation и scale компоненты (представленные атрибутами тега <a-box>). Давайте попробуем для начала применить вращение и масштабирование:
<a-scene>
<a-box color="red" rotation="0 45 45" scale="2 2 2"></a-box>
</a-scene>
Это должно повернуть наш куб на заданные углы и растянуть его по всем осям в два раза.
Трансформации предков и потомков
Граф сцены в A-Frame реализован с помощью HTML. Каждый элемент такой структуры может иметь несколько потомков и лишь одного предка. Любой потомок в такой структуре наследует свойства трансформации (position, rotation, scale) своего предка.
Например, мы можем добавить сферу как потомка куба:
<a-scene>
<a-box position="0 2 0" rotation="0 45 45" scale="2 4 2">
<a-sphere position="1 0 3"></a-sphere>
</a-box>
</a-scene>
Позиция сферы на сцене будет 1 2 3, а не 1 0 3, так как по умолчанию сфера находится в координатах своего предка, то есть куба — 0 2 0. Эта точка в данном случае будет точкой отсчета для сферы, которая имеет свои координаты 1 0 3. То же самое относится к вращению и масштабу. Если любой из атрибутов куба будет изменен, это изменение автоматически затронет всех потомков (в нашем случае сферу).
Если бы мы добавили цилиндр как потомка сферы, трансформации цилиндра были бы посчитаны исходя из трансформаций его предков: куба и сферы.
Размещаем наш куб впереди от камеры
Теперь давайте сделаем наш куб видимым для нашей камеры. Мы можем передвинуть наш куб на 5 метров по оси Z в отрицательном направлении (то есть от нас). Давайте также поднимем его на два метра вверх по оси Y. Все это можно сделать изменив атрибут position:
<a-scene>
<a-box color="red" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
</a-scene>
Теперь мы видим его! Юху!
Управление
Для плоских дисплеев (ноутбуки, компьютеры) управление камерой по умолчанию осуществляется через перетаскивание мышкой и WASD или стрелки на клавиатуре. В телефонах за это отвечает акселерометр. Несмотря на то что A-Frame является фреймворком для WebVR он поддерживает данные схемы управления для того, чтобы сцену можно было просмотреть и без шлема виртуальной реальности.
Если вы используете шлем виртуальной реальности, то управление будет осуществляться с помощью контроллеров и шлема. Чтобы попасть в виртуальную реальность из браузера вашего шлема, вам нужно нажать на иконку с изображением шлема виртуальной реальности, которая расположена в правом нижнем углу. Если вы используете шлем с 6-ю степенями свободы, и у вас есть реальное пространство, вы можете физически походить по созданной вами сцене.
Добавляем окружение
A-Frame позволяет разработчикам создавать и делиться компонентами, которые затем легко использовать в своих проектах. Компонент окружения созданный Диего Гоберна генерирует различные окружения и все это достигается всего лишь одной строчкой кода! Компонент окружения это великолепный и простой способ создать визуальную платформу для наших ВР приложений. Он позволяет создавать десятки окружений, настраиваемых многочисленным количеством параметров.
Для начала нужно подключить скрипт в раздел после A-Frame:
<head>
<script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-environment-component/dist/aframe-environment-component.min.js"></script>
</head>
После этого, уже в сцене, нужно добавить тег <a-entity> c атрибутом environment и заданными настройками, пусть это будет forest (лес) с 200-ми деревьями:
<a-scene>
<a-box color="red" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
<!-- Окружение, прямо из коробки! -->
<a-entity environment="preset: forest; dressingAmount: 200"></a-entity>
</a-scene>
Пометка: имейте ввиду, что при использовании слабого “железа” такая сцена может и притормаживать. Например, это может произойти на мобильных устройствах типа Oculus Go. Так что если вы поддерживаете и мобильные устройства, следите за количеством генерируемых объектов.
Применяем текстуры
Мы можем применить текстуру к нашему кубу, используя изображение, видео или canvas с помощью атрибута src, также как мы делаем это с обычным тегом . Также нам необходимо удалить color=«red» так как цвет все равно не будет отображаться при использовании текстуры.
<a-scene>
<a-box src="https://i.imgur.com/mYmmbrp.jpg" position="0 2 -5" rotation="0 45 45"
scale="2 2 2"></a-box>
<a-sky color="#222"></a-sky>
</a-scene>
Теперь мы должны увидеть наш куб уже с текстурой, подгруженной он-лайн.
Используем систему загрузки файлов
Чтобы улучшить производительность вашего приложения на A-Frame мы рекомендуем вам использовать систему управления загрузкой файлов. Эта система позволяет браузеру кэшировать разные файлы (изображения, видео, 3d модели). A-Frame отвечает за то, чтобы все эти файлы были загружены до рендеринга.
Если мы напишем тэг внутри <a-assets>, A-Frame не будет отображать его как в привычном HTML, он обработает источник и отправит его в Three.js. Важно отметить, что загрузив изображение один раз мы можем использовать его где угодно, например в качестве текстуры. Также A-Frame позаботится о кросс-доменности и других возможных проблемах, связанных с передачей файлов по сети.
Чтобы использовать систему управления загрузкой файлов для добавления текстуры нужно:
- Добавить тег <a-assets> внутрь тега <a-scene> (на первый уровень)
- Добавить тег <img>, атрибут src должен содержать ссылку файл изображения (также как и в привычном нам HTML)
- Нужно задать атрибут id для тега img. В идеале он должен описывать саму текстуру (например, id=”boxTexture”)
- Добавить атрибут src для объекта который должен ее содержать. Например для <a-box src="#boxTexture" >
<a-scene>
<a-assets>
<img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg">
</a-assets>
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
<a-sky color="#222"></a-sky>
</a-scene>
Создаем произвольное окружение
Мы уже говорил о компоненте окружения выше. Оно генерируется автоматически. А что если нам нужно сделать свое собственное окружение: добавить облака, землю, другие объекты? Предлагаю поговорить об этом далее.
Добавляем фоновое изображение для сцены
Для того чтобы добавить фоновое изображение для сцены в A-Frame есть специальный элемент <a-sky>. <a-sky> позволяет добавлять как чистый цвет, так и 360 изображения или видео. Например, чтобы добавить темно-серый фон нужно написать следующий код:
<a-scene>
<a-box color="red" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
<a-sky color="#222"></a-sky>
</a-scene>
Или мы можем использовать 360 фотографию:
<a-scene>
<a-assets>
<img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg">
<img id="skyTexture"
src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/sechelt.jpg">
</a-assets>
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
<a-sky src="#skyTexture"></a-sky>
</a-scene>
Добавляем землю
Чтобы добавить землю мы будем использовать элемент <a-plane>. По умолчанию плоскости в A-Frame ориентированы параллельно оси XY. Чтобы сделать плоскость паралелльной земле, нам нужно повернуть ее так чтобы она была параллельна оси XZ. Это можно сделать повернув плоскость на -90° по оси X.
<a-plane src="#groundTexture" rotation="-90 0 0" width="30" height="30"
repeat="10 10"></a-plane>
Работаем над светом
Чтобы изменить освещение нашей сцены мы можем добавить или перенастроить элемент <a-light>. По умолчанию, у нас не было никаких источников света, но A-Frame сам добавляет рассеянный свет (ambient light) и направленный свет (directional light). Если бы A-Frame не добавил эти источники света, то сцена была бы полностью черной. Если мы добавим свои собственные источники света, то те источники, которые добавляет A-Frame по умолчанию, будут удалены.
Мы добавим один источник рассеянного света, который будет иметь синевато-зеленый оттенок (чтобы гармонировать с небом). Рассеянный свет должен попадать на все элементы нашей сцены (конечно если они будут иметь материалы, но по умолчанию это так).
Мы также добавим источник точечного света (point light). Источник точечного света похож на лампочку. Эффект освещения такого источника будет полностью зависеть от дистанции до объекта.
<a-scene>
<a-assets>
<img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg">
<img id="skyTexture"
src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/sechelt.jpg">
<img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
</a-assets>
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2"></a-box>
<a-sky src="#skyTexture"></a-sky>
<a-light type="ambient" color="#445451"></a-light>
<a-light type="point" intensity="2" position="2 4 4"></a-light>
</a-scene>
Добавляем анимацию
У нас есть возможность добавить анимацию используя встроенную систему анимации A-Frame. Анимация изменяет некоторое значение (свойство компонента) с течением времени. Нам нужно, всего лишь, добавить элемент <a-animation> как потомок некоторого объекта, например <a-box>. Давайте попробуем анимировать куб таким образом, чтобы он двигался вверх-вниз.
<a-scene>
<a-assets>
<img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg">
</a-assets>
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2">
<a-animation attribute="position" to="0 2.2 -5" direction="alternate" dur="2000"
repeat="indefinite"></a-animation>
</a-box>
</a-scene>
Мы говорим <a-animation>:
- Что нужно анимировать атрибут position.
- Анимировать до 0 2.2 -5 что на 20 сантиметров выше первоночальной позиции.
- Изменять направление анимации на каждом цикле.
- Цикл анимации будет занимать 2 секунды (2000 миллисекунд).
- Повторять анимацию бесконечно.
Дополнительные детали
<a-animation> использует цикл рендеринга A-Frame, поэтому каждое изменение свойств объекта происходит один раз за кадр. Если вам нужно больше контроля и вы хотите изменять значения вручную, вы можете написать компонент A-Frame с колбэком tick и библиотекой такой как Tween.js (которая кстати доступна по ссылке AFRAME.TWEEN). Для лучшей производительности, покадровые операции должны быть выполнены на уровне A-Frame, не нужно создать вашу собственную функцию requestAnimationFrame когда A-Frame уже имеет ее.
Добавляем интерактивность
Давайте добавим возможность взаимодействовать с нашим кубом: когда мы смотрим на него, мы увеличим его размеры, а при клике он прокрутиться вокруг своей оси.
Предполагая что большинство разработчиков не имеют ВР шлемов с контроллерами мы сфокусируемся на использовании базового компонента для ввода на компьютерах и мобильных устройствах — курсоре. Курсор предоставляет возможность “кликнуть” на объект через наведение для мобильных устройств и кликом мышки для компьютеров. Но нужно понимать что курсор это всего лишь один из способов взаимодействия, все будет немного иначе если у нас будут настоящие ВР контроллеры.
Чтобы привязать курсор к камере нам нужно добавить его в качестве потомка к элементу камеры (<a-camera>).
Так как мы еще не определили камеру, A-Frame сделал это автоматически. но так как нам нужно добавить курсор в камеру, мы определим <a-camera> вручную и добавим туда <a-cursor>:
<a-scene>
<a-assets>
<img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg">
</a-assets>
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2">
<a-animation attribute="position" to="0 2.2 -5" direction="alternate" dur="2000"
repeat="indefinite"></a-animation>
</a-box>
<a-camera>
<a-cursor></a-cursor>
</a-camera>
</a-scene>
Если мы посмотрим документацию курсора, мы видим что он обрабатывает события наведения такие как mouseenter, mouseleave а еще и событие click.
Компонент слушателя событий
Один из способов обработать события курсора это добавить слушатель событий через JavaScript просто как для обычно DOM элемента. Если вы не уверены в своих знаниях по JavaScript, вы можете пропустить этот раздел до следующего.
В JavaScript мы получаем элемент через querySelector, а используя addEventListener, и setAttribute чтобы увеличить размеры куба при наведении курсора. Заметка: A-Frame изменяет setAttribute так чтобы он мог работать сразу с несколькими компонентами. Мы можем добавить {x, y, z} как второй аргумент.
<script>
var boxEl = document.querySelector('a-box');
boxEl.addEventListener('mouseenter', function () {
boxEl.setAttribute('scale', {x: 2, y: 2, z: 2});
});
</script>
Но более быстрым способом будет инкапсулировать логику внутри компонента A-Frame. Этот метод не требует ожидания загрузки сцены, нам не нужно использовать селекторы потому что компонент дает нам контекст:
<script>
AFRAME.registerComponent('scale-on-mouseenter', {
schema: {
to: {default: '2.5 2.5 2.5'}
},
init: function () {
var data = this.data;
this.el.addEventListener('mouseenter', function () {
this.setAttribute('scale', data.to);
});
}
});
</script>
Мы можем добавить этот компонент через HTML атрибут:
<script>
AFRAME.registerComponent('scale-on-mouseenter', {
// ...
});
</script>
<a-scene>
<!-- ... -->
<a-box src="#boxTexture" position="0 2 -5" rotation="0 45 45" scale="2 2 2"
scale-on-mouseenter="to: 2.2 2.2 2.2">
<a-animation attribute="position" to="0 2.2 -5" direction="alternate" dur="2000"
repeat="indefinite"></a-animation>
</a-box>
<!-- ... -->
</a-scene>
Анимация по событию
<a-animation> имеет возможность начинать и заканчивать свою анимацию когда объект продуцирует событие. Это можно сделать через атрибут begin указав имя события.
Мы можем добавить две анимации для компонента курсор — mouseenter и onemouseleave события чтобы изменить размеры нашего куба, и одно для поворотов куба вокруг оси Y по событию click:
<a-box color="#FFF" width="4" height="10" depth="2"
position="-10 2 -5" rotation="0 0 45" scale="2 0.5 3"
src="#texture">
<a-animation attribute="position" to="0 2.2 -5" direction="alternate" dur="2000"
repeat="indefinite"></a-animation>
<!-- These animations will start when the box is looked at. -->
<a-animation attribute="scale" begin="mouseenter" dur="300" to="2.3 2.3 2.3"></a-animation>
<a-animation attribute="scale" begin="mouseleave" dur="300" to="2 2 2"></a-animation>
<a-animation attribute="rotation" begin="click" dur="2000" to="360 405 45"></a-animation>
</a-box>
Добавляем аудио
Аудио очень важно для полного погружения в виртуальную реальность. Даже добавление просто белого шума на задний план может занять время. Мы рекомендуем использовать звук для каждой сцены. Один из способов добавления звука: добавить <audio> элемент в <a-assets> и атрибут autoplay:
<a-scene>
<a-assets>
<audio src="https://cdn.aframe.io/basic-guide/audio/backgroundnoise.wav" autoplay
preload></audio>
</a-assets>
<!-- ... -->
</a-scene>
Или мы можем добавить пространственное аудио с помощью элемента <a-sound>. Этот компонент делает звук громче когда мы подходим ближе к источнику. Мы можем позиционировать <a-sound> с помощью атрибута position:
<a-scene>
<!-- ... -->
<a-sound src="https://cdn.aframe.io/basic-guide/audio/backgroundnoise.wav" autoplay="true"
position="-3 1 -4"></a-sound>
<!-- ... -->
</a-scene>
Добавляем текст
A-Frame имеет компонент текст. Существует несколько способов его отобразить и каждый способ имеет свои преимущества и недостатки. A-Frame имеет SDF имплементацию через three-bmfont-text которая является достаточно производительной.
Для этого урока предлагаем использовать самую простую форму текста, <a-text>:
<a-text value="Hello, A-Frame!" color="#BBB" position="-0.9 0.2 -3" scale="1.5 1.5 1.5"></a-text>
Есть и другие способы добавить текст:
- Text Geometry сделанная Кевином Нго. Это 3D текст и он требует больше ресурсов для отрисовки.
- HTML Shader сделанный Майо Тобита — он рендерит текст как текстуру. Легко стилизуется но долго рассчитывается.
Заключение
Поиграть с кодом можно тут. Посмотреть на сцену в реальном времени можно тут.
От автора первода: Спасибо всем за внимание! В следующей статье я постараюсь рассказать про свой опыт создания игры на A-Frame: о всех тонкостях и сложностях которые поджидали меня в процессе разработки.