Tout savoir sur la boucle de frames
Récemment, j'ai publié un article qui présente les concepts de base de l'API WebXR Device sous La réalité virtuelle arrive sur le Web. J'ai également fourni des instructions pour demander, entrer et terminer une session XR.
Cet article décrit la boucle de frames, qui est une boucle infinie contrôlée par un user-agent dans laquelle le contenu est dessiné à l'écran de manière répétée. Le contenu est dessiné dans des blocs distincts appelés "frames". La succession d'images crée une illusion de mouvement.
Ce que n'est pas cet article
WebGL et WebGL2 sont les seuls moyens d'afficher du contenu lors d'une boucle de frames dans une application WebXR. Heureusement, de nombreux frameworks fournissent une couche d'abstraction en plus 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 n'est ni un tutoriel WebGL ni sur un framework. Elle explique les principes de base d'une boucle de frame à l'aide de l'exemple de session de RV immersive de l'Immersive Web Working Group (démonstration, source). Si vous souhaitez vous plonger dans WebGL ou l'un de ses frameworks, vous trouverez sur Internet une liste d'articles qui ne cesse de s'allonger.
Les joueurs et le jeu
En essayant de comprendre la boucle de trame, je n'arrêtais pas de me perdre dans les détails. De nombreux objets sont en jeu, et certains d'entre eux ne sont nommés qu'à l'aide de propriétés de référence sur d'autres objets. Pour vous aider, je vais décrire les objets, que j'appelle les "joueurs". Ensuite, je décrirai comment ils interagissent, ce que j'appelle le « jeu ».
Les joueurs
XRViewerPose
Une posture correspond à la position et à l'orientation d'un objet dans un espace 3D. Le spectateur et les périphériques d'entrée ont une posture, mais c'est celle du spectateur qui nous intéresse ici. Les poses du lecteur et de l'appareil d'entrée comportent un attribut transform
qui décrit leur position en tant que vecteur et son orientation en tant que 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 nécessitent un peu d'explication. Je les aborde en détail dans la section Réalité augmentée. L'exemple que j'utilise comme base pour cet article utilise un espace de référence 'local'
. Cela signifie que l'origine est à la position de l'utilisateur au moment de la création de la session, sans valeur minimale bien définie, et que sa position exacte peut varier selon la plate-forme.
XRView
Une vue correspond à une caméra qui visualise une scène virtuelle. Une vue possède également un attribut transform
qui décrit sa position en tant que vecteur et son orientation.
Elles sont fournies à la fois sous forme de paire vecteur/quaternion et sous forme de matrice équivalente. Vous pouvez utiliser l'une ou l'autre des représentations en fonction de ce qui correspond le mieux à votre code. Chaque vue correspond à un écran ou à une partie d'écran utilisé par un appareil pour présenter des images à l'utilisateur. 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 de RA dispose d'une vue, qui peut recouvrir ou non l'écran de l'appareil.
Les casques offrent généralement deux vues, une pour chaque œil.
XRWebGLLayer
Les calques fournissent une source d'images bitmap et des descriptions du rendu de ces images sur l'appareil. Cette description ne reflète pas
tout à fait ce que fait ce joueur. Je considère qu'il s'agit d'un intermédiaire entre un appareil et un WebGLRenderingContext
. MDN adopte un point de vue très similaire, indiquant qu'il "fournit une liaison" entre les deux. De ce fait, il permet d'accéder aux autres joueurs.
En général, les objets WebGL stockent des informations d'état pour le rendu des graphismes 2D et 3D.
WebGLFramebuffer
Un framebuffer fournit des données d'image à WebGLRenderingContext
. Après l'avoir récupéré à partir de XRWebGLLayer
, il vous suffit de le transmettre au WebGLRenderingContext
actuel. Hormis bindFramebuffer()
(nous y reviendrons plus tard), vous n'accéderez jamais directement à cet objet. Il vous suffit de le transmettre de XRWebGLLayer
à 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 d'un WebGLFramebuffer
et d'un XRViewport.
Notez la relation entre XRWebGLLayer
et WebGLRenderingContext
. L'un correspond à l'appareil du lecteur et l'autre à la page Web.
WebGLFramebuffer
et XRViewport
sont transmis du premier au second.
Le match
Maintenant que nous savons qui sont les joueurs, examinons le jeu auquel ils jouent. C'est un jeu qui recommence à chaque frame. Rappelez-vous que les frames font partie d'une boucle de frames qui se produit à un rythme qui dépend du matériel sous-jacent. Pour les applications de RV, le nombre d'images par seconde peut être compris entre 60 et 144. La RA pour Android s'exécute à 30 images par seconde. Votre code ne doit pas supposer une fréquence d'images particulière.
Le processus de base de la boucle de frames se présente comme suit:
- Appelez
XRSession.requestAnimationFrame()
. En réponse, le user-agent appelle l'élémentXRFrameRequestCallback
, que vous avez défini. - Dans votre fonction de rappel :
- Rappeler
XRSession.requestAnimationFrame()
. - Prenez la pose du spectateur.
- Transmettez ('lier') le
WebGLFramebuffer
deXRWebGLLayer
àWebGLRenderingContext
. - Itérez chaque objet
XRView
, en récupérant sonXRViewport
à partir deXRWebGLLayer
et en le transmettant àWebGLRenderingContext
. - Permet de dessiner un élément dans le framebuffer.
- Rappeler
Comme les étapes 1 et 2a ont été présentées dans l'article précédent, je vais commencer par l'étape 2b.
Prenez la pose du spectateur
Cela va probablement de soi. Pour dessiner en RA ou RV, je dois savoir
où se trouve le spectateur et où il regarde. La position et l'orientation de la visionneuse sont fournies par un objet XRViewerPose. Pour obtenir la position du spectateur, j'appelle 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 entamé 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.
}
}
L'une des positions du spectateur représente la position globale de l'utilisateur (c'est-à-dire sa tête ou l'appareil photo du téléphone, dans le cas d'un smartphone).
La pose indique à votre application où se trouve le spectateur. Le rendu réel de l'image utilise des objets XRView
, dont nous parlerons plus tard.
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ù se situent ses périphériques d'entrée par rapport à l'environnement. Le suivi peut être perdu de plusieurs manières 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 suivre l'appareil, il est possible qu'il ne puisse plus déterminer où il se trouve en cas de faible luminosité ou qu'il n'y a pas de lumière, 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. Cependant, j'ai déjà appelé XRSession.requestAnimationFrame()
de sorte que si le système parvient à récupérer, la boucle de frames se poursuit. Sinon, le user-agent met fin à la session et appelle le gestionnaire d'événements end
.
Un petit détour
L'étape suivante nécessite la création d'objets lors de la configuration de la session. Pour rappel, j'ai créé un canevas et lui ai demandé de créer un contexte de rendu Web GL compatible avec XR. J'ai obtenu cette information en appelant canvas.getContext()
. Tout le dessin est effectué à l'aide de l'API WebGL, de l'API WebGL2 ou d'un framework basé sur WebGL comme Three.js. Ce contexte a été transmis à l'objet de session via updateRenderState()
, avec 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 tampon WebGLFramebuffer
XRWebGLLayer
fournit un tampon de frame pour WebGLRenderingContext
fourni spécifiquement pour une utilisation avec WebXR et remplace le framebuffer par défaut du contexte de rendu. C'est ce qu'on appelle la "liaison" dans le langage 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
}
}
Itérer chaque objet XRView
Après avoir obtenu la pose et lié le framebuffer, vous pouvez obtenir les fenêtres d'affichage. XRViewerPose
contient un tableau d'interfaces XRView, représentant chacune un écran ou une partie d'un écran. Elles contiennent les informations nécessaires pour afficher un contenu correctement positionné pour l'appareil et l'utilisateur, telles que le champ de vision, le décalage oculaire et d'autres propriétés optiques.
Puisque je dessine pour deux yeux, j'ai deux vues, que je fais défiler et que je dessine une image distincte pour chacune.
Lors de l'implémentation pour la réalité augmentée basée sur un téléphone, je n'aurais qu'une seule vue, mais j'utiliserais toujours une boucle. Bien qu'il puisse sembler inutile d'effectuer une itération sur une seule vue, cela vous permet d'avoir un seul chemin de rendu pour un spectre 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
}
}
}
Transmettez 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 je 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
}
}
}
Le contexte webGLRenContext
En rédigeant cet article, j'ai eu un débat avec quelques collègues au sujet de la dénomination de l'objet webGLRenContext
. Les exemples de scripts et la plupart des codes WebXR appellent simplement cette variable gl
. Lorsque j'essayais de comprendre les exemples, j'oubliais sans cesse ce à quoi gl
faisait référence. Je l'ai appelé webGLRenContext
pour vous rappeler
qu'il s'agit d'une instance de WebGLRenderingContext
.
En effet, l'utilisation de gl
permet aux noms de méthodes de ressembler à leurs équivalents dans l'API OpenGL ES 2.0, utilisée pour créer une RV dans des langages compilés. Cela est évident si vous avez écrit des applications de RV à l'aide d'OpenGL, mais cela peut être déroutant si vous débutez avec cette technologie.
Dessiner un élément dans le framebuffer
Si vous vous sentez vraiment ambitieux, vous pouvez utiliser WebGL directement, mais je vous le déconseille. Il est beaucoup plus simple d'utiliser l'un des frameworks répertoriés en haut.
Conclusion
Ce n'est pas la fin des mises à jour ou des articles de WebXR. Vous trouverez une documentation de référence pour l'ensemble des interfaces et des membres WebXR sur MDN. Pour découvrir les améliorations à venir des interfaces elles-mêmes, suivez les fonctionnalités individuelles dans Chrome Status.
Photo de JESHOOTS.COM sur Unsplash