Document Forward+ measurements and Linux lighting evidence
This commit is contained in:
+106
-37
@@ -1,37 +1,101 @@
|
||||
# Add lights to a 3D scene
|
||||
# Light a 3D scene
|
||||
|
||||
Add a **Light** component to a scene entity. The entity's transform places a point
|
||||
or spot light; its rotation aims a spot light along local negative Z. A directional
|
||||
light uses the entity's orientation. Light colors and intensity contribute to the
|
||||
mesh's linear PBR illumination before tone mapping. Sprites and UI retain their
|
||||
unlit tint.
|
||||
Select an entity in the **Scene** tree, choose **+ Add Component** in the
|
||||
**Inspector**, and add **Light**. Its Transform places a point or spot light. A
|
||||
spot light points along the entity's local negative Z axis; a directional light
|
||||
uses the entity's orientation. Lights affect 3D meshes in linear PBR shading
|
||||
before tone mapping. Sprites and Editor UI remain unlit.
|
||||
|
||||
The version-1 `faset.light` component has three `kind` values:
|
||||
|
||||
| Kind | Position and direction | Useful fields |
|
||||
| Light kind | Coverage | Shadow cost |
|
||||
| --- | --- | --- |
|
||||
| `directional` | Direction from the entity transform | `color`, `intensity`, `casts_shadow` |
|
||||
| `point` | Position from the entity transform; illuminates every direction | `color`, `intensity`, `range` |
|
||||
| `spot` | Position and local negative-Z direction | `color`, `intensity`, `range`, `inner_angle`, `outer_angle` |
|
||||
| `directional` | A sun-like direction, independent of position | Up to four cascade tiles |
|
||||
| `point` | All directions within `range` | Six local-atlas tiles, assigned together |
|
||||
| `spot` | A cone within `range` | One local-atlas tile |
|
||||
|
||||
Angles are radians. A spot's inner angle must not exceed its outer angle. Intensity
|
||||
must be nonnegative and range positive. `enabled: false` keeps the component in the
|
||||
scene without contributing light. The `shadow_priority` integer is reserved for the
|
||||
bounded local-shadow scheduler; it does not change brightness.
|
||||
The Light component's fields are:
|
||||
|
||||
In the current rendering checkpoint, one enabled directional light can cast the
|
||||
existing single-map shadow. Point and spot lights illuminate meshes but do not yet
|
||||
cast shadows. The [P3 lighting plan](https://github.com/emil28092005/Faset_Engine/blob/main/docs/superpowers/plans/2026-09-24-p3-lighting.md)
|
||||
tracks cascades and the bounded local-shadow atlas. A scene with no Light component
|
||||
keeps the legacy white sun so older projects retain their appearance. Adding any
|
||||
Light component, even a disabled one, turns off that compatibility fallback. If
|
||||
several directionals are enabled, Faset chooses the one with the smallest stable
|
||||
entity ID and reports a diagnostic for the others.
|
||||
| Field | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `kind` | `directional` | `directional`, `point`, or `spot` |
|
||||
| `enabled` | `true` | A disabled light contributes no illumination |
|
||||
| `color` | `[1, 1, 1, 1]` | RGB illumination color; alpha is part of the schema color value |
|
||||
| `intensity` | `1` | Nonnegative brightness |
|
||||
| `range` | `10` | Positive reach of point and spot lights |
|
||||
| `inner_angle` | `0.35` | Full-strength spot cone half-angle, in radians |
|
||||
| `outer_angle` | `0.7` | Outer spot cone half-angle, in radians; must be at least `inner_angle` |
|
||||
| `casts_shadow` | `true` | Allow this light to use its shadow atlas |
|
||||
| `shadow_priority` | `0` | Higher local-light selection and shadow priority; does not change brightness |
|
||||
|
||||
## Author a point light through MCP
|
||||
The Inspector validates the spot angles together. Their allowed outer limit is
|
||||
below π/2 radians. A point light does not depend on the entity's rotation.
|
||||
After editing the light or Transform, save the scene as usual. [Editor
|
||||
workspace](workspace.md) explains Inspector editing, Undo, and save conflicts.
|
||||
|
||||
Use `faset_schema` to inspect the current field IDs, then send a `faset_scene_edit`
|
||||
batch with the document ID, current revision, and target entity ID. For example:
|
||||
## Sun shadows and compatibility
|
||||
|
||||
With a 3D scene camera, a shadow-casting directional light uses four cascades
|
||||
covering the camera near plane through at most **80 world units**, or the camera
|
||||
far plane if it is closer. Faset blends samples near cascade splits and snaps
|
||||
each shadow projection to texels to reduce shimmer during small camera moves.
|
||||
Objects outside the camera view can still cast into a visible receiver: shadow
|
||||
visibility uses each light's view and the source mesh's LOD 0, separately from
|
||||
the main camera's Direct or GPU visibility result. A low-level renderer Snapshot
|
||||
without an explicit camera frustum uses one compatibility sun view.
|
||||
|
||||
Only one enabled directional light is used. If there are several, Faset chooses
|
||||
the one with the smallest stable entity ID and reports the ignored lights. A
|
||||
scene with **no Light component** retains the older white sun. Adding any Light
|
||||
component, including a disabled one, suppresses that compatibility sun. Thus a
|
||||
local-only scene does not receive an unexpected directional light.
|
||||
|
||||
## Local shadow capacity and fallbacks
|
||||
|
||||
The renderer accepts at most **128** local lights per frame. It sorts candidates
|
||||
by `shadow_priority` (highest first), then projected influence, then stable ID.
|
||||
The `omitted_local_lights` counter reports lights beyond this limit; an omitted
|
||||
light contributes no illumination. All authored light records are validated,
|
||||
including candidates past the limit.
|
||||
|
||||
The separate local shadow atlas has **16 tiles**. A spot consumes one; a point
|
||||
consumes six or none. Sun cascades and local shadows share a maximum of **4096
|
||||
caster draws** per frame. A light whose shadow group does not fit the remaining
|
||||
tiles or draw budget still illuminates, **without a shadow**. Disabling
|
||||
`casts_shadow` also keeps illumination while skipping that light's shadow work.
|
||||
The renderer reports requested faces, rendered faces, tiles, and drops by cause
|
||||
in [Diagnostics](diagnostics.md) and the [Player profile](profiling.md).
|
||||
|
||||
Faset uses separate sampled D32 sun and local atlases, normally 2048×2048 pixels
|
||||
each. If a device cannot use that size, the renderer tries 1024×1024; if a
|
||||
sampled depth atlas cannot be created, the affected lights fall back to unshadowed
|
||||
illumination and report unavailable shadow views. Scheduled atlas tiles are
|
||||
cleared and redrawn each frame; there is no persistent shadow cache yet.
|
||||
Sprite-only scenes, a missing sun, and a sun with `casts_shadow: false` skip sun
|
||||
shadow raster work.
|
||||
|
||||
## Local-light rendering path
|
||||
|
||||
The normal `Auto` setting uses the measured forward light scan. It is the
|
||||
current default for Editor and Player. A C++ renderer integration can explicitly
|
||||
set `RendererConfig::lighting_mode = LightingMode::Tiled` to build depth-free
|
||||
16×16 screen-tile lists on a capable Vulkan device. Each tile stores at most
|
||||
64 light indices in stable order. If more lights touch a tile, its fragment
|
||||
shader scans the complete submitted list, so an overflow never removes
|
||||
illumination. The path falls back to forward when no local lights are present
|
||||
or the compute/buffer requirements are unavailable. Sprites and UI stay unlit.
|
||||
|
||||
This explicit path can help when light ranges occupy small parts of the screen;
|
||||
it costs extra work when nearly every light covers nearly every tile. The
|
||||
fixed dense benchmark was slower after including tile construction, so there
|
||||
is no automatic scene-dependent switch yet. The Player profile reports
|
||||
`effective_lighting_path`, tile GPU time and grid size; optional Editor
|
||||
diagnostics also report stored candidates and overflowing tiles. See
|
||||
[Profiling](profiling.md) and the [measured Forward+ study](https://github.com/emil28092005/Faset_Engine/blob/main/docs/studies/23-p3-forward-plus-2026-09-24.md).
|
||||
|
||||
## Add a point light through MCP
|
||||
|
||||
MCP edits the **Editor document**, not entities in a running game. Use
|
||||
`faset_schema` to inspect the current field IDs, then send a `faset_scene_edit`
|
||||
batch with the document ID, its current revision, and a target entity ID:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -46,22 +110,24 @@ batch with the document ID, current revision, and target entity ID. For example:
|
||||
"kind": "point",
|
||||
"color": [1, 0.15, 0.1, 1],
|
||||
"intensity": 8,
|
||||
"range": 6
|
||||
"range": 6,
|
||||
"shadow_priority": 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Move the entity with its Transform component. `component.add` fills any omitted
|
||||
light fields from the version-1 schema; use `component.set` for later edits. See
|
||||
[MCP and command line](mcp.md) for revision and retry handling.
|
||||
Move the entity with its Transform component. `component.add` fills omitted
|
||||
fields from the schema; `component.set` changes an existing field. Save the
|
||||
document with `faset_document_save`. [MCP and command line](mcp.md) covers
|
||||
revisions, retries, and transactions.
|
||||
|
||||
## Supply lights directly from C++
|
||||
|
||||
When building a `faset::render::Snapshot` yourself, set
|
||||
`authored_lights_present` to suppress the compatibility sun in a local-only scene.
|
||||
Provide a stable ID for each light so future shadow scheduling remains independent
|
||||
of submission order.
|
||||
Code that constructs a renderer `faset::render::Snapshot` can supply lights
|
||||
directly. Set `authored_lights_present` even when the only authored Light is
|
||||
disabled, so the renderer does not synthesize the compatibility sun. Use stable,
|
||||
unique IDs for deterministic capacity decisions:
|
||||
|
||||
```cpp
|
||||
faset::render::Snapshot snapshot;
|
||||
@@ -74,6 +140,7 @@ point.position = {-2, 1.5f, 0};
|
||||
point.color = {1, 0.3f, 0.1f, 1};
|
||||
point.intensity = 8;
|
||||
point.range = 6;
|
||||
point.shadow_priority = 2;
|
||||
snapshot.local_lights.push_back(point);
|
||||
|
||||
faset::render::LocalLight spot;
|
||||
@@ -88,5 +155,7 @@ spot.range = 9;
|
||||
snapshot.local_lights.push_back(spot);
|
||||
```
|
||||
|
||||
The renderer submits at most 128 local lights per frame in stable-ID order. Later
|
||||
P3 work adds explicit overflow diagnostics and measured light-list optimization.
|
||||
This is the **renderer Snapshot API**, not a gameplay `Update()` method. The
|
||||
current gameplay scripting API does not expose live Light-component creation or
|
||||
modification; author lights in the Inspector or through Editor MCP. See
|
||||
[Gameplay scripting](../scripting/index.md) for the APIs available to game code.
|
||||
|
||||
Reference in New Issue
Block a user