You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(camera): depth of field from style.depth and a focus distance (#356)
#355's tractable half. `style.depth` already drives parallax — an element at
`depth: 3` travels three times as far as the backdrop when the camera moves. The
same two numbers now drive sharpness:
sigma = aperture * |depth - focus|
`camera.focus` defaults to `1.0`, the plane an element sits on when it declares
no depth, and `camera.aperture` defaults to `0.0`. A scenario that mentions
neither is focused on everything it has, and `aperture: 0` renders byte-identical
to before this existed — pinned by a test rather than assumed.
Linear and symmetric: two units in front defocuses exactly as much as two units
behind. The blur composes into the node's existing filter chain, next to
`style.filter` and `chromatic_aberration`, and extends the same layer's bleed
bounds by `3 * sigma` so the gradient is not cut off at the box edge.
Both fields go through `interpolate_camera_property`, so keyframes came free:
animating `focus` is a rack focus, animating `aperture` opens and closes the
effect without moving the focal plane. That also means `KNOWN_CAMERA_PROPERTIES`
had to learn them — the validator rejects an unknown keyframe property by design,
and it caught `"property": "focus"` before any render did.
Measured on three cards at depths 1, 2 and 3 with `aperture: 7` and `focus`
animated 1 → 3, counting edge-gradient pixels:
focus depth1 depth2 depth3
t=0.0 1.0 0 22 41
t=1.0 2.0 21 0 20
t=2.0 3.0 43 22 0
The diagonal of zeros is the rack focus travelling.
Seven tests. Two fail without the filter, reporting `sharp=0px, defocused=0px`;
the other five assert equality and pass both ways on purpose — they pin that
aperture 0, a plane on the focal distance, and a node with no declared depth all
render exactly as they did before.
The glossy material from #355 is not here. It needs a lighting model decided
first, which is a product call rather than an implementation detail.
Refs #355
Copy file name to clipboardExpand all lines: crates/rustmotion/skills/SKILL.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -236,6 +236,7 @@ Read individual rule files for detailed explanations, GOOD/BAD examples, and con
236
236
-[rules/halo-shapes.md](rules/halo-shapes.md) - `halo` beyond circles: `radius_x`/`radius_y`/`rotation` for a wide thin band of light, and why the blur follows the short axis
237
237
-[rules/zoom-blur-transition.md](rules/zoom-blur-transition.md) - The radial "tunnel" cut: `zoom_blur`'s `strength`/`origin`, why it had to be a transition and not an effect, and the pivot-coincident-edge trap
238
238
-[rules/chromatic-aberration.md](rules/chromatic-aberration.md) - Per-element red/cyan fringe on arrival: `chromatic_aberration`'s `amount`, how its curve differs from `chromatic_wipe`'s, and the `amount`-not-`amplitude` trap
239
+
-[rules/depth-of-field.md](rules/depth-of-field.md) - Defocus by plane: `camera.focus`/`aperture` on the `style.depth` scale, rack focus by keyframe, and why nothing moves without distinct depths
239
240
-[rules/geometry-safety.md](rules/geometry-safety.md) - Keep all content inside the viewport: `white-space`, `auto_scroll`, `overflow` semantics + violation kinds
240
241
-[rules/clip-path.md](rules/clip-path.md) - Non-rectangular masking: the six `clip-path` shapes, how their percentages resolve, and why `node-path` is not one of them yet
241
242
-[rules/overlapping-scenes.md](rules/overlapping-scenes.md) - Make an element outlive a cut: overlapping `at` windows composite instead of replacing, who supplies the background, and why `snap` never creates an overlap
0 commit comments