Environment blend modes
One property — session.environmentBlendMode — tells you whether the user can see the real room behind your content, and on one of its three values black is not a colour but a hole in your scene.
The same frame on three kinds of display
One render, composited three ways. Drag the object brightness down and watch the middle panel: on an additive display, dark pixels emit no light, so the object does not get darker — it stops existing.
This demo needs WebGL, which your browser did not provide. The explanation below covers the same material on its own.
One property, three very different displays
Every XRSession exposes environmentBlendMode, a string with three possible values. It describes the physics of the hardware: whether the display blocks the real world, adds light to it, or blends with it. This is not a preference and not something you set — it is a property of the panel in front of the user's eyes, and you read it.
Most developers never read it, because most development happens on one headset where the answer never changes. The result is a class of bug that does not appear until someone opens your page on different hardware, and then appears catastrophically: content that is invisible, or a black rectangle where the room should be.
The value is available as soon as the session starts, so you can branch once at setup rather than testing per frame. What you branch on is mostly your clear colour, your lighting model, and whether you draw a background at all.
What each value actually means
The names describe compositing operations, and reading them that way makes the consequences obvious.
- opaque
- The display blocks the real world entirely. Your frame is everything the user sees, alpha is ignored, and you must draw a background or they will see whatever was in the buffer. This is every VR headset in normal VR mode — Quest, Index, Vive.
- additive
- The display can only add light to the real world; it cannot subtract any. The compositor computes real + yours. Black adds nothing, so black is transparent, and there is no way to draw a shadow or darken anything. Waveguide displays such as HoloLens and Magic Leap work this way.
- alpha-blend
- The compositor blends your frame with a camera image of the room using your alpha channel. Black is a real black, alpha 0 shows the room, and everything behaves the way you expect from ordinary compositing. This is camera passthrough AR — Quest 3, Vision Pro, phone AR.
Branching on it, once
The whole adaptation is usually three or four lines. The mistake is not that it is hard; it is that it is easy to never write.
const session = await navigator.xr.requestSession('immersive-ar', {
optionalFeatures: ['local-floor'],
});
// A property of the hardware, not a setting. Read it once at startup.
switch (session.environmentBlendMode) {
case 'opaque':
// Nothing shows through, so draw a world. Alpha is ignored entirely.
renderer.setClearColor(0x101018, 1);
scene.background = skybox;
scene.add(environmentLighting);
break;
case 'additive':
// Clear to black -- on this display black means "emit nothing",
// which is exactly the transparency you want.
renderer.setClearColor(0x000000, 1);
scene.background = null;
// Shadows and dark materials cannot be drawn: you can only add light.
// Use bright, high-contrast, mostly-line-art content instead.
disableShadows();
break;
case 'alpha-blend':
// Clear fully transparent so the camera image shows through.
renderer.setClearColor(0x000000, 0);
scene.background = null;
// Real occlusion needs depth sensing; without it your content
// always floats in front of the room.
break;
}In three.js, renderer.setClearAlpha(0) and an alpha:true context are what make the alpha-blend case work. A renderer created without alpha silently produces an opaque black frame, which on a passthrough device hides the entire room.
Why additive breaks things quietly
Additive is where the surprises live, because it violates an assumption baked into almost every renderer: that you can darken a pixel. You cannot. The display is adding photons to whatever is already reaching the eye, and there is no mechanism for removing them.
Everything downstream of that follows. Shadows are impossible — a shadow is a darkening. A dark UI panel is invisible. Anti-aliased edges that fade to black fade to nothing, so thin dark lines vanish. A skybox is not just wasteful but actively wrong: it would be a wash of light over the whole room. And contrast is fighting the environment, so the brighter the room, the less of your content survives.
Content that works on additive hardware tends to look like a heads-up display: bright, saturated, high-contrast, more line than fill, with no reliance on dark values to carry meaning. That is a design constraint, not a rendering setting, which is why discovering it late is expensive.
What the demo above is doing
One scene is rendered once, into a texture with an alpha channel — exactly what a WebXR frame is. The three panels then composite that single texture against a drawing of a room, using the three formulas the specification describes: the opaque panel ignores the room, the additive panel adds, the alpha-blend panel blends by alpha. Nothing is faked per panel; they differ only in the blend.
Object brightness is the control worth dragging. As it approaches zero the opaque panel shows a dark object against a dark background — still visible, still there. The additive panel shows nothing at all, because a black pixel contributes no light. The alpha-blend panel keeps the object visible as a genuinely dark shape, since alpha and colour are separate there.
Room brightness is the second lesson. Turn it up and the additive panel washes out long before the other two, which is why waveguide headsets struggle outdoors and why their content is designed to be bright and sparse.
Which hardware reports what
The same device reports different values in different session modes, which is the detail most often missed: a Quest 3 is opaque in VR and alpha-blend in AR.
| Device | immersive-vr | immersive-ar | Display type |
|---|---|---|---|
| Meta Quest 2 | opaque | alpha-blend | LCD with camera passthrough |
| Meta Quest 3 / Pro | opaque | alpha-blend | Colour camera passthrough |
| Apple Vision Pro | opaque | alpha-blend | Camera passthrough |
| HoloLens 2 | — | additive | Waveguide, light-adding |
| Magic Leap 2 | — | additive | Waveguide with dynamic dimming |
| Android phone AR | — | alpha-blend | Camera feed behind the canvas |
Checked 2026-09. Magic Leap 2 adds segmented dimming, which softens but does not remove the additive constraint. Verify against the WebXR spec and each vendor before relying on a row.
Mistakes that cost the most time
All of these pass every test on the headset you develop on, which is what makes them expensive rather than merely wrong.
- Never reading environmentBlendMode
- The default assumption is whatever your own headset does. Everything below is a consequence of this one omission.
- Drawing a skybox in AR
- On alpha-blend it replaces the room with your background; on additive it floods the user's view with light. In both cases the AR is gone.
- An opaque clear colour in a passthrough session
- A renderer created without alpha, or a clear alpha of 1, produces a solid frame. The camera image is behind it and the user sees a black wall.
- Relying on shadows or dark UI on additive
- Both are subtractive. On a waveguide display a drop shadow is invisible and a dark panel is a hole, so any layout that separates elements by darkness collapses.
- Testing content contrast in a dim room
- Additive content competes with ambient light. What reads perfectly at a desk at night can be unreadable by a window at noon.
Further reading
The specification text for this one is short and unusually clear — it is worth reading in full rather than in summary.
- W3C — XRSession.environmentBlendMode — The normative definitions, including the exact compositing formulas.
- MDN — XRSession.environmentBlendMode — Practical reference with per-value guidance.
- AR hit testing — The other half of AR: placing content on surfaces the runtime has recognised.
- WebXR sessions — Where the session comes from, and why immersive-ar changes what you may draw.
- Lighting estimation — Getting the light right before the blend mode decides what survives to the eye.
- Depth sensing — Whether a real surface can cut in front of your content at all.
- Stereo rendering — What the frame you are compositing actually is: views, viewports, one framebuffer.