Skip to content
Docs
Open Promoeyes
rect-area-light

rect-area-light

RECT AREA LIGHT — a one-scene port of three.js `webgl_lights_rectarealight`, kept faithful on purpose: it is the acceptance test for the area light, the torus-knot primitive and the procedural checker roughness map.

Read the rig's notes
Everything is the example's numbers × 100, because the authoring unit here is
the MILLIMETRE (packages/core/units.ts) and the example's unit is arbitrary.
Area-light irradiance is a solid angle, so a uniform scale of the whole
scene — lights, subject, camera — leaves the exposure untouched.

  three.js                          here
  RectAreaLight(0xff0000, 5, 4, 10) light(type=area color=#ff0000
                                          intensity=5 width=400 height=1000)
  RectAreaLightHelper(light)        helper=true (the light's own emissive
                                    rectangle — colour × intensity)
  TorusKnotGeometry(1.5,0.5,200,16) torusknot(r=150 tube=50
                                          tubular_segments=200
                                          radial_segments=16)
  roughnessMap = checker(400)       roughness_map="checker:400"
                                    roughness_map_filter=nearest
  rectLight.rotation.y += ±delta    a linear rotation.y track

The example spins its lights off the frame clock forever (-1, +0.5 and +1
rad/s); a reel has a length, so the same rates are baked into 12s of linear
keyframes: -12 rad = -687.55°, +6 rad = +343.77°, +12 rad = +687.55°.

NOTE ON THE LOOK: there is no ambient, no environment and no haze anywhere in
this reel, and that is the whole demonstration — every photon on every subject
and on the floor comes off the three rectangles. Tone mapping is off (`None`) so the frame
matches the example's untone-mapped output rather than the reel default.

SCENES 2–6 · THE SAME RIG, A REAL DEVICE ON THE PLINTH
The knot is a test object; a device is the thing this engine actually shoots.
Each of the five swaps the subject and keeps the composition —
but a device renders at its TRUE PHYSICAL SIZE (an iPhone is 159.5mm tall, an
iMac 461mm), so the soft-box rig is scaled to the subject exactly the way a
photographer picks a soft-box for what is on the table. One factor k per
scene multiplies every length in the rig — light size, light position, camera
distance, subject height, floor, check count — so all six scenes are the SAME
photograph at six sizes:

  s1 knot     k = 1.00   400mm subject      s4 macbook  k = 0.90   354.8mm
  s2 iphone   k = 0.40   159.5mm            s5 chrome   k = 1.20   480mm
  s3 ipad     k = 0.70   280.6mm            s6 imac     k = 1.35   547mm

Devices face +Z out of the box, so each one is yawed 180° to present its
screen to the -Z side of the rig — the side the example's camera shoots from.

BACK → FRONT. Scene 1 keeps the example's fixed camera, because it is a port
and the port has to stay honest. Every device scene instead ORBITS 155°: it
opens behind the device on the LIT side (the soft-boxes face -Z, so it is the
rear shell they rake — the iPhone's camera island, the iMac's back and stand),
swings out through a three-quarter and a full side profile, and lands dead
front on the screen. One second of hold at each end, constant angular rate in
between. The arc is authored as CHORDS — eight position keyframes sampling the
circle — because a position track interpolates in a straight line, and two
keyframes from back to front would fly the camera through the middle of the
device. `look_at` has to be a track too: a static look_at attr is read once at
build, so an animated camera with one would slide sideways still facing its
original heading.

`side=double` on the shared material is there for scene 5 alone. A <chrome>
window is not a body, it is two flat planes — and a plane is single-sided, so
without it the browser window is simply GONE for the whole first half of its
orbit. The GLB devices are closed surfaces and cannot tell the difference.

ONE MATERIAL, SIX SUBJECTS. `standard#subject` is declared at the top of this
file and nowhere else; the knot resolves it as `material(ref=subject)` and
every device as `body-material=subject`. They are not two lookalike materials
— they are the same definition, so the phone is shaded by exactly the surface
the knot is, and re-tinting that one line re-tints all six.

It is also what makes these scenes work at all. Shipped as-authored, an iPhone
GLB is a BLACK device: 37 materials whose base colours run 0.00–0.06, most of
them metal at roughness 0.001. A 2% albedo returns 2% of any light you put on
it, and a metal has no diffuse term at all — it renders only what it reflects,
so in a black room with three small rectangles it is a black cutout no matter
how hard you light it. The device's own glass still survives the swap: a
whole-shell override keeps the replaced material's transparency, or the iMac's
Screen_Glass_0 sheet would fuse into a white plate over the display.

NO SCENE HERE HAS AN ENVIRONMENT — the port doesn't, so the device scenes
don't either. That is the whole reason the subjects go from bright on the back
half of each orbit to near-black at the front: an environment map is the only
thing that would deliver light from the camera's side of the room, and there
isn't one. A #ffffff albedo returns everything it receives and nothing is
still nothing. The five scenes carried `environment(0.18)` while the casings
were the GLB's black metal and needed something to reflect; `subject` is
metalness 0, so that reason retired with them.
Topic
lights
Scenes
6
Kind
reference rig
//
    RECT AREA LIGHT — a one-scene port of three.js `webgl_lights_rectarealight`,
    kept faithful on purpose: it is the acceptance test for the area light, the
    torus-knot primitive and the procedural checker roughness map.

    Everything is the example's numbers × 100, because the authoring unit here is
    the MILLIMETRE (packages/core/units.ts) and the example's unit is arbitrary.
    Area-light irradiance is a solid angle, so a uniform scale of the whole
    scene — lights, subject, camera — leaves the exposure untouched.

      three.js                          here
      RectAreaLight(0xff0000, 5, 4, 10) light(type=area color=#ff0000
                                              intensity=5 width=400 height=1000)
      RectAreaLightHelper(light)        helper=true (the light's own emissive
                                        rectangle — colour × intensity)
      TorusKnotGeometry(1.5,0.5,200,16) torusknot(r=150 tube=50
                                              tubular_segments=200
                                              radial_segments=16)
      roughnessMap = checker(400)       roughness_map="checker:400"
                                        roughness_map_filter=nearest
      rectLight.rotation.y += ±delta    a linear rotation.y track

    The example spins its lights off the frame clock forever (-1, +0.5 and +1
    rad/s); a reel has a length, so the same rates are baked into 12s of linear
    keyframes: -12 rad = -687.55°, +6 rad = +343.77°, +12 rad = +687.55°.

    NOTE ON THE LOOK: there is no ambient, no environment and no haze anywhere in
    this reel, and that is the whole demonstration — every photon on every subject
    and on the floor comes off the three rectangles. Tone mapping is off (`None`) so the frame
    matches the example's untone-mapped output rather than the reel default.

    SCENES 2–6 · THE SAME RIG, A REAL DEVICE ON THE PLINTH
    The knot is a test object; a device is the thing this engine actually shoots.
    Each of the five swaps the subject and keeps the composition —
    but a device renders at its TRUE PHYSICAL SIZE (an iPhone is 159.5mm tall, an
    iMac 461mm), so the soft-box rig is scaled to the subject exactly the way a
    photographer picks a soft-box for what is on the table. One factor k per
    scene multiplies every length in the rig — light size, light position, camera
    distance, subject height, floor, check count — so all six scenes are the SAME
    photograph at six sizes:

      s1 knot     k = 1.00   400mm subject      s4 macbook  k = 0.90   354.8mm
      s2 iphone   k = 0.40   159.5mm            s5 chrome   k = 1.20   480mm
      s3 ipad     k = 0.70   280.6mm            s6 imac     k = 1.35   547mm

    Devices face +Z out of the box, so each one is yawed 180° to present its
    screen to the -Z side of the rig — the side the example's camera shoots from.

    BACK → FRONT. Scene 1 keeps the example's fixed camera, because it is a port
    and the port has to stay honest. Every device scene instead ORBITS 155°: it
    opens behind the device on the LIT side (the soft-boxes face -Z, so it is the
    rear shell they rake — the iPhone's camera island, the iMac's back and stand),
    swings out through a three-quarter and a full side profile, and lands dead
    front on the screen. One second of hold at each end, constant angular rate in
    between. The arc is authored as CHORDS — eight position keyframes sampling the
    circle — because a position track interpolates in a straight line, and two
    keyframes from back to front would fly the camera through the middle of the
    device. `look_at` has to be a track too: a static look_at attr is read once at
    build, so an animated camera with one would slide sideways still facing its
    original heading.

    `side=double` on the shared material is there for scene 5 alone. A <chrome>
    window is not a body, it is two flat planes — and a plane is single-sided, so
    without it the browser wi