HMD + Kinect = Дополненная виртуальность
В этой заметке я хочу рассказать об идее и Proof-Of-Concept добавления объектов реального мира в Виртуальную Реальность.
На мой взгляд, описанная идея в ближайшее время будет реализована всеми игроками VR-рынка. ИМХО, единственная причина, по которой это до сих пор не сделано — желание выкатить идеальное решение, а это не так-то просто.
Уже много лет я продумываю конструкцию гидравлической кабины для симулятора МехВоина.
Но это не мешает периодически обдумывать всевозможные варианты конструкции.
Раньше я планировал размещать внутри кабины множество дисплеев, часть из которых будет работать по назначению, а вторая часть будет эмулировать «окна»/бойницы.
Однако в современном мире в голову приходит другое решение — VR-шлем (Head-Mounted Display). Добиваться качественного погружения гораздо проще, работая со шлемом, т.к. не требуется тщательно вылизывать интерьер реальной кабины. Да и переделывать дизайн в разы проще, но есть НО.
Нормальный пульт управления мехом — сложная и интересная штука. Самый простой аутентичный контроллер выглядит вот так:
Делать в серьезном симуляторе что-то проще (например управление с геймпада) — не вариант.
Предположим, замоделить панель управления и поместить её в VR-мир не составляет проблемы.
Однако, управление таким количеством мелких тумблеров «наощупь» — очень плохая идея.
Но как же быть, ведь пользователь не видит своих рук в VR-мире.
Конечно, есть
, но сегодня не о них…
На данный момент активно развиваются камеры глубины. Первыми о них заявили Microsoft со своим Кинектом. К сожалению, МС решили что Кинект экономически не оправдан и закрыли проект. Однако дело не умерло, как можно было подумать. Apple внедрила камеру глубины в последний iPhone, именно такая камера отвечает за распознавание лица владельца.
MS тоже не отказалась от технологии, VR-шлемы на платформе Windows Mixed Reality используют технологию inside-out tracking на основе камер глубины.
Очевидным решением является прикрутить камеру глубины на VR шлем и накладывать полученную геометрию на VR-мир. Но почему-то этого никто не делает.
ZED Mini вроде бы умеют строить 3Д-мир и крепятся на шлем, но вживую я их не видел, а все проморолики используют информацию о мире только для наложения 3Д моделей на него, но не наоборот. Полагаю, проблема в низком качестве полученной модели, что будет сразу видно при попытке визуализации.
К сожалению, возможности прикрутить Кинект на шлем у меня нет. Кинект огромный и тяжелый, без инвазивного вмешательства в конструкцию шлема нормальное крепление не сделать. А шлем одолженный и портить я его не могу.
Поэтому для этого мини-проекта я разместил кинект вертикально над столом.
Этот вариант меня полностью устраивает, т.к. в случае размещения кинекта внутри кабины виртуального меха он тоже был бы размещен над панелью управления для детекта исключительно рук игрока и отсечения всех остальных объектов.
Перейдем к проекту (разбора кода не будет, только теория и немного картинок)
С помощью libfreenect2 и OpenNI — получаем карту высот.
Как визуализировать эту карту высот?
Очевидных вариантов в Unreal Engine три.
Меш с картой высот, задающей Z смещение вершины.
Очевидный и самый быстрый вариант. Меш полностью статический, меняется только текстуры (что очень быстро).
К сожалению у этого метода есть серьезный недостаток: отсутствие возможниости прикрутить физику. С точки зрения физики такой меш — абсолютно плоский и цельный. Простой прямоугольник. То что у нас часть вершин прозрачные и отрезаны альфа тестом — физика не видит. То, что у нас вершины модифицируются по Z координате — физика не видит.
Построение меша вручную на низком уровне.
Для этого нам нужно перекрыть UPrimitiveComponent и реализовать новый компонент с использованием SceneProxy. Это низкоуровневый подход, дающий лучшую скорость.
Основной недостаток — достаточно высокая сложность реализации.
Если делать полноценно — именно этот вариант и стоит избрать.
Но т.к. передо мной стояла задача сделать быстро и просто, я воспользовался третьим вариантом.
Реализация на базе UProceduralMeshComponent
Это встроенный в UE компонент, который позволяет легко и просто создавать меш и даже сразу считать объект для рассчета коллизий.
Почему нужно использовать второй вариант, а не этот?
Потому что данный компонент не предназначен для работы с динамической геометрией. Он заточен под то, чтобы мы один раз (или как минимум не очень часто и желательно не в реалтайме) передаем ему геометрию, она медленно считается и дальше мы с ней быстро работаем. У нас не тот случай…
Но для теста сойдет. Тем более сцена пустая и компьютеру больше нечего считать, так что ресурсов с запасом.
Визуализировать объекты их изображением с камеры — вариант не очень. Очень сильно выделяются реальные фото на фоне виртуального мира.
Поэтому решил визуализировать по аналогии с SteamVR — линиями. Наложил текстуру голубой сетки без заполнения. Плюс по контуру сделал обводку. Получилось вполне приемлемо. Правда с полной прозрачностью руки воспринимались чуть хуже, поэтому заполнение квадратов сделал слегка заметно голубоватым.
На скриншоте видно эффект «стекания» геометрии. Это связано с неспособностью камер глубины нормально обрабатывать грани с углом близким к 90 градусов к камере. Явно вырожденные пиксели кинект помечает значением 0, но, к сожалению, не все и часть из них «шумит», не вырождаясь. Я сделал набор несложных манипуляций, чтобы убрать основной шум, но полностью избавиться от «стекания» не получилось.
Стоит отметить, что этот эффект сильно заметен при взгляде сбоку (сидим перед столом, а кинект сверху). Если же камера глубины будет направлена параллельно взгляду и исходить из точки, близкой к реальным органам зрения пользователя — этот эффект будет направлен вперед и значительно менее заметен.
Как можно заметить на видео — реальные руки вполне работают внутри VR-мира. Единственный серьезный недостаток — дискретность перемещения.
Меш не морфирует плавно в новое состояние, а удаляется и создается заново. Из-за чего при резком движении физические объекты проваливаются сквозь него, поэтому двигаем медленно и аккуратно:
Прошу прощения за темное видео — занимался проектом по вечерам после работы, на превью видео показалось нормальным, когда уже всё оборудование снял и перенес видео на комп — оказалось, что оно очень темное.
Выкладываю в виде архива исходники плагина.
Добавляем как обычный плагин в любой UE проект.
Я не стал разбираться как подключить lib файл с помощью относительного пути, поэтому в OpenNI2CameraAndMesh.Build.cs прописываем полный путь до OpenNI2.lib
Далее размещаем ADepthMeshDirect в нужном нам месте.
При старте уровня вызываем метод startOpenNICamera из UTools.
Не забываем, что для работы с кинектом используется libfreenect2, а значит драйвер на кинект надо переопределить на libusbK в соответствии с инструкцией на странице libfreenect2
UPD:
В начале статьи я сказал, что такая система скоро будет во всех VR шлемах. Но в процессе написания как-то упустил из виду этот момент и не раскрыл его.
Поэтому процитирую свой комментарий, который написал ниже для раскрытия этой темы:
Если же говорить — зачем такая система нужна во всех VR системах без исключения — это безопасность.Сейчас границы игровой зоны отмечаются условным кубом.
Но редко кто из нас может позволить себе выделить абсолютно пустое пространство под VR. В итоге в комнате остаются предметы, иногда опасные.
Основное, что, я уверен, будет сделано — это виртуальное отображение всей комнаты перед игроком в виде едва различимого призрака, который с одной стороны не мешает восприятию игры, а с другой — позволяет не споткнуться и не умереть.
P.S.:
Хочу выразить огромную благодарность компании <которую нельзя называть вне корпоративного блога>, руководство которой выделило мне техническую базу для работы над этим проектом.