Skip to content

Commit

Permalink
Directional light (bevyengine#2112)
Browse files Browse the repository at this point in the history
This PR adds a `DirectionalLight` component to bevy_pbr.
  • Loading branch information
msklywenn authored and ostwilkens committed Jul 27, 2021
1 parent f83c1e4 commit 795d711
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 74 deletions.
6 changes: 5 additions & 1 deletion crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ pub use material::*;

pub mod prelude {
#[doc(hidden)]
pub use crate::{entity::*, light::PointLight, material::StandardMaterial};
pub use crate::{
entity::*,
light::{DirectionalLight, PointLight},
material::StandardMaterial,
};
}

use bevy_app::prelude::*;
Expand Down
108 changes: 106 additions & 2 deletions crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
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;

/// A point light
#[derive(Debug, Reflect)]
#[derive(Debug, Clone, Copy, Reflect)]
#[reflect(Component)]
pub struct PointLight {
pub color: Color,
Expand Down Expand Up @@ -37,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
Expand All @@ -52,6 +53,109 @@ 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.
///
/// 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 illuminance: f32,
direction: Vec3,
}

impl DirectionalLight {
/// Create a new directional light component.
pub fn new(color: Color, illuminance: f32, direction: Vec3) -> Self {
DirectionalLight {
color,
illuminance,
direction: direction.normalize(),
}
}

/// Set direction of light.
pub fn set_direction(&mut self, direction: Vec3) {
self.direction = direction.normalize();
}

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),
illuminance: 100000.0,
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 new(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,
];

// 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
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.illuminance * 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 * intensity).into();

DirectionalLightUniform { dir, color }
}
}

// Ambient light color.
#[derive(Debug)]
pub struct AmbientLight {
Expand Down
70 changes: 52 additions & 18 deletions crates/bevy_pbr/src/render_graph/lights_node.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::{
light::{AmbientLight, PointLight, PointLightUniform},
light::{
AmbientLight, DirectionalLight, DirectionalLightUniform, PointLight, PointLightUniform,
},
render_graph::uniform,
};
use bevy_core::{AsBytes, Byteable};
Expand All @@ -21,12 +23,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(),
}
}
Expand All @@ -48,6 +52,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],
}

Expand All @@ -59,6 +65,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,
})
Expand All @@ -74,6 +81,7 @@ pub struct LightsNodeSystemState {
staging_buffer: Option<BufferId>,
command_queue: CommandQueue,
max_point_lights: usize,
max_dir_lights: usize,
}

pub fn lights_node_system(
Expand All @@ -83,7 +91,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<RenderResourceBindings>,
query: Query<(&PointLight, &GlobalTransform)>,
point_lights: Query<(&PointLight, &GlobalTransform)>,
dir_lights: Query<&DirectionalLight>,
) {
let state = &mut state;
let render_resource_context = &**render_resource_context;
Expand All @@ -92,16 +101,31 @@ 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::<PointLightUniform>();

let point_light_count = point_lights.iter().len().min(state.max_point_lights);
let point_light_size = std::mem::size_of::<PointLightUniform>();
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::<DirectionalLightUniform>();
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::<LightCount>();
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;
}

Expand Down Expand Up @@ -133,23 +157,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());
data[ambient_light_size..light_count_size].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(),
PointLightUniform::new(&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),
) {
slot.copy_from_slice(DirectionalLightUniform::new(&dir_light).as_bytes());
}
},
);
render_resource_context.unmap_buffer(staging_buffer);
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_pbr/src/render_graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<RenderGraph>().unwrap();
Expand All @@ -39,7 +40,10 @@ pub(crate) fn add_pbr_graph(world: &mut World) {
AssetRenderResourcesNode::<StandardMaterial>::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
Expand Down
Loading

0 comments on commit 795d711

Please sign in to comment.