Virtual Reality jetzt im Web verfügbar, Teil II

Alles über die Frameschleife

Joe Medley
Joe Medley

Vor Kurzem habe ich den Artikel Virtual Reality kommt im Web veröffentlicht, in dem die grundlegenden Konzepte der WebXR Device API vorgestellt wurden. Ich habe auch Anweisungen zum Anfordern, Starten und Beenden einer XR-Sitzung bereitgestellt.

In diesem Artikel wird die Frameschleife beschrieben. Dabei handelt es sich um eine vom User-Agent gesteuerte Endlosschleife, bei der Inhalte wiederholt auf den Bildschirm gezogen werden. Inhalte werden in einzelnen Blöcken gezeichnet, die als Frames bezeichnet werden. Die Abfolge der Frames erzeugt die Illusion einer Bewegung.

Was dieser Artikel nicht ist

WebGL und WebGL2 sind die einzigen Mittel zum Rendern von Inhalten während einer Frameschleife in einer WebXR-App. Glücklicherweise bieten viele Frameworks eine Abstraktionsebene über WebGL und WebGL2. Zu diesen Frameworks gehören three.js, babylonjs und PlayCanvas, während A-Frame und React 360 für die Interaktion mit WebXR entwickelt wurden.

Dieser Artikel ist weder eine WebGL- noch eine Framework-Anleitung. Darin werden die Grundlagen einer Frameschleife mithilfe des Beispiels „Immersive VR Session“ der Immersive Web Working Group (Demo, Quelle) erläutert. Wenn Sie mehr über WebGL oder eines der Frameworks erfahren möchten, finden Sie im Internet eine umfangreiche Liste von Artikeln.

Die Spieler und das Spiel

Bei dem Versuch, die Frameschleife zu verstehen, habe ich mich immer wieder in die Details vertieft. Es gibt viele Objekte, von denen einige nur anhand von Referenzeigenschaften anderer Objekte benannt werden. Damit es gerade bleibt, beschreibe ich die Objekte, die ich „Spieler“ nenne. Dann erkläre ich, wie sie interagieren. Das bezeichne ich als „Spiel“.

Die Spieler

XRViewerPose

Eine Pose ist die Position und Ausrichtung von etwas im 3D-Raum. Sowohl Betrachter als auch Eingabegeräte haben eine Pose, aber um die Pose des Betrachters geht es hier. Sowohl die Position als auch das Eingabegerät haben ein transform-Attribut, das seine Position als Vektor und seine Ausrichtung als Quaternion relativ zum Ursprung beschreibt. Der Ursprung wird beim Aufrufen von XRSession.requestReferenceSpace() anhand des angeforderten Referenzbereichstyps angegeben.

Referenzbereiche brauchen etwas zu erklären. Ich erläutere sie ausführlich in Augmented Reality. Im Beispiel, das ich als Grundlage für diesen Artikel verwende, wird ein 'local'-Referenzraum verwendet. Das bedeutet, dass sich der Ursprung zum Zeitpunkt der Sitzungserstellung an der Position des Betrachters befindet, ohne einen klar definierten Mindestpreis, und seine genaue Position kann je nach Plattform variieren.

XRView

Eine Ansicht entspricht einer Kamera, die die virtuelle Szene betrachtet. Eine Ansicht hat außerdem ein transform-Attribut, das ihre Position als Vektor und ihre Ausrichtung beschreibt. Diese werden sowohl als Vektor/Quaternion-Paar als auch als entsprechende Matrix bereitgestellt. Sie können beide Darstellungen verwenden, je nachdem, welche am besten zu Ihrem Code passt. Jede Ansicht entspricht einer Anzeige oder einem Teil einer Anzeige, die von einem Gerät verwendet wird, um dem Betrachter Bilder zu präsentieren. XRView-Objekte werden in einem Array des XRViewerPose-Objekts zurückgegeben. Die Anzahl der Aufrufe im Array variiert. Auf Mobilgeräten hat eine AR-Szene nur eine Ansicht, die den Gerätebildschirm abdecken kann oder nicht. Headsets haben in der Regel zwei Ansichten, eine für jedes Auge.

XRWebGLLayer

Ebenen liefern eine Quelle für Bitmapbilder und Beschreibungen, wie diese Bilder auf dem Gerät gerendert werden sollen. Diese Beschreibung reflektiert nicht ganz, was dieser Player tut. Für mich ist es ein Mittler zwischen einem Gerät und einem WebGLRenderingContext. Der MDN vertritt weitgehend die gleiche Auffassung und besagt, dass er eine Verbindung zwischen den beiden herstellt. So haben andere Spieler Zugriff auf die Datei.

Im Allgemeinen speichern WebGL-Objekte Statusinformationen für das Rendern von 2D- und 3D-Grafiken.

WebGLFramebuffer

Ein Framebuffer stellt Bilddaten für WebGLRenderingContext bereit. Nachdem Sie ihn aus dem XRWebGLLayer abgerufen haben, übergeben Sie ihn einfach an den aktuellen WebGLRenderingContext. Abgesehen vom Aufrufen von bindFramebuffer() (mehr dazu später) greifen Sie nie direkt auf dieses Objekt zu. Sie übergeben sie lediglich vom XRWebGLLayer an den WebGLRenderingContext.

XRViewport

Ein Darstellungsbereich stellt die Koordinaten und Abmessungen eines rechteckigen Bereichs in WebGLFramebuffer bereit.

WebGLRenderingContext

Ein Rendering-Kontext ist ein programmatischer Zugangspunkt für ein Canvas (den Bereich, in dem wir uns gerade befinden). Dazu werden ein WebGLFramebuffer- und ein XRViewport-Element benötigt.

Beachten Sie die Beziehung zwischen XRWebGLLayer und WebGLRenderingContext. Eines für das Gerät des Viewers und das andere der Webseite. WebGLFramebuffer und XRViewport werden von ersterer an Letzteres übergeben.

Die Beziehung zwischen XRWebGLLayer und WebGLRenderingContext
Die Beziehung zwischen XRWebGLLayer und WebGLRenderingContext

Das Spiel

Jetzt wissen wir, wer die Spieler sind. Sehen wir uns nun das Spiel an, das sie spielen. Es ist ein Spiel, das mit jedem Frame neu beginnt. Frames sind Teil einer Frameschleife, die mit einer von der zugrunde liegenden Hardware abhängigen Geschwindigkeit stattfindet. Bei VR-Anwendungen können die Bilder pro Sekunde zwischen 60 und 144 Bildern pro Sekunde liegen. AR für Android wird mit 30 Bildern pro Sekunde ausgeführt. Ihr Code sollte keine bestimmte Framerate annehmen.

Der grundlegende Prozess für die Frameschleife sieht so aus:

  1. Rufen Sie einfach XRSession.requestAnimationFrame() an. Der User-Agent ruft als Antwort die von dir definierte XRFrameRequestCallback auf.
  2. Führen Sie innerhalb der Callback-Funktion folgende Schritte aus:
    1. Rufen Sie XRSession.requestAnimationFrame() noch einmal an.
    2. Stell dir die Pose des Zuschauers vor.
    3. Übergeben Sie ('bind') den WebGLFramebuffer von XRWebGLLayer an WebGLRenderingContext.
    4. Iterieren Sie über jedes XRView-Objekt, wobei Sie seine XRViewport aus dem XRWebGLLayer abrufen und an das WebGLRenderingContext übergeben.
    5. Zeichnen Sie etwas im Framebuffer.

Die Schritte 1 und 2a wurden bereits im vorherigen Artikel behandelt, daher beginne ich mit Schritt 2b.

Sieh dir die Pose des Zuschauers an

Das ist eine gute Idee. Um in AR oder VR etwas zu zeichnen, muss ich wissen, wo sich der Betrachter befindet und Die Position und Ausrichtung des Betrachters werden durch ein XRViewerPose-Objekt angegeben. Ich rufe die Pose des Zuschauers ab, indem ich XRFrame.getViewerPose() für den aktuellen Animationsframe aufrufe. Ich übergebe den Referenzraum, den ich beim Einrichten der Sitzung erhalten habe. Die von diesem Objekt zurückgegebenen Werte beziehen sich immer auf den Referenzraum, den ich beim Einstieg in die aktuelle Sitzung angefordert habe. Wie Sie sich vielleicht erinnern, muss ich den aktuellen Referenzraum übergeben, wenn ich die Pose anfordert.

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

Es gibt eine Haltung, die die Gesamtposition des Nutzers darstellt, d. h. entweder den Kopf des Betrachters oder bei einem Smartphone die Kamera des Smartphones. Die Pose teilt Ihrer Anwendung mit, wo sich der Viewer befindet. Für das tatsächliche Rendern des Bildes werden XRView-Objekte verwendet, auf die ich später noch näher komme.

Bevor wir fortfahren, teste ich, ob die Pose des Betrachters zurückgegeben wurde, falls das System die Verfolgung verliert oder die Pose aus Datenschutzgründen blockiert. Tracking ist die Fähigkeit des XR-Geräts zu erkennen, wo es und/oder seine Eingabegeräte relativ zur Umgebung sind. Das Tracking kann auf verschiedene Weise verloren gehen und variiert je nach verwendeter Methode. Wenn das Gerät beispielsweise mit den Kameras am Headset oder Smartphone getrackt wird, kann möglicherweise nicht mehr festgestellt werden, wo es sich in schlechten oder schlechten Lichtverhältnissen befindet oder ob die Kameras verdeckt sind.

Wenn auf dem Headset beispielsweise ein Sicherheitsdialogfeld, z. B. eine Berechtigungsaufforderung, angezeigt wird, stellt der Browser der Anwendung möglicherweise keine Posen mehr zur Verfügung. Ich habe aber bereits XRSession.requestAnimationFrame() aufgerufen. Wenn sich das System wiederherstellen lässt, wird die Frameschleife fortgesetzt. Andernfalls beendet der User-Agent die Sitzung und ruft den Event-Handler end auf.

Kurzer Umweg

Für den nächsten Schritt sind Objekte erforderlich, die bei der Sitzungseinrichtung erstellt wurden. Ich habe ein Canvas erstellt und angewiesen, einen XR-kompatiblen WebGL-Renderingkontext zu erstellen. Dazu habe ich canvas.getContext() aufgerufen. Alle Zeichnungen werden mit der WebGL API, der WebGL2 API oder einem WebGL-basierten Framework wie Three.js ausgeführt. Dieser Kontext wurde zusammen mit einer neuen Instanz von XRWebGLLayer über updateRenderState() an das Sitzungsobjekt übergeben.

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

WebGLFramebuffer übergeben ('bind')

Der XRWebGLLayer bietet einen Framebuffer für den WebGLRenderingContext, der speziell für die Verwendung mit WebXR bereitgestellt wird, und ersetzt den Standard-Framebuffer des Renderingkontexts. In der WebGL-Sprache wird dies als „Bindung“ bezeichnet.

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

Über jedes XRView-Objekt iterieren

Nachdem die Pose übernommen und der Framebuffer gebunden wurde, ist es an der Zeit, die Darstellungsbereiche abzurufen. XRViewerPose enthält ein Array mit XRView-Schnittstellen, von denen jede eine Anzeige oder einen Teil einer Anzeige darstellt. Sie enthalten Informationen, die zum Rendern von Inhalten erforderlich sind, die für das Gerät und den Viewer korrekt positioniert sind, z. B. das Sichtfeld, der Augenversatz und andere optische Eigenschaften. Da ich für zwei Augen zeichne, habe ich zwei Ansichten, die ich in einer Schleife zeichnee und jeweils ein separates Bild zeichne.

Bei der Implementierung für die Smartphone-basierte Augmented Reality hätte ich nur eine Ansicht, aber trotzdem eine Schleife. Auch wenn es sinnlos erscheint, durch eine Ansicht zu iterieren, können Sie so einen einzigen Rendering-Pfad für ein Spektrum an immersiven Erlebnissen haben. Dies ist ein wichtiger Unterschied zwischen WebXR und anderen immersiven Systemen.

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

Übergeben Sie das XRViewport-Objekt an den WebGLRenderingContext.

Ein XRView-Objekt bezieht sich auf das, was auf einem Bildschirm beobachtbar ist. Aber um in diese Ansicht zu zeichnen, benötige ich gerätespezifische Koordinaten und Maße. Wie beim Framebuffer fordere ich sie vom XRWebGLLayer an und übergebe sie an den 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

In diesem Artikel wurde die Benennung des webGLRenContext-Objekts mit einigen Kollegen erörtert. In den Beispielskripts und den meisten WebXR-Code wird diese Variable gl genannt. Als ich versucht habe, die Beispiele zu verstehen, habe ich immer wieder vergessen, worauf gl verwiesen hat. Ich habe sie webGLRenContext genannt, um Sie daran zu erinnern, dass dies eine Instanz von WebGLRenderingContext ist.

Der Grund dafür ist, dass Methodennamen mit gl wie ihre Entsprechungen in der OpenGL ES 2.0 API aussehen. Diese API wird zum Erstellen von VR in kompilierten Sprachen verwendet. Dies ist offensichtlich, wenn Sie VR-Apps mit OpenGL geschrieben haben. Es ist aber verwirrend, wenn Sie mit dieser Technologie noch gar nicht vertraut sind.

Etwas in den Framebuffer zeichnen

Wenn Sie wirklich ehrgeizig sind, können Sie WebGL direkt verwenden, aber das empfehle ich nicht. Es ist viel einfacher, eines der oben aufgeführten Frameworks zu verwenden.

Fazit

Die WebXR-Updates und -Artikel sind damit noch nicht zu Ende. Unter MDN finden Sie eine Referenz zu allen WebXR-Schnittstellen und -Mitgliedern. Anstehende Verbesserungen der Benutzeroberflächen selbst finden Sie in den einzelnen Funktionen unter Chrome-Status.

Foto von JESHOOTS.COM bei Unsplash