Files
Emil 518b3d0ce0
Native and manual checks / native (windows-2025) (push) Waiting to run
Native and manual checks / native (ubuntu-24.04) (push) Failing after 37s
Native and manual checks / manual (push) Successful in 33s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Link final P1 and P3 records from the Manual
2026-09-24 05:24:53 +03:00

8.3 KiB
Raw Permalink Blame History

Light a 3D scene

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.

Light kind Coverage Shadow cost
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

The Light component's fields are:

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

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 explains Inspector editing, Undo, and save conflicts.

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 and the Player profile.

Faset requires sampled D32 depth images for the renderer itself. On a supported device, it uses separate sun and local atlases, normally 2048×2048 pixels each. If an optional atlas cannot use that size, the renderer tries 1024×1024. When the local atlas cannot be allocated, or the sun atlas cannot be allocated but a 1×1 sampled D32 fallback image can, affected lights remain unshadowed and report unavailable shadow views. A device without sampled D32 support cannot start this renderer. Scheduled atlas tiles are cleared and redrawn each frame; there is no persistent shadow cache yet. Both 2048² atlases are currently allocated when the renderer starts; together they account for 32 MiB of explicit Vulkan allocation even if a scene does not render a shadow face. The clean Release reference records the resulting memory budget miss in both small example games. 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, the original Forward+ study, and the combined-code repeat.

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:

{
  "document": "REPLACE_WITH_DOCUMENT_ID",
  "revision": 4,
  "idempotency_key": "add-red-point-light",
  "operations": [{
    "op": "component.add",
    "entity": "REPLACE_WITH_ENTITY_ID",
    "type": "faset.light",
    "fields": {
      "kind": "point",
      "color": [1, 0.15, 0.1, 1],
      "intensity": 8,
      "range": 6,
      "shadow_priority": 2
    }
  }]
}

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 covers revisions, retries, and transactions.

Supply lights directly from C++

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:

faset::render::Snapshot snapshot;
snapshot.authored_lights_present = true;

faset::render::LocalLight point;
point.kind = faset::render::LocalLight::Kind::Point;
point.stable_id = "level/torch";
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;
spot.kind = faset::render::LocalLight::Kind::Spot;
spot.stable_id = "level/lamp";
spot.position = {2, 3, 0};
spot.direction = {0, -1, 0};
spot.inner_angle = 0.25f;
spot.outer_angle = 0.55f;
spot.intensity = 5;
spot.range = 9;
snapshot.local_lights.push_back(spot);

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 for the APIs available to game code.