Inline-3D web samples and a small JS SDK for the
DisplayXR Browser — the DisplayXR analog of
immersive-web/webxr-samples. This is the canonical
repo web developers clone to build glasses-free 3D pages, and the site the browser navigates to for the
live demos.
▶ See it live: https://displayxr.github.io/displayxr-web/ — open in the DisplayXR Browser on DisplayXR hardware for glasses-free 3D; in any other browser the pages render as a normal 2D fallback, so they're safe to view anywhere.
The SDK is published as @displayxr/inline3d
(dependency-free ESM, ships its own TypeScript types):
npm install @displayxr/inline3dimport { createInline3D } from '@displayxr/inline3d';
import { EyeCamera, EdgeFeather } from '@displayxr/inline3d/three'; // optional three.js glue
import { addSplat } from '@displayxr/inline3d/splat'; // experimental: 3DGS in a tile
import { addModel } from '@displayxr/inline3d/model'; // experimental: glTF/GLB in a tile
// (Draco / meshopt / KTX2 too —
// you serve the decoder files)
import { SceneViewer } from '@displayxr/inline3d/viewer'; // experimental: framing + orbitNo build step or bundler required — it's plain ES modules. You can also import a pinned version by
URL from a CDN (jsDelivr / unpkg) without npm. The samples in this repo import the SDK by relative
path (./js/inline3d.js) so they run straight off GitHub Pages; in your own app prefer the package.
three and @sparkjsdev/spark are optional peer dependencies — the core is dependency-free
and only the /three, /viewer and /splat subpaths need them. The two viewer subpaths are
experimental: they turn "one object in a tile, look around it, drag to spin" into a single
call (auto-framing on the zero-disparity plane, orbit, idle turntable, mono fallback), but their
API is not yet covered by the semver promise below.
Stability & what's covered by semver (and the deferred N-view / web-components / CSS-native roadmap
that is intentionally not in 1.0): docs/sdk-stability.md.
One SDK call turns a <canvas> into a glasses-free-3D window. Everything degrades to plain 2D on a
non-DisplayXR browser, so a page is safe to ship anywhere.
import { createInline3D } from '@displayxr/inline3d';
const wall = await createInline3D(); // opens an inline-3d session (detects support)
if (!wall.supported) {
// Not the DisplayXR Browser (or no 3D display) — your page's normal 2D content shows. Done.
} else {
// Woven, glasses-free 3D. Add content — one call per element:
wall.addImage(canvas, 'photo-sbs.png'); // a still side-by-side 3D photo
wall.addVideo(canvas, videoEl); // an SBS 3D video
wall.addScene(canvas, (views, layer) => { /* render */ });// a live three.js / WebGL stereo scene
}The browser weaves each element's stereo pair at its on-screen rect; the surrounding DOM stays flat. The runtime batches every visible window into one weave per frame, so it scales to a wall of elements.
Detection: call
createInline3D()and checkwall.supported— do not gate onnavigator.xr.isSessionSupported('inline-3d'). That async probe resolvesfalseif it runs before the OS weave service has bound (typically at page load), a false-negative that silently drops you to 2D.createInline3D()detects by actually acquiring a session, which is authoritative.
Full API + authoring guidance: docs/authoring-inline-3d.md.
Three.js glue (an off-axis EyeCamera) in js/inline3d-three.js.
index.html landing (Pages entry point)
samples/
windows/ mixed 3D windows — still photos + a live video + a real-time three.js scene,
each woven with one SDK call, all on one session
splat/ a 3D Gaussian splat in a tile, auto-framed, with a 2D price plate over it
model/ a glTF mesh, a mesh+splat scene, and a Draco-COMPRESSED glTF in three tiles
composition/ the 14-case 2D/3D overlap matrix — demo AND standing hardware regression
surface; red cases ship red (see samples/README.md)
vendor/draco/ three's Draco decoder, served for samples/model (compressed glTF needs it)
js/
inline3d.js the SDK: createInline3D() → { addImage, addVideo, addScene }, feature-detect,
SBS buffer management, and a lazy create/close lifecycle for many windows
inline3d-three.js optional three.js helper (EyeCamera: off-axis projection from the session's eyes)
inline3d-viewer.js experimental: SceneViewer — framing, orbit, idle turntable, mono fallback
inline3d-splat.js experimental: addSplat() — a Gaussian splat window via Spark
inline3d-model.js experimental: addModel() — a glTF/GLB window; wires Draco / meshopt / KTX2
from what the asset declares (you serve the decoder files — see the guide)
docs/
authoring-inline-3d.md the authoring guide
If you want the raw WebXR surface the SDK wraps, an inline-3d element:
const session = await navigator.xr.requestSession('inline-3d')— a sensorless inline session (feature-detect by whether this resolves; falls back to plain 2D).const layer = new XRDisplayLayer(session, canvas)— binds the weave to that element.- Each XR frame: render the scene as a side-by-side stereo pair into the canvas, re-projected off-axis (asymmetric-frustum / Kooima) from the eye positions the session reports that frame — so moving your head looks around the 3D content.
See the WebXR inline-3D explainer.
Any static server, e.g. python -m http.server 8080, then open http://localhost:8080/.
(Loading over file:// is fine for pure-2D, but WebXR requires a secure context — use
http://localhost or https://.)