Skip to content
Docs
Open Promoeyes
lens-flare

lens-flare

LENS-FLARE — the one accent tag that is not a mesh, and what it is FOR.

Read the rig's notes
Scene 01 is coverage.promo's cv07 moved here unchanged (ids renamed cv07-* →
lf01-*, haze match keys kept): the tag plus its four optic variants on an
empty stage. Scenes 02-11 are the part cv07 could not carry inside a
42-scene registry sweep — the flare doing its actual job, BEHIND things:
behind a device, behind a laptop lid, behind <ui3d> panels, behind a flat
<ui> billboard, on a device's own glass, and finally the same composition
twice, once without and once with.

Scenes 12-16 are the ATTACHED form, and the attribute they demo is new:
every anchor in 02-11 is a hand-solved world coordinate, which is fine until
the subject moves. 12-16 name a corner of another node instead.

16 scenes, 90s of scene plus fourteen 0.7s match-move gaps — 99.8s on
the wall. The three lobes hz0/hz1/hz2 ride every scene, so the
reel is one continuous room. Scene starts, for capturing beats:
lf01 0 · lf02 3.0 · lf03 9.2 · lf04 14.4 · lf05 19.6 · lf06 25.3 ·
lf07 30.5 · lf08 36.2 · lf09 42.4 · lf10 49.6 · lf11 56.3 · lf12 64.0 ·
lf13 72.7 · lf14 80.4 · lf15 87.1 · lf16 93.8.

  01 variants       prism · aperture · orbs · anamorphic          (= cv07)
  02 not occluded   THE rule: the core over the subject erases it
  03 behind a phone one blade + one prism off the silhouette
  04 behind a lid   two anchors rimming a yawing macbook
  05 behind ui3d    flares in the GAPS between three real panels
  06 behind ui      a flat <ui> page raked from off its corner
  07 on the glass   a tiny flare parked on a screen as a specular hit
  08 edge fade      three anchors at ndc 1.07 / 1.46 / 1.78
  09 the modules    spokes · ghosts · halo · hex · streak, isolated
  10 live channels  intensity keyframes, size does not
  11 without / with the payoff — one composition, the light switched on

  12 attach·corner  offset=0 on all four corners, then offset=18
  13 rides the rig  attached vs hand-solved, through the same 42° yaw
  14 bounds·screen  an iMac's stand vs its glass
  15 offset·depth   0 / 70 / 70 + behind, on a laptop lid
  16 any node       three <ui3d> panels, three glints, three attributes

WHAT THE TAG ACTUALLY IS. The node is only an ANCHOR: a transform that can
be positioned, keyframed and match-moved like anything else. The picture is
a FULLSCREEN additive overlay whose core is placed each frame by projecting
the anchor's world position through the live camera (layer-adapters/
lens-flare.ts). Four anchors therefore give four flares in one frame at no
extra pass, and the whole optic rides the camera for free.

THE FIVE THINGS THAT BITE, in the order they bite:

  1. IT IS NEVER OCCLUDED. blending=Additive, depthTest=false,
     renderOrder=9990 — the overlay is drawn last, over everything, and no
     geometry can hide it. "Behind the phone" is therefore a statement about
     where the ANCHOR sits, never about draw order. Park the core ON a
     subject and you erase that subject (scene 02 does it on purpose); park
     it just OFF the silhouette and the blade, ghosts and halo rake across
     it, which is the entire look. Scene 02 is the A/B.

  2. THE CHAIN MIRRORS THROUGH SCREEN CENTRE. Ghosts, hex ghosts and the
     orb chain are all placed along the light→centre axis. An anchor at dead
     centre collapses every one of them onto the core, so the flare reduces
     to a sun. Off-centre is not a taste call — it is what makes the optic
     visible at all (scene 09 anchors off-centre for exactly this reason).

  3. THE ANCHOR MAY LEAVE THE FRAME, and should. Edge fade is
     clamp(1 - (|ndc| - 1.1) / 0.7) per axis: full inside 1.1, half at ~1.45,
     gone by 1.8. So an anchor a frame-width out of shot still lays a tail
     across the picture with no core anywhere — the cheapest cinematic
     upgrade in the vocabulary, and scene 08 measures it.

  4b. IT PAINTS THE IMAGE, NOT THE SCENE. The overlay is added after shading,
     so by itself a flare puts no highlight on the casing it sits beside —
     which is most of why a bright flare over an unlit product reads as a
     sticker. Two opt-in knobs close that, both off by default, both riding
     the same keyframed `intensity` as the optic, and both demoed in scene 12:
       light="true"  a PointLight on the anchor in the flare's own colour
                     (light-intensity / -distance / -decay / -color). Defaults
                     are decay=0 + a 600mm cutoff because three's falloff is
                     1/d^decay in WORLD units, and in a millimetre world a
                     decay=2 light near a phone needs intensity in the tens of
                     thousands — the same d² trap that makes the corpus's
                     `type=point intensity=1.8` lights no-ops.
       veil="0..1"   the whole-frame lift a real barrel scatters, tinted by
                     `color`. This is precisely what the soft-clip curve was
                     shaped to PREVENT (stray tails greying a dark scene), so
                     it is an explicit term you scale, not that bug back.

  4. `size` IS NOT A CHANNEL AND `scale` DOES NOTHING. `size` zooms the whole
     optic but is read once at build; the anchor's `scale` is ignored outright
     (the overlay's vertices are clip-space). The two things that move are
     `intensity` (userData.setLensFlareIntensity) and `opacity` (read back off
     the material every frame). Scene 10.

  5. THE CORE IS UNCONDITIONAL. `spokes=0` does not remove the sun — it
     removes the 12-spoke shimmer and the starburst frill only; the core term
     and its bloom cushion always draw. Scene 09 shows each module isolated
     and that residual core is why every panel there still has a bright dot.

AND ONE THAT DOES NOT BITE: a lens-flare cannot be nested inside a device
tag. <iphone>/<macbook>/<ipad>/<imac>/<chrome> only recurse into <ui> and
<ui3d overlay> children — anything else in a device body is dropped. Put the
anchor in the scene next to the device and keyframe the two together, which
is what scenes 03, 04 and 07 do — or `attach` it, which is what 12-16 do and
what you should reach for now.

`attach` + `corner` — THE ANSWER TO ALL OF THE ABOVE

    lens-flare#glint(attach=hero corner=top-right offset=26 depth=-40)

`attach` names any node in the scene and `corner` names a corner of ITS OWN
bounding box, measured in ITS OWN local frame. The anchor mounts as a child
of that node, so the target's position, rotation and scale carry it and the
author never writes a world coordinate — which matters because a hand-solved
coordinate is not wrong when you write it, it just stops being right the
moment the subject moves, and nothing in the document says so (scene 13 is
that A/B). It is deliberately the target's LOCAL box, not a world AABB: a
world AABB of a yawed phone is bigger than the phone and its "top-right" is
a corner of empty space.

  corner  the `origin` vocabulary — top-left · top · top-right · left ·
          center · right · bottom-left · bottom · bottom-right (+ tl/tr/…)
  offset  SIGNED mm along that corner's own diagonal. THE craft knob:
          positive is outward, so one number means "clear of the
          silhouette" whichever corner was named, i.e. rule 1 as an
          attribute. At offset=0 the anchor IS the corner — scene 12's
          first beat parks four pinpoints on a phone's four corners to
          show exactly that, because "18" only means something once you
          can see what it is 18 out from. (A bounding box around a rounded
          body sits a hair outside the visible silhouette, so a zero-offset
          core reads as touching the corner, not as inset from it.)
          NEGATIVE pulls inward instead, toward the box centre, which is
          how you park a small dim core just INSIDE a corner so it reads as
          a reflection on the glass rather than a light beside the device —
          scene 07's specular idiom, without the hand-solved coordinate.
          It is a true distance and nothing clamps it, so a negative larger
          than the half-diagonal walks out the opposite side; and on
          corner=center the direction vector is zero, so offset does
          nothing at all.
  depth   mm along the target's local z — negative parks the anchor behind
          it, which is scene 04's whole idea without the arithmetic
  bounds  "union" (default, the whole subtree) or "screen" (the live
          display mesh only). Not cosmetic: an OPEN laptop's union box is
          the deck's full width by the lid's full height, so its top-left
          is a corner of air. Scene 14 measures an iMac both ways.
  mesh / mesh_contains  narrow the measured box to named GLB meshes

offset/depth are the target's own local mm, so they scale with it — a
phone at scale 2 carries a 26mm offset 52mm out, keeping the glint in the
same place relative to the phone. The flare's own position/x/y/z stay live
as a further nudge in that same frame, keyframes included, because the
corner is carried by an intermediate group rather than baked into the node.

Implementation: parsed in core/xml-scene.ts promoLensFlareNode → `flare_anchor`,
solved in engine/lib/flare-anchor.ts, mounted by promo-runtime's
_anchorLensFlares — a deferred post-loop pass, like <decal> and surface
`attach`, so the target's GLB has loaded whatever the authoring order.

Palette (coverage's, so cv07 still looks like itself): background top
#0b1020 → bottom #05070f; lobes #24406e / #3a2a6e / #1b5a68 at 0.34-0.46.
Ink #eef3ff, mute #93a4c8, accent #66e0ff, warn #ffb454, ok #7dffc4,
bad #ff7a8a. Variant tints are the adapter's own defaults: prism #a47edd,
aperture #ffc98a, orbs #9fd8ff, anamorphic #86b7ff.

Frame geometry: every scene is fov=42 at z≈765 and no camera pushes closer
than 760, i.e. ±522 x ±294 mm at z=0. Titles ride y=234, subs y=202, beat
labels y=-240 — far enough in that a dolly cannot clip them. Devices are
true physical size scaled by `scale`, so a phone at 2.0 is 319mm and fills
~54% of frame; a <macbook> is only 237.8mm tall but its DECK runs forward in
z, so it eats far more frame than its height suggests — 0.85 is a hero, not
1.5, and anything larger parks its keyboard over the beat label.

Preview: /packages/runtime/index.html?project=promos/primitives/lens-flare/lens-flare.promo
Topic
lights
Scenes
16
Kind
reference rig
//
    LENS-FLARE — the one accent tag that is not a mesh, and what it is FOR.

    Scene 01 is coverage.promo's cv07 moved here unchanged (ids renamed cv07-* →
    lf01-*, haze match keys kept): the tag plus its four optic variants on an
    empty stage. Scenes 02-11 are the part cv07 could not carry inside a
    42-scene registry sweep — the flare doing its actual job, BEHIND things:
    behind a device, behind a laptop lid, behind <ui3d> panels, behind a flat
    <ui> billboard, on a device's own glass, and finally the same composition
    twice, once without and once with.

    Scenes 12-16 are the ATTACHED form, and the attribute they demo is new:
    every anchor in 02-11 is a hand-solved world coordinate, which is fine until
    the subject moves. 12-16 name a corner of another node instead.

    16 scenes, 90s of scene plus fourteen 0.7s match-move gaps — 99.8s on
    the wall. The three lobes hz0/hz1/hz2 ride every scene, so the
    reel is one continuous room. Scene starts, for capturing beats:
    lf01 0 · lf02 3.0 · lf03 9.2 · lf04 14.4 · lf05 19.6 · lf06 25.3 ·
    lf07 30.5 · lf08 36.2 · lf09 42.4 · lf10 49.6 · lf11 56.3 · lf12 64.0 ·
    lf13 72.7 · lf14 80.4 · lf15 87.1 · lf16 93.8.

      01 variants       prism · aperture · orbs · anamorphic          (= cv07)
      02 not occluded   THE rule: the core over the subject erases it
      03 behind a phone one blade + one prism off the silhouette
      04 behind a lid   two anchors rimming a yawing macbook
      05 behind ui3d    flares in the GAPS between three real panels
      06 behind ui      a flat <ui> page raked from off its corner
      07 on the glass   a tiny flare parked on a screen as a specular hit
      08 edge fade      three anchors at ndc 1.07 / 1.46 / 1.78
      09 the modules    spokes · ghosts · halo · hex · streak, isolated
      10 live channels  intensity keyframes, size does not
      11 without / with the payoff — one composition, the light switched on

      12 attach·corner  offset=0 on all four corners, then offset=18
      13 rides the rig  attached vs hand-solved, through the same 42° yaw
      14 bounds·screen  an iMac's stand vs its glass
      15 offset·depth   0 / 70 / 70 + behind, on a laptop lid
      16 any node       three <ui3d> panels, three glints, three attributes

    WHAT THE TAG ACTUALLY IS. The node is only an ANCHOR: a transform that can
    be positioned, keyframed and match-moved like anything else. The picture is
    a FULLSCREEN additive overlay whose core is placed each frame by projecting
    the anchor's world position through the live camera (layer-adapters/
    lens-flare.ts). Four anchors therefore give four flares in one frame at no
    extra pass, and the whole optic rides the camera for free.

    THE FIVE THINGS THAT BITE, in the order they bite:

      1. IT IS NEVER OCCLUDED. blending=Additive, depthTest=false,
         renderOrder=9990 — the overlay is drawn last, over everything, and no
         geometry can hide it. "Behind the phone" is therefore a statement about
         where the ANCHOR sits, never about draw order. Park the core ON a
         subject and you erase that subject (scene 02 does it on purpose); park
         it just OFF the silhouette and the blade, ghosts and halo rake across
         it, which is the entire look. Scene 02 is the A/B.

      2. THE CHAIN MIRRORS THROUGH SCREEN CENTRE. Ghosts, hex ghosts and the
         orb chain are all placed along the light→centre axis. An anchor at dead
         centre collapses every one of them onto the core, so the flare reduces
         to a sun. Off-centre is not a taste call — it is what makes the optic
         visible at all (scene 09 anchors off-centre for exactly this reason).

      3. THE ANCHOR MAY LEAVE THE FRAME, and should. Edge fade is
         clamp(1 - (|ndc| - 1.1) / 0.7) per axis: full inside 1.1, half at ~1.45,
         gone by 1.8. So an anchor a frame-width out of shot still lays a tail
         across the picture with