feat(light): render-quality tiers 3-4 — cones, falloff, soft shadows, gels, normals, day/night (#49)
The remaining lighting tiers from the original proposal, all extending the deterministic accumulation core (light.ludic) — no new ECS plumbing: - Light.spot: cone / flashlight lights (direction + spread degrees), with a self-contained integer atan2-in-degrees and a feathered edge. - Light.falloff: a brightness-ramp exponent (1 linear, 2 quadratic, …) via repeated fixed multiply. - Light.soft: soft shadows — an area-sampled light so an occluder edge fades through a penumbra instead of a hard cut. - Light.gel + Light.clear_gel: colour cookies — a light gels from its centre colour to a rim colour. - Light.normal + Light.clear_normals + Light.height: a normal G-buffer so surfaces shade by facing (N·L), not distance alone (tier 3). - Light.time_of_day: a day/night ambient ramp from a single 0..1 value. The engine lighting system (systems_light.ludic) consumes matching optional Light2D fields — direction/spread/falloff/softness/gel — each defaulting off so an older five-field Light2D lights exactly as before. Every tier is integer + Q16.16 fixed, so scenes light identically on every run and headless. Worked example + regression: examples/library/light_tiers.ludic (1 1 1 1 1 1 1 1 1). Nine new docs/language/light pages. Full suite 76 passed, self-host C-free fixpoint intact, no golden drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
377b6d1186
commit
382826889f
18 changed files with 7752 additions and 6502 deletions
|
|
@ -88,12 +88,23 @@ function esys_light2d() -> void {
|
|||
}
|
||||
|
||||
# 3. lights — additively accumulate each `Light2D` as a radial glow, cut by the
|
||||
# occluders registered above (hard shadows).
|
||||
# occluders registered above (hard shadows). Beyond the core five fields, a
|
||||
# Light2D may carry optional tier fields, each defaulting off when absent so an
|
||||
# older five-field component still lights exactly as before:
|
||||
# direction, spread — a cone/spot (spread < 0 or absent = omnidirectional)
|
||||
# falloff — brightness ramp exponent (1 = linear, absent = 1)
|
||||
# softness — penumbra radius in px (0 / absent = hard shadows)
|
||||
# gel — outer rim colour for a colour cookie (< 0 / absent = off)
|
||||
let lxf = World.field_id(pl, "x")
|
||||
let lyf = World.field_id(pl, "y")
|
||||
let lrf = World.field_id(pl, "radius")
|
||||
let lcf = World.field_id(pl, "color")
|
||||
let lif = World.field_id(pl, "intensity")
|
||||
let ldf = World.field_id(pl, "direction")
|
||||
let lsf = World.field_id(pl, "spread")
|
||||
let lff = World.field_id(pl, "falloff")
|
||||
let lkf = World.field_id(pl, "softness")
|
||||
let lgf = World.field_id(pl, "gel")
|
||||
var e = World.query_next(pl, 0)
|
||||
while e >= 0 {
|
||||
let x = esys_pos_x(e, pl, lxf)
|
||||
|
|
@ -104,7 +115,23 @@ function esys_light2d() -> void {
|
|||
if lcf >= 0 { color = World.get(e, pl, lcf) }
|
||||
var energy = fixed(1) # full intensity default
|
||||
if lif >= 0 { energy = fixed(World.get(e, pl, lif)) / fixed(100) }
|
||||
light_point(x, y, radius, color, energy)
|
||||
|
||||
# tier controls: apply per light, resetting to defaults each time so one
|
||||
# cone/soft light does not bleed its settings onto the next.
|
||||
var falloff = 1
|
||||
if lff >= 0 { falloff = World.get(e, pl, lff) }
|
||||
light_set_falloff(falloff)
|
||||
var soft = 0
|
||||
if lkf >= 0 { soft = World.get(e, pl, lkf) }
|
||||
light_set_soft(soft)
|
||||
if lgf >= 0 { light_set_gel(World.get(e, pl, lgf)) } else { light_clear_gel() }
|
||||
|
||||
var spread = 0 - 1
|
||||
if lsf >= 0 { spread = World.get(e, pl, lsf) }
|
||||
var direction = 0
|
||||
if ldf >= 0 { direction = World.get(e, pl, ldf) }
|
||||
if spread >= 0 { light_spot(x, y, radius, color, energy, direction, spread) }
|
||||
else { light_point(x, y, radius, color, energy) }
|
||||
e = World.query_next(pl, e + 1)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue