La realtà virtuale arriva sul Web, parte II

Tutto sul loop di frame

Joe Medley
Joe Medley

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

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

Cosa non è questo articolo

WebGL e WebGL2 sono gli unici strumenti per eseguire il rendering dei contenuti durante un loop di frame in un'app WebXR. Fortunatamente molti framework offrono 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 non è né un tutorial di WebGL né un framework. Spiega le nozioni di base di un loop di frame utilizzando l'esempio di sessione immersiva di VR di Immersive Web Working Group (demo, source). Se vuoi conoscere più a fondo WebGL o uno dei framework, puoi trovare su internet un elenco di articoli sempre più vasto.

I giocatori e il gioco

Mentre cercavo di capire il loop dei frame, continuavo a perdermi nei dettagli. Ci sono molti oggetti in gioco e alcuni vengono denominati solo in base alle proprietà di riferimento su altri oggetti. Per aiutarti nella scelta, descriverò gli oggetti, che chiamo "player". Poi descriverò come interagiscono, che chiamo "il gioco".

I giocatori

XRViewerPose

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

Gli spazi di riferimento richiedono un po' di tempo da spiegare. Ne parleremo in dettaglio in Realtà aumentata. Il campione 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 prezzo minimo ben definito e la sua posizione esatta può variare a seconda della piattaforma.

XRView

Una visualizzazione corrisponde a una videocamera che guarda la scena virtuale. Una vista ha anche un attributo transform che descrive la sua posizione come vettore e il suo orientamento. Vengono fornite sia come coppia vettoriale/quaternione che come matrice equivalente, puoi utilizzare una delle due rappresentazioni, a seconda di quella che meglio si adatta al tuo codice. Ogni vista corrisponde a un display o a una parte di un display utilizzati da un dispositivo per presentare immagini allo spettatore. XRView oggetti vengono restituiti in un array dall'oggetto XRViewerPose. Il numero di visualizzazioni dell'array varia. Sui dispositivi mobili una scena AR ha un'unica visualizzazione, che potrebbe coprire o meno lo schermo del dispositivo. Le cuffie in genere hanno due visualizzazioni, una per ogni occhio.

XRWebGLLayer

I livelli forniscono una fonte di immagini bitmap e descrizioni di come queste immagini devono essere visualizzate nel dispositivo. Questa descrizione non comunica perfettamente ciò che fa il player. È stato per me un intermediario tra un dispositivo e un WebGLRenderingContext. MDN sostiene praticamente lo stesso punto di vista, affermando che "fornisce un collegamento" tra i due. In quanto tale, fornisce l'accesso agli altri giocatori.

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

WebGLFramebuffer

Un framebuffer fornisce i dati dell'immagine a WebGLRenderingContext. Dopo averlo recuperato da XRWebGLLayer, puoi semplicemente passarlo all'WebGLRenderingContext corrente. A parte la chiamata a bindFramebuffer() (ulteriori informazioni in seguito), 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 in WebGLFramebuffer.

WebGLRenderingContext

Un contesto di rendering è un punto di accesso programmatico per una tela (lo spazio in cui stiamo richiamando). Per farlo, sono necessari sia un WebGLFramebuffer che un XRViewport.

Osserva la relazione tra XRWebGLLayer e WebGLRenderingContext. Uno corrisponde al dispositivo dello spettatore e l'altro alla pagina web. WebGLFramebuffer e XRViewport vengono trasmessi 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 fanno. È un gioco che ricomincia da ogni fotogramma. Ricorda che i frame fanno parte di un loop di frame che avviene a una frequenza che dipende dall'hardware sottostante. Per le applicazioni VR, i frame al secondo possono essere compresi tra 60 e 144. AR per Android funziona a 30 frame al secondo. Il tuo codice non deve presupporre una frequenza fotogrammi particolare.

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

  1. Chiama 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. Scegli la posa dello spettatore.
    3. Supera ('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 sul framebuffer.

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

Scegli la posa dello spettatore

Probabilmente va da sé. Per disegnare qualsiasi cosa in AR o VR, devo sapere dove si trova lo spettatore e dove sta guardando. La posizione e l'orientamento del visualizzatore sono forniti da un oggetto XRViewerPose. Capisco la posa dello spettatore chiamando XRFrame.getViewerPose() nel frame dell'animazione corrente. Passo lo spazio di riferimento acquisito al momento della configurazione della sessione. I valori restituiti da questo oggetto sono sempre relativi allo spazio di riferimento richiesto quando ho avviato la 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.
  }
}

Una posa dello spettatore rappresenta la posizione complessiva dell'utente, ovvero la testa o la fotocamera del telefono nel caso di uno smartphone. La posa indica all'applicazione dove si trova il visualizzatore. Il rendering effettivo delle immagini utilizza oggetti XRView, cosa che accetterò tra poco.

Prima di andare avanti, testo se la posa dello spettatore è stata restituita nel caso in cui il sistema perda il monitoraggio o blocchi la posa per motivi di privacy. Il tracciamento è la capacità del dispositivo XR di sapere dove il dispositivo e/o i suoi dispositivi di input sono relativi all'ambiente. Il monitoraggio può andare perso in diversi modi e varia a seconda del metodo utilizzato per il monitoraggio. Ad esempio, se le fotocamere delle cuffie o del telefono vengono utilizzate per il monitoraggio del dispositivo, potrebbe perdere la capacità di determinare dove si trova in condizioni di scarsa illuminazione o in cui la luce è assente o se le fotocamere sono coperte.

Un esempio di blocco della posa per motivi di privacy è se sulle cuffie viene visualizzata una finestra di dialogo di sicurezza, come una richiesta di autorizzazione, il browser potrebbe interrompere la fornitura di pose all'applicazione mentre è in corso l'operazione. Tuttavia, ho già chiamato XRSession.requestAnimationFrame() in modo che, se il sistema è in grado di eseguire il ripristino, il loop di frame continuerà. In caso contrario, lo user agent terminerà la sessione e chiamerà il gestore di eventi end.

Una breve deviazione

Il passaggio successivo richiede oggetti creati durante la configurazione della sessione. Ho creato un canvas 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 trasmesso 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 appositamente per l'utilizzo con WebXR, sostituendo il 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
  }
}

Esegui l'iterazione su ogni oggetto XRView

Dopo aver ottenuto la posa e aver associato il framebuffer, è il momento di ottenere le aree visibili. XRViewerPose contiene un array di interfacce XRView, ognuna delle quali rappresenta un display o una porzione di display. Contengono le informazioni necessarie per visualizzare i contenuti posizionati correttamente per il dispositivo e il visualizzatore, ad esempio campo visivo, offset occhio e altre proprietà ottiche. Dato che disegna per due occhi, ho due viste, che analizzo e diseggo un'immagine separata per ognuna.

Nell'implementazione della realtà aumentata basata su telefono, avrei una sola vista, ma utilizzerei comunque un loop. Sebbene possa sembrare inutile eseguire l'iterazione attraverso una sola vista, questo ti consente di avere un unico percorso di rendering per una varietà 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 fa riferimento a ciò che è osservabile su una schermata. Per arrivare a questo punto di vista, però, ho bisogno di coordinate e dimensioni specifiche per il mio dispositivo. Come per il framebuffer, le chiedo da XRWebGLLayer e le 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
    }
  }
}

Il contestoGLRenWeb

Per scrivere questo articolo, ho discusso con alcuni colleghi in merito alla denominazione dell'oggetto webGLRenContext. Gli script di esempio e la maggior parte del codice WebXR richiamano in modo semplice questa variabile gl. Mentre cercavo di comprendere i campioni, continuavo a dimenticare il riferimento a gl. L'ho chiamata webGLRenContext per ricordarti che questa è un'istanza di WebGLRenderingContext durante l'apprendimento.

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 nelle lingue compilate. Questo è ovvio se hai scritto app VR utilizzando OpenGL, ma confondendo se hai poca familiarità con questa tecnologia.

Disegna qualcosa nel framebuffer

Se pensi di essere molto ambizioso, puoi usare direttamente WebGL, ma non lo suggerisco. È molto più semplice utilizzare uno dei framework elencati in alto.

Conclusione

Questo non è la fine degli aggiornamenti o degli articoli di 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à in Stato di Chrome.

Foto di JESHOOTS.COM su Unsplash