Первые шаги в VR

Vlad Poe
High Technologies Center
11 min readFeb 3, 2017

Перевод статьи Шейна Хадсона.

Интернет окружает нас повсюду, и мы, веб-разработчики, несем ответственность за последствия нашей работы. Забота о будущем — часть этого процесса. Веб изменяет жизнь, и если это мы создаем его, значит наши решения влияют на людей в каждом уголке Земного шара. В работе я часто разрываюсь между “правильным решением”, и желанием получить удовольствие, поэкспериментировать, поиграть. Каждый из нас знает, насколько важно уметь расслабиться и просто попробовать сделать что-то новое, испытать идею, не заботясь о том, какое значение она будет иметь впоследствии.

Я называю два эти настроя “производство” (production) и “прототипирование” (prototyping), хотя существуют промежуточные состояния для творчества. Я упомянул об этом, так как распространено мнение, что VR (виртуальная реальность) — это развлечение для богатых, что отчасти справедливо. Но WebVR позволяет создавать качественно новые продукты при относительно небольших затратах. Я хочу, чтобы мы чаще открывались новому и экспериментировали с доступными инструментами ради создания новых вещей, помогающих людям.

Каждый год выходят статьи, сообщающие, что “этот год станет годом виртуальной реальности”. Для 2016 такая повестка была особенно актуальной. Он стал годом прогресса. И хотя VR-технологии нельзя считать безусловным мейнстримом, под влиянием новостей о Playstation VR и Google Cardboard приходилось слышать о них чаще обычного. В 2016 мы также застали пользовательские выпуски Oculus Rift и HTC Vive. Поэтому, думаю, сейчас подходящее время для обзора возможностей VR в вебе.

WebVR — это API для подключения к устройствам и получения потоковых данных, таких как позиция и ориентация в пространстве. В отличие от Web Audio API и других подобных API, при работе с WebVR нет ощущения работы с “фреймворком”. Получаемые данные гибки в использовании, а такие ресурсы как Three.js, A-Frame и ReactVR делают работу с ними еще проще.

Начинаем использовать A-Frame

Я стараюсь изучать новое при каждой возможности. Обдумывая эту статью, я решил вместо изучения WebGL или даже Three.js собрать свой первый проект на A-Frame и рассказать об этом опыте. Это не будет уроком или чем-то в этом роде. Моя цель — познакомить читателя с VR. Красота A-Frame в близости к компонентному подходу. Достаточно написать HTML, чтобы создать миры, автоматически работающие на разных типах устройств. A-Frame использует WebGL и WebVR, но гораздо менее требователен к начальным знаниям разработчика. Не подумайте, что он ограничивает в создании комплексных, сложных вещей: полный контроль над Java-Script и другими инструментами останется в ваших руках.

Я ленив (на самом деле нет). Однако, изучая новый язык или фреймворк, предпочитаю создавать отдельный проект и в качестве стартовой основы использовать готовый работающий код. Его наличие позволяет сосредоточится на целях и задачах, понизить уровень стресса и пренебречь большим массивом ненужной, иррелевантной информации. Копирование кода полезно еще и потому, что вы можете быть уверены в работающей основе для проекта. Думаю, нет ничего хуже, чем застрять, прежде чем сделаешь первый шаг. Я беру код, изменяю его. Это прикольно.

В этом проекте я постараюсь делать вещи максимально понятными, чтобы после их можно было доступно объяснить, избежав классического “нарисуйте овал; теперь нарисуйте сову”. Также я набросал список целей, на которые вы тоже можете ориентироваться:

  • Проект обязательно должен работать с Google Cardboard, так как это доступное по цене устройство;
  • По этой же причине — его работоспособность не должна зависеть от наличия блока управления, контроллера;
  • Было бы здорово реализовать автоматическое движение;
  • Движение по направлению взгляда;
  • Дополнительная цель: отсчет очков/времени до момента, когда игрок наткнется на стену или потеряется в лабиринте;
  • Дополнительная цель: разные уровни, тогда карту не нужно делать случайной;
  • Дополнительная цель: снег!

В качестве базы я использовал проект Platforms Дона МакКарди — автора библиотеки очень полезных A-Frame компонентов A-Frame Extras. Platforms содержат случайно расположенные уходящие в небо кубы. Так, я взял готовый код и расположил свои кубы уже на поверхности земли.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>24 ways</title>
<script src="https://aframe.io/releases/0.3.2/aframe.js"></script>
<script src="//cdn.rawgit.com/donmccurdy/aframe-extras/v2.6.1/dist/aframe-extras.min.js"></script>
</head>
<body>
<a-scene>
<a-entity id="player"
camera
universal-controls
kinematic-body
position="0 1.8 0">
</a-entity>

<a-entity id="walls"></a-entity>

<a-grid id="ground" static-body></a-grid>

<a-sky id="sky" color="#AADDF0"></a-sky>

<!-- Lighting -->
<a-light type="ambient" color="#ccc"></a-light>
</a-scene>
<script>
document.querySelector('a-scene').addEventListener('render-target-loaded', function () {
var MAP_SIZE = 10,
PLATFORM_SIZE = 5,
NUM_PLATFORMS = 50;
var platformsEl = document.querySelector('#walls');
var v, box;
for (var i = 0; i < NUM_PLATFORMS; i++) {
// y: 0 is ground
v = {
x: (Math.floor(Math.random() * MAP_SIZE) - PLATFORM_SIZE) * PLATFORM_SIZE,
y: PLATFORM_SIZE / 2,
z: (Math.floor(Math.random() * MAP_SIZE) - PLATFORM_SIZE) * PLATFORM_SIZE
};
box = document.createElement('a-box');
platformsEl.appendChild(box);
box.setAttribute('color', '#39BB82');
box.setAttribute('width', PLATFORM_SIZE);
box.setAttribute('height', PLATFORM_SIZE);
box.setAttribute('depth', PLATFORM_SIZE);
box.setAttribute('position', v.x + ' ' + v.y + ' ' + v.z);
box.setAttribute('static-body', '');
}
console.info('Platforms loaded.');
});
</script>
</body>
</html>

Как вы видите, код читается легко, особенно если отбросить часть JavaScript, отвечающую за расположение стен-кубов . A-Frame вместе с A-Frame Extras не требует изучения гор документации, а прямо “из коробки” даёт разработчикам значительную свободу по созданию виртуального 3D пространства. Начинаем с элемента <a-scene>, который будет контейнером всего, что можно увидеть на экране. Есть несколько элементов <a-entity>. Их можно сравнить с <div> в HTML документе: они выполняют роль несемантических оберток и могут быть использованы для любых целей. Атрибуты* определяют функциональность. Так, атрибут camera задает сущности, которой принадлежит, роль камеры, а kinematic-body заставляет объект отскакивать от других объектов, вместо того, чтобы проникать сквозь них. Атрибуты также определяют позиционирование и размеры объектов и могут инициализироваться в JavaScript, что позволяет задавать эти свойства динамически.

*Прим. переводчика: здесь и далее автор использует термин “атрибуты”. Их синтаксис и положение внутри A-Frame элементов действительно напоминают положение HTML-атрибутов внутри тегов. Однако, зачастую более оправдано говорить об A-Frame компонентах.

Оформление

Наш HTML готов. Теперь нужно его стилизовать. Для этого добавим A-Frame-совместимые атрибуты, такие как color и material. Здесь я предлагаю поэкспериментировать , ведь добиться впечатляющих результатов можно сравнительно легко. Изначально я планировал сделать свой лабиринт снежным, но в итоге закончил его темным и туманным — мне понравилось настроение. Обратите внимание: вероятно, вам потребуется сервер, чтобы визуально вывести проект на устройство. Вы можете сделать это, запустив в директории с кодом команду python -m "SimpleHTTPServer и открыв результат в браузере по url “localhost:8000”.

Текстуры

Если вы специально не гонитесь за “мультяшностью”, понадобятся текстуры. Я нашел свои на textures.com: одна отлично подошла для стен, вторая — для оформления поверхности пола.

<a-assets>
<img id="texture-floor" src="floor.jpg">
<img id="texture-wall" src="wall.jpg">
</a-assets>

Тег <a-assets> определяет используемые на странице ресурсы, такие как изображения, аудио и видео-файлы (а заодно предзагружает и кеширует их). Как видите, в Asset Management System изображения — это обычные <img> теги. Обратите внимание на id, они имеют большое значение, так как потом с их помощью вы сможете обращаться ресурсам повторно.

Замените:

<a-grid id="ground" static-body></a-grid>

На:

<a-grid id="ground" static-body material="src: #texture-floor"></a-grid>

Теперь текстура на нашем полу будет повторена по всей его площади. В моем случае — вымощена булыжником. Со стенами в общих чертах все так же; разница в том, что эти объекты нам нужно формировать динамически, а значит мы создаем их атрибуты/компоненты в JavaScript.

box.setAttribute('material', 'src: #texture-wall');

По текстурам все, по крайней мере на данный момент. Они не выглядят абсолютно реалистичными, так как свет распространяется равномерно по плоской поверхности стен, а не в соответствии с “живым” рельефом камня. Но и этот недочет исправляется дополнительными текстурными картами и изменениями физических форм объектов.

Свет

Следующим шагом оформления является свет. Используя туман и различные настройки света, мы можем добавить пространству еще больше реалистичности, настроения и стиля.

A-Frame предоставляет большой выбор настроек света (большинство — из Three.js). Добавляйте их, используя сущность <a-light>, либо через атрибут light. Если не задать настройки света явно, A-Frame создаст их по-умолчанию. Таким образом, ваше пространство всегда будет освещено.

Сначала, я применил стандартный свет — type="ambient", и игра стала смотреться темно. Тогда я добавил свету красный оттенок — #92455E, немного поиграл с интенсивностью и остановился на значении 0.4. Так я добился достаточной яркости, но без лишней красноты. Еще я включил в проект элемент <a-sky> — поверхность неба, так как с белым небом все это смотрелось странно.

<a-light type="ambient" color="#92455E" intensity="0.4"></a-light>
<a-sky id="sky" color="#0000ff"></a-sky>

Мне понравился результат с красным оттенком, однако пространство по-прежнему выглядело плоско: все казалось немного одноцветным и темным. Тогда я добавил еще света, задействовав сущность #player. Это можно было сделать, установив ей соответствующий атрибут, но я создал дочерний элемент a-light. Использовав type="point" в сочетании с высоким значением для интенсивности и небольшим — для дистанции, я добился, чтобы стены вблизи камеры выглядели светлее. Это добавило реалистичности и придало проекту своего рода “ощущение игрока”, а не просто движущейся камеры.

<a-light color="#fff" distance="5" intensity="0.7" type="point"></a-light>

На этой стадии сцена стала выглядеть более-менее неплохо, и я захотел создать туман для большего объема и индивидуальности пространства. Реализуется это с помощью fog атрибута для элемента <a-scene> c type="exponential". Теперь мой туман сгущается вдали, можно почувствовать себя немного заблудившимся, но по-прежнему видеть достаточно.

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

Движение

Одна из причин, по которой я остановил свой выбор на A-Frame Extras, в том, что эта библиотека из коробки предлагает несколько наборов настроек камеры. Как вы могли заметить, я использовал параметр universal-controls, включающий управление клавиатурой. Мне хотелось сделать автоматическое движение по направлению взгляда камеры, но я не знал как это можно реализовать, не меняя текущий код контролов. Я обратился за советом к Дону МакКарди, и он любезно поделился следующим сниппетом:

AFRAME.registerComponent('automove-controls', {
init: function () {
this.speed = 0.1;
this.isMoving = true;
this.velocityDelta = new THREE.Vector3();
},
isVelocityActive: function () {
return this.isMoving;
},
getVelocityDelta: function () {
this.velocityDelta.z = this.isMoving ? -speed : 0;
return this.velocityDelta.clone();
}
});

Замените:

universal-controls

на:

universal-controls="movementControls: automove, gamepad, keyboard"

Данный код создаст компонент automove-controls, который в свою очередь придает автоматическое движение. Нативный функционал движения при этом полностью перезаписан не будет. На самом деле, этот код не имеет дела с направлением, а просто проверяет isMoving. Если оно равно true, то “игроку” придается движение с указанной скоростью. Такого рода компоненты могут быть созданы относительно просто для разных целей. Это очень мощный инструмент для разработчиков разных уровней подготовки.

Создание карты

Изначально границы лабиринта создавались случайным образом, что в общем-то замечательно, если не принимать во внимание ситуацию, при которой стена оказывается на пути игрока, и ему некуда двигаться. Поэтому я воспользовался редактором карт Tiled. Он позволяет делать собственные карты. Это инструмент, который поможет мне в достижении одной из моих “дополнительных” целей — создании разных уровней.

С помощью Tiled я собрал свой лабиринт онлайн в виде мозаики одинаковых по размеру блоков. Я использовал два типа картинок для полей: одну для стен, другую для для изображения игрока/пользователя. Затем я экспортировал получившуюся карту как JavaScript файл, открыл его в редакторе и удалил ненужные мне кусочки кода. В результате у меня получился следующий объект:

var map =
{
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
"height":10,
"width":10
}

, где 0 обозначает открытое пространство/дорогу, 1 — стены и 2 — положение игрока.

На изображении видно, что это карта 10x10 с лабиринтом внутри. Игрок начинает движение из нижнего правого угла (мой выбор, его можно расположить в любом месте). Я переписал код Дона так, чтобы скрипт проходился по моему объекту map и располагал элементы в соответствии со свойством data: стены — на месте единиц, игрок — на месте цифры “2”. Позиционирование стен я настроил так, чтобы их начальное значение равнялось 0, 1.5, 0. Ось Y в данном случае обозначает высоту. Если ей присвоить значение “0”, то часть каждой стены окажется ниже уровня пола. Поэтому для объектов значение по оси Y должно рассчитываться как высота, разделенная на два*.

*Прим. переводчика: это связано с тем, что в A-Frame позиция (компонент `position`) отсчитывается от центра объекта. Например, точка с координатами 0, 0, 0 — это середина трехмерного объекта. В данном случае, высота стен равна 3. Для выравнивания с уровнем пола, второму параметру компонента position присваиваем значение, равное половине высоты (position=`0 1.5 0`).

document.querySelector('a-scene').addEventListener('render-target-loaded', function () {
var WALL_SIZE = 5,
WALL_HEIGHT = 3;
var el = document.querySelector('#walls');
var wall;

for (var x = 0; x < map.height; x++) {
for (var y = 0; y < map.width; y++) {

var i = y*map.width + x;
var position = (x-map.width/2)*WALL_SIZE + ' ' + 1.5 + ' ' + (y-map.height/2)*WALL_SIZE;
if (map.data[i] === 1) {
// Create wall
wall = document.createElement('a-box');
el.appendChild(wall);
wall.setAttribute('color', '#fff');
wall.setAttribute('material', 'src: #texture-wall;');
wall.setAttribute('width', WALL_SIZE);
wall.setAttribute('height', WALL_HEIGHT);
wall.setAttribute('depth', WALL_SIZE);
wall.setAttribute('position', position);
wall.setAttribute('static-body', ');
}

if (map.data[i] === 2) {
// Set player position
document.querySelector('#player').setAttribute('position', position);
}

}
}
console.info('Walls added.');
});

Этот код позволит впоследствии с легкостью изменять настройки карты и добавлять ей новые свойства. Скажем, вам хочется увидеть в лабиринте монстров или другие объекты. Для этого просто назначьте новые цифры-значения свойству data объекта map и добавьте if — условие в цикл. Вы можете создавать новые слои и даже продлить лабиринт по оси Y, добавив на карту возвышенности и лестницы, а все ваши разнообразные объекты при этом будут иметь общую систему позиционирования. Все это реализуется относительно просто. Как видите, A-Frame делает вход в мир 3D и VR разработки значительно проще.

Все это не только об играх и развлечениях

Во множестве случаев VR используется для создания игр, поэтому часто ассоциируется именно с индустрией развлечений. В действительности это только малая толика его возможностей. Спектр применения VR-технологий очень широк и включает нарративные жанры, инфографику и даже… медитацию.

Существует множество примеров применения VR в терапевтических целях:

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

Подытожим

Десять лет назад на этом самом сайте (речь идет о 24ways.org) Камерон Моль опубликовал статью о мобильном интернете. Он объяснял, каким образом мобильные телефоны и новые тарифные планы для них, стандарт WAP 2.0 и XHTML Mobile Profile станут повседневным для веба явлением. “Мобильный веб быстро обрастает XHTML инфраструктурой, и поэтому нам с вами необходимо применить наши знания о “десктопном вебе”, чтобы научиться адаптировать контент под нужды мобильных пользователей”, — писал он

Сегодня мы можем посмотреть на эту цитату и улыбнуться: путь, проделанный за прошедшее десятилетие, — огромен. Сегодня даже жители развивающихся стран и с небольшим достатком имеют мобильные девайсы, а доступ к интернету с них проще, чем со стационарных компьютеров, к которым апеллировал Камерон.

Не буду утверждать, что виртуальная реальность изменит мир или заменит нам привычные устройства (мобильные телефоны). Но кто знает! Мы, веб-разработчики, можем использовать уже имеющиеся у нас знания. Чтобы попробовать, не нужно изучать новый язык программирования. Что, если в 2026 году в новом выпуске “24 ways” кто-то другой сошлется на мою статью, размышляя о том, какой большой путь проделан? Будем надеяться, мы правильно используем наши умения и сделаем мир лучше. А если VR окажется не более чем временным поветрием, модой? —Что ж, в любом случае это было прикольно, давайте двигаться дальше.

--

--