lens
Stage 4 of **The Second Sun** — how the film is seen and heard. The film director calls `Lens.tick(t, dt, frame)` last of the four stages, because the lens has to see what the other three just made and the scene's ears ride the camera.
secondsun.lens
Stage 4 of The Second Sun — how the film is seen and heard. The film
director calls Lens.tick(t, dt, frame) last of the four stages, because the
lens has to see what the other three just made and the scene's ears ride the
camera.
What it owns
- Thirteen camera set-ups driving
FilmCamera's transform andCamera.fov. - The post chain — a physical lens (defocus computed from the field of view the shot is cut for), a 180° shutter, a bounded iris, one colour table per act, bloom, a vignette and film grain.
- The sound design, in
secondsun.sound.score, ticked from here.
Angles
A station is written as a compass bearing and an elevation, because that is how the rest of the film is written — the moon is at azimuth 305, the impact at azimuth 186. Bearing 0 is north (+Z), 90 east (+X), 180 south (−Z); elevation is degrees above the horizon; roll is clockwise in frame.
Lens.orient(bearing, elevation, roll) returns the quaternion the entity takes.
It is a quaternion rather than an Euler triple because shot 9 points at the
zenith and rolls, which is exactly where a yaw/pitch/roll triple gimbal-locks.
Verified against camera.lookAt on all six axes and at the zenith.
Lens.aimAt(eye, target) gives the bearing and elevation from one point to
another, so a station can name a thing in the world instead of an angle — which
is what most of them do.
The stations
| shot | eye | lens | focus | what it is on |
|---|---|---|---|---|
| 1 | 22 cm above the water, drifting forward 1.4 m/s, bearing 22 | 46° | 30 → 19 m | the lagoon and the bar |
| 2 | 1.6 m, crabbing east 2.2 m/s, bearing ~320 | 30° | the bar | the herd against the moon track |
| 3 | 8 cm above the mud, 45 cm from the print | 62° | 0.45 m | a hatchling's foot |
| 4 | 1.4 m, bearing 186 — the impact's own bearing | 54° | 160 m | the southern horizon |
| 5 | 2.0 m, pushing in 1.5 m/s from the north | 34° | the bar | the herd, backlit, shadows toward the lens |
| 6 | 2,200 → 1,400 m, holding on the retreating waterline | 40° | the waterline | kilometres of drying flat |
| 7 | 50 cm above the channel bed, looking upstream | 58° | 4.5 m | the outflow past the lens |
| 8 | 3 m, 52 m south of the conifer edge | 44° | the edge | the forest igniting |
| 9 | 1.2 m, at the zenith, rolling −2.6°/s | 72° | ∞ | the ejecta |
| 10 | 6 m, 44 m south of the edge | 36° | the edge | the forest going over |
| 11 | shot 3's station, to the digit | 62° | 0.45 m | the dry print taking ash |
| 12 | 4 m, bearing 180 | 26° | 900 → 380 m | the wall closing |
| 13 | shot 1's station, move, rotation and lens, to the digit | 46° | 30 → 19 m | the same water, 11 m higher |
Shot 1 and shot 13 are one function called twice. Shot 13 replays shot 1's
move exactly — same start point, same 1.4 m/s forward, same bearing, same lens,
same focus rack. Every authored term is identical. eye.y is
Film.seaLevel(t) + 0.22 in both, the same expression: at t=0 that is 22 cm
above a sea at 0, and at t=88 it is 22 cm above a sea 11 m higher.
Lens.stationIdentity(tau) prints both, term by term, with an equal block.
Shot 3 and shot 11 are likewise one function, so the footprint is framed identically before and after.
Marks
Lens.GEO holds where the lens believes the sand bar, the footprint, the
channel, the forest edge and the wave front are. Every half second of film time
the lens also looks for entities named SandBar / Herd / HatchlingFoot /
Channel / ForestEdge / SurgeFront, and aims at those instead when they
exist. So a station tracks what the shore and life stages actually stood, and
falls back to the authored number until they do.
The lens as a lens
A full-frame sensor is 24 mm tall, so the focal length a vertical field of view
describes is 12 / tan(fov/2). The station names a field of view and the
defocus is computed from it, so the two can never disagree: the 26° lens of
shot 12 is 52 mm and the 72° lens of shot 9 is 16.5 mm, and each defocuses the
way that focal length and that aperture do.
The iris
Adaptation alone lags a 0.4 s white-out, so the film rides the iris as well as
metering it. AutoExposure runs with minEV −7.5 / maxEV 4.0, speedDown 6.0
and speedUp 0.45 — it stops down fast and opens slowly, the way one does —
and on top of that compensationAt(t) takes −3.1 stops into FLASH, gives
1.1 back as the scene settles, takes another −1.9 across the EJECTA ramp and
returns 1.6 under the lid. Between the two, nothing in the film clips.
The grade
One colour table per act, baked with lut.fromGrade into
/zero/source/secondsun/lens/ and bound to a single lut pass whose texture is
swapped when the act changes. Act I is the only cool one and the only one with
the saturation that far down; nothing after it goes back. The swap at FLASH is
the one act change that does not fall on a cut, so amount dips to nothing
across the 0.35 s the horizon is white and comes back on the far side.
Reading it back
Lens.report() — the live station, the focal length, the camera's actual pose,
the iris, the defocus, the stock, the grade, the post chain and the whole mix.
Lens.teardown() drops the chain and the sound sources so the next tick
rebuilds them.
Scoped to this part · feeds back into the world's score.