Начало работы с виртуальной реальностью
Эта статья предназначена для тех, кто хочет начать разрабатывать проекты виртуальной реальности в UNIGINE, и настоятельно рекомендуется всем новым пользователям. Мы рассмотрим демо-проект VR Sample, чтобы понять, что находится внутри, и научимся использовать его для создания собственного проекта виртуальной реальности. Мы также рассмотрим несколько простых способов того, как внести изменения и расширить базовую функциональность этого демо-проекта.
Итак, давайте начнем!
VR Sample#
Думая о разработчиках виртуальной реальности, мы создали демо-проект VR Sample, который позволит вам сразу приступить к созданию собственных проектов. Мы рекомендуем использовать этот демо-проект в качестве основы для вашего VR-проекта.
Демо-проект основан на шаблоне VR Template, который поддерживает все устройства, совместимые со SteamVR, из коробки. Он обеспечивает автоматическую загрузку моделей контроллеров во время выполнения. Кроме того, шаблон включает в себя реализацию основных механик, таких как захват и бросание предметов, нажатие кнопок, открывание/закрывание ящиков и многое другое.
Проект создан с использованием Компонентной системы, поэтому функциональность каждого объекта определяется подключенными компонентами.
Вы можете расширить функциональность объекта, просто добавив дополнительные компоненты. Например, на объект "лазерная указка" назначены следующие компоненты:
- ObjMovable - позволяет захватывать и бросать объект
- ObjLaserPointer - позволяет направить луч света на объект
1. Создание шаблонного проекта#
Итак, у нас есть классный демо-проект с некоторым наполнением, но как использовать его в качестве шаблона? Всё просто - откройте SDK Browser, перейдите на вкладку Samples и выберите Demos.
Найдите VR Sample в разделе Available и нажмите Install. После установки демо-проект появится в разделе Installed, и вы можете нажать Copy as Project, чтобы создать проект на основе этого демо-проекта.
В открывшемся окне Create New Project введите название вашего нового VR-проекта в соответствующее поле и нажмите Create New Project.
2. Подключение устройства и настройка проекта#
Предположим, вы установили нужный вам VR-шлем (HMD).
Более подробную информацию о настройке устройств для различных платформ VR вы можете найти по этой ссылке. Если у вас возникли трудности, обратитесь в службу поддержки Steam. В случае с устройствами Vive может быть полезно это руководство по устранению неполадок.
Все устройства, совместимые со SteamVR, поддерживаются «из коробки».
По умолчанию VR не инициализирован. Поэтому вам необходимо выполнить одно из следующих действий:
-
Если вы запускаете приложение через UNIGINE SDK Browser, установите для параметра Stereo 3D значение, соответствующее установленному HMD (OpenVR или Varjo) на вкладке Global Options и нажмите Apply.
-
Если вы запускаете приложение из командной строки, укажите параметр командной строки -vr_app при запуске приложения. Для OpenVR и Oculus это делается следующим образом:
your_app_name -vr_app openvr
Для Varjo:
your_app_name -vr_app varjo
Как вариант, можно указать этот параметр командной строки в окне Customize Run Options при запуске приложения через браузер SDK:
3. Начало работы с исходным кодом проекта#
Чтобы открыть свой VR-проект в среде IDE, выберите его на вкладке Projects в браузере UNIGINE SDK и нажмите Open Code IDE.
Открыв IDE, вы увидите, что проект содержит множество различных классов. Этот краткий обзор даст вам общее представление о них.
Не забудьте установить соответствующую платформу и параметры конфигурации для вашего проекта, прежде чем компилировать код в Visual Studio.
Теперь мы можем попробовать собрать наше приложение в первый раз.
Соберите свое приложение в Visual Studio (Build -> Build Solution) или иным способом и запустите его, выбрав проект на вкладке Projects в браузере UNIGINE SDK и нажав Run.
Перед запуском приложения через браузер UNIGINE SDK убедитесь, что выбрана соответствующая опция в Customize Run Options (в нашем случае Debug), нажав многоточие под кнопкой Run.
4. Прикрепление объектов к HMD#
Иногда может потребоваться прикрепить к HMD какой-либо объект, чтобы он следовал за камерой (например, шляпа). У всех подвижных объектов (на которые назначено свойство movable.prop и включен флаг Dynamic) есть переключатель, включающий эту опцию.
Например, если вы хотите, чтобы цилиндр на столе был прикреплен к HMD, просто выберите соответствующую ноду с именем "cylinder" в разделе World Hierarchy, нажмите Edit в разделе Reference и включите опцию Can Attach to Head.
Затем выберите ссылку на родительскую ноду и нажмите Apply.
То же самое можно сделать с помощью кода во время выполнения:
#include "Framework/Components/Objects/ObjMovable.h"
#include <UnigineWorld.h>
using namespace Unigine;
...
// retrieving a NodeReference named "cylinder" and getting its reference node
NodePtr node = checked_ptr_cast<NodeReference>(World::getNodeByName("cylinder"))->getReference();
// checking if this node is a movable object by trying to get its ObjMovable component
ObjMovable *obj = ComponentSystem::get()->getComponent<ObjMovable>(node);
if (obj != nullptr)
{
// making the object attachable to the HMD
obj->can_attach_to_head = 1;
}
5. Доступ к функциям смешанной реальности (опционально)#
Чтобы начать разработку приложения смешанной реальности в UNIGINE, вам необходимо настроить окружение, как описано выше. Не забудьте установить Varjo Base и убедиться, что VR успешно инициализирован.
Параметр VR Template содержит свойство MixedRealityMenuGui, назначенное на ноду графического интерфейса head_menu. Оно демонстрирует работу доступных настроек смешанной реальности: вы можете настроить их, чтобы увидеть, как они влияют на визуализируемое изображение.
С помощью этого меню вы можете переключать видеосигнал в режиме реального времени, настраивать различные настройки камеры, такие как коррекция баланса белого, ISO и другие. Виджеты для меню инициализируются во время выполнения, но нода создается с помощью UnigineEditor.
Для управления смешанной реальностью используйте методы классов VRMixedReality и VRMarkerObject из UNIGINE API.
6. Доступ к функции отслеживания движения глаз (опционально)#
В VR Sample есть свойство eyetracking_pointer, назначенное на ноду с таким же именем. Оно показывает название ноды, на которую направлен взгляд. Реализация свойства доступна в папке vr_sample/Demo/Global. Вы можете расширить или изменить функциональность, изменив компонент EyetrackingPointer.
7. Прикрепление объектов к контроллерам#
Если вам нужно прикрепить к контроллеру какой-либо объект, загруженный во время выполнения (например, меню), вы можете присвоить этому объекту свойство AttachToHand.
Например, если у вас есть объект GUI и вы хотите подключить его к контроллеру, выберите этот объект в окне World Hierarchy, нажмите Add New Property в окне Parameters и укажите свойство AttachToHand.
Настройки свойств позволяют указать контроллер, к которому должен быть подключен объект (левый или правый), а также способ преобразования объекта.
В VR Sample этот компонент назначен на ноду hand_menu и инициализирует виджеты во время выполнения.
8. Переключение нод жестом (опционально)#
Когда приложение запущено с помощью интеграционного плагина Ultraleap, вы можете управлять объектами при помощи рук.
В шаблоне VR Template есть свойство NodeSwitchEnableByGesture, назначенное на Node Dummy vr_layer -> VR -> Ultraleap, оно доступно при использовании устройства виртуальной реальности с поддержкой отслеживания движения рук. Настройки свойств позволяют указать количество нод, между которыми вы можете переключаться, ноды для переключения и тип жеста для переключения.
Когда вы возьметесь правой рукой за левое запястье, появится меню:
9. Добавление нового взаимодействия#
Предположим, мы хотим расширить функциональность лазерной указки в нашем проекте, чтобы можно было захватывать, бросать и использовать (включать) объекты, добавив альтернативное действие использования (изменить материал объекта, на который направлена указка, при нажатии определенной кнопки).
Итак, мы собираемся добавить новый метод altUseIt() в класс VRInteractable для этого нового действия и связать его с состоянием определенной кнопки контроллера.
VRInteractable.h
#pragma once
#include <UnigineNode.h>
#include <UniginePhysics.h>
#include "../Framework/ComponentSystem.h"
#include "Players/VRPlayer.h"
using namespace Unigine;
using namespace Math;
class VRPlayer;
class VRInteractable : public ComponentBase
{
public:
// ...
// interact methods
virtual void grabIt(VRPlayer* player, int hand_num) {}
virtual void holdIt(VRPlayer* player, int hand_num) {}
virtual void useIt(VRPlayer* player, int hand_num) {}
virtual void altuseIt(VRPlayer* player, int hand_num) {} //<-- method for new alternative use action
virtual void throwIt(VRPlayer* player, int hand_num) {}
};
Объявите и реализуйте переопределение метода altUseIt() для лазерной указки в файлах ObjLaserPointer.h и ObjLaserPointer.cpp соответственно:
ObjLaserPointer.h
#pragma once
#include <UnigineWorld.h>
#include "../VRInteractable.h"
class ObjLaserPointer : public VRInteractable
{
public:
// ...
// interact methods
// ...
// alternative use method override
void altuseIt(VRPlayer* player, int hand_num) override;
// ...
private:
// ...
int change_material; //<-- "change material" state
// ...
};
ObjLaserPointer.cpp
// ...
void ObjLaserPointer::init()
{
// setting the "change material" state to 0
change_material = 0;
// ...
}
void ObjLaserPointer::update()
{
if (laser->isEnabled())
{
// ...
// show text
if (hit_obj && hit_obj->getProperty() && grabbed)
{
//---------CODE TO BE ADDED TO PERFORM MATERIAL SWITCHING--------------------
if (change_material)// if "alternative use" button was pressed
{
// change object's material to mesh_base
hit_obj->setMaterialPath("Unigine::mesh_base", "*");
}
//---------------------------------------------------------------------------
// ...
}
else
obj_text->setEnabled(0);
}
// unsetting the "change material" state
change_material = 0;
}
// ...
// alternative use method override
void ObjLaserPointer::altuseIt(VRPlayer* player, int hand_num)
{
// setting the "change material" state
change_material = 1;
}
// ...
Теперь свяжем это действие с состоянием кнопки YB контроллера. Для этого мы должны изменить класс VRPlayer (который является базовым классом для всех VR-плееров), добавив следующий код в его метод postUpdate():
VRPlayer.cpp
// ...
void VRPlayer::postUpdate()
{
for (int i = 0; i < getNumHands(); i++)
{
int hand_state = getHandState(i);
if (hand_state != HAND_FREE)
{
auto &components = getGrabComponents(i);
// ...
//-------------CODE TO BE ADDED--------------------------
// alternative use of the grabbed object
if (getControllerButtonDown(i, BUTTON::YB))
{
for (int j = 0; j < components.size(); j++)
components[j]->altuseIt(this, i);
// add callback processing if necessary
}
//--------------------------------------------------------
}
}
update_button_states();
}
// ...
10. Добавление нового интерактивного объекта#
Следующим шагом в расширении функциональности демо-проекта VR Sample является добавление нового интерактивного объекта.
Давайте добавим новый тип интерактивного объекта, который можно взять, удержать и бросить, с дополнительной функцией: объект изменит свою форму (на заранее предустановленную), когда мы его захватим, и вернет ее обратно, когда мы его отпустим. Также будет отображаться определенный текст в консоли, если включена соответствующая опция.
Итак, мы собираемся использовать следующие компоненты:
- ObjMovable - для включения базовых функций захвата и бросания
- новый компонент ObjTransformer для реализации функционала изменения формы и печати сообщения в лог
Необходимо выполнить следующие действия:
-
Добавьте новый класс ObjTransformer, унаследованный от VRInteractable. В Visual Studio это можно сделать так: выбираем Project -> Add Class в главном меню, нажимаем Add, указываем название класса и базовый класс в открывшемся окне и нажимаем Finish:
-
Реализуйте функциональность преобразования в указанную ноду при захвате и восстановления предыдущей формы, когда ноду отпускают.
Ниже вы найдете заголовочные файлы и файлы реализации для нашего нового класса ObjTransformer:
ObjTransformer.h
#pragma once #include <UnigineNode.h> #include "Components/VRInteractable.h" #include "Framework/Utils.h" class ObjTransformer : public VRInteractable { public: ObjTransformer(const NodePtr &node, int num) : VRInteractable(node, num) {} virtual ~ObjTransformer() {} // property name UNIGINE_INLINE static const char* getPropertyName() { return "transformer"; } // parameters PROPERTY_PARAMETER(Toggle, show_text, 1); // Flag indicating if messages are to be printed to the console PROPERTY_PARAMETER(String, text, "TRANSFORMATION"); // Text to be printed to the console when grabbing or releasing the node PROPERTY_PARAMETER(Node, target_object); // Node to be displayed instead of the transformer-node, when it is grabbed // interact methods void grabIt(VRPlayer* player, int hand_num) override; // override grab action handler void throwIt(VRPlayer* player, int hand_num) override; // override trow action handler void holdIt(VRPlayer* player, int hand_num) override; // override hold action handler protected: void init() override; };
ObjTransformer.cpp
#include "ObjTransformer.h" REGISTER_COMPONENT( ObjTransformer ); // macro for component registration by the Component System // initialization void ObjTransformer::init(){ // hiding the target object (if any) if (target_object){ target_object->setEnabled(0); } } // grab action handler void ObjTransformer::grabIt(VRPlayer* player, int hand_num) { // if a target object is assigned, showing it, hiding the original object and displaying a message in the log if (target_object){ target_object->setEnabled(1); // hide original object's surfaces without disabling components ObjectPtr obj = checked_ptr_cast<Object>(node); for (int i = 0; i < obj->getNumSurfaces(); i++) obj->setEnabled(0, i); if (show_text) Log::message("\n Transformer's message: %s", text.get()); } } // throw action handler void ObjTransformer::throwIt(VRPlayer* player, int hand_num) { // if a target object is assigned, hiding it, and showing back the original object if (target_object){ target_object->setEnabled(0); // show original object's surfaces back ObjectPtr obj = checked_ptr_cast<Object>(node); for (int i = 0; i < obj->getNumSurfaces(); i++) obj->setEnabled(1, i); } } // hold action handler void ObjTransformer::holdIt(VRPlayer* player, int hand_num) { // changing the position of the target object target_object->setWorldPosition(player->getHandNode(hand_num)->getWorldPosition()); }
- Создайте свое приложение и запустите его, как мы делали ранее — для нашего нового компонента будет создан новый файл свойств (transformer.prop).
- Откройте сцену в UnigineEditor, создайте новый прямоугольный примитив (Create -> Primitive -> Box) и поместите его где-нибудь рядом со столом, создайте сферический примитив (Create -> Primitive -> Sphere), который будет использоваться для преобразования.
-
Чтобы добавить компоненты к объекту box, выберите его и нажмите Add New Property в разделе Node Properties, затем перетащите свойство movable.prop в появившееся новое пустое поле. Повторите то же самое для свойства transformer.prop и перетащите сферу из окна World Hierarchy в поле Target Object.
- Сохраните мир и закройте UnigineEditor.
- Запустите приложение.
11. Ограничение телепортации#
По умолчанию можно телепортироваться в любую точку сцены. Чтобы избежать ошибок при взаимодействии пользователей с объектами в виртуальной реальности (например, при телепортации в стену или потолок), можно указать области, в пределах которых телепортация возможна. Для этого выполните следующие действия:
- Создайте меш, определяющий область, которая будет ограничивать телепортацию пользователя.
-
Установите значение intersection mask для желаемой поверхности (поверхностей) этого меша либо в UnigineEditor, либо с помощью метода setIntersectionMask():
// defining the teleportation mask as a hexadecimal value (e.g. with only the last bit enabled) int teleport_mask = 0x80000000; // setting the teleportation mask to the MyAreaMesh object's surface with the num index MyAreaMesh->setIntersectionMask(num, teleport_mask);
- Установите такую же маску пересечения для луча телепорта, используя следующий метод: VRPlayerVR::setTeleportationMask(teleport_mask).
Куда идти дальше#
Поздравляю! Теперь вы знаете, как создать свой собственный VR-проект на основе демо-проекта VR Sample и расширить его функциональность. Так что можете продолжить разработку самостоятельно. Вот несколько рекомендаций, которые могут оказаться полезными:
- Попробуйте еще раз проанализировать исходный код примера и выяснить, как он работает, используйте его для написания своего собственного.
- Прочтите статью Лучшие практики VR для получения дополнительной информации и полезных советов по подготовке контента для виртуальной реальности и улучшению пользовательского опыта.
- Прочтите статью Система компонентов для получения дополнительной информации о работе с системой компонентов.
- Ознакомьтесь с Примерами использования компонентной системы для получения более подробной информации о реализации логики с использованием компонентной системы.