diff --git a/examples/3d/3d_scene_pipelined.rs b/examples/3d/3d_scene_pipelined.rs index 711a5b0bc0ce3..f3bef825b1a8f 100644 --- a/examples/3d/3d_scene_pipelined.rs +++ b/examples/3d/3d_scene_pipelined.rs @@ -110,6 +110,7 @@ fn setup( // transform: Transform::from_xyz(5.0, 8.0, 2.0), transform: Transform::from_xyz(1.0, 2.0, 0.0), point_light: PointLight { + intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb color: Color::RED, ..Default::default() }, @@ -136,6 +137,7 @@ fn setup( // transform: Transform::from_xyz(5.0, 8.0, 2.0), transform: Transform::from_xyz(-1.0, 2.0, 0.0), point_light: PointLight { + intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb color: Color::GREEN, ..Default::default() }, @@ -162,6 +164,7 @@ fn setup( // transform: Transform::from_xyz(5.0, 8.0, 2.0), transform: Transform::from_xyz(0.0, 4.0, 0.0), point_light: PointLight { + intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb color: Color::BLUE, ..Default::default() }, diff --git a/pipelined/bevy_pbr2/src/light.rs b/pipelined/bevy_pbr2/src/light.rs index 44f2c2b6f7bb2..06bf06b24eef6 100644 --- a/pipelined/bevy_pbr2/src/light.rs +++ b/pipelined/bevy_pbr2/src/light.rs @@ -1,6 +1,22 @@ use bevy_render2::{camera::OrthographicProjection, color::Color}; /// A light that emits light in all directions from a central point. +/// +/// Real-world values for `intensity` (luminous power in lumens) based on the electrical power +/// consumption of the type of real-world light are: +/// +/// | Luminous Power (lumen) (i.e. the intensity member) | Incandescent non-halogen (Watts) | Incandescent halogen (Watts) | Compact fluorescent (Watts) | LED (Watts | +/// |------|-----|----|--------|-------| +/// | 200 | 25 | | 3-5 | 3 | +/// | 450 | 40 | 29 | 9-11 | 5-8 | +/// | 800 | 60 | | 13-15 | 8-12 | +/// | 1100 | 75 | 53 | 18-20 | 10-16 | +/// | 1600 | 100 | 72 | 24-28 | 14-17 | +/// | 2400 | 150 | | 30-52 | 24-30 | +/// | 3100 | 200 | | 49-75 | 32 | +/// | 4000 | 300 | | 75-100 | 40.5 | +/// +/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit)#Lighting) #[derive(Debug, Clone, Copy)] pub struct PointLight { pub color: Color, @@ -18,7 +34,8 @@ impl Default for PointLight { fn default() -> Self { PointLight { color: Color::rgb(1.0, 1.0, 1.0), - intensity: 200.0, + /// Luminous power in lumens + intensity: 800.0, // Roughly a 60W non-halogen incandescent bulb range: 20.0, radius: 0.0, shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, @@ -61,6 +78,7 @@ impl PointLight { #[derive(Debug, Clone)] pub struct DirectionalLight { pub color: Color, + /// Illuminance in lux pub illuminance: f32, pub shadow_projection: OrthographicProjection, pub shadow_depth_bias: f32, @@ -95,11 +113,11 @@ impl DirectionalLight { pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6; } -// Ambient light color. +/// Ambient light. #[derive(Debug)] pub struct AmbientLight { pub color: Color, - /// Color is premultiplied by brightness before being passed to the shader + /// A direct scale factor multiplied with `color` before being passed to the shader pub brightness: f32, } diff --git a/pipelined/bevy_pbr2/src/render/light.rs b/pipelined/bevy_pbr2/src/render/light.rs index 45ab08a3a8fb0..20756a84f9bb7 100644 --- a/pipelined/bevy_pbr2/src/render/light.rs +++ b/pipelined/bevy_pbr2/src/render/light.rs @@ -25,6 +25,7 @@ pub struct ExtractedAmbientLight { pub struct ExtractedPointLight { color: Color, + /// luminous intensity in lumens per steradian intensity: f32, range: f32, radius: f32, @@ -239,7 +240,10 @@ pub fn extract_lights( for (entity, point_light, transform) in point_lights.iter() { commands.get_or_spawn(entity).insert(ExtractedPointLight { color: point_light.color, - intensity: point_light.intensity, + // NOTE: Map from luminous power in lumens to luminous intensity in lumens per steradian + // for a point light. See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower + // for details. + intensity: point_light.intensity / (4.0 * std::f32::consts::PI), range: point_light.range, radius: point_light.radius, transform: *transform, diff --git a/pipelined/bevy_pbr2/src/render/pbr.wgsl b/pipelined/bevy_pbr2/src/render/pbr.wgsl index d7668d331ec07..6e866689fa044 100644 --- a/pipelined/bevy_pbr2/src/render/pbr.wgsl +++ b/pipelined/bevy_pbr2/src/render/pbr.wgsl @@ -349,17 +349,20 @@ fn point_light( let diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); + // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩ // where // f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color - // Φ is light intensity - + // Φ is luminous power in lumens // our rangeAttentuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius - // It's not 100% clear where the 1/4π goes in the derivation, but we follow the filament shader and leave it out - // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation + // For a point light, luminous intensity, I, in lumens per steradian is given by: + // I = Φ / 4 π + // The derivation of this can be seen here: https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower + + // NOTE: light.color.rgb is premultiplied with light.intensity / 4 π (which would be the luminous intensity) on the CPU + // TODO compensate for energy loss https://google.github.io/filament/Filament.html#materialsystem/improvingthebrdfs/energylossinspecularreflectance - // light.color.rgb is premultiplied with light.intensity on the CPU return ((diffuse + specular_light) * light.color.rgb) * (rangeAttenuation * NoL); }