La realtà virtuale arriva sul Web, parte II

Informazioni sul loop dei frame

Joe Medley
Joe Medley

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

Questo articolo descrive il loop del frame, ovvero un loop infinito controllato dallo user agent in cui i contenuti vengono ripetutamente attirati sullo schermo. I contenuti vengono disegnati in blocchi distinti chiamati fotogrammi. La successione di frame crea l'illusione del movimento.

Cosa non è questo articolo

WebGL e WebGL2 sono gli unici mezzi per eseguire il rendering dei contenuti durante un ciclo di frame in un'app WebXR. Fortunatamente, molti framework forniscono un livello di astrazione su 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 non è un tutorial su WebGL né su un framework. Spiega le nozioni di base di un loop di frame utilizzando l'esempio di sessione VR immersiva del gruppo di lavoro Immersive Web (demo, source). Se vuoi approfondire WebGL o uno dei framework, su internet è disponibile un elenco crescente di articoli.

I giocatori e il gioco

Nel cercare di capire il loop dei fotogrammi, continuavo a perdermi nei dettagli. Ci sono molti oggetti in gioco, alcuni dei quali vengono denominati solo tramite proprietà di riferimento su altri oggetti. Per aiutarti a capire, descriverò gli oggetti, che chiamerò "player". Poi descriverò come interagiscono, che chiamo "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. Sia il visualizzatore che le pose del dispositivo di input hanno un attributo transform che descrive la sua posizione come vettore e il suo orientamento come quaternione rispetto all'origine. L'origine viene specificata in base al tipo di spazio di riferimento richiesto quando si chiama XRSession.requestReferenceSpace().

Gli spazi di riferimento sono un po' complicati da spiegare. Ne parlo in modo approfondito nella sezione Realtà virtuale. Il sample che sto utilizzando come base per questo articolo utilizza uno spazio di riferimento 'local', il che significa che l'origine si trova nella posizione dello spettatore al momento della creazione della sessione senza un piano ben definito e la sua posizione esatta può variare in base alla piattaforma.

XRView

Una visualizzazione corrisponde a una videocamera che vede la scena virtuale. Una vista ha anche un attributo transform che descrive la sua posizione come vettore e il suo orientamento. Questi vengono forniti sia come coppia di vettori/quaternioni 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 allo spettatore. 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 visualizzazione che può o meno coprire lo schermo del dispositivo. Gli occhiali VR hanno in genere due visualizzazioni, una per ciascun occhio.

XRWebGLLayer

I livelli forniscono una fonte di immagini bitmap e descrizioni di come queste immagini devono essere visualizzate sul dispositivo. Questa descrizione non cattura del tutto cosa fa questo player. Ho finito per considerarlo un intermediario tra un dispositivo e un WebGLRenderingContext. MDN ha un punto di vista molto simile, affermando che "fornisce un collegamento" tra i due. In quanto tale, dà accesso agli altri giocatori.

In generale, gli oggetti WebGL archiviano informazioni sullo stato per il rendering di immagini 2D e 3D.

WebGLFramebuffer

Un framebuffer fornisce i dati dell'immagine al WebGLRenderingContext. Dopo averla recuperata dal XRWebGLLayer, devi semplicemente passarla al WebGLRenderingContext corrente. A parte chiamare bindFramebuffer() (di cui parleremo più avanti), non accederai mai direttamente a questo oggetto. Dovrai semplicemente passarlo da XRWebGLLayer a WebGLRenderingContext.

XRViewport

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

WebGLRenderingContext

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

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

La relazione tra XRWebGLLayer e WebGLRenderingContext
La relazione tra XRWebGLLayer e WebGLRenderingContext

Il gioco

Ora che sappiamo chi sono i giocatori, diamo un'occhiata al gioco che giocano. È un gioco che ricomincia a ogni frame. Ricorda che i fotogrammi fanno parte di un loop di fotogrammi che si verifica a una frequenza che dipende dall'hardware di base. Per le applicazioni VR, i frame al secondo possono essere compresi tra 60 e 144. La realtà aumentata per Android viene eseguita a 30 fotogrammi al secondo. Il codice non deve presupporre un frame rate specifico.

La procedura di base per il loop dei frame è la seguente:

  1. Chiama il numero XRSession.requestAnimationFrame(). In risposta, lo user agent richiama il XRFrameRequestCallback, che è definito da te.
  2. All'interno della funzione di callback:
    1. Chiama di nuovo XRSession.requestAnimationFrame().
    2. Fai in modo che lo spettatore si metta in posa.
    3. Passa ('bind') il WebGLFramebuffer dal XRWebGLLayer al WebGLRenderingContext.
    4. Esegui l'iterazione su 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.

Mostra la posa dello spettatore

Probabilmente non serve dirlo. Per disegnare qualcosa in AR o VR, devo sapere dove si trova lo spettatore e dove sta guardando. La posizione e l'orientamento dello spettatore vengono forniti da un oggetto VRViewerPose. Prendo la posa dello spettatore chiamando XRFrame.getViewerPose() nel frame dell'animazione corrente. Trasferisco 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 richiesto quando ho acceso alla sessione corrente. Come ricorderai, devo passare lo spazio di riferimento attuale 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 dello spettatore che rappresenta la posizione complessiva dell'utente, ovvero la testa dello spettatore o la fotocamera dello smartphone nel caso di uno smartphone. La posa indica alla tua applicazione dove si trova lo spettatore. Il rendering effettivo delle immagini utilizza gli oggettiXRView, di cui parlerò tra poco.

Prima di andare avanti, verifichiamo se la posa dello spettatore è stata restituita nel caso in cui il sistema perdesse il tracciamento o la blocchi per motivi di privacy. Il rilevamento è la capacità del dispositivo XR di sapere dove si trova e/o 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 fotocamere sull'auricolare o sullo smartphone vengono utilizzate per il monitoraggio, il dispositivo potrebbe perdere la capacità di determinare la propria posizione in situazioni di scarsa illuminazione o se le fotocamere sono coperte.

Un esempio di bloccare la posa per motivi di privacy è se le cuffie mostrano una finestra di dialogo di sicurezza, come un prompt di autorizzazione, il browser potrebbe smettere di fornire pose all'applicazione durante questa operazione. Tuttavia, ho già chiamato XRSession.requestAnimationFrame() in modo che, se il sistema riesce a recuperare, il loop delle cornici continui. In caso contrario, l'agente utente terminerà la sessione e chiamerà il gestore eventi end.

Una breve deviazione

Il passaggio successivo richiede gli oggetti creati durante la configurazione della sessione. Ricorda che ho creato una tela e le ho chiesto di creare un contesto di rendering Web GL 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 tramite updateRenderState(), insieme 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 sostituisce il framebuffer predefinito dei contesti di rendering. Questa operazione è chiamata "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
  }
}

Esegui l'iterazione su ogni oggetto XRView

Dopo aver ottenuto la posa e aver associato il framebuffer, è il momento di ottenere i viewport. XRViewerPose contiene un array di interfacce XRView, ciascuna delle quali rappresenta un display o una parte di un display. Contengono informazioni necessarie per visualizzare i contenuti in modo corretto per il dispositivo e lo spettatore, ad esempio il campo visivo, lo spostamento degli occhi e altre proprietà ottiche. Dato che sto disegnando per due occhi, ho due viste, che eseguo in loop e trasformo un'immagine separata per ognuna.

Quando implemento la realtà aumentata basata su smartphone, ho una sola visualizzazione, ma utilizzo comunque un loop. Anche se potrebbe sembrare inutile eseguire l'iterazione di una vista, in questo modo puoi avere un unico percorso di rendering per una serie 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
    }
  }
}

Passare l'oggetto XRViewport a WebGLRenderingContext

Un oggetto XRView si riferisce a ciò che è osservabile su uno schermo. Tuttavia, per disegnare in quella vista, ho bisogno di coordinate e dimensioni specifiche per il mio dispositivo. Come per il framebuffer, li richiedo da 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
    }
  }
}

webGLRenContext

Durante la stesura di questo articolo ho discusso con alcuni colleghi sulla denominazione dell'oggetto webGLRenContext. Gli script di esempio e la maggior parte del codice WebXR richiamano questa variabile in modo semplice gl. Quando cercavo di capire i sample, dimenticavo sempre a cosa si riferisse gl. L'ho chiamata 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 avere l'aspetto delle loro controparti nell'API OpenGL ES 2.0, utilizzata per creare VR in linguaggi compilati. Questo è ovvio se hai scritto app VR con OpenGL, ma non è facile se non hai mai usato questa tecnologia.

Disegna qualcosa nel framebuffer

Se hai grandi ambizioni, puoi utilizzare direttamente WebGL, ma non lo consiglio. È molto più semplice utilizzare uno dei framework elencati in cima.

Conclusione

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

Foto di JESHOOTS.COM su Unsplash