Informazioni sul loop dei frame
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.
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:
- Chiama il numero
XRSession.requestAnimationFrame()
. In risposta, lo user agent richiama ilXRFrameRequestCallback
, che è definito da te. - All'interno della funzione di callback:
- Chiama di nuovo
XRSession.requestAnimationFrame()
. - Fai in modo che lo spettatore si metta in posa.
- Passa ('bind') il
WebGLFramebuffer
dalXRWebGLLayer
alWebGLRenderingContext
. - Esegui l'iterazione su ogni oggetto
XRView
, recuperando il relativoXRViewport
daXRWebGLLayer
e passandolo aWebGLRenderingContext
. - Disegna qualcosa nel framebuffer.
- Chiama di nuovo
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