Camera
The scene camera, reached at scene.camera. It's a Node, so the full transform
surface works — position, eulerAngles, lookAt, forward. On top of that it exposes
projection reads and screen→world ray helpers for picking.
You never construct one: every Scene creates its own camera (overlays share the
parent's). In an ARScene the camera is driven by device tracking — read it, don't
place it.
At a glance
scene.camera.position = [0, 2, 4]
scene.camera.lookAt([0, 0.5, 0])
scene.addEventListener('touchstart', ev => {
const ray = scene.camera.getRay(ev.clientX, ev.clientY)
const hit = Physics.raycast(ray.origin, ray.dir)
if (hit) console.log('tapped', hit.node?.name)
})Projection
camera.fov // vertical field of view, DEGREES — settable (default 60)
camera.near // near clip distance, settable (default 0.01)
camera.far // far clip distance = view range, settable (default 1000)
camera.setProjection({ fov: 45, far: 5000 }) // any subset, one call
camera.horizontalFov // horizontal field of view, degrees (read-only, derived from fov + aspect)
camera.displaySize // [width, height] of the 3D viewport, px (read-only)
camera.projectionMatrix // Float32Array(16), live — the host updates it in place on resize/fov changeThe aspect ratio stays host-owned: you set the lens, the host keeps it across every resize. Nothing
nearer than near or further than far draws, so far is the scene's view range — raise it for
open worlds. Shadows keep their own (shorter) range, so a long view distance costs nothing in
shadow sharpness.
In a scene file the same three live on the camera block:
camera: { position: [0, 6, 12], target: [0, 0, 0], fov: 45 } // top-level block
nodes: { cam: { camera: { fov: 45, far: 5000 } } } // …or the camera NODE (it wins)Hosts that predate the projection call keep the fixed defaults — setting it there is inert, not an error. See
docs/parity/MATRIX.md(gl-camera-projection).
Exposure
camera.exposureCompensation = -2 // stops: +1 doubles the scene's brightness, −2 quarters it
camera.setExposure({ iso: 400 }) // the physical camera: aperture (f-stops), shutterSpeed (s), iso, compensationThe default is f/16 · 1/125 s · ISO 100 — EV100 15, bright sunlight, the exposure every light
intensity in these docs is tuned against — and exposureCompensation (default 0) is the one knob
most games need on top of it: a dark interior, a white-out, or eye adaptation — when the sun
swings into view, drop it a couple of stops over ~0.15 s and let it recover over a second or two.
The sun stays at the display's peak (its scene value is far above the tone curve's shoulder) while
everything else goes dark, which is what makes it read as blinding; on an HDR display
(device.hdr) doubly so. Cheap to set every frame.
Scene-referred light — sun, sky, IBL, lit surfaces — follows the exposure. Particle emissive is
post-exposure (1 = white on screen) and does not, so a muzzle flash or a fireball keeps its
on-screen brightness while the world around it darkens.
Hosts that predate the call keep the fixed default. See
docs/parity/MATRIX.md(gl-camera-exposure).
Screen → world rays
camera.getViewDirection(screenX, screenY): Vec3 // world-space ray direction through a screen point
camera.getRay(screenX, screenY): Ray // origin = camera.worldPosition, dir = view directionScreen coordinates are logical px — the same space as ev.clientX / ev.clientY. getRay
pairs directly with Physics.raycast for tap-picking; Ray is documented in
ray-plane-noise.
there is no worldToScreen helper — the inverse projection is only available raw, via
projectionMatrix + the camera's worldMatrix.
Pitfalls
// ✗ moving the camera in an ARScene — tracking overwrites it every frame
arScene.camera.position = [0, 2, 4]
// ✓ move the WORLD instead: parent content under scene.root and place thatSee also
- Scene — where the camera comes from; overlays share it
- Node — the inherited transform surface (
position,lookAt,forward) - 3D physics & shapes — raycasting the rays you build here
- Ray, Plane, Noise — the
Rayreturned bygetRay