AI + XR Integration Patterns
XRAIThe convergence of AI and XR is not a future trend — it's happening now. Vision-language models (VLMs) can see what your headset sees. LLMs can power NPCs that hold real conversations. Neural networks can reconstruct 3D scenes from sparse camera input.
This article catalogs production-ready integration patterns you can build today with WebXR, browser-based ML, and self-hosted AI infrastructure.
Pattern 1: VLM-Powered Scene Understanding
What it solves
Your XR app needs to understand the physical world — not just track planes and anchors, but recognize objects, read text, identify hazards, or describe a scene to a visually impaired user.
Architecture
WebXR Camera → Canvas Capture → VLM (API or local) → JSON structured output → XR overlay
Implementation
// Capture the WebXR camera feed as a frame
function captureFrame(xrFrame, referenceSpace, gl) {
const viewerPose = xrFrame.getViewerPose(referenceSpace);
if (!viewerPose) return null;
const canvas = document.createElement('canvas');
canvas.width = gl.drawingBufferWidth;
canvas.height = gl.drawingBufferHeight;
// Read back WebXR layer pixels (requires WEBGL_draw_pixels or copyTexImage2D)
// This is simplified — production code uses XRWebGLBinding or WebGPU
const pixels = new Uint8Array(canvas.width * canvas.height * 4);
gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
// Send to VLM (e.g., Gemini Pro Vision, Claude 3.5 Sonnet, or local Florence-2)
return describeScene(pixels, canvas.width, canvas.height);
}
async function describeScene(pixels, width, height) {
const response = await fetch('/api/ai/describe-scene', {
method: 'POST',
body: JSON.stringify({
image: arrayBufferToBase64(pixels),
width,
height,
prompt: 'List all objects, their positions, and any readable text in this scene.'
})
});
return response.json(); // e.g., [{object: "chair", position: [1.2, 0, -0.5]}, ...]
}
When to use on-device vs cloud
| Factor | On-device (WebNN/WASM) | Cloud API |
|---|---|---|
| Latency | 50-200ms | 500-3000ms |
| Privacy | Full — data never leaves | Requires trust |
| Model quality | Smaller, less capable | Frontier models |
| Cost | Fixed (compute) | Per-token |
| Offline capable | Yes | No |
Recommendation: Use on-device for real-time spatial understanding (object detection, plane classification) and cloud VLMs for complex queries ("what does this sign say?" or "find my keys").
Real-world example
Aria glasses use on-device SLAM + cloud VLM for contextual AI assistance. The same pattern works with Meta Quest Pro, Apple Vision Pro, or any WebXR-capable device with a camera pass-through.
Pattern 2: Spatial AI Assistants
What it solves
Virtual assistants today live in a chat window. In XR, they should live in the space — anchored to a real table, pointing at real objects, aware of their physical surroundings.
Architecture
Spatial Anchor → LLM Context (scene description + user query) → LLM Response → TTS + Spatial Audio + Animated Avatar
Implementation
// Create an AI assistant anchored to a real-world location
const assistantAnchor = await xrFrame.createAnchor(
new XRRigidTransform(
{ x: 1.5, y: 1.2, z: -2.0 }, // position in room space
{ x: 0, y: 0, z: 0, w: 1 } // facing user
),
referenceSpace
);
// Build context from the current scene understanding
const sceneContext = await getCurrentSceneDescription();
const userQuery = "What's that device on my desk?";
const response = await fetch('/api/ai/assistant', {
method: 'POST',
body: JSON.stringify({
query: userQuery,
context: {
scene: sceneContext,
user_position: userPose.transform.position,
assistant_position: assistantAnchor.position,
conversation_history: recentMessages
}
})
});
// Render the assistant's response as speech + text
speakText(response.answer);
showSpeechBubble(response.answer, assistantAnchor);
Key design decisions
-
Spatial memory — the assistant should remember where objects are between sessions. Use a spatial database (e.g., PostgreSQL + PostGIS, or a lightweight R-tree) to persist anchor positions and their semantic labels.
-
Gaze awareness — track what the user is looking at using WebXR gaze or hand tracking. Pass the gazed-at object into the LLM context so the assistant can answer "what's that?" without explicit pointing.
-
Turn-taking — use WebXR microphone access with voice activity detection (VAD). Don't make users press a button to speak in VR.
// Gaze-aware query
function getGazedObject(frame, referenceSpace) {
const gazeRay = frame.getJointPose?.(xrFrame, 'gaze') ||
frame.getViewerPose(referenceSpace)?.transform;
// Raycast into your spatial scene graph
return scene.raycast(gazeRay);
}
Pattern 3: Neural Rendering and Scene Reconstruction
What it solves
High-quality 3D reconstruction from sparse camera input enables persistent AR — objects stay in the same place even when you leave the room and come back.
The stack
- Instant NGP / 3D Gaussian Splatting — reconstruct a scene from 2-5 minutes of video
- WebGPU compute shaders — render splats in real-time on device
- SLAM + NeRF fusion — combine real-time tracking with neural scene representation
async function reconstructScene(videoFrames) {
// Upload frames to reconstruction server (or run on-device with WebNN)
const scene = await fetch('/api/xr/reconstruct', {
method: 'POST',
body: videoFrames // MP4 or frame sequence
});
// Receive back a 3D Gaussian splat scene representation
const splatData = await scene.arrayBuffer();
// Render with WebGPU
const renderer = new GaussianSplatRenderer(device, context);
renderer.uploadSplats(splatData);
renderer.render(viewMatrix, projectionMatrix);
}
Self-hosted inference
For production use, run reconstruction on your own GPU infrastructure:
# Docker Compose for neural rendering
services:
gaussian-splatting:
image: ghcr.io/graphdeco-inria/gaussian-splatting:latest
ports:
- "7860:7860"
volumes:
- ./output:/output
- ./input:/input
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
Pattern 4: Voice-First XR Interfaces
What it solves
Typing is terrible in VR. A voice interface with NLP parsing is the natural input modality for immersive environments.
Implementation
// Web speech API + LLM intent parsing
const recognition = new webkitSpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.onresult = async (event) => {
const transcript = event.results[event.results.length - 1][0].transcript;
// Parse intent with a lightweight NLU model or LLM
const intent = await fetch('/api/ai/parse-intent', {
method: 'POST',
body: JSON.stringify({ text: transcript, context: currentSpatialContext })
});
// Execute spatial commands
switch (intent.action) {
case 'place_object':
placeObject(intent.object, userGazePosition);
break;
case 'resize':
resizeSelectedObject(intent.scale);
break;
case 'query':
showInformationPanel(intent.query, userGazePosition);
break;
}
};
Putting It All Together: A Complete XR + AI Stack
┌─────────────────────────────────────────────┐
│ WebXR Application │
├─────────────────────────────────────────────┤
│ Scene │ AI │ Spatial │ Voice │
│ Renderer │ Agent │ Assisant │ UI │
├─────────────────────────────────────────────┤
│ WebGPU / Three.js │
│ WebXR API (AR/VR session) │
│ MediaDevices (camera + mic) │
├─────────────────────────────────────────────┤
│ On-device (WebNN) │ Cloud / Self-hosted │
│ - Object detection │ - VLM scene desc. │
│ - Voice activity │ - LLM conversation │
│ - Hand tracking │ - 3D reconstruction │
│ - Splat rendering │ - TTS generation │
└─────────────────────────────────────────────┘
Complete Demo: Spatial Object Labeler
Below is a self-contained HTML page that combines WebXR, on-device object detection, and LLM-powered labeling. It captures the XR camera frame, runs a local ML model via WebNN (or falls back to a mock), and renders labeled bounding boxes in 3D space.
<!DOCTYPE html>
<html>
<head>
<title>Spatial Object Labeler — XR + AI Demo</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.min.js"></script>
</head>
<body>
<script>
// ─── WebXR Setup ─────────────────────────────────────
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.xr.enabled = true;
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
// ─── AI Integration ──────────────────────────────────
async function detectObjects(frame, referenceSpace) {
const viewerPose = frame.getViewerPose(referenceSpace);
if (!viewerPose) return [];
// Capture a canvas frame (simplified — production uses XRWebGLBinding)
const canvas = document.createElement('canvas');
canvas.width = 640; canvas.height = 480;
// Mock detections for demo. In production, replace with a call to
// a self-hosted VLM endpoint (e.g., Ollama + LLaVA, vLLM + Pixtral):
//
// const response = await fetch('/api/ai/detect-objects', {
// method: 'POST',
// body: JSON.stringify({ image: canvas.toDataURL('image/jpeg', 0.8) })
// });
// return await response.json();
return [
{ label: 'chair', confidence: 0.92, x: 0.5, y: -0.3, z: -1.2 },
{ label: 'table', confidence: 0.88, x: 0.8, y: 0.0, z: -1.5 },
{ label: 'laptop', confidence: 0.76, x: 0.3, y: 0.4, z: -0.8 }
];
}
// ─── Render Detections ───────────────────────────────
function renderDetections(objects) {
// Clear previous labels
scene.children.filter(c => c.userData?.isLabel).forEach(c => scene.remove(c));
for (const obj of objects) {
const geometry = new THREE.SphereGeometry(0.05, 8, 8);
const material = new THREE.MeshBasicMaterial({
color: obj.confidence > 0.8 ? 0x00ff00 : 0xffaa00
});
const sphere = new THREE.Mesh(geometry, material);
sphere.position.set(obj.x, obj.y, obj.z);
sphere.userData.isLabel = true;
scene.add(sphere);
// Text label (using sprite)
const canvas2 = document.createElement('canvas');
canvas2.width = 256; canvas2.height = 64;
const ctx = canvas2.getContext('2d');
ctx.fillStyle = 'rgba(0,0,0,0.7)';
ctx.fillRect(0, 0, 256, 64);
ctx.fillStyle = 'white';
ctx.font = '24px monospace';
ctx.fillText(`${obj.label} (${(obj.confidence*100).toFixed(0)}%)`, 16, 44);
const texture = new THREE.CanvasTexture(canvas2);
const spriteMat = new THREE.SpriteMaterial({ map: texture });
const sprite = new THREE.Sprite(spriteMat);
sprite.position.set(obj.x, obj.y + 0.15, obj.z);
sprite.scale.set(0.4, 0.1, 1);
sprite.userData.isLabel = true;
scene.add(sprite);
}
}
// ─── Session Loop ────────────────────────────────────
const session = await navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local']
});
renderer.xr.setSession(session);
renderer.setAnimationLoop((timestamp, frame) => {
if (frame) {
const objects = await detectObjects(frame, renderer.xr.getReferenceSpace());
renderDetections(objects);
}
renderer.render(scene, camera);
});
</script>
</body>
</html>
To run this yourself:
- Serve the page over HTTPS (required for WebXR)
- Deploy a VLM endpoint at
/api/ai/detect-objects— or let the mock run - Open on a WebXR-capable device with AR passthrough
Next Steps
To build on these patterns in your own projects:
- Start with on-device ML — WebNN and ONNX Runtime Web give you real-time inference without server latency. Good for object detection, hand tracking classification, and voice activity detection.
- Add a cloud VLM for complex queries — only send structured scene data to the cloud, never raw camera frames. This keeps latency acceptable and privacy intact.
- Build the spatial assistant incrementally — start with anchor placement and a simple LLM call. Add gaze awareness and spatial memory once the base is working.
- Measure everything — WebXR performance is fragile. Profile frame times, ML inference latency, and network round-trips. A dropped frame in VR == motion sickness.
- Self-host your AI stack — running Ollama + vLLM on your own GPU gives you predictable costs and full data control. See Self-Hosted AI Stack for setup.
Privacy Considerations
XR + AI is the most sensor-rich computing paradigm we've built. Cameras see everything. Microphones hear everything. Models run everywhere.
- Process raw sensor data on-device — only send structured, anonymized data to cloud models
- User consent per capability — camera on/off, mic on/off, scene capture on/off — each independently controllable
- Data retention policy — don't store raw frames. Store structured scene graphs.
- Self-host your AI stack — keep inference on your own GPU. The patterns above work with Ollama, vLLM, and local VLMs.
Further Reading
- WebXR Hand Tracking — foundation for gesture-driven AI interactions
- WebXR Depth Sensing — spatial understanding prerequisites
- Self-Hosted AI Stack — run your own inference infrastructure