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

[Merged by Bors] - Directional light #2112

Closed
wants to merge 12 commits into from
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
103 changes: 101 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,104 @@ 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.
///
/// An `intensity` of 100000.0 is a good start for a sunlight.
#[derive(Debug, Clone, Copy, 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.is_normalized(),
"Light direction vector should have been normalized."
);
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.is_normalized(),
"Light direction vector should have been normalized."
);
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,
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.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 * 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