From a1d079520333e8447235f5c6d6c06b471a4ee648 Mon Sep 17 00:00:00 2001 From: MsK` Date: Wed, 5 May 2021 18:24:48 +0200 Subject: [PATCH 01/12] basic directional lighting, probably not physically correct # Conflicts: # crates/bevy_pbr/src/lib.rs --- crates/bevy_pbr/src/entity.rs | 2 +- crates/bevy_pbr/src/lib.rs | 2 +- crates/bevy_pbr/src/light.rs | 75 ++++++++++ .../bevy_pbr/src/render_graph/lights_node.rs | 62 +++++--- crates/bevy_pbr/src/render_graph/mod.rs | 3 +- .../src/render_graph/pbr_pipeline/pbr.frag | 133 +++++++++++------- 6 files changed, 206 insertions(+), 71 deletions(-) diff --git a/crates/bevy_pbr/src/entity.rs b/crates/bevy_pbr/src/entity.rs index 9b1d3c6314743..5ad9458eb99be 100644 --- a/crates/bevy_pbr/src/entity.rs +++ b/crates/bevy_pbr/src/entity.rs @@ -46,4 +46,4 @@ pub struct PointLightBundle { pub point_light: PointLight, pub transform: Transform, pub global_transform: GlobalTransform, -} +} \ No newline at end of file diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 9737a39e28d13..ce23ecd61c20e 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -10,7 +10,7 @@ pub use material::*; pub mod prelude { #[doc(hidden)] - pub use crate::{entity::*, light::PointLight, material::StandardMaterial}; + pub use crate::{entity::*, light::{PointLight, DirectionalLight}, material::StandardMaterial}; } use bevy_app::prelude::*; diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 7e5b9766a4dd9..7014dacc7c7bc 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -3,6 +3,7 @@ use bevy_ecs::reflect::ReflectComponent; use bevy_reflect::Reflect; use bevy_render::color::Color; use bevy_transform::components::GlobalTransform; +use bevy_math::Vec3; /// A point light #[derive(Debug, Reflect)] @@ -52,6 +53,80 @@ impl PointLightUniform { } } +/// A Directional light. +/// +/// Directional lights don't exist in reality but they are a good +/// approximation for light sources VERY far away, like the sun or +/// the moon. +#[derive(Debug, Reflect)] +#[reflect(Component)] +pub struct DirectionalLight { + pub color: Color, + pub intensity: f32, + direction: Vec3 +} + +impl DirectionalLight { + /// Create a new directional light component. + /// + /// # Panics + /// Will panic if `direction` is not normalized. + pub fn new(color: Color, intensity: f32, direction: Vec3) -> Self { + assert!((direction.length_squared() - 1.0).abs() < 0.0001); + DirectionalLight { + color, intensity, direction + } + } + + /// Set direction of light. + /// + /// # Panics + /// Will panic if `direction` is not normalized. + pub fn set_direction(&mut self, direction: Vec3) { + assert!((direction.length_squared() - 1.0).abs() < 0.0001); + self.direction = direction; + } + + pub fn get_direction(&self) -> Vec3 { + self.direction + } +} + +impl Default for DirectionalLight { + fn default() -> Self { + DirectionalLight { + color: Color::rgb(1.0, 1.0, 1.0), + intensity: 100000.0, // good start for a sun light + direction: Vec3::new(0.0, -1.0, 0.0) + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub(crate) struct DirectionalLightUniform { + pub dir: [f32; 4], + pub color: [f32; 4], +} + +unsafe impl Byteable for DirectionalLightUniform {} + +impl DirectionalLightUniform { + pub fn from(light: &DirectionalLight) -> DirectionalLightUniform { + // direction is negated to be ready for N.L + let dir: [f32; 4] = [-light.direction.x, -light.direction.y, -light.direction.z, 0.0]; + + // premultiply color by intensity + // we don't use the alpha at all, so no reason to multiply only [0..3] + let color: [f32; 4] = (light.color * light.intensity).into(); + + DirectionalLightUniform { + dir, + color, + } + } +} + // Ambient light color. #[derive(Debug)] pub struct AmbientLight { diff --git a/crates/bevy_pbr/src/render_graph/lights_node.rs b/crates/bevy_pbr/src/render_graph/lights_node.rs index 8f89570eae1a4..bd2bdd85e0376 100644 --- a/crates/bevy_pbr/src/render_graph/lights_node.rs +++ b/crates/bevy_pbr/src/render_graph/lights_node.rs @@ -1,5 +1,5 @@ use crate::{ - light::{AmbientLight, PointLight, PointLightUniform}, + light::{AmbientLight, PointLight, PointLightUniform, DirectionalLight, DirectionalLightUniform}, render_graph::uniform, }; use bevy_core::{AsBytes, Byteable}; @@ -21,12 +21,14 @@ use bevy_transform::prelude::*; pub struct LightsNode { command_queue: CommandQueue, max_point_lights: usize, + max_dir_lights: usize, } impl LightsNode { - pub fn new(max_lights: usize) -> Self { + pub fn new(max_point_lights: usize, max_dir_lights: usize) -> Self { LightsNode { - max_point_lights: max_lights, + max_point_lights, + max_dir_lights, command_queue: CommandQueue::default(), } } @@ -48,6 +50,8 @@ impl Node for LightsNode { #[derive(Debug, Clone, Copy)] struct LightCount { // storing as a `[u32; 4]` for memory alignement + // Index 0 is for point lights, + // Index 1 is for directional lights pub num_lights: [u32; 4], } @@ -59,6 +63,7 @@ impl SystemNode for LightsNode { config.0 = Some(LightsNodeSystemState { command_queue: self.command_queue.clone(), max_point_lights: self.max_point_lights, + max_dir_lights: self.max_dir_lights, light_buffer: None, staging_buffer: None, }) @@ -74,6 +79,7 @@ pub struct LightsNodeSystemState { staging_buffer: Option, command_queue: CommandQueue, max_point_lights: usize, + max_dir_lights: usize, } pub fn lights_node_system( @@ -83,7 +89,8 @@ pub fn lights_node_system( // TODO: this write on RenderResourceBindings will prevent this system from running in parallel // with other systems that do the same mut render_resource_bindings: ResMut, - query: Query<(&PointLight, &GlobalTransform)>, + point_lights: Query<(&PointLight, &GlobalTransform)>, + dir_lights: Query<&DirectionalLight>, ) { let state = &mut state; let render_resource_context = &**render_resource_context; @@ -92,16 +99,29 @@ pub fn lights_node_system( let ambient_light: [f32; 4] = (ambient_light_resource.color * ambient_light_resource.brightness).into(); let ambient_light_size = std::mem::size_of::<[f32; 4]>(); - let point_light_count = query.iter().len().min(state.max_point_lights); - let size = std::mem::size_of::(); + + let point_light_count = point_lights.iter().len().min(state.max_point_lights); + let point_light_size = std::mem::size_of::(); + let point_light_array_size = point_light_size * point_light_count; + let point_light_array_max_size = point_light_size * state.max_point_lights; + + let dir_light_count = dir_lights.iter().len().min(state.max_dir_lights); + let dir_light_size = std::mem::size_of::(); + let dir_light_array_size = dir_light_size * dir_light_count; + let dir_light_array_max_size = dir_light_size * state.max_dir_lights; + let light_count_size = ambient_light_size + std::mem::size_of::(); - let point_light_array_size = size * point_light_count; - let point_light_array_max_size = size * state.max_point_lights; - let current_point_light_uniform_size = light_count_size + point_light_array_size; - let max_light_uniform_size = light_count_size + point_light_array_max_size; + + let point_light_uniform_start = light_count_size; + let point_light_uniform_end = light_count_size + point_light_array_size; + + let dir_light_uniform_start = light_count_size + point_light_array_max_size; + let dir_light_uniform_end = light_count_size + point_light_array_max_size + dir_light_array_size; + + let max_light_uniform_size = light_count_size + point_light_array_max_size + dir_light_array_max_size; if let Some(staging_buffer) = state.staging_buffer { - if point_light_count == 0 { + if point_light_count == 0 && dir_light_count == 0 { return; } @@ -133,23 +153,33 @@ pub fn lights_node_system( let staging_buffer = state.staging_buffer.unwrap(); render_resource_context.write_mapped_buffer( staging_buffer, - 0..current_point_light_uniform_size as u64, + 0..max_light_uniform_size as u64, &mut |data, _renderer| { // ambient light data[0..ambient_light_size].copy_from_slice(ambient_light.as_bytes()); // light count data[ambient_light_size..light_count_size] - .copy_from_slice([point_light_count as u32, 0, 0, 0].as_bytes()); + .copy_from_slice([point_light_count as u32, dir_light_count as u32, 0, 0].as_bytes()); - // light array - for ((point_light, global_transform), slot) in query.iter().zip( - data[light_count_size..current_point_light_uniform_size].chunks_exact_mut(size), + // point light array + for ((point_light, global_transform), slot) in point_lights.iter().zip( + data[point_light_uniform_start..point_light_uniform_end].chunks_exact_mut(point_light_size), ) { slot.copy_from_slice( PointLightUniform::from(&point_light, &global_transform).as_bytes(), ); } + + // directional light array + for (dir_light, slot) in dir_lights.iter().zip( + data[dir_light_uniform_start ..dir_light_uniform_end].chunks_exact_mut(dir_light_size), + ) { + println!("Sending light dir={:?} color={:?}", dir_light.get_direction(), dir_light.color); + slot.copy_from_slice( + DirectionalLightUniform::from(&dir_light).as_bytes(), + ); + } }, ); render_resource_context.unmap_buffer(staging_buffer); diff --git a/crates/bevy_pbr/src/render_graph/mod.rs b/crates/bevy_pbr/src/render_graph/mod.rs index 55e3fbaa868d4..8205a761bb043 100644 --- a/crates/bevy_pbr/src/render_graph/mod.rs +++ b/crates/bevy_pbr/src/render_graph/mod.rs @@ -27,6 +27,7 @@ use bevy_render::{ use bevy_transform::prelude::GlobalTransform; pub const MAX_POINT_LIGHTS: usize = 10; +pub const MAX_DIRECTIONAL_LIGHTS: usize = 1; pub(crate) fn add_pbr_graph(world: &mut World) { { let mut graph = world.get_resource_mut::().unwrap(); @@ -39,7 +40,7 @@ pub(crate) fn add_pbr_graph(world: &mut World) { AssetRenderResourcesNode::::new(true), ); - graph.add_system_node(node::LIGHTS, LightsNode::new(MAX_POINT_LIGHTS)); + graph.add_system_node(node::LIGHTS, LightsNode::new(MAX_POINT_LIGHTS, MAX_DIRECTIONAL_LIGHTS)); // TODO: replace these with "autowire" groups graph diff --git a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag index 3891e90e0ba5d..0f5ebedfd80b7 100644 --- a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag +++ b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag @@ -34,13 +34,20 @@ // // The above integration needs to be approximated. -const int MAX_LIGHTS = 10; +// reflects the constants defined bevy_pbr/src/render_graph/mod.rs +const int MAX_POINT_LIGHTS = 10; +const int MAX_DIRECTIONAL_LIGHTS = 1; struct PointLight { vec4 pos; vec4 color; vec4 lightParams; }; + +struct DirectionalLight { + vec4 direction; + vec4 color; +}; layout(location = 0) in vec3 v_WorldPosition; layout(location = 1) in vec3 v_WorldNormal; @@ -61,8 +68,9 @@ layout(std140, set = 0, binding = 1) uniform CameraPosition { layout(std140, set = 1, binding = 0) uniform Lights { vec4 AmbientColor; - uvec4 NumLights; - PointLight PointLights[MAX_LIGHTS]; + uvec4 NumLights; // x = point lights, y = directional lights + PointLight PointLights[MAX_POINT_LIGHTS]; + DirectionalLight DirectionalLights[MAX_DIRECTIONAL_LIGHTS]; }; layout(set = 3, binding = 0) uniform StandardMaterial_base_color { @@ -277,6 +285,71 @@ vec3 reinhard_extended_luminance(vec3 color, float max_white_l) { #endif +vec3 do_point_light(PointLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) { + vec3 light_to_frag = light.pos.xyz - v_WorldPosition.xyz; + float distance_square = dot(light_to_frag, light_to_frag); + float rangeAttenuation = + getDistanceAttenuation(distance_square, light.lightParams.r); + + // Specular. + // Representative Point Area Lights. + // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 + float a = roughness; + float radius = light.lightParams.g; + vec3 centerToRay = dot(light_to_frag, R) * R - light_to_frag; + vec3 closestPoint = light_to_frag + centerToRay * saturate(radius * inversesqrt(dot(centerToRay, centerToRay))); + float LspecLengthInverse = inversesqrt(dot(closestPoint, closestPoint)); + float normalizationFactor = a / saturate(a + (radius * 0.5 * LspecLengthInverse)); + float specularIntensity = normalizationFactor * normalizationFactor; + + vec3 L = closestPoint * LspecLengthInverse; // normalize() equivalent? + vec3 H = normalize(L + V); + float NoL = saturate(dot(N, L)); + float NoH = saturate(dot(N, H)); + float LoH = saturate(dot(L, H)); + + vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); + + // Diffuse. + // Comes after specular since its NoL is used in the lighting equation. + L = normalize(light_to_frag); + H = normalize(L + V); + NoL = saturate(dot(N, L)); + NoH = saturate(dot(N, H)); + LoH = saturate(dot(L, H)); + + vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); + + // 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 + + // 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 + // 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.color.rgb) * (rangeAttenuation * NoL); +} + +vec3 do_dir_light(DirectionalLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) { + vec3 L = light.direction.xyz; + + float NdotL = clamp(dot(N, light.direction.xyz), 0.0, 1.0); + vec3 H = normalize(L + V); + float NoL = saturate(dot(N, L)); + float NoH = saturate(dot(N, H)); + float LoH = saturate(dot(L, H)); + + vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); + float specularIntensity = 1.0; + vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); + + return (specular + diffuse) * light.color.rgb * NoL; +} + void main() { vec4 output_color = base_color; #ifdef STANDARDMATERIAL_BASE_COLOR_TEXTURE @@ -343,55 +416,11 @@ void main() { // accumulate color vec3 light_accum = vec3(0.0); - for (int i = 0; i < int(NumLights.x) && i < MAX_LIGHTS; ++i) { - PointLight light = PointLights[i]; - vec3 light_to_frag = light.pos.xyz - v_WorldPosition.xyz; - float distance_square = dot(light_to_frag, light_to_frag); - float rangeAttenuation = - getDistanceAttenuation(distance_square, light.lightParams.r); - - // Specular. - // Representative Point Area Lights. - // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 - float a = roughness; - float radius = light.lightParams.g; - vec3 centerToRay = dot(light_to_frag, R) * R - light_to_frag; - vec3 closestPoint = light_to_frag + centerToRay * saturate(radius * inversesqrt(dot(centerToRay, centerToRay))); - float LspecLengthInverse = inversesqrt(dot(closestPoint, closestPoint)); - float normalizationFactor = a / saturate(a + (radius * 0.5 * LspecLengthInverse)); - float specularIntensity = normalizationFactor * normalizationFactor; - - vec3 L = closestPoint * LspecLengthInverse; // normalize() equivalent? - vec3 H = normalize(L + V); - float NoL = saturate(dot(N, L)); - float NoH = saturate(dot(N, H)); - float LoH = saturate(dot(L, H)); - - vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); - - // Diffuse. - // Comes after specular since its NoL is used in the lighting equation. - L = normalize(light_to_frag); - H = normalize(L + V); - NoL = saturate(dot(N, L)); - NoH = saturate(dot(N, H)); - LoH = saturate(dot(L, H)); - - vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); - - // 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 - - // 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 - // 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 - light_accum += - ((diffuse + specular) * light.color.rgb) * (rangeAttenuation * NoL); + for (int i = 0; i < int(NumLights.x) && i < MAX_POINT_LIGHTS; ++i) { + light_accum += do_point_light(PointLights[i], roughness, NdotV, N, V, R, F0, diffuseColor); + } + for (int i = 0; i < int(NumLights.y) && i < MAX_DIRECTIONAL_LIGHTS; ++i) { + light_accum += do_dir_light(DirectionalLights[i], roughness, NdotV, N, V, R, F0, diffuseColor); } vec3 diffuse_ambient = EnvBRDFApprox(diffuseColor, 1.0, NdotV); From d65f3974f614c514c3c0560e9815d050758deec3 Mon Sep 17 00:00:00 2001 From: MsK` Date: Wed, 5 May 2021 18:36:54 +0200 Subject: [PATCH 02/12] format # Conflicts: # crates/bevy_pbr/src/lib.rs --- crates/bevy_pbr/src/entity.rs | 2 +- crates/bevy_pbr/src/lib.rs | 6 +++- crates/bevy_pbr/src/light.rs | 22 +++++++----- .../bevy_pbr/src/render_graph/lights_node.rs | 35 ++++++++++++------- crates/bevy_pbr/src/render_graph/mod.rs | 5 ++- 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/crates/bevy_pbr/src/entity.rs b/crates/bevy_pbr/src/entity.rs index 5ad9458eb99be..9b1d3c6314743 100644 --- a/crates/bevy_pbr/src/entity.rs +++ b/crates/bevy_pbr/src/entity.rs @@ -46,4 +46,4 @@ pub struct PointLightBundle { pub point_light: PointLight, pub transform: Transform, pub global_transform: GlobalTransform, -} \ No newline at end of file +} diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index ce23ecd61c20e..e279fe8b8d90f 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -10,7 +10,11 @@ pub use material::*; pub mod prelude { #[doc(hidden)] - pub use crate::{entity::*, light::{PointLight, DirectionalLight}, material::StandardMaterial}; + pub use crate::{ + entity::*, + light::{DirectionalLight, PointLight}, + material::StandardMaterial, + }; } use bevy_app::prelude::*; diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 7014dacc7c7bc..f11e1bd49db3c 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -1,9 +1,9 @@ use bevy_core::Byteable; use bevy_ecs::reflect::ReflectComponent; +use bevy_math::Vec3; use bevy_reflect::Reflect; use bevy_render::color::Color; use bevy_transform::components::GlobalTransform; -use bevy_math::Vec3; /// A point light #[derive(Debug, Reflect)] @@ -63,7 +63,7 @@ impl PointLightUniform { pub struct DirectionalLight { pub color: Color, pub intensity: f32, - direction: Vec3 + direction: Vec3, } impl DirectionalLight { @@ -74,7 +74,9 @@ impl DirectionalLight { pub fn new(color: Color, intensity: f32, direction: Vec3) -> Self { assert!((direction.length_squared() - 1.0).abs() < 0.0001); DirectionalLight { - color, intensity, direction + color, + intensity, + direction, } } @@ -97,7 +99,7 @@ impl Default for DirectionalLight { DirectionalLight { color: Color::rgb(1.0, 1.0, 1.0), intensity: 100000.0, // good start for a sun light - direction: Vec3::new(0.0, -1.0, 0.0) + direction: Vec3::new(0.0, -1.0, 0.0), } } } @@ -114,16 +116,18 @@ unsafe impl Byteable for DirectionalLightUniform {} impl DirectionalLightUniform { pub fn from(light: &DirectionalLight) -> DirectionalLightUniform { // direction is negated to be ready for N.L - let dir: [f32; 4] = [-light.direction.x, -light.direction.y, -light.direction.z, 0.0]; + let dir: [f32; 4] = [ + -light.direction.x, + -light.direction.y, + -light.direction.z, + 0.0, + ]; // premultiply color by intensity // we don't use the alpha at all, so no reason to multiply only [0..3] let color: [f32; 4] = (light.color * light.intensity).into(); - DirectionalLightUniform { - dir, - color, - } + DirectionalLightUniform { dir, color } } } diff --git a/crates/bevy_pbr/src/render_graph/lights_node.rs b/crates/bevy_pbr/src/render_graph/lights_node.rs index bd2bdd85e0376..b8b223060a823 100644 --- a/crates/bevy_pbr/src/render_graph/lights_node.rs +++ b/crates/bevy_pbr/src/render_graph/lights_node.rs @@ -1,5 +1,7 @@ use crate::{ - light::{AmbientLight, PointLight, PointLightUniform, DirectionalLight, DirectionalLightUniform}, + light::{ + AmbientLight, DirectionalLight, DirectionalLightUniform, PointLight, PointLightUniform, + }, render_graph::uniform, }; use bevy_core::{AsBytes, Byteable}; @@ -99,26 +101,28 @@ pub fn lights_node_system( let ambient_light: [f32; 4] = (ambient_light_resource.color * ambient_light_resource.brightness).into(); let ambient_light_size = std::mem::size_of::<[f32; 4]>(); - + let point_light_count = point_lights.iter().len().min(state.max_point_lights); let point_light_size = std::mem::size_of::(); let point_light_array_size = point_light_size * point_light_count; let point_light_array_max_size = point_light_size * state.max_point_lights; - + let dir_light_count = dir_lights.iter().len().min(state.max_dir_lights); let dir_light_size = std::mem::size_of::(); let dir_light_array_size = dir_light_size * dir_light_count; let dir_light_array_max_size = dir_light_size * state.max_dir_lights; - + let light_count_size = ambient_light_size + std::mem::size_of::(); let point_light_uniform_start = light_count_size; let point_light_uniform_end = light_count_size + point_light_array_size; let dir_light_uniform_start = light_count_size + point_light_array_max_size; - let dir_light_uniform_end = light_count_size + point_light_array_max_size + dir_light_array_size; + let dir_light_uniform_end = + light_count_size + point_light_array_max_size + dir_light_array_size; - let max_light_uniform_size = light_count_size + point_light_array_max_size + dir_light_array_max_size; + let max_light_uniform_size = + light_count_size + point_light_array_max_size + dir_light_array_max_size; if let Some(staging_buffer) = state.staging_buffer { if point_light_count == 0 && dir_light_count == 0 { @@ -159,12 +163,14 @@ pub fn lights_node_system( data[0..ambient_light_size].copy_from_slice(ambient_light.as_bytes()); // light count - data[ambient_light_size..light_count_size] - .copy_from_slice([point_light_count as u32, dir_light_count as u32, 0, 0].as_bytes()); + data[ambient_light_size..light_count_size].copy_from_slice( + [point_light_count as u32, dir_light_count as u32, 0, 0].as_bytes(), + ); // point light array for ((point_light, global_transform), slot) in point_lights.iter().zip( - data[point_light_uniform_start..point_light_uniform_end].chunks_exact_mut(point_light_size), + data[point_light_uniform_start..point_light_uniform_end] + .chunks_exact_mut(point_light_size), ) { slot.copy_from_slice( PointLightUniform::from(&point_light, &global_transform).as_bytes(), @@ -173,12 +179,15 @@ pub fn lights_node_system( // directional light array for (dir_light, slot) in dir_lights.iter().zip( - data[dir_light_uniform_start ..dir_light_uniform_end].chunks_exact_mut(dir_light_size), + data[dir_light_uniform_start..dir_light_uniform_end] + .chunks_exact_mut(dir_light_size), ) { - println!("Sending light dir={:?} color={:?}", dir_light.get_direction(), dir_light.color); - slot.copy_from_slice( - DirectionalLightUniform::from(&dir_light).as_bytes(), + println!( + "Sending light dir={:?} color={:?}", + dir_light.get_direction(), + dir_light.color ); + slot.copy_from_slice(DirectionalLightUniform::from(&dir_light).as_bytes()); } }, ); diff --git a/crates/bevy_pbr/src/render_graph/mod.rs b/crates/bevy_pbr/src/render_graph/mod.rs index 8205a761bb043..bc0fcb4732599 100644 --- a/crates/bevy_pbr/src/render_graph/mod.rs +++ b/crates/bevy_pbr/src/render_graph/mod.rs @@ -40,7 +40,10 @@ pub(crate) fn add_pbr_graph(world: &mut World) { AssetRenderResourcesNode::::new(true), ); - graph.add_system_node(node::LIGHTS, LightsNode::new(MAX_POINT_LIGHTS, MAX_DIRECTIONAL_LIGHTS)); + graph.add_system_node( + node::LIGHTS, + LightsNode::new(MAX_POINT_LIGHTS, MAX_DIRECTIONAL_LIGHTS), + ); // TODO: replace these with "autowire" groups graph From e0a3a0606ff228341e05ec169262dfe20fc8db57 Mon Sep 17 00:00:00 2001 From: Daniel Borges Date: Wed, 5 May 2021 22:39:29 +0200 Subject: [PATCH 03/12] removed debug log --- crates/bevy_pbr/src/render_graph/lights_node.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/bevy_pbr/src/render_graph/lights_node.rs b/crates/bevy_pbr/src/render_graph/lights_node.rs index b8b223060a823..5f5b874cede5a 100644 --- a/crates/bevy_pbr/src/render_graph/lights_node.rs +++ b/crates/bevy_pbr/src/render_graph/lights_node.rs @@ -182,11 +182,6 @@ pub fn lights_node_system( data[dir_light_uniform_start..dir_light_uniform_end] .chunks_exact_mut(dir_light_size), ) { - println!( - "Sending light dir={:?} color={:?}", - dir_light.get_direction(), - dir_light.color - ); slot.copy_from_slice(DirectionalLightUniform::from(&dir_light).as_bytes()); } }, From 05e03aa20fea7595509d34fe035f398480e8b2c8 Mon Sep 17 00:00:00 2001 From: Daniel Borges Date: Thu, 6 May 2021 10:54:20 +0200 Subject: [PATCH 04/12] better assert + better comment --- crates/bevy_pbr/src/light.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index f11e1bd49db3c..924b501fca18a 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -58,6 +58,8 @@ impl PointLightUniform { /// Directional lights don't exist in reality but they are a good /// approximation for light sources VERY far away, like the sun or /// the moon. +/// +/// An `intensity` of 100000.0 is a good start for a sunlight. #[derive(Debug, Reflect)] #[reflect(Component)] pub struct DirectionalLight { @@ -72,7 +74,7 @@ impl DirectionalLight { /// # Panics /// Will panic if `direction` is not normalized. pub fn new(color: Color, intensity: f32, direction: Vec3) -> Self { - assert!((direction.length_squared() - 1.0).abs() < 0.0001); + assert!(direction.is_normalized(), "Light direction vector should have been normalized."); DirectionalLight { color, intensity, @@ -85,7 +87,7 @@ impl DirectionalLight { /// # Panics /// Will panic if `direction` is not normalized. pub fn set_direction(&mut self, direction: Vec3) { - assert!((direction.length_squared() - 1.0).abs() < 0.0001); + assert!(direction.is_normalized(), "Light direction vector should have been normalized."); self.direction = direction; } @@ -98,7 +100,7 @@ impl Default for DirectionalLight { fn default() -> Self { DirectionalLight { color: Color::rgb(1.0, 1.0, 1.0), - intensity: 100000.0, // good start for a sun light + intensity: 100000.0, direction: Vec3::new(0.0, -1.0, 0.0), } } From 2076542676366dbe45707bf6e497535d203dc524 Mon Sep 17 00:00:00 2001 From: Daniel Borges Date: Fri, 7 May 2021 22:06:33 +0200 Subject: [PATCH 05/12] refacto --- crates/bevy_pbr/src/light.rs | 14 +++++++++---- .../bevy_pbr/src/render_graph/lights_node.rs | 4 ++-- .../src/render_graph/pbr_pipeline/pbr.frag | 21 +++++++++---------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 924b501fca18a..d2aebef6a8632 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -38,7 +38,7 @@ pub(crate) struct PointLightUniform { unsafe impl Byteable for PointLightUniform {} impl PointLightUniform { - pub fn from(light: &PointLight, global_transform: &GlobalTransform) -> PointLightUniform { + pub fn new(light: &PointLight, global_transform: &GlobalTransform) -> PointLightUniform { let (x, y, z) = global_transform.translation.into(); // premultiply color by intensity @@ -74,7 +74,10 @@ impl DirectionalLight { /// # Panics /// Will panic if `direction` is not normalized. pub fn new(color: Color, intensity: f32, direction: Vec3) -> Self { - assert!(direction.is_normalized(), "Light direction vector should have been normalized."); + assert!( + direction.is_normalized(), + "Light direction vector should have been normalized." + ); DirectionalLight { color, intensity, @@ -87,7 +90,10 @@ impl DirectionalLight { /// # Panics /// Will panic if `direction` is not normalized. pub fn set_direction(&mut self, direction: Vec3) { - assert!(direction.is_normalized(), "Light direction vector should have been normalized."); + assert!( + direction.is_normalized(), + "Light direction vector should have been normalized." + ); self.direction = direction; } @@ -116,7 +122,7 @@ pub(crate) struct DirectionalLightUniform { unsafe impl Byteable for DirectionalLightUniform {} impl DirectionalLightUniform { - pub fn from(light: &DirectionalLight) -> DirectionalLightUniform { + pub fn new(light: &DirectionalLight) -> DirectionalLightUniform { // direction is negated to be ready for N.L let dir: [f32; 4] = [ -light.direction.x, diff --git a/crates/bevy_pbr/src/render_graph/lights_node.rs b/crates/bevy_pbr/src/render_graph/lights_node.rs index 5f5b874cede5a..19ea0faee7af2 100644 --- a/crates/bevy_pbr/src/render_graph/lights_node.rs +++ b/crates/bevy_pbr/src/render_graph/lights_node.rs @@ -173,7 +173,7 @@ pub fn lights_node_system( .chunks_exact_mut(point_light_size), ) { slot.copy_from_slice( - PointLightUniform::from(&point_light, &global_transform).as_bytes(), + PointLightUniform::new(&point_light, &global_transform).as_bytes(), ); } @@ -182,7 +182,7 @@ pub fn lights_node_system( data[dir_light_uniform_start..dir_light_uniform_end] .chunks_exact_mut(dir_light_size), ) { - slot.copy_from_slice(DirectionalLightUniform::from(&dir_light).as_bytes()); + slot.copy_from_slice(DirectionalLightUniform::new(&dir_light).as_bytes()); } }, ); diff --git a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag index 0f5ebedfd80b7..52a005dfe7608 100644 --- a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag +++ b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag @@ -285,7 +285,7 @@ vec3 reinhard_extended_luminance(vec3 color, float max_white_l) { #endif -vec3 do_point_light(PointLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) { +vec3 point_light(PointLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) { vec3 light_to_frag = light.pos.xyz - v_WorldPosition.xyz; float distance_square = dot(light_to_frag, light_to_frag); float rangeAttenuation = @@ -334,18 +334,17 @@ vec3 do_point_light(PointLight light, float roughness, float NdotV, vec3 N, vec3 return ((diffuse + specular) * light.color.rgb) * (rangeAttenuation * NoL); } -vec3 do_dir_light(DirectionalLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) { - vec3 L = light.direction.xyz; +vec3 dir_light(DirectionalLight light, float roughness, float NdotV, vec3 normal, vec3 view, vec3 R, vec3 F0, vec3 diffuseColor) { + vec3 incident_light = light.direction.xyz; - float NdotL = clamp(dot(N, light.direction.xyz), 0.0, 1.0); - vec3 H = normalize(L + V); - float NoL = saturate(dot(N, L)); - float NoH = saturate(dot(N, H)); - float LoH = saturate(dot(L, H)); + vec3 half_vector = normalize(incident_light + view); + float NoL = saturate(dot(normal, incident_light)); + float NoH = saturate(dot(normal, half_vector)); + float LoH = saturate(dot(incident_light, half_vector)); vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); float specularIntensity = 1.0; - vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); + vec3 specular = specular(F0, roughness, half_vector, NdotV, NoL, NoH, LoH, specularIntensity); return (specular + diffuse) * light.color.rgb * NoL; } @@ -417,10 +416,10 @@ void main() { // accumulate color vec3 light_accum = vec3(0.0); for (int i = 0; i < int(NumLights.x) && i < MAX_POINT_LIGHTS; ++i) { - light_accum += do_point_light(PointLights[i], roughness, NdotV, N, V, R, F0, diffuseColor); + light_accum += point_light(PointLights[i], roughness, NdotV, N, V, R, F0, diffuseColor); } for (int i = 0; i < int(NumLights.y) && i < MAX_DIRECTIONAL_LIGHTS; ++i) { - light_accum += do_dir_light(DirectionalLights[i], roughness, NdotV, N, V, R, F0, diffuseColor); + light_accum += dir_light(DirectionalLights[i], roughness, NdotV, N, V, R, F0, diffuseColor); } vec3 diffuse_ambient = EnvBRDFApprox(diffuseColor, 1.0, NdotV); From 6bc467b944273f67d7491835861b5b2d66f38002 Mon Sep 17 00:00:00 2001 From: Daniel Borges Date: Sat, 8 May 2021 01:50:06 +0200 Subject: [PATCH 06/12] properly convert illuminance to candelas with hardcoded camera settings for the moment --- crates/bevy_pbr/src/light.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index d2aebef6a8632..97f9403168faa 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -131,9 +131,21 @@ impl DirectionalLightUniform { 0.0, ]; + // convert from illuminance (lux) to candelas + // + // exposure is hard coded at the moment but should be replaced + // by values coming from the camera + // see: https://google.github.io/filament/Filament.html#imagingpipeline/physicallybasedcamera/exposuresettings + let aperture = 4.0; + let shutter_speed = 1.0 / 250.0; + let sensitivity = 100.0; + let ev100 = f32::log2(aperture * aperture / shutter_speed) - f32::log2(sensitivity / 100.0); + let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2); + let intensity = light.intensity * exposure; + // premultiply color by intensity // we don't use the alpha at all, so no reason to multiply only [0..3] - let color: [f32; 4] = (light.color * light.intensity).into(); + let color: [f32; 4] = (light.color * intensity).into(); DirectionalLightUniform { dir, color } } From 2528bf553a85c4da192f43d113aace53cfd964e4 Mon Sep 17 00:00:00 2001 From: Daniel Borges Date: Sat, 8 May 2021 01:50:28 +0200 Subject: [PATCH 07/12] format... for a space... grmbl --- crates/bevy_pbr/src/light.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 97f9403168faa..45efddb80eaef 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -132,7 +132,7 @@ impl DirectionalLightUniform { ]; // convert from illuminance (lux) to candelas - // + // // exposure is hard coded at the moment but should be replaced // by values coming from the camera // see: https://google.github.io/filament/Filament.html#imagingpipeline/physicallybasedcamera/exposuresettings From fa1bd270c8f8e3004e358d4fffc07f1f5155b760 Mon Sep 17 00:00:00 2001 From: Daniel Borges Date: Sat, 8 May 2021 01:55:20 +0200 Subject: [PATCH 08/12] added clone and copy to light components --- crates/bevy_pbr/src/light.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 45efddb80eaef..b4caf8eabb51a 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -6,7 +6,7 @@ use bevy_render::color::Color; use bevy_transform::components::GlobalTransform; /// A point light -#[derive(Debug, Reflect)] +#[derive(Debug, Clone, Copy, Reflect)] #[reflect(Component)] pub struct PointLight { pub color: Color, @@ -60,7 +60,7 @@ impl PointLightUniform { /// the moon. /// /// An `intensity` of 100000.0 is a good start for a sunlight. -#[derive(Debug, Reflect)] +#[derive(Debug, Clone, Copy, Reflect)] #[reflect(Component)] pub struct DirectionalLight { pub color: Color, From 6e1476aa40e53b8d7a5bbb4183b626b47fd95b28 Mon Sep 17 00:00:00 2001 From: Daniel Borges Date: Sat, 8 May 2021 11:57:30 +0200 Subject: [PATCH 09/12] consts --- crates/bevy_pbr/src/light.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index b4caf8eabb51a..475ce08a2ce21 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -136,10 +136,10 @@ impl DirectionalLightUniform { // exposure is hard coded at the moment but should be replaced // by values coming from the camera // see: https://google.github.io/filament/Filament.html#imagingpipeline/physicallybasedcamera/exposuresettings - let aperture = 4.0; - let shutter_speed = 1.0 / 250.0; - let sensitivity = 100.0; - let ev100 = f32::log2(aperture * aperture / shutter_speed) - f32::log2(sensitivity / 100.0); + const APERTURE: f32 = 4.0; + const SHUTTER_SPEED: f32 = 1.0 / 250.0; + const SENSITIVITY: f32 = 100.0; + let ev100 = f32::log2(APERTURE * APERTURE / SHUTTER_SPEED) - f32::log2(SENSITIVITY / 100.0); let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2); let intensity = light.intensity * exposure; From 5ce7f62c3f056db6cae87da142fa26e7b38376f9 Mon Sep 17 00:00:00 2001 From: Daniel Borges Date: Sun, 9 May 2021 10:33:07 +0200 Subject: [PATCH 10/12] removed panics, always normalize + illuminance value table --- crates/bevy_pbr/src/light.rs | 49 ++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 475ce08a2ce21..d957c7f06ee1d 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -59,42 +59,47 @@ impl PointLightUniform { /// approximation for light sources VERY far away, like the sun or /// the moon. /// -/// An `intensity` of 100000.0 is a good start for a sunlight. +/// Valid values for `illuminance` are: +/// +/// | Illuminance (lux) | Surfaces illuminated by | +/// |-------------------|------------------------------------------------| +/// | 0.0001 | Moonless, overcast night sky (starlight) | +/// | 0.002 | Moonless clear night sky with airglow | +/// | 0.05–0.3 | Full moon on a clear night | +/// | 3.4 | Dark limit of civil twilight under a clear sky | +/// | 20–50 | Public areas with dark surroundings | +/// | 50 | Family living room lights | +/// | 80 | Office building hallway/toilet lighting | +/// | 100 | Very dark overcast day | +/// | 150 | Train station platforms | +/// | 320–500 | Office lighting | +/// | 400 | Sunrise or sunset on a clear day. | +/// | 1000 | Overcast day; typical TV studio lighting | +/// | 10,000–25,000 | Full daylight (not direct sun) | +/// | 32,000–100,000 | Direct sunlight | +/// +/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux) #[derive(Debug, Clone, Copy, Reflect)] #[reflect(Component)] pub struct DirectionalLight { pub color: Color, - pub intensity: f32, + pub illuminance: f32, direction: Vec3, } impl DirectionalLight { /// Create a new directional light component. - /// - /// # Panics - /// Will panic if `direction` is not normalized. - pub fn new(color: Color, intensity: f32, direction: Vec3) -> Self { - assert!( - direction.is_normalized(), - "Light direction vector should have been normalized." - ); + pub fn new(color: Color, illuminance: f32, direction: Vec3) -> Self { DirectionalLight { color, - intensity, - direction, + illuminance, + direction: direction.normalize(), } } /// Set direction of light. - /// - /// # Panics - /// Will panic if `direction` is not normalized. pub fn set_direction(&mut self, direction: Vec3) { - assert!( - direction.is_normalized(), - "Light direction vector should have been normalized." - ); - self.direction = direction; + self.direction = direction.normalize(); } pub fn get_direction(&self) -> Vec3 { @@ -106,7 +111,7 @@ impl Default for DirectionalLight { fn default() -> Self { DirectionalLight { color: Color::rgb(1.0, 1.0, 1.0), - intensity: 100000.0, + illuminance: 100000.0, direction: Vec3::new(0.0, -1.0, 0.0), } } @@ -141,7 +146,7 @@ impl DirectionalLightUniform { const SENSITIVITY: f32 = 100.0; let ev100 = f32::log2(APERTURE * APERTURE / SHUTTER_SPEED) - f32::log2(SENSITIVITY / 100.0); let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2); - let intensity = light.intensity * exposure; + let intensity = light.illuminance * exposure; // premultiply color by intensity // we don't use the alpha at all, so no reason to multiply only [0..3] From 9a78b7119be1ceb3e0f732fa6d69d8b0e2662ec3 Mon Sep 17 00:00:00 2001 From: Daniel Borges Date: Sun, 9 May 2021 10:42:09 +0200 Subject: [PATCH 11/12] tabs -> spaces --- crates/bevy_pbr/src/light.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index d957c7f06ee1d..5052dac3052af 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -63,19 +63,19 @@ impl PointLightUniform { /// /// | Illuminance (lux) | Surfaces illuminated by | /// |-------------------|------------------------------------------------| -/// | 0.0001 | Moonless, overcast night sky (starlight) | -/// | 0.002 | Moonless clear night sky with airglow | +/// | 0.0001 | Moonless, overcast night sky (starlight) | +/// | 0.002 | Moonless clear night sky with airglow | /// | 0.05–0.3 | Full moon on a clear night | -/// | 3.4 | Dark limit of civil twilight under a clear sky | -/// | 20–50 | Public areas with dark surroundings | -/// | 50 | Family living room lights | -/// | 80 | Office building hallway/toilet lighting | -/// | 100 | Very dark overcast day | -/// | 150 | Train station platforms | -/// | 320–500 | Office lighting | -/// | 400 | Sunrise or sunset on a clear day. | -/// | 1000 | Overcast day; typical TV studio lighting | -/// | 10,000–25,000 | Full daylight (not direct sun) | +/// | 3.4 | Dark limit of civil twilight under a clear sky | +/// | 20–50 | Public areas with dark surroundings | +/// | 50 | Family living room lights | +/// | 80 | Office building hallway/toilet lighting | +/// | 100 | Very dark overcast day | +/// | 150 | Train station platforms | +/// | 320–500 | Office lighting | +/// | 400 | Sunrise or sunset on a clear day. | +/// | 1000 | Overcast day; typical TV studio lighting | +/// | 10,000–25,000 | Full daylight (not direct sun) | /// | 32,000–100,000 | Direct sunlight | /// /// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux) From 52bffae01111dfb8c8ae042d72f683294920540c Mon Sep 17 00:00:00 2001 From: Daniel Borges Date: Sun, 9 May 2021 10:53:23 +0200 Subject: [PATCH 12/12] tab->space... --- crates/bevy_pbr/src/light.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 5052dac3052af..dff5f475d880d 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -65,7 +65,7 @@ impl PointLightUniform { /// |-------------------|------------------------------------------------| /// | 0.0001 | Moonless, overcast night sky (starlight) | /// | 0.002 | Moonless clear night sky with airglow | -/// | 0.05–0.3 | Full moon on a clear night | +/// | 0.05–0.3 | Full moon on a clear night | /// | 3.4 | Dark limit of civil twilight under a clear sky | /// | 20–50 | Public areas with dark surroundings | /// | 50 | Family living room lights |