Lighting
Hard-edged light shafts through windows and doorways. Furniture carves them as you drag — occluders are the same AABBs the level design solver maintains, including tip. Doorways use the same shaft treatment as windows.
Occlusion from gameplay AABBs
Carved tunnels & the tip
The carve, from eye level
Froxel fog
Godot’s volumetric fog is a froxel grid: camera-aligned volume texture, sun shadow maps per cell, temporally smoothed. Soft edges only — a hard silhouette would sit between froxels and get smeared by the temporal filter.
Shafts are a separate system. Froxels stay at low resolution for ambient gloom.
Beams
Each beam is a parallelepiped — aperture rectangle sheared along the sun direction. A non-orthonormal shear basis maps world space to a unit cube in beam-local space; inside-test is three axis comparisons. No shadow map, no raymarch on the CPU path — ALU only; runs on Compatibility / web too.
# CPU: aperture right × up × sun-scaled depth → unit cube under xf
var shear := Basis(wall_tangent * w, Vector3.UP * h, sun_dir * depth)
var xf := Transform3D(shear, center + sun_dir * (depth * 0.5))
var inv := xf.affine_inverse() # columns + origin uploaded as uniforms
// GPU: world → beam-local, then three comparisons against the unit cube
vec3 local = beam_cx[b] * p.x + beam_cy[b] * p.y + beam_cz[b] * p.z + beam_org[b];
if (all(lessThan(abs(local), vec3(0.5)))) {
// fragment is inside this beam
}
The shear basis
(u, v, w) turns “is this fragment lit?” into three comparisons — pure ALU, no shadow map.Solver AABBs upload as occluders (slight shrink so pieces don’t self-shadow). Shadow tunnels update mid-drag. Godot has no max-blend; overlapping additive beams double-brighten, so one union-AABB cover mesh shades each fragment once — max across ≤10 beams / ≤32 occluders in the shader.
Current path: prism mesh per beam, raymarch inside, slab-test furniture AABBs per sample.
Bugs that shaped the path
Vanishing beam. Standard back-face culling: step inside a beam, look at the window — shafts disappear. Inside a convex hull every visible face is a back-face. Fix: cull front faces, shade back faces, march the segment between.
The vanishing beam
depth_draw_never does not disable depth tests.Missing chunks. Tipped mattress → wedges of beam behind it gone.
depth_draw_never only suppresses depth writes, not tests — hull fragments
behind furniture die in the z-buffer before the shader runs. Fix:
depth_test_disabled + manual clamp against scene depth in the shader.
Flooded doorway. Haze volume at 2× room depth flooded the door opening. Fix: fit the fog volume to the room.
Sun + shadows
Window mullion shapes on furniture come from the ordinary sun + shadow map. Soft penumbras against hard shafts look wrong, so the sun uses zero angular distance and lower shadow filter quality.