Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADR 276: light sources #276

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft

ADR 276: light sources #276

wants to merge 4 commits into from

Conversation

nearnshaw
Copy link
Member

No description provided.

Copy link

cloudflare-workers-and-pages bot commented Nov 13, 2024

Deploying adr with  Cloudflare Pages  Cloudflare Pages

Latest commit: c408d59
Status: ✅  Deploy successful!
Preview URL: https://2b93c91a.adr-cvq.pages.dev
Branch Preview URL: https://light-sources.adr-cvq.pages.dev

View logs

@aixaCode aixaCode changed the title light soruces light sources Nov 14, 2024
content/ADR-253-light-sources.md Outdated Show resolved Hide resolved
content/ADR-253-light-sources.md Outdated Show resolved Hide resolved
content/ADR-253-light-sources.md Outdated Show resolved Hide resolved
@aixaCode aixaCode changed the title light sources ADR-253: light sources Nov 14, 2024
@aixaCode aixaCode changed the title ADR-253: light sources ADR 276: ight sources Nov 14, 2024
@aixaCode aixaCode changed the title ADR 276: ight sources ADR 276: light sources Nov 14, 2024
Copy link
Contributor

@leanmendoza leanmendoza left a comment

Choose a reason for hiding this comment

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

Thank you for putting this ADR for light sources! This is a crucial feature for Decentraland.
I want to take this place to debate this, given the previous work we've done in the protocol. Please take a look before reading the next part:

I had written a whole text to comment as a review, but then I split into different topics and comment each part of the ADR to keep the conversation cleaner :)

The following fields will be available on both types of light:

- Color: _Color4_ The color of the light
- Intensity: _number_ The luminosity value of the light, from 0 to 1.
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest reconsidering the use of arbitrary 0-1 values for light intensity in favor of physically-based units (lumens/m² or lux). This would provide several advantages:

  1. Predictability: Artists and developers can reference real-world light measurements

    • A 100W household bulb (~1200 lumens) = ~100 lux at 1m
    • Sunlight varies from 400 lux (sunrise) to 10,000 lux (midday)
  2. Consistency: Light behavior becomes more predictable across different implementations and scenes

  3. Realism: Natural light falloff can be accurately modeled using the inverse square law

  4. Interoperability: Better alignment with industry standards and other 3D tools

We have a working implementation of this approach in the protocol files that you might want to review: light.proto

The property could be renamed to illuminance to better reflect its physical meaning. The actual visual impact would still be subject to the renderer's exposure and tone mapping, but the relative relationships between lights would be preserved.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good suggestion, how about we use Lumens instead, it measure the total amount of light emitted by a source. This makes it the ideal metric for describing the output of a light source, independent of where or how it is used. Lumens are inherently tied to the light source itself, while lux depends on the environment. Since we want to define properties of the light sources rather than the effect of light on a surface, lumens are a better fit, in my option.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could use Lumens of course, that would be equivalent to divide the luxs by 4*π. But you should keep using lux for directional lights since the source is practically infinity. And this differentiation is the one glTF extension uses.

glTF extension:

intensity Brightness of light in. The units that this is defined in depend on the type of light. point and spot lights use luminous intensity in candela (lm/sr) while directional lights use illuminance in lux (lm/m2)

Copy link
Member Author

Choose a reason for hiding this comment

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

After talking it over and looking at other platforms, we changed the value to be measured in lumens and renamed to "brightness"

Copy link
Contributor

Choose a reason for hiding this comment

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

And luxs for directional/global?

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right, I was leaving that out bc global lights aren't in the plan for our first implementation, but it should be in the ADR.
I just included a clarification for that:

For Spot and Point light, this value is expressed in Lumens, for Global light, it's expressed in Lux units (lumens per square metre).


- Color: _Color4_ The color of the light
- Intensity: _number_ The luminosity value of the light, from 0 to 1.
- Range: _number_ The maximum distance that can be affected by the light source.
Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding light range, I suggest following GLTF's KHR_lights_punctual approach:

  1. The range should be optional (or even more not being added now). When unspecified, light naturally SHOULD attenuate following the inverse square law (1/d²)

  2. When specified, GLTF uses this attenuation formula after following the inverse square law, then:

attenuation = max(min(1.0 - (distance/range)^4, 1), 0) / distance^2

Within the range of the light, attenuation should follow the inverse square law as closely as possible, although some non-quadratic falloff near the edge of the range may be used to avoid a hard cutoff

This provides:

  • Natural physical falloff (1/d²)
  • Smooth cutoff at the range distance
  • Performance optimization opportunity (GPU can skip calculations beyond range)

Instead of having developers guess appropriate range values, we could:

  • Make range optional
  • Calculate a reasonable default cutoff based on illuminance
  • Allow manual override when needed for performance tuning

This aligns with industry standards while maintaining physically-based behavior.

Copy link
Member Author

Choose a reason for hiding this comment

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

We agree that the attenuation curve of light should mimic real life as closely as possible
The "range" field should serve as a hard cut-off, but not distort the attenuation curve.

It's mostly there to allow creators to optimize. The default value should be something reasonable, and most novice creators might not need to tweak it

Comment on lines 34 to 38
## Component description

We should create a `LightSource` component that, when added to an entity, tells the engine to shine a light from that entity’s Transform position.

A Type field will let you chose between _Spot_ and _Point_ light. We believe these two types of lights are enough for now, the component could be expanded in the future if we want to include other types.
Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding the component structure, I'd like to discuss an alternative approach based on composition rather than a single component with type discrimination:

Instead of:

interface LightSource {
  type: 'point' | 'spot' | 'directional';
  intensity: number;
  // ... common properties ...
  // spot-specific properties when type === 'spot'
  angle?: number;
  innerAngle?: number;
}

Consider a composable approach:

interface Light {
  illuminance: number;
  color: Color3;
  shadows: boolean;
}

interface Spotlight {
  angle: number;
  innerAngle?: number;
}

interface GlobalLight {
  direction?: Vector3;
  ambientColor?: Color3;
  ambientBrightness?: number;
}

Benefits:

  1. Cleaner Separation: Each component handles one specific aspect of lighting
  2. Type Safety: No need for runtime type checking or optional properties
  3. Extensibility: New light types can be added without modifying existing components
  4. Flexibility: SDKs can provide their own abstractions while the protocol remains clean
  5. Clarity: Entity behavior is determined by component composition:
    • Point Light = Entity + Light
    • Spot Light = Entity + Light + Spotlight
    • Directional Light = Root Entity + Light + GlobalLight

Copy link
Member Author

Choose a reason for hiding this comment

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

We debated this point, and reached the conclusion that we still much prefer to use a single component, for the following reasons:

  1. It's consistent with how many other components in our SDK already work (eg Material, Tween, MeshRenderer, MeshCollider)
  2. It's friendlier to have to remember a single component name, and have the field suggestions do the rest for you. If you need to add two separate components, you don't have that guidance, you need to remember the two component names by heart. Users can also get potentially lost if they try to only add the SpotLight component and see no effect.
  3. We can add helpers, like we already do on other components to make this friendlier. eg:
    LightSource.spotLight(myEntity, { brightness: 1000, angle: 10})


The `Active` flag lets creators easily turn a light source on or off. We could otherwise achieve the same by setting intensity to 0, but offering a switch makes it easier to retain the prior configuration.

## Shadows
Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding shadows, I suggest expanding this section to address several important aspects:

  1. Shadow Control
    Instead of a simple boolean, consider these key properties:
  • shadows: boolean - Whether the light casts shadows
  • Default values should differ by light type:
    • Point/Spot lights: false (performance consideration)
    • Global/Directional light: true (essential for scene depth)
  1. Implementation Considerations
    Add a note that shadow rendering may vary by platform:
// Even when shadows = true, the engine may:
// - Not display shadows at all
// - Show shadows for limited number of lights
// - Vary shadow quality
// Based on:
// - Platform capabilities
// - User settings
// - Performance requirements
  1. Priority System
    Consider adding guidance for implementations about shadow priority:
  • Global/Directional light shadows take precedence
  • Point/Spot lights could use distance/illuminance for priority

This approach:

  • Gives developers clear expectations
  • Allows implementations to optimize performance
  • Maintains flexibility for different platforms
  • Provides predictable behavior across scenes

See our protocol implementation for a working example of these concepts.

Copy link
Member Author

Choose a reason for hiding this comment

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

We agree that it's better to have shadows as a simple boolean, and let each engine determine things like shadow resolution, priorities, etc.
I changed this in the document


Engines can add a default behavior of fading lights in/out over a period of a second whenever you switch scenes. This is to avoid abrupt light changes when you walk from one parcel to another.

### Affect global ambient light
Copy link
Contributor

Choose a reason for hiding this comment

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

The Global Light section needs more detailed specification. Currently, there's a gap between point/spot lights and scene-wide lighting. Consider:

  1. Global Light Components
    A scene's global lighting should be configurable through two complementary components:
// Core light properties (when attached to root)
interface Light {
  illuminance: number;  // 400 (sunrise) to 10,000 (midday)
  color: Color3;
  shadows: boolean;
}

// Global light specific properties
interface GlobalLight {
  direction?: Vector3;      // Sunlight direction
  ambientColor?: Color3;    // Sky/environment contribution
  ambientBrightness?: number; // Overall scene brightness multiplier
}
  1. Root Entity Behavior
  • Light on root = Override default directional (sun) light
  • GlobalLight on root = Control direction and ambient settings
  • Both components can work independently:
    • Light alone = Change sun color/intensity
    • GlobalLight alone = Change direction/ambient
    • Both = Full control
  1. Default Behavior
    Add specification for default values:
  • Default directional light should match typical daylight
  • Direction could vary with time-of-day (if implemented)
  • Reasonable ambient light for scene visibility

This matches our protocol implementation while providing clear guidance for scene creators and engine implementations.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, I expanded the section, and added more to the initial definition. I however added this as an additional type, rather than its own component.

- The creator can chose the shadow resolution as a setting on each light source
- The shadow resolution is picked by the player in the user’s settings, as a global option. If they have low settings they’ll always use low res

## Limitations
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest removing the "Limitations and Considerations" section from this ADR. Here's why:

  1. Separation of Concerns
  • This ADR should focus on defining the light component interfaces and their behavior
  • Implementation details like performance limits should be handled separately:
    • Engine-specific optimizations
    • Platform-specific limitations
    • Scene-level restrictions
  1. Flexibility for Implementations
    Let each explorer/renderer decide how to handle:
  • Maximum number of lights
  • Shadow map allocation
  • Performance optimizations
  • Mobile vs desktop capabilities
  • Low-end vs high-end configurations
  1. SDK's Role
    The SDK can provide:
  • Platform-specific warnings
  • Best practice guidelines
  • Performance recommendations
  • Scene validation tools
  1. Better Documentation Structure
    These topics deserve their own documentation:
  • Performance best practices ADR
  • Scene optimization guide
  • Platform compatibility matrix
  • Implementation guidelines

This approach:

  • Keeps the component specification clean and focused
  • Allows for platform-specific optimizations
  • Enables future improvements without spec changes
  • Lets implementations evolve independently

Copy link
Contributor

Choose a reason for hiding this comment

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

I’m okay with removing the Limitations and Considerations section, but I’d like to challenge this approach slightly. These limitations guide how each client should behave, ensuring consistency across implementations. Without them, we risk significant divergence in how the feature works across clients, which might shift responsibility to creators to adapt to different behaviors.

Creating a separate ADR or RFC for these constraints could work, but we’d need to align on how the protocol should remain generic while still providing a baseline for consistent behavior. I also see the value in keeping this ADR abstract to allow for experimentation and flexibility, but it’s crucial we ensure creators aren’t left with unpredictable experiences due to discrepancies between clients.

Are we ok with having that differences between clients?
If we move this to another ADR, then we need to provide a clear guidelines, tools and documentation for creators to navigate that different clients.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree with both, the ADR should focus on the protocol, but sometimes the protocol is more than just the data sent via the transport, it's also about the expected behavior.

I simplified these lines to something more generic:

Each engine is free to determine considerations like shadow resolutions, or putting a limit on the number of shadows being computed and how to prioritize these. It's recommendable to make these variables dependent on user quality settings.

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.

3 participants