# AURA ONE — studio product shot (Three.js / WebGL2)

A procedurally modelled pair of over-ear headphones on a turntable in a soft-box studio.
Everything is generated in code: geometry, leather/rubber/brushed-metal textures and the HDR gradient
environment. No models or images are downloaded. Three.js r169 is vendored in `vendor/`, and there is no build step.

## Run

ES modules need to be served over HTTP. Opening the file directly (`file://`) won't work.

```sh
python3 -m http.server 8000
# open http://localhost:8000/
```

## Controls

| Control | Mouse / UI | Keyboard |
|---|---|---|
| Orbit camera | drag on the scene | – |
| Zoom | scroll wheel / pinch | – |
| Colourway (Midnight · Sandstone · Oxblood) | swatches in the dock | `1` `2` `3` |
| Shell material (Leather · Rubber · Brushed metal) | segmented toggle | `M` cycles |
| Exposure (−2 … +2 EV) | slider | `[` / `]` (or `-` / `+`) |
| Auto-play on/off | Turntable button | `Space` or `A` |

**Idle auto-play.** Auto-play starts on load with no input. The turntable turns slowly (one revolution every
48 s) and the colourway cross-fades to the next one every 6 seconds. Any interaction (dragging, scrolling,
UI clicks, keys) pauses it so you can inspect the shot. After 10 s without input it resumes, and the button
shows the countdown. Switching auto-play off with the button keeps it off.

The shell material toggle changes the ear-cup shells, hinge housings and headband outer. The cushions stay
leather and the yokes, sliders and cup plates stay brushed metal. Exposure is applied after accumulation, so
changing it doesn't restart refinement.

## Status panel (top right)

* **Light bake · AO + contact shadows**: an object-space Monte-Carlo bake (up to 1024 directions). Each
  direction renders a depth map of the product and adds visibility per vertex (ambient and specular
  occlusion on the product) and into textures on the turntable and studio floor (contact shadows). The bake
  is stored in turntable space, so it **keeps refining while the turntable spins**. Directions are
  importance-sampled towards the overhead softbox, which makes contact shadows look clean after a few
  dozen samples.
* **Image accumulation**: while the view is still (auto-play paused, camera at rest), every frame is
  rendered with sub-pixel jitter and with the key light moved to a different point on its softbox. The
  frames are averaged like a path tracer's running mean (up to 256 spp). This gives anti-aliased edges and
  real area-light soft shadows. Any motion drops back to the fast raster preview.
  * *Raster preview*: live single frame (MSAA, PCF shadows).
  * *Refining*: accumulating.
  * *Converged*: target sample count reached. Rendering stops and the image just stays on screen.

On software rasterisers (SwiftShader / llvmpipe) the live preview turns off MSAA and uses FXAA instead, and
the bake uses a smaller depth map. A note in the panel tells you when this happens. The progressive passes
still converge to the same image, just more slowly.

## Rendering summary

* `WebGLRenderer` (WebGL2) only, no WebGPU features. Lighting is done with `MeshPhysicalMaterial`:
  * leather: pebble-grain normal and roughness maps, sheen, light clearcoat
  * soft-touch rubber: stipple normal, high roughness, sheen
  * brushed metal: `anisotropy` + `anisotropyMap`, streaked roughness. Cup plates have circular spun brushing.
* Environment: a PMREM of an HDR gradient dome with five emissive softboxes (key, fill, two rim strips,
  overhead). The same softboxes are visible as studio props when you zoom out.
* HDR is accumulated in half-float targets. A final pass applies exposure, Khronos PBR Neutral tone
  mapping, vignette and dither.

## Files

* `index.html`: page, UI and import map
* `js/main.js`: app state, auto-play, UI wiring, frame loop
* `js/headphones.js`: procedural headphone geometry, materials and colourways
* `js/textures.js`: procedural leather, brushed metal, rubber and fabric maps
* `js/studio.js`: environment, cyclorama, turntable, key light
* `js/progressive.js`: light baker (AO and contact shadows), screen-space accumulation, display pass
* `vendor/three/`: three.js r169 module and OrbitControls

Optional URL parameters (useful for testing): `?autoplay=0`, `colour=0..2`, `finish=leather|rubber|metal`,
`ev=-2..2`, `angle=<radians>`, `samples=<spp>`, `bake=<directions>`, `msaa=0|4`, `dpr=<pixel ratio>`,
`idle=<seconds>`.
