Урок · Середній · ~40 хв
AR у браузерах: виявлення площин і hit-тестування
У Chrome на Android WebXR надає доступ до потоку камери, SLAM-трекінгу та виявлення площин — без встановлення застосунку. Hit-тестування пускає промінь від центру екрана й повідомляє про перетин із виявленими поверхнями, дозволяючи вам розміщувати віртуальні об'єкти на реальних підлогах і столах.
1Запит сесії immersive-ar
const supported = await
navigator.xr?.isSessionSupported('immersive-ar'); if (!supported) {
document.getElementById('ar-btn').textContent = 'AR not available';
return; } let xrSession, hitTestSource, refSpace;
document.getElementById('ar-btn').addEventListener('click', async ()
=> { xrSession = await navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['hit-test'], optionalFeatures: ['plane-detection',
'dom-overlay'], domOverlay: { root: document.getElementById('overlay')
}, }); await renderer.xr.setSession(xrSession); // Налаштовуємо опорний
простір (viewer = гарнітура, local = світ) refSpace = await
xrSession.requestReferenceSpace('local'); // Запитуємо джерело hit-тесту
відносно погляду глядача const viewerRef = await
xrSession.requestReferenceSpace('viewer'); hitTestSource = await
xrSession.requestHitTestSource({ space: viewerRef });
xrSession.addEventListener('end', cleanup); });
Функція dom-overlay дозволяє накладати HTML
(кнопки, рахунки, підписи) поверх AR-зображення — необхідна для
охайного інтерфейсу AR.
2Прозоре тло для AR
import * as THREE from
'https://cdn.jsdelivr.net/npm/three@0.160/build/three.module.js';
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha:
true, // прозоре полотно, щоб камера просвічувала });
renderer.setPixelRatio(devicePixelRatio); renderer.xr.enabled = true;
// Тло сцени має бути null (прозоре) — не чорне const scene
= new THREE.Scene(); scene.background = null; // Світло, що працює в
AR (HemisphereLight наближено імітує реальне середовище) scene.add(new
THREE.HemisphereLight(0xffffff, 0xbbbbff, 1)); const dirLight = new
THREE.DirectionalLight(0xffffff, 0.8); dirLight.position.set(0.5, 1,
0.866); scene.add(dirLight); const camera = new
THREE.PerspectiveCamera();
3Hit-тестування від центру екрана
Джерело hit-тесту пускає промінь у напрямку погляду глядача. У циклі рендерингу перевіряйте результати перетину:
// Приціл — показує, де приземлиться об'єкт const reticleGeo = new
THREE.RingGeometry(0.05, 0.07, 32).rotateX(-Math.PI / 2); const
reticle = new THREE.Mesh(reticleGeo, new THREE.MeshBasicMaterial({
color: 0x22c55e })); reticle.visible = false; reticle.matrixAutoUpdate
= false; scene.add(reticle); renderer.setAnimationLoop((time, frame)
=> { if (frame && hitTestSource) { const results =
frame.getHitTestResults(hitTestSource); if (results.length > 0) {
const hit = results[0]; const pose = hit.getPose(refSpace);
reticle.visible = true;
reticle.matrix.fromArray(pose.transform.matrix); } else {
reticle.visible = false; } } renderer.render(scene, camera); });
4Розміщення об'єкта по дотику
xrSession.addEventListener('select', () => { if (!reticle.visible)
return; // Клонуємо матрицю прицілу, щоб розмістити об'єкт у точці
влучання const pos = new THREE.Vector3(); const quat = new
THREE.Quaternion(); const scl = new THREE.Vector3();
reticle.matrix.decompose(pos, quat, scl); const obj = new THREE.Mesh(
new THREE.BoxGeometry(0.1, 0.1, 0.1), new THREE.MeshStandardMaterial({
color: 0xff4444 }) ); obj.position.copy(pos);
obj.quaternion.copy(quat); scene.add(obj); // Необов'язково: відтворити
тактильний клік const gp = xrSession.inputSources[0]?.gamepad;
gp?.hapticActuators?.[0]?.pulse(0.3, 30); });
5Виявлення та візуалізація площин
// Виявляємо знайдені площини й малюємо напівпрозоре накладання const
planeMap = new Map(); // XRPlane → THREE.Mesh
renderer.setAnimationLoop((time, frame) => { if (!frame) return; //
detectedPlanes — це Set об'єктів XRPlane (потребує
'plane-detection') const planes = frame.detectedPlanes ?? new Set();
for (const plane of planes) { const pose =
frame.getPose(plane.planeSpace, refSpace); if (!pose) continue; if
(!planeMap.has(plane)) { // Будуємо полігональний меш із точок межі
площини const pts = plane.polygon; // Масив {x,y,z} у просторі
площини const shape = new THREE.Shape(pts.map(p => new
THREE.Vector2(p.x, p.z))); const geo = new THREE.ShapeGeometry(shape);
const mat = new THREE.MeshBasicMaterial({ color: 0x22c55e, side:
THREE.DoubleSide, transparent: true, opacity: 0.2, }); const mesh =
new THREE.Mesh(geo, mat); mesh.rotation.x = -Math.PI / 2;
scene.add(mesh); planeMap.set(plane, mesh); } const mesh =
planeMap.get(plane); mesh.matrix.fromArray(pose.transform.matrix);
mesh.matrixAutoUpdate = false; } renderer.render(scene, camera); });
Виявлення площин підтримується у Chrome на Android з ARCore. На iOS
Safari підтримує WebXR AR через прапорець функції
worldSensing,
доступний у Safari 17+.