Interfejs Hit Test API umożliwia umieszczanie wirtualnych obiektów w widoku rzeczywistym.
Interfejs WebXR Device API został wprowadzony jesienią ubiegłego roku w Chrome 79. Jak już wspomnieliśmy, prace nad implementacją interfejsu API w Chrome są w toku. Z przyjemnością informujemy, że część prac jest już ukończona. W Chrome 81 pojawiły się 2 nowe funkcje:
Z tego artykułu dowiesz się, jak korzystać z interfejsu API WebXR Hit Test, który umożliwia umieszczanie wirtualnych obiektów w widoku z kamery.
W tym artykule zakładam, że wiesz już, jak tworzyć sesje rozszerzonej rzeczywistości i jak uruchamiać pętlę klatki. Jeśli nie znasz tych pojęć, przeczytaj wcześniejsze artykuły z tej serii.
- Wirtualna rzeczywistość w internecie
- Wirtualna rzeczywistość w internecie, część II
- AR w internecie: być może wiesz już, jak z niego korzystać
Przykład wciągającej sesji AR
Kod w tym artykule został opracowany na podstawie przykładowego testu trafienia z grupy Immersive WebWork Group (demo, source), lecz nie jest identyczny. W tym przykładzie możesz umieścić wirtualne słoneczniki na powierzchniach w rzeczywistym świecie.
Po pierwszym uruchomieniu aplikacji zobaczysz niebieski okrąg z kropką w środku. Punkt jest miejscem przecięcia wyimaginowanej linii od urządzenia do punktu w środowisku. Przesuwa się ona wraz z urządzeniem. Gdy znajdzie punkty przecięcia, wydaje się, że przykleja się do powierzchni, takich jak podłoga, blaty stołów czy ściany. Dzieje się tak, ponieważ testowanie trafień dostarcza informacji o pozycji i orientacji punktu przecięcia, ale nie o samych powierzchniach.
Ten okrąg nazywa się siatka. Jest to tymczasowy obraz, który pomaga umieszczać obiekty w rzeczywistości rozszerzonej. Gdy klikniesz ekran, na powierzchni w miejscu, w którym znajduje się celownik, pojawi się obrazek słońca, niezależnie od tego, gdzie klikniesz ekran. Siatka siatkowa cały czas przesuwa się razem z urządzeniem.
Tworzenie siatki
Musisz samodzielnie utworzyć obraz siatki celowniczej, ponieważ nie jest on udostępniany przez przeglądarkę ani interfejs API. Sposób wczytywania i rysowania zależy od konkretnej platformy.
Jeśli nie rysujesz go bezpośrednio za pomocą WebGL lub WebGL 2, zapoznaj się z dokumentacją platformy. Z tego powodu nie będę szczegółowo omawiać sposobu, w jaki siatkówka jest narysowana w próbce. Poniżej pokazujemy jeden wiersz tego kodu tylko z jednego powodu. Aby w późniejszych przykładach kodu uświadomiłem Ci, do czego mam odniesienia, używając zmiennej reticle
.
let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});
Prośba o sesję
Gdy żądasz sesji, musisz poprosić o 'hit-test'
w tablicy requiredFeatures
, jak pokazano poniżej.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
Rozpoczynanie sesji
W poprzednich artykułach przedstawiłem kod do otwierania sesji XR. Poniżej przedstawiam wersję z kilkoma dodatkami. Najpierw dodałem listenera zdarzenia select
. Gdy użytkownik dotknie ekranu, w widoku aparatu zostanie umieszczony kwiat dopasowany do pozycji siatki. Opiszę ten odbiorca zdarzeń później.
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);
});
}
Wiele przestrzeni referencyjnych
Zwróć uwagę, że zaznaczony kod wywołuje 2 razy XRSession.requestReferenceSpace()
. Początkowo było to dla mnie niejasne. Zapytanie o to, dlaczego kod testu kolizji nie prosi o ramkę animacji (rozpoczynającą pętlę ramek) i dlaczego pętla ramek wydaje się nie uwzględniać testów kolizji. Pomylenie wynikało z nieporozumienia pojęcia przestrzeni referencyjnych. Przestrzeń referencyjna wyraża relacje między punktem początkowym a światem.
Aby zrozumieć, do czego służy ten kod, udawaj, że wyświetlasz ten przykład za pomocą oddzielnego zestawu i masz zestaw słuchawkowy oraz kontroler. Aby mierzyć odległości od kontrolera, należy użyć ramki odniesienia z ośrodkiem w kontrolerze. Aby jednak coś narysować na ekranie, użyjesz współrzędnych skoncentrowanych na użytkowniku.
W tym przykładzie urządzenie wyświetlające i urządzenie sterujące to to samo urządzenie. Mam jednak problem. To, co rysuję, musi być stabilne w stosunku do otoczenia, ale „kontroler”, którym rysuję, się porusza.
Do rysowania obrazu używam przestrzeni odniesienia local
, która zapewnia mi stabilność w zakresie środowiska. Po uzyskaniu tego uruchamiam pętlę klatek, wywołując funkcję requestAnimationFrame()
.
Do testowania trafień używam przestrzeni referencyjnej viewer
, która jest określana na podstawie pozycji urządzenia w momencie testu trafień. Etykieta „widz” jest w tym kontekście nieco myląca, ponieważ mówię o kontrolerze. To ma sens, jeśli potraktujesz kontroler jako elektroniczny wyświetlacz. Potem nazywam się xrSession.requestHitTestSource()
, co tworzy źródło danych testowych trafień, których użyję do rysowania.
Wykonywanie pętli klatki
Nowy kod do obsługi testowania trafień jest też przekazywany do funkcji wywołania zwrotnego requestAnimationFrame()
.
Gdy ruszasz urządzeniem, siatka celownika musi przemieszczać się razem z nim, szukając powierzchni. Aby stworzyć iluzję ruchu, narysuj siatkę w każdym ujęciu.
Nie pokazuj jednak siatki, jeśli test trafienia się nie powiedzie. W przypadku siatki, którą wcześniej utworzyłem, ustawiłem właściwość visible
na 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
}
Aby narysować coś w AR, muszę wiedzieć, gdzie znajduje się użytkownik i w jaką stronę patrzy. Sprawdzam, czy hitTestSource
i xrViewerPose
są nadal prawidłowe.
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
}
Teraz dzwonię pod numer getHitTestResults()
. Przyjmuje jako argument element hitTestSource
i zwraca tablicę instancji HitTestResult
. Test kolizji może wykryć wiele powierzchni. Pierwszy element tablicy jest najbliżej aparatu.
Zwykle jest ona używana, ale w zaawansowanych przypadkach użycia zwracana jest tablica. Wyobraź sobie na przykład, że kamera jest skierowana na pudełko na stole na podłodze. Test trafienia może zwrócić wszystkie 3 powierzchnie w tablicy. W większości przypadków będzie to pole „Mnie interesuje”. Jeśli długość zwróconego tablicy wynosi 0, czyli jeśli nie zwrócono żadnego testu dopasowania, przejdź dalej. Spróbuj ponownie w następnym ujęciu.
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
}
Na koniec muszę przetworzyć wyniki testu działań. Oto podstawowy proces. Uzyskaj pozę z wyniku testu uderzenia, przetransformuj (przesuń) obraz siatki do pozycji testu uderzenia, a potem ustaw jego właściwość visible
na wartość true. Ta pozycja odzwierciedla położenie punktu na powierzchni.
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
}
Umieszczanie obiektu
Gdy użytkownik kliknie ekran, obiekt zostanie umieszczony w rozszerzonej rzeczywistości. Do sesji został już dodany element obsługi zdarzenia select
. (patrz powyżej).
Ważne jest, aby wiedzieć, gdzie go umieścić. Ponieważ poruszający się celownik zapewnia stałe źródło testów kolizji, najprostszym sposobem umieszczenia obiektu jest narysowanie go w miejscu celownika w miejscu ostatniego testu kolizji.
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);
}
}
Podsumowanie
Najlepszym sposobem na zapoznanie się z tą funkcją jest użycie przykładowego kodu lub skorzystanie z ćwiczenia z kodem. Mam nadzieję, że udało mi się zrozumieć oba te zagadnienia.
Nie skończyliśmy jeszcze tworzenia interfejsów API dla immersywnej sieci. W miarę postępów będziemy publikować tutaj nowe artykuły.
Zdjęcie: Daniel Frank z Unsplash