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, wciąż pracujemy nad wdrażaniem interfejsu API w Chrome. Z przyjemnością informujemy, że część prac została już ukończona. W Chrome 81 pojawiły się 2 nowe funkcje:
W tym artykule omawiamy WebXR Hit Test API, który służy do umieszczania wirtualnych obiektów w widoku kamery w świecie rzeczywistym.
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 jest oparty na przykładowym teście trafień grupy roboczej Immersive Web (demo, źródło), ale nie jest z nim identyczny. W tym przykładzie możesz umieścić wirtualne słoneczniki na powierzchniach w rzeczywistym świecie.
Po pierwszym uruchomieniu aplikacji zobaczysz niebieskie kółko z kropką pośrodku. Punkt jest miejscem przecięcia wyimaginowanej linii od urządzenia do punktu w środowisku. Przesuwa się ona wraz z urządzeniem. Po znalezieniu punktów przecięcia powoduje wrażenie przyciągania do powierzchni takich jak podłogi, blaty stołu i ś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. Celownik będzie się nadal poruszać wraz 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. Dlatego nie będę szczegółowo omawiać tego, jak wyznacza się siatkę w próbce. Poniżej podaję tylko jedną linię kodu z jednego powodu: aby w późniejszych przykładach kodu wiedzieć, do czego się odnoszę, gdy używam 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 wejścia w sesję XR. Poniżej przedstawiam wersję z kilkoma dodatkami. Najpierw dodałem listenera zdarzenia select
. Gdy użytkownik kliknie ekran, w widoku aparatu pojawi się kwiat, który będzie widoczny w ramce celownika. Później opiszę ten detektor zdarzeń.
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 wyróżniony kod wywołuje funkcję XRSession.requestReferenceSpace()
dwa razy. 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. Ale mam pewien 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 testów trafień korzystam z przestrzeni referencyjnej viewer
, która określa pozycję urządzenia w momencie testu trafienia. Etykieta „przeglądający” jest w tym kontekście nieco myląca,
bo mam na myśli kontroler. 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.
Uruchamianie pętli ramek
Nowy kod do obsługi testowania trafień jest też przekazywany do funkcji wywołania zwrotnego requestAnimationFrame()
.
Gdy przenosisz urządzenie, celownik musi się poruszać razem z nim, aby znajdować powierzchnie. 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. Możliwe, że test trafienia zwróci wszystkie 3 płaszczyzny 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 uderzeń. 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
Obiekt jest umieszczany w AR, gdy użytkownik kliknie ekran. Do sesji został już dodany element obsługi zdarzenia select
. (patrz wyżej).
Ważne jest, aby wiedzieć, gdzie go umieścić. Ruchomy siatka celownika to stałe źródło testów trafień, dlatego najprostszym sposobem na umieszczenie obiektu jest narysowanie go w lokalizacji celownika podczas ostatniego testu trafienia.
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