API проверки попадания позволяет размещать виртуальные объекты в реальном пространстве.
API WebXR Device был представлен прошлой осенью в Chrome 79. Как было заявлено тогда, реализация API в Chrome находится в стадии разработки. Chrome рад сообщить, что часть работы завершена. В Chrome 81 появились две новые функции:
В этой статье рассматривается API WebXR Hit Test , позволяющий размещать виртуальные объекты в поле зрения камеры реального мира.
В этой статье я предполагаю, что вы уже знаете, как создать сеанс дополненной реальности и как запустить цикл покадровой съемки. Если вы не знакомы с этими понятиями, вам следует прочитать предыдущие статьи этой серии.
- Виртуальная реальность приходит в интернет.
- Виртуальная реальность приходит в интернет, часть II.
- Веб-дополненная реальность: возможно, вы уже знаете, как ею пользоваться.
Пример иммерсивной сессии дополненной реальности.
Код в этой статье основан на примере Hit Test от Immersive Web Working Group ( демо , исходный код ), но не идентичен ему. Этот пример позволяет размещать виртуальные подсолнухи на поверхностях в реальном мире.
При первом открытии приложения вы увидите синий круг с точкой посередине. Точка — это точка пересечения воображаемой линии, проведенной от вашего устройства к точке в окружающей среде. Она перемещается вместе с вашим движением устройства. Находя точки пересечения, она как бы прикрепляется к таким поверхностям, как пол, столешницы и стены. Это происходит потому, что проверка на попадание предоставляет положение и ориентацию точки пересечения, но ничего не говорит о самих поверхностях.
Этот круг называется прицельной сеткой (retice) — это временное изображение, помогающее размещать объект в дополненной реальности. Если вы коснетесь экрана, на поверхности в месте расположения прицельной сетки и в соответствии с ее ориентацией будет размещен подсолнух, независимо от того, где вы коснулись экрана. Прицельная сетка продолжает перемещаться вместе с вашим устройством.

Создайте прицельную сетку
Изображение прицела необходимо создавать самостоятельно, поскольку оно не предоставляется браузером или API. Способ загрузки и отрисовки зависит от используемой платформы. Если вы не отрисовываете его напрямую с помощью WebGL или WebGL2, обратитесь к документации вашей платформы. По этой причине я не буду подробно описывать, как отрисовывается прицел в примере. Ниже я покажу одну строку кода только по одной причине: чтобы в последующих примерах кода вы понимали, на что я ссылаюсь, когда использую переменную reticle .
let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});
Запросить сессию
При запросе сессии необходимо указать 'hit-test' в массиве requiredFeatures , как показано ниже.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
Вход в сессию
В предыдущих статьях я представил код для входа в XR-сессию. Ниже я показал его версию с некоторыми дополнениями. Во-первых, я добавил обработчик события select . Когда пользователь касается экрана, цветок будет размещен в поле зрения камеры в зависимости от положения прицельной сетки. Я опишу этот обработчик события позже.
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
xrSession.addEventListener('select', onSelect);
let canvas = document.createElement('canvas');
gl = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(session, gl)
});
xrSession.requestReferenceSpace('viewer').then((refSpace) => {
xrViewerSpace = refSpace;
xrSession.requestHitTestSource({ space: xrViewerSpace })
.then((hitTestSource) => {
xrHitTestSource = hitTestSource;
});
});
xrSession.requestReferenceSpace('local').then((refSpace) => {
xrRefSpace = refSpace;
xrSession.requestAnimationFrame(onXRFrame);
});
}
Множественные пространства отсчета
Обратите внимание, что выделенный фрагмент кода дважды вызывает XRSession.requestReferenceSpace() . Сначала это меня смутило. Я спросил, почему код проверки попадания не запрашивает кадр анимации (запускающий цикл кадров) и почему цикл кадров, кажется, не включает проверки попадания. Источником путаницы стало неправильное понимание пространств отсчета. Пространства отсчета выражают отношения между началом координат и миром.
Чтобы понять, что делает этот код, представьте, что вы просматриваете этот пример с помощью автономного устройства, имея в распоряжении и гарнитуру, и контроллер. Для измерения расстояний от контроллера вы бы использовали систему координат, центрированную на контроллере. Но для отрисовки чего-либо на экране вы бы использовали координаты, центрированные на пользователе.
В этом примере устройство просмотра и контроллер — одно и то же. Но у меня проблема. То, что я рисую, должно быть стабильным относительно окружающей среды, но «контроллер», которым я рисую, постоянно движется.
Для отрисовки изображений я использую local пространство отсчета, что обеспечивает стабильность в контексте окружающей среды. После этого я запускаю цикл покадровой анимации, вызывая requestAnimationFrame() .
Для проверки попадания я использую пространство отсчета viewer , которое основано на положении устройства в момент проверки попадания. Название «средство просмотра» в данном контексте несколько сбивает с толку, поскольку речь идет о контроллере. Это становится понятнее, если рассматривать контроллер как электронное средство просмотра. После получения этих данных я вызываю метод xrSession.requestHitTestSource() , который создает источник данных для проверки попадания, которые я буду использовать при отрисовке.
Запуск циклической записи кадров
В функцию обратного вызова requestAnimationFrame() также добавлен новый код для обработки проверки попадания.
При перемещении устройства прицельная сетка должна двигаться вместе с ним, пытаясь обнаружить поверхности. Чтобы создать иллюзию движения, перерисовывайте прицельную сетку в каждом кадре. Но не отображайте прицельную сетку, если проверка попадания не удалась. Поэтому для созданной ранее прицельной сетки я установил свойство visible в false .
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Чтобы что-либо отобразить в дополненной реальности, мне нужно знать, где находится зритель и куда он смотрит. Поэтому я проверяю, что hitTestSource и xrViewerPose по-прежнему действительны.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Теперь я вызываю getHitTestResults() . Он принимает в качестве аргумента hitTestSource и возвращает массив экземпляров HitTestResult . Проверка попадания может обнаружить несколько поверхностей. Первая в массиве — это та, которая находится ближе всего к камере. В большинстве случаев вы будете использовать именно её, но для более сложных случаев возвращается массив. Например, представьте, что ваша камера направлена на коробку на столе на полу. Возможно, проверка попадания вернет все три поверхности из массива. В большинстве случаев меня будет интересовать именно коробка. Если длина возвращаемого массива равна 0, то есть если проверка попадания не была возвращена, продолжайте. Попробуйте снова в следующем кадре.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Наконец, мне нужно обработать результаты проверки попадания. Основной процесс таков: получить позу из результатов проверки попадания, преобразовать (переместить) изображение прицельной сетки в позицию проверки попадания, а затем установить его свойство visible в значение true. Поза представляет собой положение точки на поверхности.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.matrix = pose.transform.matrix;
reticle.visible = true;
}
}
// Draw to the screen
}
Размещение объекта
Объект размещается в дополненной реальности, когда пользователь касается экрана. Я уже добавил обработчик события select в сессию. ( См. выше .)
На этом этапе важно знать, куда его поместить. Поскольку подвижный прицел обеспечивает постоянный источник проверок попадания, самый простой способ разместить объект — нарисовать его в том месте, где прицел находился в момент последней проверки попадания.
function onSelect(event) {
if (reticle.visible) {
// The reticle should already be positioned at the latest hit point,
// so we can just use its matrix to save an unnecessary call to
// event.frame.getHitTestResults.
addARObjectAt(reticle.matrix);
}
}
Заключение
Лучший способ разобраться в этом — пошагово просмотреть пример кода или попробовать выполнить задания из CodeLab . Надеюсь, я предоставил вам достаточно информации, чтобы вы поняли и то, и другое.
Мы ещё далеко не закончили разработку иммерсивных веб-API. По мере продвижения мы будем публиковать здесь новые статьи.
Фотография Даниэля Франка на Unsplash.