Skip to content

Feat/light source component#2007

Open
Dogebusters wants to merge 11 commits into
decentraland:mainfrom
Dogebusters:feat/light-source-component
Open

Feat/light source component#2007
Dogebusters wants to merge 11 commits into
decentraland:mainfrom
Dogebusters:feat/light-source-component

Conversation

@Dogebusters

@Dogebusters Dogebusters commented May 10, 2026

Copy link
Copy Markdown
Contributor

Scene LightSource support, runtime controls, and debug tooling

Summary

This PR adds runtime support and debugging tools for scene-authored LightSource components.

It introduces a Godot-side LightSource component that renders scene-authored point and spot lights, supports projector / shadow-mask textures for spot lights, and exposes runtime controls through Settings. The goal is to make scene lighting visible, testable, and safe to run on mobile.

The implementation focuses on three areas:

  1. Runtime LightSource rendering

    • Point lights
    • Spot lights
    • Projector / shadow-mask textures
    • Light removal / updates through CRDT changes
  2. Performance guardrails

    • Global lights on/off
    • Shadows on/off
    • Player/avatar-based activation range
    • Automatic activation range for intense lights
    • Global active light budget
    • Configurable maximum active lights
  3. Developer validation tools

    • Light Debug Gizmos
    • Runtime state indicators
    • Budget rank display
    • Range / intensity / projector texture information
    • Settings controls under Dev Tools

What was added

LightSource component support

This PR adds a reusable LightSource scene node backed by DclLightSourceComponent.

The component supports:

  • scene-authored point lights through OmniLight3D,
  • scene-authored spot lights through SpotLight3D,
  • authored light color,
  • authored intensity,
  • authored range,
  • authored spot inner / outer angles,
  • projector / shadow-mask textures for spot lights,
  • texture path resolution through scene content mapping,
  • a separate authored texture path for debug display,
  • cached projector textures so repeated texture usage does not trigger repeated downloads,
  • runtime light removal when the LightSource component is removed from CRDT state.

The Rust scene update pipeline now processes the LightSource component as part of the regular scene update state machine, creates the Godot light node when needed, updates it when CRDT data changes, and removes it when the component is cleared.


Runtime light controls

Settings now expose runtime controls for scene lights.

Control Purpose
Lights Enables or disables scene light rendering globally.
Light Shadows Enables or disables dynamic shadow contribution independently from the light itself.
Max Active Lights Limits how many scene lights are allowed to render at the same time.
Light Debug Gizmos Shows visual debug helpers for authored lights.
Auto Activation Range Expands the activation range for high-intensity lights when needed.
Global Light Budget Enables global ranking of active lights by distance to the player/avatar reference position.

These controls are intentionally runtime-editable, so developers can test lighting behavior without restarting the client or changing scene code.


Runtime behavior

Player/avatar-based activation range

Scene lights are only rendered when the player/avatar reference position is inside the light activation range.

The activation range can come from either:

  • the authored light range, or
  • an automatically computed range based on light intensity when Auto Activation Range is enabled.

This keeps lights outside the relevant player area disabled, while still allowing important bright lights to become active even if their authored range is small.


Auto Activation Range

Some scenes author lights with high intensity but relatively small range values. In practice, those lights may still need to be considered relevant from farther away.

When Auto Activation Range is enabled, the component computes an additional intensity-based activation range and uses the larger of:

  • authored light range,
  • intensity-derived range.

This helps bright lights activate more consistently without forcing every authored light to be active globally.


Global light budget

The global light budget keeps only the closest eligible lights active up to the configured limit.

A light is considered eligible when:

  • it exists and has been initialized,
  • it is enabled by runtime state,
  • it is inside activation range of the avatar/player reference position,
  • and global light budgeting is enabled.

Eligible lights are sorted by distance to the avatar/player reference position. Only the closest max_active_lights are allowed to render.

This prevents scenes with many authored lights from enabling all of them at once, which is especially important on mobile.


Shadow handling

The implementation separates light visibility from dynamic shadow casting.

This is needed because Godot requires shadow_enabled = true for SpotLight3D.light_projector to work. If shadows are disabled by the developer setting, projector lights may still keep shadow_enabled = true so the projector texture works, while dynamic shadow casters are removed through shadow_caster_mask.

This allows:

  • scene lights to render without dynamic shadows,
  • projector textures to continue working,
  • shadow contribution to be controlled independently,
  • and debug/testing to compare light-only vs. light-with-shadows behavior.

Projector / shadow-mask textures

Spot lights can use a projector texture from the authored shadow_mask_texture.

The implementation:

  • resolves scene-authored texture paths through content mapping,
  • supports already-resolved HTTPS texture URLs,
  • keeps the original authored path available for debug display,
  • avoids reloading the same projector texture repeatedly through a static texture cache,
  • applies the texture to SpotLight3D.light_projector.

This is useful for scenes that rely on shaped light masks, projected patterns, or authored spotlight effects.


Light Debug Gizmos

Light Debug Gizmos make scene-authored lights visible in the world for validation.

The gizmos show:

  • point light position,
  • spot light cone direction,
  • spot inner cone,
  • spot outer cone,
  • light intensity,
  • authored/render activation range,
  • projector texture path when available,
  • global budget rank,
  • distance to avatar/player when budgeted,
  • active/inactive state.

The status marker uses:

Marker Meaning
🟢 In range and allowed by the global budget.
🟡 In range, but excluded by the global light budget.
🔴 Out of range, inactive, or missing a valid player/avatar reference.

This makes it much easier to debug scenes where lights are invisible, too far away, budgeted out, hidden inside scene geometry, or unexpectedly expensive.


Settings integration

The Light Debug tools are exposed through the Dev Tools section in Settings.

This gives developers a quick way to validate lighting behavior without code changes:

  • toggle all scene lights,
  • toggle dynamic shadows,
  • enable / disable debug gizmos,
  • enable / disable automatic activation range,
  • enable / disable global light budgeting,
  • increase / decrease the maximum active light count.

The UI also adapts the max-active-lights stepper size for portrait vs. landscape layout.


Why this is needed

Some scenes already author LightSource data, but without runtime support and debugging tools it is difficult to validate whether lights are:

  • broken or causing unexpected performance issues,
  • missing,
  • disabled,
  • out of range,
  • too expensive,
  • hidden inside scene geometry,
  • blocked by budget,
  • using unexpected projector textures,
  • or behaving differently with shadows on/off.

This PR makes scene-authored lighting visible and controllable while adding guardrails for performance.

The result is a safer path to enabling scene-authored lighting across desktop and mobile.


Validation scope

Visual validation was performed across multiple scenes and coordinates:

Scene / Coordinate Open link
rituals.dcl.eth Open scene
doriangray.dcl.eth Open scene
41,55 4RC Open scene
jeffgoldblum.dcl.eth Open scene
148,49 RAGE Open scene

Mobile screenshots were captured on Nothing Phone (1).


Visual validation

Dev Tools UI

The Light Debug tools are exposed through the Dev Tools section in Settings.

Dev Tools Light Debug Settings

Lighting state validation

The same scene setup was validated with lighting disabled, lighting enabled without shadows, and lighting enabled with shadows.

lights off lights on, shadows off lights on, shadows on
lights off lights on, shadows off lights on, shadows on

Light Debug Gizmos reveal hidden or hard-to-identify lights

Activating Light Debug Gizmos makes previously hidden or hard-to-identify light entities visible in the scene. In some scenes, there are more authored lights than expected. Genesis Plaza is a good example: even with light rendering disabled, these LightSource entities still exist and have to be created, tracked, and updated, so they can still add scene overhead before they ever contribute visible lighting.

Light Debug Gizmos reveal hidden entities

Mobile visual validation

All screenshots below were captured on Nothing Phone (1).


rituals.dcl.eth

Open scene: rituals.dcl.eth

Scene comparison A

lights off lights on
rituals reference view A rituals updated lighting result A

Scene comparison B

lights off lights on
rituals reference view B rituals updated lighting result B

doriangray.dcl.eth

Open scene: doriangray.dcl.eth

doriangray focused lighting validation view

Low graphics

lights off lights on, shadows off lights on, shadows on
doriangray lights off doriangray lights on, shadows off lights on, shadows on

Medium graphics

Medium / lights off Medium / lights on, shadows off Medium / lights on, shadows on
doriangray medium lights off doriangray medium lights on, shadows off doriangray medium lights on, shadows on

High graphics

High / lights off High / lights on, shadows off High / lights on, shadows on
doriangray high graphics lights off doriangray high graphics lights on, shadows off doriangray high graphics lights on, shadows on
doriangray full-width inspection frame

41,55 / 4RC

Open scene: 41,55

lights off lights on
41,55 lights off 41,55 lights on
41,55 full-width inspection frame

jeffgoldblum.dcl.eth

Open scene: jeffgoldblum.dcl.eth

lights off lights on
jeffgoldblum lights off jeffgoldblum lights on

148,49 / RAGE

Open scene: 148,49

Light Debug Gizmos enabled

148,49 light debug gizmos enabled

Medium graphics comparison

lights off lights on
148,49 medium graphics reference 148,49 updated lighting debug result

Dogebusters added 10 commits May 9, 2026 18:35
Registers the new `light_source` component module so it is compiled and can be imported by the rest of the crate.

Signed-off-by: MeanGreenDoge <118309896+Dogebusters@users.noreply.github.com>
Added `LightSource` to `SceneUpdateState` so the scene runner can process light component updates in order with the rest of the SDK components.

Signed-off-by: MeanGreenDoge <118309896+Dogebusters@users.noreply.github.com>
Adds `LightSource` to the scene processing loop so light component updates are applied through the regular time-budgeted component pipeline.

Signed-off-by: MeanGreenDoge <118309896+Dogebusters@users.noreply.github.com>
Adds a Lights Debug section to the Dev Tools settings panel with controls for enabling lights, shadows, debug gizmos, auto activation range, global light budget, and max active lights. + seperator for LiveKit

Signed-off-by: MeanGreenDoge <118309896+Dogebusters@users.noreply.github.com>
The local avatar periodically updates `DclLightSourceComponent` with its global position, which is used as the reference point for light activation range and budget decisions.

Signed-off-by: MeanGreenDoge <118309896+Dogebusters@users.noreply.github.com>
d
Adds a Godot scene wrapper for the DCL Light Source component so it can be instantiated as a Node3D in the scene hierarchy.

Signed-off-by: MeanGreenDoge <118309896+Dogebusters@users.noreply.github.com>
This PR adds the runtime side of the DCL Light Source component. The new `DclLightSourceComponent` is responsible for creating and updating Godot lights from scene-authored light data, while keeping rendering controlled by runtime settings, avatar distance, and a global light budget.

### What this adds

- Adds support for runtime-created DCL point and spot lights.
  - Point lights are represented as `OmniLight3D`.
  - Spot lights are represented as `SpotLight3D`.
  - The component keeps the authored entity transform as the source of truth and does not apply extra local rotation to the generated Godot light.

- Adds global runtime light settings.
  - Lights can be enabled or disabled globally from the settings/debug UI.
  - Shadows can be toggled separately from light rendering.
  - The maximum number of active DCL lights can be changed at runtime.
  - Debug gizmos, auto activation range, and global light budget can be toggled independently.

- Adds avatar-distance-based light activation.
  - DCL lights only render when the local avatar is inside the calculated activation range.
  - The activation range uses the authored light range, and can optionally be expanded based on intensity.
  - Debug gizmos remain visible even when the actual `Light3D` is not rendering, making it easier to inspect inactive lights.

- Adds a global light budget.
  - All registered DCL lights are ranked by distance to the local avatar.
  - Only the closest `max_active_dcl_lights` candidates are allowed to render.
  - Lights outside the avatar activation range are excluded from the budget.
  - This avoids enabling too many real Godot lights at once while still allowing scenes to define multiple lights.

- Adds projector texture support for spot lights.
  - Spot lights can receive a projector texture.
  - Projector textures are loaded from local resources or HTTP URLs.
  - HTTP-loaded projector textures are cached globally to avoid loading the same texture multiple times.
  - Loaded projector textures are resized to a fixed resolution before being assigned.

- Handles the Godot projector/shadow limitation.
  - Godot requires `shadow_enabled = true` for `light_projector` to work.
  - Because of that, disabling DCL light shadows does not always disable `shadow_enabled` directly.
  - Instead, dynamic shadow casting is disabled through `shadow_caster_mask`.
  - This allows projector lights to keep working while still preventing dynamic shadow casters when shadows are disabled.

- Adds debug visualization for DCL lights.
  - Spot lights show fixed-size outer and inner cone gizmos.
  - Point lights show a fixed-size sphere gizmo.
  - The gizmos intentionally do not scale to the real light range, so they stay readable in the scene.
  - A status marker shows whether a light is active, out of range, or inside range but blocked by the light budget.
  - Debug labels show intensity, authored range, computed activation range, projector texture path, and budget rank/distance.

- Adds runtime state separation.
  - Rust/runtime code can disable individual lights without fighting the avatar-range and global settings logic.
  - The component keeps separate flags for global system state, rendering state, per-light runtime state, shadow state, range activation, and budget activation.

### Why

DCL scenes can now define Light Source components, but enabling every authored light directly would be too expensive and difficult to debug. This implementation adds the rendering bridge to Godot while also introducing runtime controls to keep the feature safe for performance.

The activation range and global light budget make sure only relevant nearby lights are rendered. The debug gizmos make it possible to inspect scene-authored lights even when the real `Light3D` is currently disabled by range, budget, or global settings.

Signed-off-by: MeanGreenDoge <118309896+Dogebusters@users.noreply.github.com>
This adds the Rust-side processing for the SDK `LightSource` component and connects it to the Godot runtime component.

The new `light_source.rs` module reads dirty `LIGHT_SOURCE` CRDT updates, creates a `LightSource` Godot node when needed, updates it when the component changes, and removes it when the component is deleted from the entity.

### What this does

- Processes dirty `LIGHT_SOURCE` LWW component updates from the scene CRDT state.
- Ensures each entity with a `LightSource` component has a matching Godot `LightSource` child node.
- Instantiates `res://src/decentraland_components/light_source_component.tscn` for new light components.
- Calls into the GDScript `DclLightSourceComponent` API to configure the actual runtime light.
- Removes and frees the Godot light node when the SDK component is removed.
- Respects the scene processing time budget by stopping mid-batch and re-queuing the remaining dirty light updates.

### Light mapping

The SDK light data is mapped into the Godot runtime component as follows:

- `PbLightSource.color` is converted into a Godot `Color`.
- `intensity` falls back to `300.0` when not provided.
- `range` falls back to `15.0` when not provided.
- `Point` lights call `set_point(...)` on the Godot component.
- `Spot` lights call `set_spot(...)` with inner and outer angles.
- Unknown or missing light types fall back to point lights.

### Projector / shadow mask texture support

For spot lights with `shadow_mask_texture`, this code resolves the authored texture source into a usable Godot texture path.

- Scene-local paths are resolved through the scene content mapping.
- If a matching content hash is found, the final URL is built from the scene content base URL.
- The original authored path is also passed to Godot as the display path for debug labels.
- The Godot component only reloads the projector texture when the resolved path changes.

This keeps runtime texture loading in GDScript while Rust remains responsible for resolving scene content paths.

### Why the node is added before setting texture/debug data

When a new light node is created, it is added to the entity node before configuration:

```rust
node_3d.add_child(&new_light_node.clone().upcast::<Node>());

Signed-off-by: MeanGreenDoge <118309896+Dogebusters@users.noreply.github.com>
Added a new Lights Debug section to the Dev Tools settings panel.

This exposes runtime controls for the Light Source system:

- Enable / disable scene lights rendering.
- Enable / disable light shadows.
- Toggle light debug gizmos.
- Toggle automatic activation range calculation.
- Toggle the global light budget system.
- Adjust the maximum number of active lights using a stepper control.

The settings UI now syncs its initial state from `DclLightSourceComponent.get_light_settings()` and applies changes through `DclLightSourceComponent.apply_light_settings(...)`, so all existing light instances are updated immediately when a setting changes.

The max active lights control is clamped between 0 and  64 and its plus / minus buttons are disabled at the boundaries. The stepper layout also adapts for portrait mode.

Signed-off-by: MeanGreenDoge <118309896+Dogebusters@users.noreply.github.com>
I accidentally placed the file in the wrong path initially, so I moved it to `scene_runner/components/light_source.rs`.

Signed-off-by: MeanGreenDoge <118309896+Dogebusters@users.noreply.github.com>

@kuruk-mm kuruk-mm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Dogebusters! Thank you for the contribution! This looks amazing.

We are not planning to implement Light Source at the moment due to performance concerns.

We plan to work on Renderer performance improvements over the coming weeks/months, as this is our current priority.

If you really want this to be merged, please:

  • Publish benchmark metrics so we can understand how much performance drops with lights enabled compared to without lights. You can check our current benchmarks in the code. Usually @manuelmaceira runs them, so you can look at his PRs and the tooling we have for that in the repo.
  • Add an option to enable/disable lights in the graphics settings, and make sure it is turned off by default. Please do not attach it to any profile.

Once we have benchmarks that validate the performance impact is acceptable, we can review the code.

Another option is to wait, and we can revisit this PR after July/August or after the performance improvements are completed.

@rodrigo685 rodrigo685 linked an issue Jul 14, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scene Dynamic Lights

2 participants