Proposal: 2D lighting — ambient, point lights, occluders/shadows, normal maps, as ECS components #4
Labels
No labels
area:ci
area:docs
area:input
area:net
area:rendering
area:repo
area:stdlib
area:tooling
area:types
cleanup
dx
priority:high
priority:low
priority:medium
proposal
status:in-progress
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: workshopsoft/ludic#4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
2D lighting turns a flat scene into an atmosphere — caves, night, torches, explosions, glow. Ludic renders its own framebuffer (a software renderer), so lighting is a light-accumulation pass we own end to end. This proposes 2D lighting modeled on Godot's 2D lights & shadows (the clearest reference: an emitter, an occluder, an ambient modulate) but expressed ECS-natively, which fits Ludic better than a node tree.
Model (three parts, like Godot)
Screen.ambient(color)/ aCanvasModulate-style call. This alone gives night/cave mood.ECS-native shape (the Ludic-idiomatic part)
Lights and occluders as components the renderer consumes automatically each frame — no wiring:
The built-in Render phase composites: draw scene → multiply by ambient → additively accumulate each
Light2D(radial falloff) → subtract shadow regions cast byOccluders.Imperative escape hatch for one-offs (explosions, muzzle flash):
Light.point(pos, radius, color, energy),Light.ambient(color),Light.occlude(rect).Feature tiers (ship incrementally; software-renderer cost matters)
Light.time_of_day(t)animating ambient + sun color/direction via aDirectionalLight2D-style global light).Performance notes
Software 2D lighting is fill-rate bound. Keep it opt-in and tiered: radial falloff is a per-pixel add over each light's bounding box; shadows are the expensive part (start with a small number of shadow-casting lights). Deterministic and diffable for headless tests.
Phasing
Screen.ambient+Light2Dcomponent + additive radial accumulation.Occludercomponent + hard shadows.References
PointLight2D,Light2D(Color/Energy/Blend,LightOccluder2D,CanvasModulate, 2D SDF shadows, normal maps)colortype in #1;Vec.*in #2.Software 2D light-accumulation, expressed as ECS components — deterministic and diffable.
Shipped in
a71279a— theLight.*namespace: a deterministic software light-accumulation pass over the framebuffer, the imperative surface the proposal sketched (Light.point,Light.ambient,Light.occlude).What landed (tiers 1 & 2):
Light.ambient(color)— the CanvasModulate: multiplies the whole scene toward a tint (channel * tint / 255) for night/cave mood before lights add brightness back.Light.point(x, y, radius, color, energy)— additive radial light, linear falloff to zero at the radius, scaled by afixedenergy and clamped per channel at 255 so overlapping lights add without wrapping. Only the light's bounding box is touched.Light.occlude(x, y, w, h)+Light.clear_occluders()— hard shadows: a point light is cut wherever its ray to a pixel crosses (or the pixel lies inside) a registered rectangle. Clear-then-rebuild each frame, up to 64 occluders.It owns the framebuffer end to end (
rt_fb), so lighting is a rendering concern only — it never touches game state or replays — and it is fully deterministic (integer + Q16.16 fixed, integer isqrt for falloff so it's overflow-safe at any screen-scale radius). Same scene lights identically every run and in a headless render, so screenshots stay diffable.How it's built: engine in
runtime/native/light.ludic, spliced on demand (g_uses_light) the same way the regex/query runtimes are; namespace wired inemit_call.ludic. Docs atdocs/language/light/. A 14-assertion pixel-readback test (examples/library/lighting.ludic) is in the regression suite (x test, now 66 passing), andx check-impl/check-docscover the new pages.Deferred to follow-ups (tiers 3-4 + the ECS-native shape): normal-mapped sprites, soft shadows/penumbra, colored shadows/cookies, and a day/night
DirectionalLight2D. The Godot-styleLight2D/Occludercomponents consumed automatically by a built-in Render phase need an engine-owned system over user components — the same ECS hook #43 is blocked on. Until that lands,Light.*is the imperative escape hatch the proposal named for exactly this. Closing the core; the auto-component layer will ride along with the #43 work.