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 istruzioni per richiedere, accedere e terminare una sessione XR.
Questo articolo descrive il loop del frame, ovvero un loop infinito controllato dall'agente utente in cui i contenuti vengono disegnati ripetutamente 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 la partita
Quando ho provato a capire il loop dei frame, mi perdevo nei dettagli. Sono presenti molti oggetti e alcuni sono denominati solo tramite proprietà di riferimento su altri oggetti. Per aiutarti a capire, descriverò gli oggetti, che chiamerò "player". Poi descriverò il modo in cui 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. Sia le pose dello spettatore che quelle 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 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 visuale corrisponde a una videocamera che visualizza 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 nel dispositivo. Questa descrizione non descrive esattamente 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. Di conseguenza, fornisce l'accesso agli altri giocatori.
In generale, gli oggetti WebGL memorizzano informazioni sullo stato per il rendering di grafica 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 viewport fornisce le coordinate e le dimensioni di una regione rettangolare nel 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 WebGLFramebuffer
sia di XRViewport.
Nota la relazione tra XRWebGLLayer
e WebGLRenderingContext
. Uno corrisponde al dispositivo dello spettatore 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 variare da 60 a 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 invocaXRFrameRequestCallback
, 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
, recupera il relativoXRViewport
daXRWebGLLayer
e passalo aWebGLRenderingContext
. - Disegna qualcosa nel framebuffer.
- Chiama di nuovo
Poiché i passaggi 1 e 2a sono stati trattati nell'articolo precedente, inizierò dal passaggio 2b.
Fai in modo che lo spettatore si metta in posa
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. recupero la posa dello spettatore chiamando XRFrame.getViewerPose()
sul frame animato corrente. Trasferisco lo spazio di riferimento che ho acquisito durante la configurazione della sessione. I valori restituiti da questo oggetto sono sempre relativi allo spazio di riferimento che ho richiesto quando ho acceso alla 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 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 procedere, verifico se la posa dello spettatore è stata restituita nel caso in cui il sistema perda il rilevamento o blocchi la posa 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 blocco della posa per motivi di privacy è se il visore mostra una finestra di dialogo di sicurezza, ad esempio una richiesta di autorizzazione. In questo caso, il browser potrebbe smettere di fornire pose all'applicazione. 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
.
Un 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 disegno per due occhi, ho due visualizzazioni, che eseguo in loop e disegno
un'immagine separata per ciascuna.
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 chiamano semplicemente questa variabile 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 lo stesso aspetto delle loro controparti nell'API OpenGL ES 2.0, utilizzata per creare la realtà virtuale in linguaggi compilati. Questo fatto è ovvio se hai scritto app VR utilizzando OpenGL, ma può essere fonte di confusione se non hai mai utilizzato 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 prossimi miglioramenti alle interfacce stesse, segui le singole funzionalità su Chrome Status.
Foto di JESHOOTS.COM su Unsplash