Core and initialization
XR Blocks runs one engine singleton. Application code imports the public module,
registers scripts and scene objects, then calls xb.init(options) once.
import * as xb from 'xrblocks';
const options = new xb.Options();
xb.add(new MainScript());
await xb.init(options);
The active singleton is available as xb.core. Normal applications do not
construct another Core, renderer, camera, animation loop, input resolver, UI
renderer, or WebXR session manager.
Engine ownership
Core owns:
- the public scene, camera, renderer, and frame loop;
- WebXR session entry and exit;
- script discovery and lifecycle calls;
- input sampling and interaction resolution;
- built-in UI rendering;
- enabled world, depth, physics, sound, context, AI, and simulator subsystems;
- subsystem initialization and disposal order.
Put application behavior in Script objects. Register them before
initialization. Engine-created objects can be undefined in a constructor, so use
them in or after init().
Configure before initialization
Options is the application configuration boundary:
const options = new xb.Options();
options.enableHands();
options.enableReticles();
options.enablePlaneDetection();
xb.add(new MainScript());
await xb.init(options);
Configure optional dependencies, permissions, WebXR session features, and
subsystem options before xb.init(options). Changing a configuration object
after initialization does not reconstruct the runtime automatically.
Use the feature's manual page for exact prerequisites. There is no general rule
that every subsystem has an enable*() method. For example, Rapier is assigned
to options.physics.RAPIER, while built-in UI starts automatically when a UI
root enters the scene.
Instead of accessing singletons or looking up dependencies inside constructor code, scripts declare their requirements as static properties. During script initialization, the Registry automatically resolves them.
- Specify dependencies by defining a static
dependenciesdictionary mapping local keys to constructor classes. - The manager will automatically query those classes and inject the resolved instances as an object parameter inside your script's
init()method.
Permissions before immersive XR
Browser permission prompts can be restricted after an immersive session starts. Declare required permissions before initialization:
const options = new xb.Options();
options.permissions.camera = true;
options.permissions.microphone = true;
await xb.init(options);
Top-level helpers such as enableCamera(), enableHumanDetection(), and
enableFaceDetection() set their documented permission prerequisites. Verify
each helper instead of assuming that a nested option does the same work.
The Enter XR flow requests declared browser permissions from the flat page before session entry. Applications must still present denied, unsupported, and session-start failure states.
Public singleton access
Use the narrowest public facade needed by the application:
xb.scene;
xb.user;
xb.world;
xb.depth;
xb.context;
xb.ai;
xb.sound;
xb.input;
xb.camera;
Some advanced public services remain under xb.core, such as the screenshot
synthesizer and wait-frame utility:
The ScreenshotSynthesizer captures visual snapshots of the 3D scene (with optional overlay on top of the physical camera texture in AR). This is useful for capturing camera frames to feed into visual generative AI queries.
const dataUrl = await xb.core.screenshotSynthesizer.getScreenshot(true);
await xb.core.waitFrame.waitFrame();
Guard optional runtime services. An exported type or constructor does not prove that its subsystem is enabled in the current runtime.
Construction, injection, and type imports
These are separate uses of a class name:
| Use | Meaning |
|---|---|
xb.core | Access the one active engine singleton |
new xb.ModelViewer() | Construct a public application-owned scene object or helper |
static dependencies = {world: xb.World} | Ask the engine registry to inject an enabled runtime service into a script |
import type {SelectEvent} from 'xrblocks' | Use a TypeScript declaration only; no runtime object is constructed |
Dependency injection is useful for reusable scripts that need explicit, testable runtime services:
class WorldAwareScript extends xb.Script {
static dependencies = {world: xb.World};
init({world}: {world: xb.World}) {
this.world = world;
}
}
Ordinary applications can use the public singleton facades. SDK source should prefer declared dependencies when a subsystem relationship must be testable. The registry itself is engine architecture, not an application service locator for guessing internal components.
XR rendering effects
XREffects is the engine-owned post-processing
pipeline. It renders the scene to intermediate targets, runs registered
XRPass objects in order, and writes the final image to
the active XR or simulator target. Normal applications do not construct an
XREffects instance or replace the engine renderer.
Post-processing is off by default. Enable it before initialization:
const options = new xb.Options();
options.usePostprocessing = true;
xb.add(new MainScript());
await xb.init(options);
When enabled, the engine constructs xb.core.effects before it initializes
application scripts. A script can therefore register a pass in init():
class MainScript extends xb.Script {
init() {
const effects = xb.core.effects;
if (!effects) {
throw new Error('This application requires post-processing.');
}
this.depthPass = new DepthVisualizationPass();
effects.addPass(this.depthPass);
}
update() {
this.depthPass.updateEnvironmentalDepthTexture(xb.depth);
}
}
This is the setup pattern used by the
Depth Map sample. Register passes once during
initialization, not once per frame. addPass() appends a pass, so registration
order is also rendering order.
Render flow
With post-processing enabled, one frame follows this path:
scene and camera
-> scene color + depth render target
-> XRPass 1
-> XRPass 2
-> ...
-> active XR framebuffer or simulator canvas
In an immersive XR session, XREffects renders each XR camera separately. It
calls each pass with a viewId, normally 0 for the first eye and 1 for the
second. In the desktop simulator, it calls the same pass interface with
viewId = 0. A pass can use the identifier to choose an eye-specific depth or
camera texture without creating a separate pipeline.
The XRPass.render() arguments follow the three.js post-processing convention:
render(
renderer,
writeBuffer,
readBuffer,
deltaTime,
maskActive,
viewId
) {
this.uniforms.tDiffuse.value = readBuffer.texture;
this.uniforms.uView.value = viewId;
renderer.setRenderTarget(writeBuffer);
this.fullScreenQuad.render(renderer);
}
readBuffercontains the scene or the preceding pass output.writeBufferis the next intermediate target, or the final active target for the last pass.deltaTimeis the elapsed frame time in seconds.viewIdselects the current XR view.maskActiveis provided for compatibility with the pass interface.
The engine resizes and reuses its intermediate color and depth targets when the drawing-buffer dimensions change. It also updates the public camera from the headset pose before scripts and view-space UI run, because the effects pipeline renders XR eyes manually.
Pass ownership and limits
XREffects owns registered passes after addPass(). During engine disposal it
disposes the intermediate render targets and calls dispose() on each pass.
Custom passes must release their shader materials, full-screen quads, textures,
and other GPU resources from dispose().
There is no pass-removal API. To turn an effect on and off at runtime, keep the
pass registered and change a uniform or an enabled field implemented by the
pass. The Depth Map sample follows this pattern by changing its visualization
opacity.
Do not enable usePostprocessing for an immersive XR application without
registering a pass. The XR pipeline expects at least one pass to copy or
transform the offscreen scene into the active framebuffer. Applications that
do not need image-space effects should leave post-processing disabled and let
the engine render the scene directly.
AR and VR background transitions
XRTransition creates a smooth visual change between transparent passthrough
and a colored virtual background while the application continues to run. It is
an optional engine component, accessed as xb.core.transition. Applications do
not construct it directly.
Enable and configure it before initialization:
const options = new xb.Options().enableXRTransitions();
options.transition.transitionTime = 0.75;
options.transition.defaultBackgroundColor = 0x101828;
xb.add(new MainScript());
await xb.init(options);
| Option | Default | Meaning |
|---|---|---|
transition.enabled | false | Whether the engine creates the component |
transition.transitionTime | 0.5 | Fade duration in seconds |
transition.defaultBackgroundColor | 0xffffff | Color used by toVR() when none is supplied |
The component starts in AR mode with a transparent background. Call toVR()
to fade toward a colored background and toAR() to fade back to passthrough:
class MainScript extends xb.Script {
onSelectEnd() {
const transition = xb.core.transition;
if (!transition) return;
if (transition.currentMode === 'AR') {
transition.toVR({color: 0x101828});
} else {
transition.toAR();
}
}
}
See the complete interactive pattern in the XR Modes template.
toVR() also accepts targetAlpha:
xb.core.transition?.toVR({
color: 0x172554,
targetAlpha: 0.85,
});
targetAlpha is clamped to the range 0 through 1. Use 1 for an opaque
background or a lower value to keep some passthrough visible. Calling toVR()
without a color uses defaultBackgroundColor. Calling toAR() always targets
zero opacity.
Internally, the component keeps a back-facing sphere centered on the active camera and interpolates its material opacity each frame. Virtual scene content continues to render in front of this background. The component does not receive pointer events, so it does not block ray or direct-touch interaction.
currentMode changes to 'AR' or 'VR' when a transition starts. It reports
the requested target mode, not whether the fade has finished. A new toAR() or
toVR() call can reverse an in-progress fade from its current opacity.
Visual mode is not WebXR session mode
XRTransition does not start or end a WebXR session, and it does not replace an
immersive-ar session with an immersive-vr session. It changes the visual
background inside the current application session.
Use options.enableVR() before xb.init() when the application must request an
immersive-vr session instead of the default immersive-ar session:
const options = new xb.Options();
options.enableVR();
await xb.init(options);
enableVR() and enableXRTransitions() solve separate problems and neither
enables the other. The transition component also does not require
usePostprocessing.
Do not confuse the transition APIs
| API | Owner | What changes |
|---|---|---|
xb.core.effects | Engine render pipeline | The final rendered pixels through XRPass objects |
xb.core.transition | Optional engine script | Passthrough-to-color background opacity |
VisibilityTransition | Application scene object | One parent object's scale and visibility |
Use VisibilityTransition when one panel,
model, or other object must animate in or out. It does not change the XR
background or add a post-processing pass.
Simulator and automation startup
new xb.Options() defaults to automatic form-factor selection. Use
?formFactor=desktop to force the simulator. Use
options.enableAutomationMode() or ?xrAutomation=1 for the simulator and
context preset used by external automation. ?debug=1 separately exposes
window.xb and window.xbReady to an in-page driver.
Read Simulator for modes, manifests, synthetic sensors, and handoff limits.