Squash of 25 work commits (Sep 1–12):237da90render: the sprite lane hands the screen draw back the way it found it895b9a7particles: an emitter can ride a body's own frame914471fai-hub: a feed session whose last socket left ends on its idle timeout; skin: parent, skinned centroid and a nodes-only rig for retargets3fcccf3sim: the whole world is implicitly editable, and one seam says where the ground is5692fabsim: the landform world proves itself — walker through the tunnel includedf4af6dfsim: terrain knows who changed it — a plan layer over player historyadbb078render: a water volume can be physics without a picturec17480frender: a non-rigid body may carry its own orientationacad401sim: an agent with no route holds and retries instead of walking into the wall22b2bf9sim + chat: the composed world surface takes a map floor; the chat gets plan toolsfaf8112web path: the tessellator's lap timer, the trace span and the fusion cycle timer have no clock on the webd3472ebtsdf: the clock-taking XR helpers are native-only — the browser has no depth camera and no Instant4946c9esim + render: a repaint is not a world edit — colour and glow restyles never rebake the lightmap4317f58sim + render + chat: walk decks as a surface, the filmed body is no obstruction, the brief never asks9791279render: support rigged models and custom materials across viewers3ce2792sim: use deck geometry for collision and sensing c8b79ff Add portable PBR, rig and soft-body authoring support 108d423 Add transactional polygon modeling and editable asset documents 38f4d3b Refine editable modeling and firm yarn character behavior 08a2637 sim: add entity-owned lights and vehicle headlights 00d96a7 render: add clustered lights, local shadows and incremental GI cf916af render: import glTF asset extensions and wire clustered GI c6d2cca model: cut transaction memory and raise capacity limits 41af47a raytrace: add a CPU probe-ray BVH budget example c963d0a libs: the game sim splits into makepad-scene and makepad-soft-body; render, model, fab and the asset importer retarget Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
101 lines
5.3 KiB
Markdown
101 lines
5.3 KiB
Markdown
# Clustered forward lighting
|
||
|
||
`Renderer` defaults to clustered local lights. Geometry submission, static slabs,
|
||
instancing, and GPU skinning are unchanged: one forward material pass, with
|
||
separate sun-shadow passes. No G-buffer, geometry prepass, compute shader, depth
|
||
readback, or per-object light selection is required by clustering.
|
||
|
||
This follows the CPU-assigned clustered-forward approach described in
|
||
[Filament's rendering documentation](https://google.github.io/filament/main/filament.html).
|
||
It is a first lighting increment, not Filament feature or visual parity.
|
||
|
||
## Lighting and shadow policy
|
||
|
||
| Local lighting | Sun mode | World lighting work |
|
||
| --- | --- | --- |
|
||
| Clustered (default) | Realtime | CSM; no world atlas or CPU probe bake |
|
||
| Clustered | OnChange | Sun-only atlas and legacy CPU probes |
|
||
| Legacy (`MAKEPAD_CLUSTERED=off`) | Either | Original eight-light selection and lamp atlas |
|
||
|
||
Baked per-model AO is retained in every mode. Clustered lights reach primitives,
|
||
terrain, diffuse/PBR/custom models, and skinned characters per fragment. PBR
|
||
materials evaluate local GGX specular as well as diffuse. Explicitly prelit
|
||
materials retain their existing unlit/baked-color semantics.
|
||
|
||
Local lights can request budgeted realtime shadows; see [Entity lights](ENTITY_LIGHTS.md)
|
||
for Splash headlights, point/spot fixtures and shadow budgets. Generic lights
|
||
default to unshadowed and can shine through walls. The sun retains its own tier.
|
||
Engine-authored lights use `(1 - distance / radius)^2`; imported glTF punctual
|
||
lights use their separate photometric mode. Full linear HDR, exposure/tone
|
||
mapping, IBL and unified PBR materials remain follow-ups. Clustering does not
|
||
itself add MSAA.
|
||
|
||
## Budgets and stereo
|
||
|
||
`Renderer::set_cluster_config(ClusterConfig { ... })` controls XY tiles, depth
|
||
slices, lights per cluster, and total uploaded lights. Defaults are 16 × 9 × 24,
|
||
32 lights/cluster, and 1024 total. Hard limits are 32 × 24 × 32, 64 lights/cluster,
|
||
and 4096 total. `set_clustered_lighting(bool)` switches the complete legacy/new
|
||
lighting policy and invalidates old baked state. Policy survives realm changes.
|
||
|
||
Flat views use frustum tiles with logarithmic depth. The backend currently
|
||
late-latches XR eye matrices without exposing them to this host. XR therefore
|
||
uses a conservative world-space 3D grid over light influence, shared by both
|
||
eyes—not clusters derived from an unrelated flat camera. Large, dispersed worlds
|
||
make that fallback less selective. Exact stereo/per-eye frustum clustering needs
|
||
an eye-view snapshot API before it can replace this fallback.
|
||
|
||
Smaller `lights_per_cluster` bounds fragment cost but may visibly omit lights;
|
||
finer grids cost CPU/memory but can reduce false-positive light references. Start
|
||
device tuning with the counters, not a claimed universal Quest preset. This path
|
||
has not yet been benchmarked on a headset.
|
||
|
||
`RenderStats::clustered` / `Renderer::cluster_stats()` report build/upload-queue
|
||
CPU microseconds, uploaded bytes, occupancy, references, rejected lights, and
|
||
overflow. They do **not** measure GPU time. Per-cluster overflow retains lights
|
||
with the strongest conservative contribution estimate; equal scores retain the
|
||
earlier input. The global cap keeps the first valid input lights. Counts are
|
||
explicit because neither cap promises all lights survive. Overflow logs are
|
||
throttled; `MAKEPAD_CLUSTER_STATS=1` also logs frame counters periodically.
|
||
|
||
## Texture ABI
|
||
|
||
One nearest-sampled RGBA32F texture contains cluster headers, packed light
|
||
indices (four scalar indices per texel), then three texels per light:
|
||
position/radius, RGB/angular falloff, and normalized emission direction/mode. Headers
|
||
store scalar index offset/count. CPU list storage and texture upload vectors are
|
||
reused. Integer-valued floats are exactly representable within the hard caps.
|
||
The shader mixin and CPU packing live together in `src/clustered.rs`.
|
||
|
||
Local shadows use a separate nearest-sampled metadata texture (one light header,
|
||
four camera rows per face) and a depth atlas, preserving the light-data stride.
|
||
Inherited texture bindings are resolved by name for derived PBR materials.
|
||
|
||
## Reproduce
|
||
|
||
Build the sandbox in its own workspace, then launch from the checkout root:
|
||
|
||
```sh
|
||
# In apps/sandbox:
|
||
cargo build --release -p makepad-sandbox
|
||
# From the checkout root:
|
||
SANDBOX_WORLD=clustered MAKEPAD_CLUSTER_STATS=1 ./apps/sandbox/target/release/makepad-sandbox --remote
|
||
```
|
||
|
||
The fixture supplies 256 moving lights and a single large floor, independent of
|
||
downloaded models. `MAKEPAD_CLUSTER_DEMO_LIGHTS=N` changes the count;
|
||
`MAKEPAD_CLUSTER_DEMO_FREEZE=1` fixes light motion for A/B images. Add
|
||
`MAKEPAD_CLUSTERED=off` for the old path. That comparison deliberately shows
|
||
missing lights in the old eight-slot path; it is **not** equal-quality GPU work.
|
||
F8 switches sun modes. Finish owned test instances via the printed remote port's
|
||
`/gq` endpoint.
|
||
|
||
```sh
|
||
cargo test --release -p makepad-render
|
||
cargo test --release -p makepad-render clustered::tests::benchmark_build_and_pack -- --ignored --nocapture
|
||
```
|
||
|
||
Coverage includes more than eight overlapping lights, depth/tile boundaries,
|
||
rotated and orthographic cameras, XR/world-grid lookup, empty/invalid inputs,
|
||
bounded overflow, texture packing, material shader compilation/binding order,
|
||
and mode/realm lifecycle. The ignored benchmark measures CPU build+pack only.
|