Tutto sul ciclo dei fotogrammi
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.
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:
- Chiama il numero
XRSession.requestAnimationFrame(). In risposta, lo user agent richiamaXRFrameRequestCallback, che è definito da te. - All'interno della funzione di callback:
- Chiama di nuovo
XRSession.requestAnimationFrame(). - Ottieni la posa dello spettatore.
- Passa ('bind') il
WebGLFramebufferdaXRWebGLLayeraWebGLRenderingContext. - Itera ogni oggetto
XRView, recuperando il relativoXRViewportdaXRWebGLLayere 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.
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.