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

Wszystko o pętli klatki

Joe Medley
Joe Medley

Niedawno opublikowałem opublikowany artykuł Wirtualna rzeczywistość w internecie, w którym przedstawiono podstawowe pojęcia związane z interfejsem WebXR Device API. Przekazałem też instrukcje dotyczące żądania, rozpoczęcia i zakończenia sesji XR.

W tym artykule opisano pętlę ramki, czyli pętlę nieskończoną kontrolowaną przez użytkownika, w której treści są wielokrotnie wyświetlane na ekranie. Zawartość jest rysowana w oddzielnych blokach nazywanych ramkami. Kolejność klatek tworzy 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 frameworków zapewnia warstwę abstrakcji nad WebGL i WebGL2. Do takich frameworków należą three.js, babylonjs i PlayCanvas, a frameworki A-Frame i React 360 zostały zaprojektowane do interakcji z WebXR.

Ten artykuł nie jest samouczkiem ani samouczkiem na temat WebGL. Wyjaśnia on podstawy pętli ramki za pomocą przykładowej sesji w wirtualnej rzeczywistości opracowanej przez grupę roboczą Immersive Web Working Group (prezentacja, źródło). Jeśli chcesz dowiedzieć się więcej o WebGL lub jednej z ramek, w internecie znajdziesz stale rosnącą listę artykułów.

Zawodnicy i gra

Gdy próbowałem zrozumieć zapętlenie klatki, nie udawało mi się zatracić się w szczegółach. W grze jest wiele obiektów, a niektóre z nich mają nazwy tylko na podstawie właściwości referencyjnych innych obiektów. Aby Ci to ułatwić, opiszę obiekty, które nazywam „graczami”. Potem opiszę ich interakcje – nazywam to „grą”.

Zawodnicy

XRViewerPose

Pozycja i orientacja obiektu w przestrzeni 3D. Zarówno widzowie, jak i urządzenia wejściowe mają pozę, ale w tym przypadku interesuje nas tylko pozycja widza. Zarówno pozy widza, jak i urządzenia wejściowego mają atrybut transform opisujący jego pozycję jako wektor i orientację jako wektor kwadratowy względem punktu wyjścia. Podczas wywoływania funkcji XRSession.requestReferenceSpace() pochodzenie jest określane na podstawie żądanego typu przestrzeni odniesienia.

Pokoje referencyjne wymagają nieco wyjaśnień. Omawiam je szczegółowo w artykule o rozszerzonej rzeczywistości. Przykład, którego używam w tym artykule, korzysta z przestrzeni odniesienia 'local', co oznacza, że punkt początkowy znajduje się w miejscu, w którym znajdował się widz w momencie tworzenia sesji, bez wyraźnie określonej podłogi. Dokładna pozycja może się różnić w zależności od platformy.

XRView

Widok odpowiada kamerze, która rejestruje wirtualną scenę. Widok ma też atrybut transform opisujący jego położenie jako wektora i jego orientację. Są one podawane jako para wektorów lub kwaternionów oraz jako macierz równoważna. Możesz użyć 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, której urządzenie używa do zaprezentowania obrazu użytkownikowi. Obiekty XRView są zwracane w tablicy obiektu XRViewerPose. Liczba wyświetleń w tablicy jest różna. Na urządzeniach mobilnych scena AR ma jeden widok, który może, ale nie musi wypełniać ekran urządzenia. Słuchawki mają zwykle 2 widoki, po jednym dla każdego oka.

XRWebGLLayer

Warstwy stanowią źródło obrazów bitmapowych i opisów sposobu ich renderowania na urządzeniu. Opis nie oddaje w pełni funkcji tego odtwarzacza. Uważam, że jest to pośrednik między urządzeniem a WebGLRenderingContext. MDN ma podobne zdanie, twierdząc, że „zapewnia połączenie” między tymi dwoma elementami. W ten sposób zapewnia dostęp do innych graczy.

Ogólnie obiekty WebGL przechowują informacje o stanie na potrzeby renderowania grafiki 2D i 3D.

WebGLFramebuffer

Bufor ramki przekazuje dane obrazu do WebGLRenderingContext. Po pobraniu XRWebGLLayer przekaż go do bieżącego zasobu WebGLRenderingContext. Poza wywołaniem funkcji bindFramebuffer() (więcej informacji znajdziesz poniżej) nie będziesz mieć bezpośredniego dostępu do tego obiektu. Wystarczy, że przekażesz go z XRWebGLLayer do WebGLRenderingContext.

XRViewport

Widoczny obszar udostępnia współrzędne i wymiary prostokątnego obszaru w polu WebGLFramebuffer.

WebGLRenderingContext

Kontekst renderowania to zautomatyzowany punkt dostępu do obszaru roboczego (przestrzeni, na której się opieramy). Aby to zrobić, potrzebuje zarówno WebGLFramebuffer, jak i XRViewport.

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

Związek między XRWebGLLayer i WebGLRenderingContext
Zależność między XRWebGLLayer a WebGLRenderingContext

Gra

Teraz, gdy już wiemy, kim są gracze, przyjrzyjmy się grze, w którą grają. To gra, która zaczyna się od nowa z każdą klatką. Pamiętaj, że klatki są częścią pętli klatek, która odbywa się z częstotliwością zależną od sprzętu. W aplikacjach VR liczba klatek na sekundę może wynosić od 60 do 144. AR na Androidzie działa z prędkością 30 klatek na sekundę. Twój kod nie powinien zakładać żadnej konkretnej szybkości klatek.

Podstawowy proces tworzenia pętli klatki wygląda tak:

  1. Zadzwoń do firmy XRSession.requestAnimationFrame(). W odpowiedzi klient użytkownika wywołuje zdefiniowaną przez Ciebie wartość XRFrameRequestCallback.
  2. W funkcji wywołania zwrotnego:
    1. Jeszcze raz zadzwoń pod numer XRSession.requestAnimationFrame().
    2. Uzyskaj pozę widza.
    3. Przekaż („Powiąż”) WebGLFramebuffer z: XRWebGLLayer do: WebGLRenderingContext.
    4. Wykonaj iterację dla każdego obiektu XRView, pobierając jego wartość XRViewport z XRWebGLLayer i przekazując ją do WebGLRenderingContext.
    5. Narysuj coś w buforze ramek.

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

Uchwyć się w pozycji widza

To chyba oczywiste. Aby narysować coś w AR lub VR, muszę wiedzieć, gdzie jest widz i co patrzy. Położenie i orientację użytkownika są określane przez obiekt XRViewerPose. Pobieram pozę widza, wywołując funkcję XRFrame.getViewerPose() w ramce animacji. Przekazuję mu przestrzeń referencyjną uzyskaną podczas konfigurowania sesji. Wartości zwracane przez ten obiekt zawsze odnoszą się do przestrzeni referencyjnej, o którą poproszono podczas wejścia do bieżącej sesji. Jak zapewne pamiętasz, muszę przekazać bieżącą przestrzeń odniesienia, gdy proszę o pozę.

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 przedstawia ogólną pozycję użytkownika, czyli głowę widza lub w przypadku smartfona kamerę telefonu. Poza informuje aplikację, gdzie znajduje się widz. Do renderowania obrazu służą obiekty XRView, o których opowiem za chwilę.

Zanim przejdę dalej, sprawdzam, czy położenie widza zostało zwrócone, na wypadek gdyby system stracił śledzenie lub zablokował położenie ze względów prywatności. Śledzenie to zdolność urządzenia XR do sprawdzania, gdzie względem środowiska znajduje się dane urządzenie lub urządzenia wejściowe. Śledzenie może zostać utracone na kilka sposobów, w zależności od metody śledzenia. Jeśli na przykład kamery w zestawie słuchawkowym lub w telefonie są używane do śledzenia urządzenia, mogą stracić zdolność do określenia, gdzie się znajduje w sytuacjach, gdy jest słabo oświetlone lub nie ma go w telefonie, albo czy 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 zabezpieczeń, np. prośbę o przyznanie uprawnień, i w tym czasie przeglądarka może przestać udostępniać aplikacji. Ale już zadzwoniłeś/zadzwoniłaś do XRSession.requestAnimationFrame(), więc jeśli system wróci do normalnego działania, pętla będzie kontynuowana. W przeciwnym razie agent użytkownika zakończy sesję i wywoła interfejs end.

Krótki objazd

Następny krok wymaga obiektów utworzonych podczas konfiguracji sesji. Pamiętaj, że utworzyłem już canvas i instrukcję tworzenia kontekstu renderowania WebGL zgodnego z XR, który otrzymałem po wywołaniu funkcji canvas.getContext(). Rysowanie odbywa się z użyciem interfejsów API WebGL, WebGL2 API lub platformy opartej na WebGL, takiej jak Three.js. Ten kontekst został przekazany do obiektu sesji za pomocą updateRenderState() wraz z nowym wystąpieniem 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)
  });

Przekazywanie (bind) WebGLFramebuffer

XRWebGLLayer udostępnia framebuffer dla WebGLRenderingContext, który jest przeznaczony do użytku w ramach WebXR i zastępuje domyślny framebuffer kontekstu renderowania. W języku WebGL nazywa się to „wiązaniem”.

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
  }
}

Przetwarzanie każdego obiektu XRView

Po pobraniu pozy i powiązaniu z ramką obrazu nadszedł czas na pobranie widocznych obszarów. 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 pozycjonowane 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 przewijam i rysuję oddzielne obrazy.

Gdy wdrażam rzeczywistość rozszerzoną na telefonie, mam tylko 1 widok, ale używam pętli. Chociaż może się wydawać, że przechodzenie przez jeden widok jest bezcelowe, pozwala to na użycie jednej ścieżki renderowania dla całego spektrum wciągających wrażeń. To istotna 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
    }
  }
}

Przekazuj obiekt XRViewport do WebGLRenderingContext

Obiekt XRView odnosi się do elementów, które można obserwować na ekranie. Aby jednak narysować widok, potrzebuję współrzędnych i wymiarów specyficznych dla mojego urządzenia. Podobnie jak w przypadku ramki framebuffera, proszę o te dane z poziomu XRWebGLLayer i przesyłam je 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
    }
  }
}

webGLRenContext

Podczas pisania tego artykułu prowadziłem z kilkoma kolegami debatę na temat nazwy obiektu webGLRenContext. Przykładowe skrypty i większość kodu WebXR po prostu nazywają tę zmienną gl. Podczas analizowania próbek ciągle zapominałam, do czego odnosi się gl. Nazwałam ją webGLRenContext, żeby Ci przypomnieć, że aplikacja to WebGLRenderingContext.

Dzieje się tak, ponieważ użycie gl pozwala nazwom metod wyglądać tak samo jak ich odpowiedniki w interfejsie OpenGL ES 2.0, używanym do tworzenia VR w kompilowanych językach. Jest to oczywiste, jeśli tworzysz aplikacje VR za pomocą OpenGL, ale może być mylące, jeśli dopiero zaczynasz korzystać z tej technologii.

Rysowanie czegoś na framebufferze

Jeśli masz szczególne ambicje, możesz używać WebGL bezpośrednio, ale nie polecam tego. Dużo prościej jest użyć jednej z platform wymienionych na górze.

Podsumowanie

To nie koniec aktualizacji ani artykułów na temat WebXR. Informacje o interfejsach i elementach WebXR znajdziesz w MDN. Informacje o nadchodzących ulepszeniach interfejsów znajdziesz w Raporcie stanu Chrome dotyczących poszczególnych funkcji.

Zdjęcie: JESHOOTS.COM, Unsplash