Урок · Середній · ~40 хв
WebXR · AR · Hit-тестування · Виявлення площин

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+.