Mit der Trefferprüfung-API können Sie virtuelle Elemente in einer realen Ansicht positionieren.
Die WebXR Device API wurde im letzten Herbst in Chrome 79 eingeführt. Wie damals erwähnt, ist die Implementierung der API in Chrome noch in Arbeit. Chrome freut sich, Ihnen mitteilen zu können, dass ein Teil der Arbeit abgeschlossen ist. In Chrome 81 wurden zwei neue Funktionen eingeführt:
In diesem Artikel geht es um die WebXR Hit Test API, mit der virtuelle Objekte in einer realen Kameraansicht platziert werden können.
In diesem Artikel wird davon ausgegangen, dass Sie bereits wissen, wie Sie eine Augmented-Reality-Sitzung erstellen und eine Frame-Schleife ausführen. Wenn Sie mit diesen Konzepten nicht vertraut sind, sollten Sie die vorherigen Artikel dieser Reihe lesen.
- Virtual Reality im Web
- Virtual Reality kommt ins Web, Teil II
- Web-AR: Sie wissen vielleicht schon, wie Sie es verwenden können
Beispiel für eine immersive AR-Sitzung
Der Code in diesem Artikel basiert auf dem Beispiel für die Trefferprüfung der Immersive Web Working Group (Demo, Quelle), ist aber nicht mit diesem identisch. In diesem Beispiel können Sie virtuelle Sonnenblumen auf Oberflächen in der realen Welt platzieren.
Wenn du die App zum ersten Mal öffnest, siehst du einen blauen Kreis mit einem Punkt in der Mitte. Der Punkt ist der Schnittpunkt zwischen einer imaginären Linie von Ihrem Gerät zum Punkt in der Umgebung. Er bewegt sich, wenn du das Gerät bewegst. Wenn Schnittpunkte gefunden werden, wird der Cursor an Oberflächen wie Böden, Tischplatten und Wänden ausgerichtet. Das liegt daran, dass die Trefferprüfung zwar die Position und Ausrichtung des Schnittpunkts liefert, aber keine Informationen zu den Oberflächen selbst.
Dieser Kreis wird als Fadenkreuz bezeichnet. Es handelt sich um ein temporäres Bild, das beim Platzieren eines Objekts in Augmented Reality hilft. Wenn Sie auf das Display tippen, wird eine Sonnenblume an der Position und Ausrichtung des Fadenkreuzes auf der Oberfläche platziert, unabhängig davon, wo Sie auf das Display getippt haben. Das Fadenkreuz bewegt sich weiterhin mit Ihrem Gerät.
Fadenkreuz erstellen
Sie müssen das Bild für das Fadenkreuz selbst erstellen, da es nicht vom Browser oder der API bereitgestellt wird. Die Methode zum Laden und Zeichnen ist frameworkspezifisch.
Wenn Sie die Karte nicht direkt mit WebGL oder WebGL2 zeichnen, lesen Sie die Dokumentation zu Ihrem Framework. Aus diesem Grund gehe ich nicht näher darauf ein, wie das Fadenkreuz im Beispiel gezeichnet wird. Unten sehen Sie eine Zeile davon. Sie dient nur dazu, dass Sie in späteren Codebeispielen wissen, worauf ich mich beziehe, wenn ich die Variable reticle verwende.
let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});
Sitzung anfordern
Wenn Sie eine Sitzung anfordern, müssen Sie 'hit-test' im Array requiredFeatures anfordern, wie unten dargestellt.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
Sitzung starten
In früheren Artikeln habe ich Code zum Starten einer XR-Sitzung vorgestellt. Unten sehen Sie eine Version davon mit einigen Ergänzungen. Zuerst habe ich den Event-Listener select hinzugefügt. Wenn der Nutzer auf das Display tippt, wird basierend auf der Position des Fadenkreuzes eine Blume in der Kameraansicht platziert. Ich werde diesen Event-Listener später beschreiben.
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);
});
}
Mehrere Referenzbereiche
Beachten Sie, dass der hervorgehobene Code XRSession.requestReferenceSpace() zweimal aufruft. Das hat mich anfangs verwirrt. Ich habe gefragt, warum der Code für die Trefferprüfung keinen Animationsframe anfordert (wodurch die Frame-Schleife gestartet wird) und warum die Frame-Schleife anscheinend keine Trefferprüfungen umfasst. Die Ursache für die Verwirrung war ein Missverständnis der Referenzräume. Referenzräume drücken Beziehungen zwischen einem Ursprung und der Welt aus.
Um zu verstehen, was dieser Code bewirkt, stellen Sie sich vor, Sie sehen sich dieses Beispiel mit einem eigenständigen Rig an und haben sowohl ein Headset als auch einen Controller. Um Entfernungen vom Controller zu messen, verwenden Sie ein controllerzentriertes Bezugssystem. Wenn Sie jedoch etwas auf dem Bildschirm zeichnen möchten, verwenden Sie nutzerzentrierte Koordinaten.
In diesem Beispiel sind der Viewer und der Controller dasselbe Gerät. Aber ich habe ein Problem. Was ich zeichne, muss in Bezug auf die Umgebung stabil sein, aber der „Controller“, mit dem ich zeichne, bewegt sich.
Für das Zeichnen von Bildern verwende ich den Referenzraum local, der mir Stabilität in Bezug auf die Umgebung bietet. Danach starte ich die Frame-Schleife, indem ich requestAnimationFrame() aufrufe.
Für die Trefferprüfung verwende ich den Referenzraum viewer, der auf der Position des Geräts zum Zeitpunkt der Trefferprüfung basiert. Die Bezeichnung „Viewer“ ist in diesem Kontext etwas verwirrend, da ich über einen Controller spreche. Es ist sinnvoll, sich den Controller als elektronischen Betrachter vorzustellen. Danach rufe ich xrSession.requestHitTestSource() auf, wodurch die Quelle der Trefferprüfungsdaten erstellt wird, die ich beim Zeichnen verwende.
Frame-Schleife ausführen
Der Callback requestAnimationFrame() erhält außerdem neuen Code für die Trefferprüfung.
Wenn du dein Gerät bewegst, muss sich das Fadenkreuz mitbewegen, während es nach Oberflächen sucht. Um die Illusion einer Bewegung zu erzeugen, zeichnen Sie das Fadenkreuz in jedem Frame neu.
Das Fadenkreuz wird aber nicht angezeigt, wenn die Trefferprüfung fehlschlägt. Für das zuvor erstellte Fadenkreuz habe ich die Eigenschaft visible auf false festgelegt.
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
}
Um etwas in AR zu zeichnen, muss ich wissen, wo sich der Betrachter befindet und wohin er blickt. Ich teste also, ob hitTestSource und xrViewerPose noch gültig sind.
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
}
Jetzt rufe ich getHitTestResults() an. Sie verwendet hitTestSource als Argument und gibt ein Array von HitTestResult-Instanzen zurück. Bei der Trefferprüfung werden möglicherweise mehrere Oberflächen gefunden. Das erste Element im Array ist dasjenige, das der Kamera am nächsten ist.
Meistens wird es verwendet, aber für erweiterte Anwendungsfälle wird ein Array zurückgegeben. Stellen Sie sich beispielsweise vor, Ihre Kamera ist auf eine Box auf einem Tisch auf einem Boden gerichtet. Es ist möglich, dass die Trefferprüfung alle drei Oberflächen im Array zurückgibt. In den meisten Fällen ist es die Box, die mich interessiert. Wenn die Länge des zurückgegebenen Arrays 0 ist, also keine Trefferprüfung zurückgegeben wird, fahre fort. Versuchen Sie es im nächsten Frame noch einmal.
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
}
Schließlich muss ich die Ergebnisse der Trefferprüfung verarbeiten. Der grundlegende Ablauf ist folgender: Rufen Sie eine Pose aus dem Ergebnis der Trefferprüfung ab, transformieren (verschieben) Sie das Fadenkreuzbild an die Position der Trefferprüfung und setzen Sie dann die Property visible auf „true“. Die Pose stellt die Pose eines Punkts auf einer Oberfläche dar.
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
}
Objekt platzieren
Ein Objekt wird in AR platziert, wenn der Nutzer auf den Bildschirm tippt. Ich habe der Sitzung bereits einen select-Ereignis-Handler hinzugefügt. (Siehe oben.)
Wichtig ist hierbei, zu wissen, wo es platziert werden muss. Da das bewegliche Fadenkreuz eine konstante Quelle für Trefferprüfungen ist, lässt sich ein Objekt am einfachsten platzieren, indem es an der Position des Fadenkreuzes bei der letzten Trefferprüfung gezeichnet wird.
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);
}
}
Fazit
Am besten lässt sich das anhand des Beispielcodes oder des Codelabs nachvollziehen. Ich hoffe, ich habe Ihnen genug Hintergrundinformationen gegeben, um beides zu verstehen.
Wir sind noch lange nicht fertig mit der Entwicklung immersiver Web-APIs. Wir werden hier neue Artikel veröffentlichen, sobald wir Fortschritte machen.