L'API Test de positionnement vous permet de positionner des éléments virtuels dans une vue du monde réel.
L'API WebXR Device a été publiée l'automne dernier dans Chrome 79. Comme indiqué à l'époque, l'implémentation de l'API dans Chrome est en cours. Chrome est heureux d'annoncer que certains travaux sont terminés. Chrome 81 propose deux nouvelles fonctionnalités :
Cet article présente l'API WebXR Hit Test, qui permet de placer des objets virtuels dans une vue d'appareil photo du monde réel.
Dans cet article, je suppose que vous savez déjà créer une session de réalité augmentée et que vous savez exécuter une boucle de frames. Si vous ne connaissez pas ces concepts, nous vous recommandons de lire les articles précédents de cette série.
- La réalité virtuelle arrive sur le Web
- La réalité virtuelle arrive sur le Web, partie II
- RA sur le Web: vous savez peut-être déjà comment l'utiliser
Exemple de session de RA immersive
Le code présenté dans cet article est basé sur celui obtenu dans l'exemple du test de positionnement du groupe de travail Immersive Web (demo, source), mais n'est pas identique à celui-ci. Cet exemple vous permet de placer des tournesols virtuels sur des surfaces du monde réel.
Lorsque vous ouvrez l'application pour la première fois, un cercle bleu avec un point au milieu s'affiche. Le point correspond à l'intersection entre une ligne imaginaire partant de votre appareil et le point de l'environnement. Il se déplace lorsque vous déplacez l'appareil. À mesure qu'il trouve des points d'intersection, il semble s'ancrer à des surfaces telles que les sols, les dessus de table et les murs. En effet, le test des intersections fournit la position et l'orientation du point d'intersection, mais rien sur les surfaces elles-mêmes.
Ce cercle est appelé réticule, qui est une image temporaire qui aide à placer un objet en réalité augmentée. Si vous appuyez sur l'écran, un tournesol est placé à la surface à l'emplacement et à l'orientation du réticule, quel que soit l'endroit où vous avez appuyé sur l'écran. Le réticule continue de bouger avec votre appareil.
Créer le réticule
Vous devez créer vous-même l'image du réticule, car elle n'est pas fournie par le navigateur ni par l'API. La méthode de chargement et de dessin est spécifique au framework.
Si vous ne le dessinez pas directement à l'aide de WebGL ou WebGL2, consultez la documentation de votre framework. C'est pourquoi je ne vais pas entrer dans les détails de la façon dont le réticule est dessiné dans l'exemple. Je n'affiche ci-dessous qu'une seule ligne pour une seule raison: afin que, dans les exemples de code suivants, vous sachiez à quoi je fais référence lorsque j'utilise la variable reticle
.
let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});
Demander une session
Lorsque vous demandez une session, vous devez demander 'hit-test'
dans le tableau requiredFeatures
, comme indiqué ci-dessous.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
Ouverture d'une session
Dans des articles précédents, j'ai présenté le code permettant d'accéder à une session XR. Vous en trouverez une version ci-dessous, avec quelques ajouts. J'ai d'abord ajouté l'écouteur d'événements select
. Lorsque l'utilisateur appuie sur l'écran, une fleur est placée dans la vue de l'appareil photo en fonction de la position du réticule. Je décrirai cet écouteur d'événement plus tard.
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
xrSession.addEventListener('select', onSelect);
let canvas = document.createElement('canvas');
gl = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(session, gl)
});
xrSession.requestReferenceSpace('viewer').then((refSpace) => {
xrViewerSpace = refSpace;
xrSession.requestHitTestSource({ space: xrViewerSpace })
.then((hitTestSource) => {
xrHitTestSource = hitTestSource;
});
});
xrSession.requestReferenceSpace('local').then((refSpace) => {
xrRefSpace = refSpace;
xrSession.requestAnimationFrame(onXRFrame);
});
}
Plusieurs espaces de référence
Notez que le code mis en surbrillance appelle XRSession.requestReferenceSpace()
deux fois. J'ai trouvé cela déroutant au début. Je me suis demandé pourquoi le code de test de collision ne demande pas de frame d'animation (démarrage de la boucle de frame) et pourquoi la boucle de frame ne semble pas impliquer de tests de collision. La source de la confusion était une
malentendue des espaces de référence. Les espaces de référence expriment les relations entre un point d'origine et le monde.
Pour comprendre ce que fait ce code, imaginez que vous regardez cet exemple à l'aide d'un appareil autonome et que vous disposez à la fois d'un casque et d'une manette. Pour mesurer les distances à partir du contrôleur, vous devez utiliser un cadre de référence centré sur le contrôleur. Toutefois, pour dessiner quelque chose à l'écran, vous devez utiliser des coordonnées centrées sur l'utilisateur.
Dans cet exemple, le lecteur et le contrôleur sont le même appareil. Mais j'ai un problème. Ce que je dessine doit être stable par rapport à l'environnement, mais le "contrôleur" avec lequel je dessine bouge.
Pour le dessin d'images, j'utilise l'espace de référence local
, qui me donne de la stabilité en termes d'environnement. Une fois que j'ai obtenu cela, je lance la boucle de frame en appelant requestAnimationFrame()
.
Pour les tests de positionnement, j'utilise l'espace de référence viewer
, qui est basé sur la position de l'appareil au moment du test de positionnement. Le libellé "lecteur" est quelque peu déroutant dans ce contexte, car je parle d'un contrôleur. Cela a du sens si vous considérez le contrôleur comme un visionneuse électronique. Après avoir obtenu cela, j'appelle xrSession.requestHitTestSource()
, qui crée la source de données de test de collision que j'utiliserai lors du dessin.
Exécuter une boucle de frame
Le rappel requestAnimationFrame()
reçoit également un nouveau code pour gérer les tests de requêtes.
Lorsque vous déplacez votre appareil, le réticule doit le faire également pour tenter de trouver des surfaces. Pour créer l'illusion de mouvement, redessinez le réticule dans chaque frame.
Toutefois, n'affichez pas le réticule si le test de collision échoue. Pour le réticule que j'ai créé précédemment, j'ai défini sa propriété visible
sur false
.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Pour dessiner quoi que ce soit en RA, je dois savoir où se trouve le spectateur et où il regarde. Je vérifie donc que hitTestSource
et xrViewerPose
sont toujours valides.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
J'appelle maintenant getHitTestResults()
. Elle utilise hitTestSource
comme argument et renvoie un tableau d'instances HitTestResult
. Le test de positionnement peut trouver plusieurs surfaces. Le premier élément du tableau est celui qui se trouve le plus près de l'appareil photo.
La plupart du temps, vous l'utiliserez, mais un tableau est renvoyé pour les cas d'utilisation avancés. Par exemple, imaginez que votre caméra est dirigée vers une boîte sur une table au sol. Il est possible que le test de collision renvoie les trois surfaces du tableau. Dans la plupart des cas, il s'agit de la case qui m'intéresse. Si la longueur du tableau renvoyé est de 0, en d'autres termes, si aucun test de contact n'est renvoyé, continuez. Réessayez dans le cadre suivant.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Enfin, je dois traiter les résultats du test de contact. Voici le processus de base. Obtenez une pose à partir du résultat du test de collision, transformez (déplacez) l'image du réticule vers la position du test de collision, puis définissez sa propriété visible
sur "true". La pose représente la pose d'un point sur une surface.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.matrix = pose.transform.matrix;
reticle.visible = true;
}
}
// Draw to the screen
}
Placer un objet
Un objet est placé dans la RA lorsque l'utilisateur appuie sur l'écran. J'ai déjà ajouté un gestionnaire d'événements select
à la session. (Voir ci-dessus.)
L'essentiel à cette étape est de savoir où le placer. Étant donné que le réticule mobile vous fournit une source constante de tests de positionnement, le moyen le plus simple de placer un objet consiste à le dessiner à l'emplacement du réticule lors du dernier test de positionnement.
function onSelect(event) {
if (reticle.visible) {
// The reticle should already be positioned at the latest hit point,
// so we can just use its matrix to save an unnecessary call to
// event.frame.getHitTestResults.
addARObjectAt(reticle.matrix);
}
}
Conclusion
Le meilleur moyen d'y parvenir est de parcourir l'exemple de code ou de suivre l'atelier de programmation. J'espère vous avoir donné assez d'informations pour comprendre les deux.
Nous n'avons pas fini de créer des API Web immersives. Nous y publierons de nouveaux articles au fur et à mesure de notre avancement.
Photo de Daniel Frank sur Unsplash