Написать пост

Как разрабатывать приложения смешанной реальности для Microsoft HoloLens: взаимодействие с голограммами

Аватар Иван Бирюков

Обложка поста Как разрабатывать приложения смешанной реальности для Microsoft HoloLens: взаимодействие с голограммами

В предыдущей части мы создали заготовку для нашего проекта. Настало время писать код. В этой части мы добавим в наш проект голограмму и научимся взаимодействовать с нею.

Разработка

Все материалы, используемые в статье, можно скачать по ссылке.

Добавьте папку Article в папку Asset вашего проекта. Выберите префаб Car, переместите его в вашу сцену. Значения transform задайте как на скриншоте.

Как разрабатывать приложения смешанной реальности для Microsoft HoloLens: взаимодействие с голограммами 1

Transform объекта

Вид основной сцены представлен на рисунке:

Как разрабатывать приложения смешанной реальности для Microsoft HoloLens: взаимодействие с голограммами 2
Главная сцена в Unity

Теперь необходимо изменить компонент Shader у каждого материала нашей машины. Стандартные шейдеры Unity не подходят, т.к. они оказывают высокую нагрузку на HPU. Для наших целей хватит и шейдеров HoloToolkit, например, Standart Fast. Задайте следующие настройки шейдера:

  • Rendering Mode → Fade
  • Metallic = 0.4

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

Как разрабатывать приложения смешанной реальности для Microsoft HoloLens: взаимодействие с голограммами 3
Установка шейдеров

Существует три основных типа взаимодействия пользователя с голограммами:

  • Взаимодействие взглядом (Gaze);
  • Взаимодействие жестами (Gesture);
  • Взаимодействие голосом (Voice).

Взаимодействие взглядом

Для того, чтобы можно было взаимодействовать с автомобилем (или с отдельной его частью), необходимо добавить компонент Collider (в меню Сomponent → Physics, или в инспекторе Add Component → Physics). Добавьте Mesh Collider для каждой двери автомобиля. Добавьте в сцену префаб Cursor. Создайте для него простой скрипт:

			using System.Linq;
using UnityEngine;
using HoloToolkit.Unity;

public class GazeTest : Singleton {

    private MeshRenderer _meshRenderer;

    private static RaycastHit _hitInfo;

    public GameObject FocusedObject { get; private set; }

    void Start() {
        _meshRenderer = gameObject.GetComponent();
    }

    void Update() {
        // Используем стандартное API Unity Physics.Raycast
        // Выпускаем "луч" из камеры, взаимодействующий с коллайдерами
        var gazeOrigin = Camera.main.transform.position;
        var gazeDirection = Camera.main.transform.forward;
        Debug.DrawRay(gazeOrigin, gazeDirection, Color.red); // Отрисовка "луча" в Unity.
        if (Physics.Raycast(gazeOrigin, gazeDirection, out _hitInfo))
        {
            // отображаем курсор, если луч попадает на объект коллайдер голограммы
            _meshRenderer.enabled = true;

            // Задаём позицию и поворот курсора в то место, куда направлен луч.
            transform.position = _hitInfo.point + _hitInfo.normal * 0.0001f;

            transform.rotation = Quaternion.FromToRotation(Vector3.back, _hitInfo.normal);

            // Запоминаем голограмму, на которую направлен фокус
            FocusedObject = _hitInfo.collider.gameObject;

        }
        else //Сбрасываем, если луч не направлен на голограмму.
        {
            FocusedObject = null;
            _meshRenderer.enabled = false;
        }
    }
}
		

Прикрепите скрипт к префабу. Теперь, при наведении взгляда на дверь автомобиля, высветится курсор:

Как разрабатывать приложения смешанной реальности для Microsoft HoloLens: взаимодействие с голограммами 4

Можно поэкспериментировать, добавив взаимодействие при наведении взгляда на нужный объект, например, подсветку объекта. Или осуществить нажатие на объект при длительном удержании взгляда. Однако, такие функции есть даже у приложений для CardBoard, ввиду ограниченных возможностей которого нельзя реализовать более сложный интерактив (если я ошибаюсь — просветите меня).

Взаимодействие жестами

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

  • нажатие (Air-Tap);
  • удержание (Hold);
  • удержание + движение руки (Manipulation и Navigation).

Существует также Bloom-жест, предназначенный для вызова меню или постановки работающего приложения на паузу. Все жесты описаны в операционной системе, а разрабатывать свои на данный момент нельзя, но, надеюсь, эту возможность рано или поздно добавят. Самый распространенный — Tap (он же AirTap, он же «щелчок»). На картинке можно увидеть, как он выполняется.

Как разрабатывать приложения смешанной реальности для Microsoft HoloLens: взаимодействие с голограммами 5

Hololens также распознаёт Double Tap — двойной AirTap. Hold — просто опускаем указательный палец, не поднимая вверх. Navigation и Manipulation используются в связке Hold + движение руки.

В Unity управление жестами реализуется с помощью двух классов: GestureRecognizer и InteractionManager. Первый просто распознаёт указанные жесты, а второй определяет тип взаимодействия, позволяя получить более детальную информацию. Оба класса принадлежат пространству имён UnityEngine.VR.WSA.Input, поэтому обязательно добавляйте его в свои скрипты. Мы будем использовать GestureRecognizer. Сделаем так, чтобы при нажатии на дверь запускалась анимация её открытия. Создадим сначала простой класс CarController:

			public class CarController : Singleton {

    public void OpenDoors() {
        var openDoorAnimation = GetComponentInChildren();
        openDoorAnimation.Play();
    }
}
		

Теперь создадим класс GestureTest:

			using UnityEngine.VR.WSA.Input;

public class GestureTest : Singleton {
    //Наш распознаватель.
    private GestureRecognizer _recognizer;
    void Start () {
        _recognizer = new GestureRecognizer();
        _recognizer.SetRecognizableGestures(GestureSettings.Tap);
        _recognizer.TappedEvent += _recognizer_TappedEvent;
        _recognizer.StartCapturingGestures();
    }

    //tapCount = 1 для Tap, tapCount = 2 – для DoubleTap.
    private void _recognizer_TappedEvent(InteractionSourceKind source, int tapCount, Ray headRay) {
        CarController.Instance.OpenDoors();
    }
}
		

Окей, а если нужно повернуть автомобиль? Или рассмотреть какую-то из его частей отдельно? Давайте слегка поменяем наш код, чтобы можно было «оторвать» у автомобиля дверь. В список распознаваемых жестов добавьте GestureSettings.ManipulationTranslate, и подпишитесь на события _recognizer. Полностью код будет выглядеть вот так:

			using UnityEngine.VR.WSA.Input;

public class GestureTest : Singleton {

    //Наш распознаватель.
    private GestureRecognizer _recognizer;

    //Позиция объекта начальная.
    private Vector3 _defaultPosition;

    private bool _isMoving;

    void Start ()
    {
        _recognizer = new GestureRecognizer();
        // Распознаваемые жесты задаются через |
        _recognizer.SetRecognizableGestures(GestureSettings.Tap | GestureSettings.ManipulationTranslate);
        _recognizer.TappedEvent += _recognizer_TappedEvent;
        _recognizer.ManipulationStartedEvent += _recognizer_ManipulationStartedEvent;
        _recognizer.ManipulationUpdatedEvent += _recognizer_ManipulationUpdatedEvent;
        _recognizer.ManipulationCompletedEvent += _recognizer_ManipulationCompletedEvent;
        _recognizer.ManipulationCanceledEvent += _recognizer_ManipulationCanceledEvent;
        _recognizer.StartCapturingGestures();
       }

    #region - Манипуляция -

    private void _recognizer_ManipulationStartedEvent(InteractionSourceKind source, Vector3 cumulativeDelta, Ray headRay)
    {
        if (GazeTest.Instance.FocusedObject != null) {  //Взаимодействуем с объектом, на который направлен взгляд.
            _defaultPosition = cumulativeDelta; //запоминаем начальную позицию.
            _isMoving = true;
        }
    }

    private void _recognizer_ManipulationUpdatedEvent(InteractionSourceKind source, Vector3 cumulativeDelta, Ray headRay)
    {
        if (_isMoving) { // Рассчитываем вектор перемещения.
            var position = cumulativeDelta - _defaultPosition;
            _defaultPosition = position;
            GazeTest.Instance.FocusedObject.transform.position += position;
        }
    }

    //Cancel вызывается, если рука вышла за поле зрения устройства...
    private void _recognizer_ManipulationCanceledEvent(InteractionSourceKind source, Vector3 cumulativeDelta,
        Ray headRay)
    {
        _isMoving = false;
    }

    //А Complete - когда мы отпускаем палец.
    private void _recognizer_ManipulationCompletedEvent(InteractionSourceKind source, Vector3 cumulativeDelta,
        Ray headRay)
    {
        _isMoving = false;
    }

    #endregion

    private void _recognizer_TappedEvent(InteractionSourceKind source, int tapCount, Ray headRay) {
        CarController.Instance.OpenDoors();
    }
}
		

Здесь необходимо дать небольшое пояснение, почему мы использовали Manipulation. Дело в том, что при манипуляции устройство считывает координаты положения руки в абсолютной величине. Это очень удобно использовать при перемещении голограмм в пространстве: они будут следовать за вашей рукой. В случае, если вам необходимо ограничить перемещение, повернуть объект или выполнить Scale объекта, лучше использовать Navigation. При Navigation подразумевается, что передвижение вашей руки происходит вдоль оси X, Y или Z. Как только срабатывает событие OnStart, в пространстве рисуется нормированный куб с координатами вершин -1, 0 и 1, где 0 — центр, который расположен в точке зажатия пальцев. Вместо cumulativeDelta (см. события Manipulation) мы получаем вектор normalizedOffset, значение X, Y или Z которого меняется от -1 до 1, при этом оставшиеся две координаты будут равны 0.

В таком случае, вместо GestureSettings.ManipulationTranslate в метод SetRecognizableGestures добавится GestureSettings.NavigationX (при навигации по оси X). Почему вместо? Потому что Navigation и Manipulation не совместимы в рамках одного экземпляра GestureRecognizer, более того, вы не сможете добавить одновременно навигацию по всем трём осям. Вам придётся создавать несколько классов, отвечающих за то или иное взаимодействие, при этом вы не сможете использовать несколько GestureRecognizer одновременно, даже если они отвечают за разные типы взаимодействий. Переключаться между ними придётся в Runtime, один из способов сделать это — голосовое управление, речь о котором пойдёт ниже.

Взаимодействие голосом

Hololens имеет на борту 4 микрофона и прекрасно распознаёт человеческую речь. Речь можно использовать как для ввода текста, так и для выполнения команд.

В предыдущем примере мы сделали так, чтобы пользователь мог убрать двери автомобиля и заглянуть внутрь. А если захочется детально рассмотреть двигатель? Или выхлопную систему? Можно воспользоваться предыдущим примером и разобрать автомобиль как конструктор. А можно сказать «Покажи мне двигатель» и сразу всё увидеть. Давайте так и сделаем. В качестве «распознавателя голоса» будем использовать класс KeywordRecognizer :

			using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Windows.Speech; //Обязательно добавляем.

public class VoiceTest : MonoBehaviour {
    //Словарь, где ключ - фраза, которую произносит пользователь, а значение - выполняемое действие.
    private readonly Dictionary _keywords = new Dictionary();

    private KeywordRecognizer _keywordRecognizer;

    void Start () {
        _keywords.Add("Show Engine", CarController.Instance.ShowEngine);
        _keywords.Add("Show Car", CarController.Instance.ShowCar);

        _keywordRecognizer = new KeywordRecognizer(_keywords.Keys.ToArray()); // Все константы необходимо добавить во время создания объекта.
        _keywordRecognizer.OnPhraseRecognized += _keywordRecognizer_OnPhraseRecognized;
        _keywordRecognizer.Start();
    }

    private void _keywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
    {
        System.Action keywordAction;
        Debug.LogWarning(args.text);
        if (_keywords.TryGetValue(args.text, out keywordAction))
        {
            keywordAction.Invoke();
        }
    }
}
		

Для того, чтобы работали голосовые команды, необходимо в Capabilities активировать микрофон. В части 2 мы выполнили этот шаг. Осталось поменять код CarController, чтобы осуществить задуманное:

			using UnityEngine;
using HoloToolkit.Unity;

public enum CarDisplayState { Full, Engine}

public class CarController : Singleton {
    private GameObject _engine;

    private CarDisplayState _carDisplayState;

    private void SetChildObjectsActive(bool active) {
        for (int i = 0; i < transform.childCount; i++)
        {
            transform.GetChild(i).gameObject.SetActive(active);
        }
    }

    public void OpenDoors() {
        var openDoorAnimation = GetComponentInChildren();
        openDoorAnimation.Play();
    }

    public void ShowEngine() {
        if (_carDisplayState == CarDisplayState.Engine)
            return;
        var engine = GameObject.FindWithTag("Engine");
        _engine = Instantiate(engine,engine.transform.position,engine.transform.rotation) as GameObject;
        _engine.transform.localScale = engine.transform.lossyScale;
        SetChildObjectsActive(false);
        _carDisplayState = CarDisplayState.Engine;
    }

    public void ShowCar() {
        if (_carDisplayState == CarDisplayState.Full)
            return;
        Destroy(_engine);
        SetChildObjectsActive(true);
        _carDisplayState = CarDisplayState.Full;
    }
}
		

Распознавание голоса в Hololens не очень удобное, так как устройство пока дружит только с английским языком. Зато оно точное и универсальное, и его можно использовать, например, чтобы переключаться между разными GestureRecognizer. При этом, в отличие от GestureRecognizer, у вас может быть несколько KeyWordRecognizer, работающих одновременно. Но делать так не рекомендуется, так как это приводит к дополнительной нагрузке на программу и путанице.

Мы познакомились с основными способами пользовательского взаимодействия с голограммами в Hololens. В следующей части мы научимся взаимодействовать с окружающим пространством.

Выражаем благодарность Тимуру Ахметову, разработчику из компании HoloGroup и департаменту стратегических технологий Microsoft за предоставленный материал.

Для справки: HoloGroup является одним из первых разработчиков для HoloLens в России и 1 сентября 2016 года выпустила первое русскоязычное приложение HoloStudy.

Виртуальная реальность
Microsoft
HoloLens
Материалы от друзей Tproger
3384