Rzeczywistość wirtualna w internecie pojawia się w części II

Wszystko o pętli klatek

Joe Medley
Joe Medley

Niedawno opublikowałem artykuł Rzeczywistość wirtualna w sieci, w którym przedstawiłem podstawowe koncepcje interfejsu WebXR Device API. Podałem też instrukcje dotyczące wysyłania prośby o sesję XR, jej rozpoczynania i kończenia.

W tym artykule opisujemy pętlę klatek, czyli kontrolowaną przez agenta użytkownika pętlę nieskończoną, w której treść jest wielokrotnie rysowana na ekranie. Treści są rysowane w oddzielnych blokach zwanych klatkami. Kolejne klatki tworzą iluzję ruchu.

Czego nie znajdziesz w tym artykule

WebGL i WebGL2 to jedyne sposoby renderowania treści podczas pętli klatek w aplikacji WebXR. Na szczęście wiele platform zapewnia warstwę abstrakcji nad WebGL i WebGL2. Do takich platform należą three.js, babylonjs i PlayCanvas, a A-Frame i React 360 zostały zaprojektowane do interakcji z WebXR.

W tym artykule wyjaśniamy podstawy pętli klatek na przykładzie sesji VR w technologii Immersive Web Working Group (demo, źródło). Jeśli chcesz zagłębić się w WebGL lub jeden z frameworków, w internecie znajdziesz coraz więcej materiałów.

Gracze i gra

Próbując zrozumieć pętlę klatek, ciągle gubiłem się w szczegółach. W grze jest wiele obiektów, a niektóre z nich są nazywane tylko przez właściwości referencyjne innych obiektów. Aby Ci to ułatwić, opiszę obiekty, które będę nazywać „graczami”. Następnie opiszę, jak ze sobą współpracują, co nazywam „grą”.

Gracze

XRViewerPose

Pozycja to położenie i orientacja obiektu w przestrzeni 3D. Zarówno urządzenia wyświetlające, jak i urządzenia wejściowe mają pozycję, ale w tym przypadku interesuje nas pozycja urządzenia wyświetlającego. Zarówno pozycja widza, jak i urządzenia wejściowego mają atrybut transform opisujący ich położenie jako wektor i orientację jako kwaternion względem punktu początkowego. Pochodzenie jest określane na podstawie typu żądanej przestrzeni odniesienia podczas wywoływania funkcji XRSession.requestReferenceSpace().

Wyjaśnienie przestrzeni odniesienia zajmuje trochę czasu. Szczegółowo omawiam je w artykule Rzeczywistość rozszerzona. Przykładowa przestrzeń, na której opieram ten artykuł, to 'local'przestrzeń odniesienia. Oznacza to, że punkt początkowy znajduje się w miejscu, w którym użytkownik był w momencie utworzenia sesji, bez dobrze zdefiniowanej podłogi, a jego dokładne położenie może się różnić w zależności od platformy.

XRView

Widok odpowiada kamerze obserwującej scenę wirtualną. Widok ma też atrybut transform opisujący jego położenie jako wektor i orientację. Są one podawane jako para wektor/kwaternion i jako równoważna macierz. Możesz używać dowolnej reprezentacji w zależności od tego, która lepiej pasuje do Twojego kodu. Każdy widok odpowiada wyświetlaczowi lub jego części używanej przez urządzenie do wyświetlania obrazów użytkownikowi. Obiekty XRView są zwracane w tablicy z obiektu XRViewerPose. Liczba widoków w tablicy jest różna. Na urządzeniach mobilnych scena AR ma jeden widok, który może obejmować ekran urządzenia lub nie. Zestawy słuchawkowe mają zwykle 2 widoki, po jednym na każde oko.

XRWebGLLayer

Warstwy zawierają źródło obrazów bitmapowych i opisy sposobu renderowania tych obrazów na urządzeniu. Ten opis nie do końca oddaje funkcje tego odtwarzacza. Uważam, że jest to pośrednik między urządzeniem a WebGLRenderingContext. MDN ma podobne zdanie i stwierdza, że „zapewnia powiązanie” między tymi dwoma elementami. Dzięki temu inni gracze mają do niego dostęp.

Obiekty WebGL przechowują informacje o stanie renderowania grafiki 2D i 3D.

WebGLFramebuffer

Bufor ramki dostarcza dane obrazu do WebGLRenderingContext. Po pobraniu go z XRWebGLLayer przekazujesz go do bieżącego WebGLRenderingContext. Poza wywołaniem funkcji bindFramebuffer() (więcej informacji na ten temat znajdziesz poniżej) nigdy nie uzyskasz bezpośredniego dostępu do tego obiektu. Będziesz tylko przekazywać go z XRWebGLLayer do WebGLRenderingContext.

XRViewport

Viewport zawiera współrzędne i wymiary prostokątnego obszaru w WebGLFramebuffer.

WebGLRenderingContext

Kontekst renderowania to punkt dostępu programowego do obszaru rysowania (przestrzeni, w której rysujemy). Aby to zrobić, potrzebuje zarówno WebGLFramebuffer, jak i XRViewport.

Zwróć uwagę na zależność między kolumnami XRWebGLLayer i WebGLRenderingContext. Jeden z nich odpowiada urządzeniu przeglądającego, a drugi – stronie internetowej. WebGLFramebufferXRViewport są przekazywane z pierwszego do drugiego.

Relacja między XRWebGLLayer a WebGLRenderingContext
Zależność między XRWebGLLayer i WebGLRenderingContext

Gra

Skoro wiemy już, kto bierze udział w grze, przyjrzyjmy się jej zasadom. To gra, która zaczyna się od nowa w każdej klatce. Pamiętaj, że klatki są częścią pętli klatek, która jest wykonywana z częstotliwością zależną od sprzętu. W przypadku aplikacji VR liczba klatek na sekundę może wynosić od 60 do 144. Rzeczywistość rozszerzona na Androida działa z szybkością 30 klatek na sekundę. Kod nie powinien zakładać żadnej konkretnej liczby klatek na sekundę.

Podstawowy proces pętli klatek wygląda tak:

  1. Zadzwoń do firmy XRSession.requestAnimationFrame(). W odpowiedzi klient użytkownika wywołuje funkcję XRFrameRequestCallback zdefiniowaną przez Ciebie.
  2. W funkcji wywołania zwrotnego:
    1. Zadzwoń ponownie pod numer XRSession.requestAnimationFrame().
    2. Uzyskiwanie pozycji widza.
    3. Przenieś (powiąż) kartę WebGLFramebufferXRWebGLLayer na WebGLRenderingContext.
    4. Iteruj po każdym obiekcie XRView, pobierając jego XRViewportXRWebGLLayer i przekazując go do WebGLRenderingContext.
    5. Narysuj coś w buforze ramki.

Kroki 1 i 2a zostały opisane w poprzednim artykule, więc zacznę od kroku 2b.

Pobieranie pozycji widza

Prawdopodobnie nie musimy tego dodawać. Aby narysować cokolwiek w AR lub VR, muszę wiedzieć, gdzie znajduje się widz i gdzie patrzy. Pozycja i orientacja widza są podawane przez obiekt XRViewerPose. Pozycję widza uzyskuję, wywołując funkcję XRFrame.getViewerPose() w bieżącej klatce animacji. Przekazuję do niego przestrzeń odniesienia, którą uzyskałem podczas konfigurowania sesji. Wartości zwracane przez ten obiekt są zawsze względne w stosunku do przestrzeni odniesienia, o którą poprosiłem(-am) podczas wchodzenia w bieżącą sesję. Jak być może pamiętasz, podczas wysyłania prośby o określenie pozycji muszę przekazać bieżącą przestrzeń odniesienia.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    // Render based on the pose.
  }
}

Jest jedna pozycja widza, która reprezentuje ogólną pozycję użytkownika, czyli głowę widza lub kamerę telefonu. Pozycja informuje aplikację, gdzie znajduje się widz. Rzeczywiste renderowanie obrazu wykorzystuje obiektyXRView, o których opowiem za chwilę.

Zanim przejdę dalej, sprawdzam, czy pozycja widza została zwrócona na wypadek, gdyby system utracił śledzenie lub zablokował pozycję ze względu na ochronę prywatności. Śledzenie to zdolność urządzenia XR do określania swojego położenia i położenia urządzeń wejściowych względem otoczenia. Śledzenie może zostać utracone na kilka sposobów, w zależności od użytej metody. Jeśli na przykład do śledzenia urządzenia używane są kamery na goglach lub telefonie, urządzenie może utracić możliwość określania swojego położenia w sytuacjach, gdy jest mało światła lub nie ma go wcale, albo gdy kamery są zasłonięte.

Przykładem blokowania pozycji ze względu na ochronę prywatności jest sytuacja, w której zestaw słuchawkowy wyświetla okno dialogowe zabezpieczeń, np. prośbę o uprawnienia. W takim przypadku przeglądarka może przestać przekazywać aplikacji pozycje. Ale już dzwoniłem XRSession.requestAnimationFrame(), więc jeśli system może się zregenerować, pętla klatek będzie kontynuowana. W przeciwnym razie agent użytkownika zakończy sesję i wywoła procedurę obsługi zdarzenia end.

Krótki objazd

Kolejny krok wymaga obiektów utworzonych podczas konfigurowania sesji. Pamiętaj, że utworzyłem element canvas i wydałem mu polecenie utworzenia kontekstu renderowania WebGL zgodnego z XR, który uzyskałem, wywołując funkcję canvas.getContext(). Całe rysowanie odbywa się za pomocą interfejsu WebGL API, WebGL2 API lub platformy opartej na WebGL, takiej jak Three.js. Ten kontekst został przekazany do obiektu sesji za pomocą updateRenderState(), a także nowej instancji XRWebGLLayer.

let canvas = document.createElement('canvas');
// The rendering context must be based on WebGL or WebGL2
let webGLRenContext = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(xrSession, webGLRenContext)
  });

Przekaż (powiąż) WebGLFramebuffer

XRWebGLLayer udostępnia bufor ramki dla WebGLRenderingContext, który jest przeznaczony do używania z WebXR i zastępuje domyślny bufor ramki kontekstów renderowania. W języku WebGL jest to określane jako „wiązanie”.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    // Iterate over the views
  }
}

Iterowanie po każdym obiekcie XRView

Po uzyskaniu pozycji i powiązaniu bufora ramki czas na pobranie obszarów wyświetlania. XRViewerPose zawiera tablicę interfejsów XRView, z których każdy reprezentuje wyświetlacz lub jego część. Zawierają one informacje potrzebne do renderowania treści, które są prawidłowo umieszczone na urządzeniu i dla widza, takie jak pole widzenia, przesunięcie oka i inne właściwości optyczne. Rysuję dla 2 oczu, więc mam 2 widoki, które przeglądam w pętli i dla każdego z nich rysuję osobny obraz.

W przypadku implementacji rzeczywistości rozszerzonej na telefonie miałbym tylko jeden widok, ale nadal używałbym pętli. Chociaż iterowanie po jednym widoku może wydawać się bezcelowe, pozwala to na uzyskanie jednej ścieżki renderowania dla całego spektrum immersyjnych doświadczeń. To ważna różnica między WebXR a innymi systemami immersyjnymi.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    for (let xrView of xrViewerPose.views) {
      // Pass viewports to the context
    }
  }
}

Przekazywanie obiektu XRViewport do WebGLRenderingContext

Obiekt XRView odnosi się do tego, co jest widoczne na ekranie. Aby jednak narysować ten widok, potrzebuję współrzędnych i wymiarów, które są specyficzne dla mojego urządzenia. Podobnie jak w przypadku bufora ramki, żądam ich od XRWebGLLayer i przekazuję do WebGLRenderingContext.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    for (let xrView of xrViewerPose.views) {
      let viewport = glLayer.getViewport(xrView);
      webGLRenContext.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
      // Draw something to the framebuffer
    }
  }
}

The webGLRenContext

Podczas pisania artykułu dyskutowałem z kilkoma współpracownikami na temat nazwy obiektu webGLRenContext. W przykładowych skryptach i większości kodu WebXR ta zmienna jest nazywana gl. Podczas analizowania próbek ciągle zapominałem, do czego odnosi się symbol gl. Nazwałem go webGLRenContext, aby przypomnieć Ci, że podczas nauki jest to instancja WebGLRenderingContext.

Dzieje się tak, ponieważ użycie gl pozwala, aby nazwy metod wyglądały jak ich odpowiedniki w interfejsie OpenGL ES 2.0 API, który jest używany do tworzenia VR w językach kompilowanych. Jest to oczywiste, jeśli piszesz aplikacje VR przy użyciu OpenGL, ale może być mylące, jeśli dopiero zaczynasz korzystać z tej technologii.

Rysowanie w buforze ramki

Jeśli masz duże ambicje, możesz użyć WebGL bezpośrednio, ale nie polecam tego rozwiązania. O wiele łatwiej jest użyć jednej z platform wymienionych na górze.

Podsumowanie

To nie koniec aktualizacji ani artykułów dotyczących WebXR. Dokumentację wszystkich interfejsów i elementów WebXR znajdziesz w MDN. Informacje o nadchodzących ulepszeniach interfejsów znajdziesz na stronie Chrome Status.