Mit der Hit Test API können Sie virtuelle Elemente in einer realen Ansicht positionieren.
Die WebXR Device API wurde letzten Herbst in Chrome 79 eingeführt. Wie damals erwähnt, ist die Implementierung der API in Chrome noch in Arbeit. Chrome hat gute Neuigkeiten: Ein Teil der Arbeit ist abgeschlossen. 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 einen Frame-Loop ausführen. Wenn Sie mit diesen Konzepten nicht vertraut sind, sollten Sie die vorherigen Artikel dieser Reihe lesen.
- Virtual Reality im Web
- Virtual Reality im Web – Teil 2
- Web-AR: Sie wissen vielleicht schon, wie sie funktioniert
Beispiel für eine immersive AR-Sitzung
Der Code in diesem Artikel basiert auf dem Hit-Test-Beispiel der Immersive Web Working Group (Demo, Quelle), ist aber nicht identisch damit. In diesem Beispiel können Sie virtuelle Sonnenblumen auf Oberflächen in der realen Welt platzieren.
Wenn Sie die App zum ersten Mal öffnen, sehen Sie 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. Sie bewegt sich, wenn Sie das Gerät bewegen. Wenn Schnittpunkte gefunden werden, rastet das Tool an Oberflächen wie Böden, Tischplatten und Wänden ein. Das liegt daran, dass beim Hit-Testen die Position und Ausrichtung des Schnittpunkts ermittelt werden, aber nichts über die Oberflächen selbst.
Dieser Kreis wird als Fadenkreuz bezeichnet. Es ist ein temporäres Bild, das beim Platzieren eines Objekts in der Augmented Reality hilft. Wenn Sie auf das Display tippen, wird eine Sonnenblume an der Stelle des Fadenkreuzes und in der Ausrichtung des Fadenkreuzpunkts 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 Fadenkreuzbild selbst erstellen, da es nicht vom Browser oder der API bereitgestellt wird. Die Methode zum Laden und Zeichnen ist frameworkspezifisch.
Wenn Sie es nicht direkt mit WebGL oder WebGL2 zeichnen, sehen Sie in der Dokumentation Ihres Frameworks nach. Aus diesem Grund gehe ich nicht näher darauf ein, wie das Fadenkreuz im Beispiel gezeichnet wird. Unten sehen Sie eine Zeile davon. Das hat nur einen Grund: Damit 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 requiredFeatures-Array anfordern, wie unten dargestellt.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
Sitzung eingeben
In früheren Artikeln habe ich Code zum Starten einer XR-Sitzung vorgestellt. Unten sehen Sie eine Version mit einigen Ergänzungen. Zuerst habe ich den select-Event-Listener hinzugefügt. Wenn der Nutzer auf das Display tippt, wird eine Blume in der Kameraansicht platziert, die auf der Position des Fadenkreuzes basiert. 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 Referenzräume
Beachten Sie, dass der hervorgehobene Code XRSession.requestReferenceSpace() zweimal aufruft. Anfangs fand ich das verwirrend. Ich habe gefragt, warum im Hit-Test-Code kein Animationsframe angefordert wird (wodurch die Frame-Schleife gestartet wird) und warum die Frame-Schleife keine Hit-Tests zu enthalten scheint. Die Verwirrung entstand durch ein Missverständnis von Referenzräumen. Referenzräume stellen Beziehungen zwischen einem Ursprung und der Welt dar.
Um zu verstehen, was dieser Code macht, stell dir vor, du siehst dir dieses Beispiel mit einem eigenständigen Rig an und hast sowohl ein Headset als auch einen Controller. Wenn Sie Entfernungen vom Controller aus messen möchten, verwenden Sie ein controllerzentriertes Bezugssystem. Wenn Sie etwas auf dem Bildschirm zeichnen möchten, verwenden Sie nutzerzentrierte Koordinaten.
In diesem Beispiel sind das Wiedergabegerät und das Steuergerät dasselbe Gerät. Aber ich habe ein Problem. Was ich zeichne, muss stabil in Bezug auf die Umgebung 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 mit dem Aufruf von requestAnimationFrame().
Für Hit-Tests verwende ich den viewer-Referenzraum, der auf der Position des Geräts zum Zeitpunkt des Hit-Tests basiert. Das Label „Zuschauer“ ist in diesem Zusammenhang etwas verwirrend, da es sich um einen Controller handelt. Das ist sinnvoll, wenn Sie sich den Controller als elektronisches Anzeigegerät vorstellen. Danach rufe ich xrSession.requestHitTestSource() auf, um die Quelle der Hit-Testdaten zu erstellen, die ich beim Zeichnen verwenden werde.
Frame-Schleife ausführen
Der requestAnimationFrame()-Callback erhält auch neuen Code für das Treffertestverfahren.
Wenn Sie Ihr Gerät bewegen, muss sich das Fadenkreuz mitbewegen, während es nach Oberflächen sucht. Um die Illusion von Bewegung zu erzeugen, muss das Fadenkreuz in jedem Frame neu gezeichnet werden.
Das Fadenkreuz soll aber nicht angezeigt werden, wenn der Treffertest fehlschlägt. Für das Fadenkreuz, das ich zuvor erstellt habe, lege ich die Eigenschaft visible auf false fest.
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
}
Damit ich etwas in AR zeichnen kann, muss ich wissen, wo sich der Zuschauer befindet und wohin er schaut. Ich prüfe 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
}
Ich rufe jetzt getHitTestResults() an. Sie verwendet hitTestSource als Argument und gibt ein Array von HitTestResult-Instanzen zurück. Beim Hit-Test werden möglicherweise mehrere Oberflächen gefunden. Der erste im Array ist der, der der Kamera am nächsten ist.
In den meisten Fällen wird es verwendet, aber für erweiterte Anwendungsfälle wird ein Array zurückgegeben. Stellen Sie sich beispielsweise vor, Ihre Kamera ist auf einen Karton auf einem Tisch gerichtet. Möglicherweise werden beim Hit-Test alle drei Oberflächen im Array zurückgegeben. In den meisten Fällen ist es das Feld, das mich interessiert. Wenn die Länge des zurückgegebenen Arrays 0 ist, d. h., wenn kein Treffertest zurückgegeben wird, fahren Sie 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 des Hit-Tests verarbeiten. Das ist der grundlegende Prozess. Rufen Sie eine Position aus dem Treffertestergebnis ab, verschieben Sie das Fadenkreuzbild an die Position des Treffertests und legen Sie die visible-Eigenschaft auf „true“ fest. Die Pose stellt die Pose eines Punktes 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-Event-Handler hinzugefügt. Siehe oben.
Wichtig ist in diesem Schritt, dass Sie wissen, wo Sie es platzieren müssen. Da das sich bewegende Fadenkreuz eine konstante Quelle für Treffertests ist, ist es am einfachsten, ein Objekt an der Position des Fadenkreuzes beim letzten Treffertest zu platzieren.
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 sehen Sie sich den Beispielcode an oder probieren das Codelab aus. Ich hoffe, ich habe Ihnen genug Hintergrundinformationen gegeben, um beides nachvollziehen zu können.
Wir sind noch lange nicht fertig mit der Entwicklung immersiver Web-APIs. Wir werden hier neue Artikel veröffentlichen, sobald es Neuigkeiten gibt.
Foto von Daniel Frank auf Unsplash