L'API Hit Test vous permet de positionner des éléments virtuels dans une vue du monde réel.
L'API WebXR Device a été lancée l'automne dernier dans Chrome 79. Comme indiqué à l'époque, l'implémentation de l'API par Chrome est en cours de développement. Chrome est heureux de vous annoncer que certains travaux sont terminés. Dans Chrome 81, deux nouvelles fonctionnalités sont disponibles :
Cet article traite de l'API WebXR Hit Test, qui permet de placer des objets virtuels dans le champ de vision d'une caméra du monde réel.
Dans cet article, je pars du principe que vous savez déjà comment créer une session de réalité augmentée et comment exécuter une boucle de frame. Si vous ne connaissez pas ces concepts, nous vous conseillons 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, deuxième partie
- RA Web : vous savez peut-être déjà comment l'utiliser
Exemple de session de RA immersive
Le code de cet article est basé sur l'exemple de test de frappe du groupe de travail Immersive Web (démonstration, source), mais n'y est pas identique. Cet exemple vous permet de placer des tournesols virtuels sur des surfaces dans le 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 est l'intersection entre une ligne imaginaire allant de votre appareil au point dans l'environnement. Il se déplace en même temps que l'appareil. Lorsqu'il trouve des points d'intersection, il semble s'accrocher aux surfaces telles que les sols, les plateaux de table et les murs. En effet, le test de sélection fournit la position et l'orientation du point d'intersection, mais rien sur les surfaces elles-mêmes.
Ce cercle est appelé réticule. Il s'agit d'une image temporaire qui aide à placer un objet en réalité augmentée. Si vous appuyez sur l'écran, un tournesol est placé sur la surface à l'emplacement et dans l'orientation du point du réticule, où que vous ayez 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. Pour cette raison, je ne vais pas entrer dans les détails de la façon dont le réticule est dessiné dans l'échantillon. Je n'affiche qu'une seule ligne pour une seule raison : afin que, dans les exemples de code ultérieurs, 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
});
Rejoindre une session
Dans les articles précédents, j'ai présenté du code pour saisir une session XR. Vous trouverez ci-dessous une version de ce code 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 la caméra en fonction de la pose 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. Au début, je trouvais cela déroutant. J'ai demandé pourquoi le code de test de sélection ne demandait pas de frame d'animation (démarrage de la boucle de frame) et pourquoi la boucle de frame ne semblait pas impliquer de tests de sélection. La confusion provenait d'une mauvaise compréhension des espaces de référence. Les espaces de référence expriment les relations entre une origine et le monde.
Pour comprendre ce que fait ce code, imaginez que vous consultez cet exemple à l'aide d'un équipement 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 est en mouvement.
Pour le dessin d'images, j'utilise l'espace de référence local, qui me donne de la stabilité en termes d'environnement. Après cela, je lance la boucle de frame en appelant requestAnimationFrame().
Pour les tests de sélection, j'utilise l'espace de référence viewer, qui est basé sur la pose de l'appareil au moment du test de sélection. 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 lecteur électronique. Une fois que j'ai obtenu cela, j'appelle xrSession.requestHitTestSource(), ce qui crée la source de données de test de sélection que j'utiliserai lors du dessin.
Exécuter une boucle de frames
Le rappel requestAnimationFrame() reçoit également un nouveau code pour gérer les tests de sélection.
Lorsque vous déplacez votre appareil, le réticule doit se déplacer avec lui lorsqu'il essaie de trouver des surfaces. Pour créer l'illusion du mouvement, redessinez le réticule à chaque frame.
Mais n'affichez pas le réticule si le test de sélection échoue. Ainsi, 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(). Il prend 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 est le plus proche de la caméra.
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 pointée vers une boîte posée sur une table sur un sol. Il est possible que le test de sélection renvoie les trois surfaces dans le tableau. Dans la plupart des cas, il s'agit de la boîte qui m'intéresse. Si la longueur du tableau renvoyé est égale à 0, c'est-à-dire si aucun test de sélection n'est renvoyé, poursuivez. Réessayez dans la prochaine image.
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 sélection. Voici le processus de base : Obtenez une pose à partir du résultat du test de sélection, transformez (déplacez) l'image du réticule vers la position du test de sélection, 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é en 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'important dans cette étape est de savoir où le placer. Étant donné que le réticule mobile vous fournit une source constante de tests de sélection, le moyen le plus simple de placer un objet consiste à le dessiner à l'emplacement du réticule lors du dernier test de sélection.
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 de comprendre cela est de parcourir l'exemple de code ou d'essayer le atelier de programmation. J'espère vous avoir donné suffisamment d'informations pour que vous puissiez comprendre les deux.
Nous sommes loin d'avoir fini de créer des API Web immersives. Nous publierons de nouveaux articles sur cette page au fur et à mesure de nos progrès.
Photo de Daniel Frank sur Unsplash