La realtà virtuale arriva sul Web, parte II

Tutto sul ciclo dei fotogrammi

Joe Medley
Joe Medley

Di recente, ho pubblicato La realtà virtuale arriva sul web, un articolo che introduce i concetti di base alla base dell'API WebXR Device. Ho anche fornito istruzioni per richiedere, avviare e terminare una sessione XR.

Questo articolo descrive il ciclo di frame, un ciclo infinito controllato dall'user agent in cui i contenuti vengono disegnati ripetutamente sullo schermo. I contenuti vengono disegnati in blocchi discreti chiamati frame. La successione dei frame crea l'illusione del movimento.

Cosa non è questo articolo

WebGL e WebGL2 sono gli unici mezzi per il rendering dei contenuti durante un ciclo di frame in un'app WebXR. Fortunatamente, molti framework forniscono un livello di astrazione sopra WebGL e WebGL2. Questi framework includono three.js, babylonjs e PlayCanvas, mentre A-Frame e React 360 sono stati progettati per interagire con WebXR.

Questo articolo spiega le nozioni di base di un ciclo di frame utilizzando l'esempio di sessione VR immersiva del gruppo di lavoro Immersive Web (demo, origine). Se vuoi approfondire WebGL o uno dei framework, online è disponibile un elenco crescente di risorse.

I giocatori e la partita

Quando ho cercato di capire il ciclo del frame, mi sono perso nei dettagli. Ci sono molti oggetti in gioco e alcuni di questi sono denominati solo in base alle proprietà di riferimento di altri oggetti. Per aiutarti a fare chiarezza, descriverò gli oggetti, che chiamerò "giocatori". Poi descriverò come interagiscono, che chiamerò "il gioco".

I giocatori

XRViewerPose

Una posa è la posizione e l'orientamento di un oggetto nello spazio 3D. Sia gli spettatori che i dispositivi di input hanno una posa, ma qui ci interessa la posa dello spettatore. Le pose del visualizzatore e del dispositivo di input hanno un attributo transform che descrive la posizione come vettore e l'orientamento come quaternione rispetto all'origine. L'origine viene specificata in base al tipo di spazio di riferimento richiesto quando viene chiamato XRSession.requestReferenceSpace().

Gli spazi di riferimento richiedono un po' di tempo per essere spiegati. Li tratto in modo approfondito nella sezione Realtà aumentata. Il campione che utilizzo come base per questo articolo utilizza uno spazio di riferimento 'local', il che significa che l'origine si trova nella posizione del visualizzatore al momento della creazione della sessione senza un piano ben definito e la sua posizione precisa può variare in base alla piattaforma.

XRView

Una visualizzazione corrisponde a una videocamera che guarda la scena virtuale. Una visualizzazione ha anche un attributo transform che descrive la sua posizione come vettore e il suo orientamento. Questi valori vengono forniti sia come coppia vettore/quaternione sia come matrice equivalente. Puoi utilizzare una delle due rappresentazioni a seconda di quella più adatta al tuo codice. Ogni visualizzazione corrisponde a un display o a una parte di un display utilizzato da un dispositivo per presentare le immagini al visualizzatore. Gli oggetti XRView vengono restituiti in un array dall'oggetto XRViewerPose. Il numero di visualizzazioni nell'array varia. Sui dispositivi mobili una scena AR ha una sola visualizzazione, che può coprire o meno lo schermo del dispositivo. I visori hanno in genere due visualizzazioni, una per ogni occhio.

XRWebGLLayer

I livelli forniscono un'origine di immagini bitmap e descrizioni di come queste immagini devono essere visualizzate nel dispositivo. Questa descrizione non descrive in modo accurato le funzionalità di questo lettore. Lo considero un intermediario tra un dispositivo e un WebGLRenderingContext. MDN ha un punto di vista simile e afferma che "fornisce un collegamento" tra i due. Pertanto, fornisce l'accesso agli altri giocatori.

In generale, gli oggetti WebGL memorizzano le informazioni sullo stato per il rendering di grafica 2D e 3D.

WebGLFramebuffer

Un framebuffer fornisce i dati dell'immagine a WebGLRenderingContext. Dopo averlo recuperato da XRWebGLLayer, lo passi all'attuale WebGLRenderingContext. A parte chiamare bindFramebuffer() (ne parleremo più avanti), non accederai mai direttamente a questo oggetto. Lo passerai semplicemente da XRWebGLLayer a WebGLRenderingContext.

XRViewport

Un'area visibile fornisce le coordinate e le dimensioni di una regione rettangolare nel WebGLFramebuffer.

WebGLRenderingContext

Un contesto di rendering è un punto di accesso programmatico per un canvas (lo spazio su cui disegniamo). Per farlo, ha bisogno sia di un WebGLFramebuffer sia di un XRViewport.

Nota la relazione tra XRWebGLLayer e WebGLRenderingContext. Uno corrisponde al dispositivo dello spettatore e l'altro alla pagina web. WebGLFramebuffer e XRViewport vengono trasferiti dal primo al secondo.

Relazione tra XRWebGLLayer e WebGLRenderingContext
La relazione tra XRWebGLLayer e WebGLRenderingContext

La partita

Ora che sappiamo chi sono i giocatori, diamo un'occhiata al gioco che praticano. È un gioco che ricomincia a ogni fotogramma. Ricorda che i frame fanno parte di un ciclo di frame che si verifica a una velocità che dipende dall'hardware sottostante. Per le applicazioni VR, i frame al secondo possono variare da 60 a 144. La realtà aumentata per Android viene eseguita a 30 fotogrammi al secondo. Il codice non deve presupporre una frequenza fotogrammi specifica.

La procedura di base per il ciclo dei fotogrammi è la seguente:

  1. Chiama il numero XRSession.requestAnimationFrame(). In risposta, lo user agent richiama XRFrameRequestCallback, che è definito da te.
  2. All'interno della funzione di callback:
    1. Chiama di nuovo XRSession.requestAnimationFrame().
    2. Ottieni la posa dello spettatore.
    3. Passa ('bind') il WebGLFramebuffer da XRWebGLLayer a WebGLRenderingContext.
    4. Itera ogni oggetto XRView, recuperando il relativo XRViewport da XRWebGLLayer e passandolo a WebGLRenderingContext.
    5. Disegna qualcosa nel framebuffer.

Poiché i passaggi 1 e 2a sono stati trattati nell'articolo precedente, inizierò dal passaggio 2b.

Ottenere la posa dello spettatore

Probabilmente è superfluo dirlo. Per disegnare qualsiasi cosa in AR o VR, ho bisogno di sapere dove si trova lo spettatore e dove sta guardando. La posizione e l'orientamento dello spettatore sono forniti da un oggetto XRViewerPose. Ottengo la posa dello spettatore chiamando XRFrame.getViewerPose() sul frame di animazione corrente. Gli passo lo spazio di riferimento che ho acquisito quando ho configurato la sessione. I valori restituiti da questo oggetto sono sempre relativi allo spazio di riferimento che ho richiesto quando ho avviato la sessione corrente. Come forse ricordi, devo passare lo spazio di riferimento corrente quando richiedo la posa.

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

Esiste una posa del visualizzatore che rappresenta la posizione complessiva dell'utente, ovvero la testa del visualizzatore o la videocamera dello smartphone. La posa indica all'applicazione la posizione dello spettatore. Il rendering effettivo dell'immagine utilizza oggetti XRView, che vedremo tra poco.

Prima di andare avanti, verifico se la posa del visore è stata restituita nel caso in cui il sistema perda il tracciamento o blocchi la posa per motivi di privacy. Il tracciamento è la capacità del dispositivo XR di sapere dove si trova e dove si trovano i suoi dispositivi di input rispetto all'ambiente. Il monitoraggio può essere perso in diversi modi e varia a seconda del metodo utilizzato. Ad esempio, se le videocamere sul visore o sullo smartphone vengono utilizzate per il monitoraggio, il dispositivo potrebbe non essere più in grado di determinare la propria posizione in situazioni di scarsa illuminazione o assenza di luce oppure se le videocamere sono coperte.

Un esempio di blocco della postura per motivi di privacy è se il visore mostra una finestra di dialogo di sicurezza, ad esempio una richiesta di autorizzazione, il browser potrebbe interrompere la fornitura di pose all'applicazione durante questo periodo. Ma ho già chiamato XRSession.requestAnimationFrame() in modo che, se il sistema può ripristinarsi, il ciclo di frame continui. In caso contrario, lo user agent terminerà la sessione e chiamerà il gestore di eventi end.

Una breve deviazione

Il passaggio successivo richiede gli oggetti creati durante la configurazione della sessione. Ricorda che ho creato un canvas e gli ho chiesto di creare un contesto di rendering WebGL compatibile con XR, che ho ottenuto chiamando canvas.getContext(). Tutti i disegni vengono eseguiti utilizzando l'API WebGL, l'API WebGL2 o un framework basato su WebGL come Three.js. Questo contesto è stato passato all'oggetto sessione con updateRenderState(), oltre a una nuova istanza di 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)
  });

Passa ('bind') il WebGLFramebuffer

XRWebGLLayer fornisce un framebuffer per WebGLRenderingContext fornito specificamente per l'utilizzo con WebXR e la sostituzione del framebuffer predefinito dei contesti di rendering. Questo è chiamato "binding" nel linguaggio di WebGL.

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

Itera su ogni oggetto XRView

Dopo aver ottenuto la posa e associato il framebuffer, è il momento di ottenere i viewport. XRViewerPose contiene un array di interfacce XRView, ognuna delle quali rappresenta un display o una parte di un display. Contengono informazioni necessarie per eseguire il rendering di contenuti posizionati correttamente per il dispositivo e il visore, come il campo visivo, l'offset degli occhi e altre proprietà ottiche. Poiché disegno per due occhi, ho due viste, che scorro e disegno un'immagine separata per ciascuna.

Quando implemento la realtà aumentata basata sullo smartphone, ho una sola visualizzazione, ma uso comunque un ciclo. Anche se può sembrare inutile scorrere una sola visualizzazione, in questo modo puoi avere un unico percorso di rendering per uno spettro di esperienze immersive. Questa è una differenza importante tra WebXR e altri sistemi immersivi.

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

Passa l'oggetto XRViewport a WebGLRenderingContext

Un oggetto XRView si riferisce a ciò che è osservabile su uno schermo. Ma per disegnare questa visualizzazione ho bisogno di coordinate e dimensioni specifiche per il mio dispositivo. Come per il framebuffer, li richiedo a XRWebGLLayer e li passo a 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

Nella scrittura, ho avuto un dibattito con alcuni colleghi sulla denominazione dell'oggetto webGLRenContext. Gli script di esempio e la maggior parte del codice WebXR chiamano questa variabile gl. Mentre cercavo di capire i campioni, continuavo a dimenticare a cosa si riferisse gl. L'ho chiamato webGLRenContext per ricordarti durante l'apprendimento che si tratta di un'istanza di WebGLRenderingContext.

Il motivo è che l'utilizzo di gl consente ai nomi dei metodi di assomigliare alle loro controparti nell'API OpenGL ES 2.0, utilizzata per creare VR in linguaggi compilati. Questo fatto è ovvio se hai scritto app VR utilizzando OpenGL, ma confuso se non hai mai utilizzato questa tecnologia.

Disegnare qualcosa nel framebuffer

Se ti senti particolarmente ambizioso, puoi utilizzare direttamente WebGL, ma non lo consiglio. È molto più semplice utilizzare uno dei framework elencati nella parte superiore.

Conclusione

Questo non è l'ultimo aggiornamento o articolo su WebXR. Puoi trovare un riferimento per tutte le interfacce e i membri di WebXR su MDN. Per i prossimi miglioramenti alle interfacce, segui le singole funzionalità su Chrome Status.