How to Prepare a GLB for WebXR
Follow a concrete GLB-to-WebXR release procedure covering real-world scale, unrestricted views, stereo budgets, placement, interaction proxies, compression, and headset testing.
A GLB that works in a desktop orbit viewer is not automatically ready for WebXR. Immersive sessions add real-world scale, stereo rendering, unrestricted head movement, close inspection, mobile-class memory, tracking-sensitive frame deadlines, and interaction volumes. Preparing the file means validating those conditions before it reaches the session.
This guide is a release procedure for one asset. Preserve the source, create staged outputs, and test the exact GLB with the exact WebXR application.
Write the XR acceptance profile#
Record whether the experience supports immersive-ar, immersive-vr, inline preview, or a combination. List browsers, headsets, phones, refresh rates, lowest device tier, renderer version, and extensions configured by the loader.
Define:
- physical dimensions and placement surface;
- initial orientation and forward direction;
- closest natural viewing distance;
- maximum instance count;
- teleport, grab, scale, or selection behavior;
- required animation and audio;
- lighting, shadows, passthrough, and environment assumptions;
- first placement and full-detail byte targets;
- CPU and GPU frame-time thresholds;
- fallback when XR is unavailable or denied.
For 72 Hz, a displayed frame interval is about 13.9 milliseconds; for 90 Hz, about 11.1 milliseconds. The application receives only part of that after platform and compositor work, so leave measured margin.
Preserve and inspect the source GLB#
Hash the original and run the Khronos glTF Validator. Record total bytes, triangles, vertices, primitives, nodes, materials, images, texture dimensions, animation, skins, morphs, dimensions, and extensions.
Render fixed reference views under neutral light. Include every side, the closest expected view, transparent regions, thin parts, and important animations. Keep a high-quality editable source for rebakes.
Use a neutral viewer and the shipping renderer. A source can appear correct in one tool because of different tone mapping, light intensity, normal handling, or extension support.
Classify every warning. Unsupported required extensions, invalid accessor bounds, missing tangents for a used normal-map path, and non-finite transforms are blockers rather than cosmetic notes.
Correct meters, axes, and origin#
glTF uses meters as the intended unit. Set the object to authoritative physical dimensions and compare all 3 bounds numerically. In AR, place it beside a real reference. A 0.6-meter appliance must not arrive as 60 meters because a centimeter source was exported at scale 1.
Choose an origin that supports placement and manipulation. A floor object should rest with its contact plane at the origin; a handheld tool may rotate naturally around its grip; a hanging object may require a semantic anchor distinct from its geometric center.
Remove remote fragments and empty animation nodes that expand bounds. Apply negative scale carefully, then recheck winding and normals. Keep interaction anchors in named nodes or an external manifest so geometry simplification does not move them.
Test initial orientation in the actual app. The runtime may apply a placement transform beyond the GLB root.
Audit unrestricted viewpoints#
XR users can lean behind, below, and inside an object. Inspect rear and underside surfaces that a desktop camera previously hid. Close holes only when the object is physically closed; otherwise model the correct interior or restrict the interaction honestly.
Check face winding and backface policy. Making every material double-sided can hide missing surfaces but doubles or increases fragment work and produces physically wrong thinness. Add only the surfaces the experience requires.
At the closest allowed distance, inspect silhouette facets, clipping, normal-map texel size, and transparent layers. In VR, review both eyes. One-eye screenshots can miss stereo discomfort from inconsistent transparency or overly aggressive depth detail.
Remove internal authoring geometry that becomes visible only when clipping through the object, then prevent the camera or grabbed object from reaching invalid positions using interaction constraints.
Create an XR geometry budget#
Measure the whole view, not just one file. Account for environment geometry, hands, controllers, UI, occlusion meshes, avatars, and multiple instances.
Reduce invisible internals, duplicate shells, and micro-detail first. Protect outline, holes, contact edges, and features used for interaction. Replace small relief with normal detail where it remains stable in stereo.
Build at least 2 tiers when the experience spans placement and close inspection. A placement GLB might contain 18,000 triangles in 4 opaque draws; the close tier 65,000 triangles in 6 draws. Load and compile the upgrade before swapping, and preserve the placement tier until one correct frame is ready.
Create LOD transitions using projected size or measured distance with hysteresis. Test while moving the head, not only with a stationary camera. Popping at the stereo silhouette is more distracting than a modest static error.
For repeated assets, share geometry and materials or use instancing supported by the scene architecture. Profile the production count.
Reduce draws and shader variants#
Inventory primitives, material assignments, alpha modes, double-sided flags, skinning, morphs, and vertex-color variants. Each partition can contribute a draw in each view and again in shadow or depth passes.
Merge duplicate materials and combine primitives only when material, animation, selection, culling, and lifecycle agree. Keep semantic IDs in a stable manifest rather than depending on primitive order.
Prefer opaque PBR materials. Use alpha mask or blending only where the design needs it. Layered transparency over passthrough video can consume fill and remain difficult to read. Review glass, foliage, hair, labels, and holographic UI in motion.
Limit live transmission, clearcoat, animated shader effects, and unique defines on mobile headsets. A lower geometry count cannot fix a fragment-bound material covering both eye buffers.
Budget textures as decoded memory#
List encoded bytes, dimensions, color space, and semantic use for every image. A 2048 × 2048 RGBA image is about 16 MiB at the top level and 21.3 MiB with mips before GPU block compression. Six such images can exceed 128 MiB.
Resize by XR projected need. Close labels and facial features may justify 2048; uniform secondary surfaces may use 512. Improve UV occupancy before increasing dimensions.
Use KTX 2.0 when the loader configures its transcoder and target devices support the path. Compare ETC1S and UASTC candidates for transfer, transcode, memory, and quality. Prewarm the transcoder before immersion where appropriate.
Generate mips and inspect head movement at distance. Shimmering high-frequency maps can be uncomfortable. Correct normal and scalar map color spaces, and eliminate unused alpha.
Keep an emergency quality tier with smaller textures or simpler materials for constrained devices.
Optimize animation and skinning#
Remove unused clips, tracks, morph targets, joints, and influences. Trim ranges and reduce redundant keys with pose error checks. Test loop seams and interpolation after GLB export.
Skinned meshes are transformed per view and per frame. Profile the real number of characters or hands. Use animation LOD: lower update frequency, fewer bones, disabled distant morphs, or static representation where visually acceptable.
Facial avatars can include dozens of morph targets and large attribute buffers. Retain only the expressions used by the experience. Verify the loader and shader variant count.
Avoid starting animation or compiling a new skinned material on the first head-tracked frame. Prepare the initial pose before entering XR.
Build interaction and collision proxies#
Do not raycast or collide against the densest render mesh by default. Create coarse named volumes for grab, placement footprint, pointer selection, navigation obstacle, and physics.
Fit proxies to user expectations. A product handle should be selectable in the visible opening; a table collision plane should align with its top; a floor footprint should not include a wide invisible scan base.
Record proxy-to-visual mapping outside topology indices. When an LOD swaps, interaction must retain the same semantic target. Test near the boundaries with tracked controllers, gaze, hands, and touch as applicable.
Simplify physics more aggressively than rendering. A 12-box compound can represent a 70,000-triangle cabinet better for collision than the render mesh, with more predictable contact.
Package the GLB and loader together#
Prune unused content and deduplicate. Quantize and apply Meshopt or Draco only according to the configured decoder profile. Compress textures only according to the configured KTX2 path. Required extensions without their loader are release failures.
Serve GLB, WebAssembly, and KTX2 assets over HTTPS with correct MIME, CORS, CSP, and cache headers. Use immutable asset URLs. Confirm service-worker policy and cancellation when the user exits before loading completes.
Separate placement and detail milestones. A 700 KB placement tier can enter quickly while a 4 MB detail tier follows explicit intent or stable tracking. Do not preload the entire XR catalog on an ordinary page visit.
Provide inline 3D, image, and semantic HTML fallbacks. XR content should remain discoverable and useful without an immersive session.
Prewarm predictable work#
Before presenting the enter-XR action, fetch small shared decoders and the likely placement asset when user intent supports it. Compile predictable materials and upload essential textures before relying on a steady tracked loop.
Do not block the page's critical content to prewarm XR. Use staged intent such as selecting the product, opening its 3D view, then choosing AR or VR.
After session start, avoid large synchronous allocations, shader variant creation, garbage collection pressure, and unbounded state updates. Reuse math and raycast objects. Let the renderer own the XR animation loop.
Record request, decode, transcode, upload, shader-ready, session start, tracking ready, placement, and full-detail times separately.
Run a device and session matrix#
Diagnose a missed-frame capture#
When the session misses its target, capture one repeatable 20-second path and test controlled variants. Replace all materials with opaque unlit, hold geometry constant, and compare. Restore materials and halve framebuffer dimensions. Then restore resolution and force the lowest mesh tier. These 3 comparisons distinguish shader or overdraw, pixel, and geometry pressure more reliably than random edits.
If CPU submission is limiting, compare primitive consolidation and instance sharing. If GPU fragment time falls 40% at lower resolution, work on fill, lighting, and post effects before decimating silhouette. If only the low mesh changes time, inspect vertex attributes, skinning, and shadow passes. Record each build hash so captures remain attributable.
Repeat the winning change during sustained use. A 2-millisecond gain in the first minute can disappear after thermal throttling or be offset by a texture upgrade later in the path.
Retest session entry after every packaging change. A frame-stable detail tier is still unacceptable if its new decoder or texture format adds a multi-second blank placement state on the lowest supported device. Keep acquisition and steady-state evidence in the same release report.
Test representative standalone headsets and AR phones, including the lowest supported tier. Use the production URL and build.
Run:
- cold and warm launch;
- permission granted and denied;
- unsupported-device fallback;
- enter and exit 10 times;
- placement, move, scale, rotate, grab, and release;
- closest inspection and unrestricted rear view;
- rapid head turns and controller movement;
- transparent materials against varied environments;
- animation at production population;
- network interruption during detail upgrade;
- session interruption, background, and resume;
- 15–20 minutes of sustained use.
Capture p50, p95, p99, and worst CPU and GPU frame milliseconds, missed frames, memory trends, battery or thermal behavior where available, asset hash, device, runtime, refresh rate, framebuffer size, and quality tier.
WebXR GLB release checklist#
- Modes, devices, runtimes, refresh rates, and fallback are explicit.
- Source hash, validator report, metrics, and visual references are archived.
- Meters, bounds, origin, forward direction, and placement contact are correct.
- Rear, underside, interior, close, and stereo views pass.
- Geometry tiers preserve silhouette and switch without empty frames.
- Draws, shaders, transparency, and shadow passes meet scene budgets.
- Textures fit decoded memory and remain stable under head movement.
- Animation, skinning, morphs, and first-frame preparation are bounded.
- Interaction and collision proxies remain aligned across LODs.
- Compression extensions match configured, cached, and tested decoders.
- HTTPS delivery, immutable caching, cancellation, and fallback work.
- Cold, sustained, interrupted, and repeated sessions pass on devices.
Sources and further reading#
- W3C WebXR Device API
- MDN: WebXR Device API
- Immersive Web WebXR Samples
- Khronos glTF 2.0 specification
- Khronos glTF Validator
Preparing a GLB for WebXR means proving physical truth and real-time stability under tracked, stereo, unrestricted use. Correct scale and viewpoints first, build placement and detail tiers, separate interaction from render topology, and approve the complete hosted session rather than the file in isolation.