Tout savoir sur la boucle de frames
J'ai récemment publié l'article La réalité virtuelle arrive sur le Web, qui présente les concepts de base de l'API WebXR Device. J'ai également fourni des instructions pour demander, saisir et mettre fin à une session XR.
Cet article décrit la boucle de frame, qui est une boucle infinie contrôlée par l'agent utilisateur dans laquelle le contenu est affiché à l'écran de manière répétée. Le contenu est dessiné dans des blocs distincts appelés "frames" (images). La succession d'images crée l'illusion du mouvement.
Ce que cet article n'est pas
WebGL et WebGL2 sont les seuls moyens de rendre du contenu pendant une boucle de frame dans une application WebXR. Heureusement, de nombreux frameworks fournissent une couche d'abstraction au-dessus de WebGL et WebGL2. Ces frameworks incluent three.js, babylonjs et PlayCanvas, tandis que A-Frame et React 360 ont été conçus pour interagir avec WebXR.
Cet article explique les principes de base d'une boucle de frame à l'aide de l'exemple de session VR immersive du groupe de travail Immersive Web (démonstration, source). Si vous souhaitez en savoir plus sur WebGL ou l'un des frameworks, vous trouverez une liste croissante de ressources en ligne.
Les joueurs et le jeu
Lorsque j'essayais de comprendre la boucle de frames, je me perdais toujours dans les détails. De nombreux objets sont en jeu, et certains ne sont nommés que par des propriétés de référence sur d'autres objets. Pour vous aider à vous y retrouver, je vais décrire les objets, que j'appellerai "joueurs". Je décrirai ensuite comment ils interagissent, ce que j'appelle "le jeu".
Les joueurs
XRViewerPose
Une pose correspond à la position et à l'orientation d'un élément dans l'espace 3D. Les lecteurs et les périphériques d'entrée ont une pose, mais c'est la pose du lecteur qui nous intéresse ici. Les poses du spectateur et du périphérique d'entrée ont toutes deux un attribut transform décrivant leur position sous forme de vecteur et leur orientation sous forme de quaternion par rapport à l'origine. L'origine est spécifiée en fonction du type d'espace de référence demandé lors de l'appel de XRSession.requestReferenceSpace().
Les espaces de référence sont un peu difficiles à expliquer. Je les aborde en détail dans Réalité augmentée. L'exemple que j'utilise comme base pour cet article utilise un espace de référence 'local', ce qui signifie que l'origine se trouve à la position du spectateur au moment de la création de la session, sans plancher bien défini, et que sa position précise peut varier selon la plate-forme.
XRView
Une vue correspond à une caméra qui observe la scène virtuelle. Une vue possède également un attribut transform qui décrit sa position sous forme de vecteur et son orientation.
Ces valeurs sont fournies à la fois sous forme de paire vecteur/quaternion et sous forme de matrice équivalente. Vous pouvez utiliser l'une ou l'autre représentation en fonction de celle qui convient le mieux à votre code. Chaque vue correspond à un affichage ou à une partie d'un affichage utilisé par un appareil pour présenter des images au spectateur. Les objets XRView sont renvoyés dans un tableau à partir de l'objet XRViewerPose. Le nombre de vues dans le tableau varie. Sur les appareils mobiles, une scène AR ne comporte qu'une seule vue, qui peut ou non couvrir l'écran de l'appareil.
Les casques ont généralement deux vues, une pour chaque œil.
XRWebGLLayer
Les calques fournissent une source d'images bitmap et décrivent la manière dont ces images doivent être affichées sur l'appareil. Cette description ne reflète pas tout à fait ce que fait ce lecteur. Je le considère comme un intermédiaire entre un appareil et un WebGLRenderingContext. MDN adopte une approche similaire, en indiquant qu'il "fournit un lien" entre les deux. Il permet donc d'accéder aux autres joueurs.
En général, les objets WebGL stockent des informations d'état pour le rendu de graphiques 2D et 3D.
WebGLFramebuffer
Un tampon de frame fournit des données d'image à WebGLRenderingContext. Après l'avoir récupéré à partir de XRWebGLLayer, vous le transmettez à l'WebGLRenderingContext actuel. À part en appelant bindFramebuffer() (nous y reviendrons plus tard), vous n'accéderez jamais directement à cet objet. Vous ne ferez que le transmettre de XRWebGLLayer au WebGLRenderingContext.
XRViewport
Une fenêtre d'affichage fournit les coordonnées et les dimensions d'une région rectangulaire dans le WebGLFramebuffer.
WebGLRenderingContext
Un contexte de rendu est un point d'accès programmatique à un canevas (l'espace sur lequel nous dessinons). Pour ce faire, il a besoin à la fois d'un WebGLFramebuffer et d'un XRViewport.
Notez la relation entre XRWebGLLayer et WebGLRenderingContext. L'une correspond à l'appareil du lecteur et l'autre à la page Web.
WebGLFramebuffer et XRViewport sont transmis du premier au second.
XRWebGLLayer et WebGLRenderingContext
Le jeu
Maintenant que nous connaissons les acteurs, examinons le jeu auquel ils jouent. Il s'agit d'un jeu qui recommence à chaque frame. Rappelez-vous que les frames font partie d'une boucle de frames qui se produit à une fréquence qui dépend du matériel sous-jacent. Pour les applications de réalité virtuelle, le nombre d'images par seconde peut varier de 60 à 144. La RA pour Android fonctionne à 30 images par seconde. Votre code ne doit pas supposer une fréquence d'images particulière.
Voici le processus de base pour la boucle de frame :
- Appelez
XRSession.requestAnimationFrame(). En réponse, l'agent utilisateur appelleXRFrameRequestCallback, que vous avez défini. - Dans votre fonction de rappel :
- Rappelez
XRSession.requestAnimationFrame(). - Obtenez la pose du spectateur.
- Transmettez (liez) le
WebGLFramebufferduXRWebGLLayerauWebGLRenderingContext. - Itérez sur chaque objet
XRView, en récupérant sonXRViewportà partir deXRWebGLLayeret en le transmettant àWebGLRenderingContext. - Dessinez quelque chose dans le framebuffer.
- Rappelez
Étant donné que les étapes 1 et 2a ont été abordées dans l'article précédent, je vais commencer par l'étape 2b.
Obtenir la pose du spectateur
Cela va sans dire. Pour dessiner quoi que ce soit en RA ou VR, je dois savoir où se trouve le spectateur et où il regarde. La position et l'orientation du spectateur sont fournies par un objet XRViewerPose. J'obtiens la pose du spectateur en appelant XRFrame.getViewerPose() sur le frame d'animation actuel. Je lui transmets l'espace de référence que j'ai acquis lors de la configuration de la session. Les valeurs renvoyées par cet objet sont toujours relatives à l'espace de référence que j'ai demandé lorsque j'ai rejoint la session en cours. Comme vous vous en souvenez peut-être, je dois transmettre l'espace de référence actuel lorsque je demande la pose.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
if (xrViewerPose) {
// Render based on the pose.
}
}
Une pose de spectateur représente la position globale de l'utilisateur, c'est-à-dire la tête du spectateur ou la caméra du téléphone.
La pose indique à votre application où se trouve le spectateur. Le rendu réel des images utilise des objets XRView, dont je parlerai dans un instant.
Avant de continuer, je vérifie si la pose du spectateur a été renvoyée au cas où le système perdrait le suivi ou bloquerait la pose pour des raisons de confidentialité. Le suivi est la capacité de l'appareil XR à savoir où il se trouve, ainsi que ses périphériques d'entrée, par rapport à l'environnement. Le suivi peut être perdu de plusieurs façons, et varie en fonction de la méthode utilisée pour le suivi. Par exemple, si les caméras du casque ou du téléphone sont utilisées pour le suivi, l'appareil peut perdre sa capacité à déterminer où il se trouve dans des situations de faible luminosité ou d'obscurité, ou si les caméras sont couvertes.
Par exemple, si le casque affiche une boîte de dialogue de sécurité telle qu'une invite d'autorisation, le navigateur peut cesser de fournir des poses à l'application pendant ce temps. Mais j'ai déjà appelé XRSession.requestAnimationFrame() pour que la boucle de frame continue si le système peut récupérer. Dans le cas contraire, l'agent utilisateur mettra fin à la session et appellera le gestionnaire d'événements end.
Un petit détour
L'étape suivante nécessite des objets créés lors de la configuration de la session.
Rappelez-vous que j'ai créé un canevas et lui ai demandé de créer un contexte de rendu WebGL compatible avec la réalité étendue, que j'ai obtenu en appelant canvas.getContext(). Tous les dessins sont réalisés à l'aide de l'API WebGL, de l'API WebGL2 ou d'un framework basé sur WebGL tel que Three.js. Ce contexte a été transmis à l'objet de session avec updateRenderState(), en plus d'une nouvelle instance de 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)
});
Transmettre ("bind") le WebGLFramebuffer
XRWebGLLayer fournit un framebuffer pour WebGLRenderingContext, spécifiquement destiné à être utilisé avec WebXR et remplaçant le framebuffer par défaut des contextes de rendu. Dans le langage WebGL, on parle de "liaison".
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
}
}
Itérer sur chaque objet XRView
Une fois la pose obtenue et le framebuffer lié, il est temps d'obtenir les fenêtres d'affichage. XRViewerPose contient un tableau d'interfaces XRView, chacune représentant un écran ou une partie d'un écran. Ils contiennent les informations nécessaires pour afficher le contenu correctement positionné pour l'appareil et le spectateur, telles que le champ de vision, le décalage des yeux et d'autres propriétés optiques.
Comme je dessine pour deux yeux, j'ai deux vues, que je parcours en boucle et pour lesquelles je dessine une image distincte.
Lorsque j'implémente la réalité augmentée sur téléphone, je n'ai qu'une seule vue, mais j'utilise quand même une boucle. Bien qu'il puisse sembler inutile d'itérer sur une seule vue, cela vous permet d'avoir un chemin de rendu unique pour un éventail d'expériences immersives. Il s'agit d'une différence importante entre WebXR et les autres systèmes immersifs.
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
}
}
}
Transmettre l'objet XRViewport à WebGLRenderingContext
Un objet XRView fait référence à ce qui est observable à l'écran. Mais pour dessiner dans cette vue, j'ai besoin de coordonnées et de dimensions spécifiques à mon appareil. Comme pour le framebuffer, je les demande à XRWebGLLayer et les transmets à 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
J'ai eu une discussion par écrit avec quelques collègues sur le nom de l'objet webGLRenContext. Les exemples de scripts et la plupart du code WebXR appellent cette variable gl. Lorsque j'essayais de comprendre les exemples, je n'arrêtais pas d'oublier ce à quoi gl faisait référence. Je l'ai appelé webGLRenContext pour vous rappeler que vous apprenez à identifier une instance de WebGLRenderingContext.
En effet, l'utilisation de gl permet aux noms de méthodes de ressembler à leurs homologues dans l'API OpenGL ES 2.0, utilisée pour créer de la VR dans des langages compilés. Cela peut sembler évident si vous avez déjà écrit des applications de RV à l'aide d'OpenGL, mais cela peut être déroutant si vous débutez dans ce domaine.
Dessiner quelque chose dans le framebuffer
Si vous vous sentez vraiment ambitieux, vous pouvez utiliser WebGL directement, mais je ne le recommande pas. Il est beaucoup plus simple d'utiliser l'un des frameworks listés en haut de la page.
Conclusion
Ce n'est pas la fin des articles et des mises à jour sur WebXR. Vous trouverez une référence pour toutes les interfaces et tous les membres de WebXR sur MDN. Pour en savoir plus sur les améliorations à venir des interfaces elles-mêmes, suivez les fonctionnalités individuelles sur État de Chrome.