Directional light and shadow (#6)

Directional light and shadow
This commit is contained in:
Robert Swain 2021-07-08 04:49:33 +02:00 committed by Carter Anderson
parent a2cac6a0d5
commit 326b20643f
11 changed files with 640 additions and 67 deletions

View file

@ -146,6 +146,10 @@ path = "examples/3d/3d_scene.rs"
name = "3d_scene_pipelined"
path = "examples/3d/3d_scene_pipelined.rs"
[[example]]
name = "cornell_box_pipelined"
path = "examples/3d/cornell_box_pipelined.rs"
[[example]]
name = "load_gltf"
path = "examples/3d/load_gltf.rs"

View file

@ -169,7 +169,7 @@ impl GlobalTransform {
#[doc(hidden)]
#[inline]
pub fn rotate(&mut self, rotation: Quat) {
self.rotation *= rotation;
self.rotation = rotation * self.rotation;
}
/// Multiplies `self` with `transform` component by component, returning the

View file

@ -181,7 +181,7 @@ impl Transform {
/// Rotates the transform by the given rotation.
#[inline]
pub fn rotate(&mut self, rotation: Quat) {
self.rotation *= rotation;
self.rotation = rotation * self.rotation;
}
/// Multiplies `self` with `transform` component by component, returning the

View file

@ -42,6 +42,7 @@ macro_rules! glam_vectors {
}
glam_vectors! {
glam::UVec4, UVec4, (x, y, z, w),
glam::Vec2, Vec2, (x, y),
glam::Vec3, Vec3, (x, y, z),
glam::Vec4, Vec4, (x, y, z, w),

View file

@ -4,10 +4,13 @@ use bevy::{
ecs::prelude::*,
input::Input,
math::{Quat, Vec3},
pbr2::{PbrBundle, PointLight, PointLightBundle, StandardMaterial},
pbr2::{
AmbientLight, DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight,
PointLightBundle, StandardMaterial,
},
prelude::{App, Assets, BuildChildren, KeyCode, Transform},
render2::{
camera::PerspectiveCameraBundle,
camera::{OrthographicProjection, PerspectiveCameraBundle},
color::Color,
mesh::{shape, Mesh},
},
@ -21,6 +24,7 @@ fn main() {
.add_plugin(LogDiagnosticsPlugin::default())
.add_startup_system(setup.system())
.add_system(movement.system())
.add_system(animate_light_direction.system())
.run();
}
@ -32,11 +36,15 @@ fn setup(
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.insert_resource(AmbientLight {
color: Color::ORANGE_RED,
brightness: 0.02,
});
// plane
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
mesh: meshes.add(Mesh::from(shape::Plane { size: 10.0 })),
material: materials.add(StandardMaterial {
base_color: Color::INDIGO,
base_color: Color::WHITE,
perceptual_roughness: 1.0,
..Default::default()
}),
@ -46,7 +54,7 @@ fn setup(
let mut transform = Transform::from_xyz(2.5, 2.5, 0.0);
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))),
transform,
material: materials.add(StandardMaterial {
base_color: Color::INDIGO,
@ -59,7 +67,7 @@ fn setup(
let mut transform = Transform::from_xyz(0.0, 2.5, -2.5);
transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2));
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))),
transform,
material: materials.add(StandardMaterial {
base_color: Color::INDIGO,
@ -76,7 +84,7 @@ fn setup(
base_color: Color::PINK,
..Default::default()
}),
transform: Transform::from_xyz(0.0, 1.0, 0.0),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..Default::default()
})
.insert(Movable);
@ -174,12 +182,43 @@ fn setup(
});
});
// camera
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(-2.0, 5.0, 7.5)
.looking_at(Vec3::new(0.0, 2.0, 0.0), Vec3::Y),
const HALF_SIZE: f32 = 10.0;
commands.spawn_bundle(DirectionalLightBundle {
directional_light: DirectionalLight {
// Configure the projection to better fit the scene
shadow_projection: OrthographicProjection {
left: -HALF_SIZE,
right: HALF_SIZE,
bottom: -HALF_SIZE,
top: HALF_SIZE,
near: -10.0 * HALF_SIZE,
far: 10.0 * HALF_SIZE,
..Default::default()
},
..Default::default()
},
transform: Transform {
translation: Vec3::new(0.0, 2.0, 0.0),
rotation: Quat::from_rotation_x(-1.2),
..Default::default()
},
..Default::default()
});
// camera
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
}
fn animate_light_direction(
time: Res<Time>,
mut query: Query<&mut Transform, With<DirectionalLight>>,
) {
for mut transform in query.iter_mut() {
transform.rotate(Quat::from_rotation_y(time.delta_seconds() * 0.5));
}
}
fn movement(

View file

@ -0,0 +1,178 @@
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
ecs::prelude::*,
math::{Mat4, Quat, Vec3},
pbr2::{
AmbientLight, DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight,
PointLightBundle, StandardMaterial,
},
prelude::{App, Assets, BuildChildren, Transform},
render2::{
camera::{OrthographicProjection, PerspectiveCameraBundle},
color::Color,
mesh::{shape, Mesh},
},
PipelinedDefaultPlugins,
};
fn main() {
App::new()
.add_plugins(PipelinedDefaultPlugins)
.add_plugin(FrameTimeDiagnosticsPlugin::default())
.add_plugin(LogDiagnosticsPlugin::default())
.add_startup_system(setup.system())
.run();
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let box_size = 2.0;
let box_thickness = 0.15;
let box_offset = (box_size + box_thickness) / 2.0;
// left - red
let mut transform = Transform::from_xyz(-box_offset, box_offset, 0.0);
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Box::new(
box_size,
box_thickness,
box_size,
))),
transform,
material: materials.add(StandardMaterial {
base_color: Color::rgb(0.63, 0.065, 0.05),
..Default::default()
}),
..Default::default()
});
// right - green
let mut transform = Transform::from_xyz(box_offset, box_offset, 0.0);
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Box::new(
box_size,
box_thickness,
box_size,
))),
transform,
material: materials.add(StandardMaterial {
base_color: Color::rgb(0.14, 0.45, 0.091),
..Default::default()
}),
..Default::default()
});
// bottom - white
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Box::new(
box_size + 2.0 * box_thickness,
box_thickness,
box_size,
))),
material: materials.add(StandardMaterial {
base_color: Color::rgb(0.725, 0.71, 0.68),
..Default::default()
}),
..Default::default()
});
// top - white
let transform = Transform::from_xyz(0.0, 2.0 * box_offset, 0.0);
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Box::new(
box_size + 2.0 * box_thickness,
box_thickness,
box_size,
))),
transform,
material: materials.add(StandardMaterial {
base_color: Color::rgb(0.725, 0.71, 0.68),
..Default::default()
}),
..Default::default()
});
// back - white
let mut transform = Transform::from_xyz(0.0, box_offset, -box_offset);
transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2));
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Box::new(
box_size + 2.0 * box_thickness,
box_thickness,
box_size + 2.0 * box_thickness,
))),
transform,
material: materials.add(StandardMaterial {
base_color: Color::rgb(0.725, 0.71, 0.68),
..Default::default()
}),
..Default::default()
});
// ambient light
commands.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 0.02,
});
// top light
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 0.4 })),
transform: Transform::from_matrix(Mat4::from_scale_rotation_translation(
Vec3::ONE,
Quat::from_rotation_x(std::f32::consts::PI),
Vec3::new(0.0, box_size + 0.5 * box_thickness, 0.0),
)),
material: materials.add(StandardMaterial {
base_color: Color::WHITE,
emissive: Color::WHITE * 100.0,
..Default::default()
}),
..Default::default()
})
.with_children(|builder| {
builder.spawn_bundle(PointLightBundle {
point_light: PointLight {
color: Color::WHITE,
shadow_bias_min: 0.00001,
shadow_bias_max: 0.0001,
intensity: 25.0,
..Default::default()
},
transform: Transform::from_translation((box_thickness + 0.05) * Vec3::Y),
..Default::default()
});
});
// directional light
const HALF_SIZE: f32 = 10.0;
commands
.spawn_bundle(DirectionalLightBundle {
directional_light: DirectionalLight {
illuminance: 10000.0,
shadow_bias_min: 0.00001,
shadow_bias_max: 0.0001,
shadow_projection: OrthographicProjection {
left: -HALF_SIZE,
right: HALF_SIZE,
bottom: -HALF_SIZE,
top: HALF_SIZE,
near: -10.0 * HALF_SIZE,
far: 10.0 * HALF_SIZE,
..Default::default()
},
..Default::default()
},
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::PI / 2.0)),
..Default::default()
});
// camera
commands
.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, box_offset, 4.0)
.looking_at(Vec3::new(0.0, box_offset, 0.0), Vec3::Y),
..Default::default()
});
}

View file

@ -1,4 +1,4 @@
use crate::{PointLight, StandardMaterial};
use crate::{DirectionalLight, PointLight, StandardMaterial};
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
use bevy_render2::mesh::Mesh;
@ -23,10 +23,18 @@ impl Default for PbrBundle {
}
}
/// A component bundle for "light" entities
/// A component bundle for "point light" entities
#[derive(Debug, Bundle, Default)]
pub struct PointLightBundle {
pub point_light: PointLight,
pub transform: Transform,
pub global_transform: GlobalTransform,
}
/// A component bundle for "directional light" entities
#[derive(Debug, Bundle, Default)]
pub struct DirectionalLightBundle {
pub directional_light: DirectionalLight,
pub transform: Transform,
pub global_transform: GlobalTransform,
}

View file

@ -1,15 +1,14 @@
use bevy_ecs::reflect::ReflectComponent;
use bevy_reflect::Reflect;
use bevy_render2::color::Color;
use bevy_render2::{camera::OrthographicProjection, color::Color};
/// A light that emits light in all directions from a central point.
#[derive(Debug, Clone, Copy, Reflect)]
#[reflect(Component)]
#[derive(Debug, Clone, Copy)]
pub struct PointLight {
pub color: Color,
pub intensity: f32,
pub range: f32,
pub radius: f32,
pub shadow_bias_min: f32,
pub shadow_bias_max: f32,
}
impl Default for PointLight {
@ -19,10 +18,78 @@ impl Default for PointLight {
intensity: 200.0,
range: 20.0,
radius: 0.0,
shadow_bias_min: Self::DEFAULT_SHADOW_BIAS_MIN,
shadow_bias_max: Self::DEFAULT_SHADOW_BIAS_MAX,
}
}
}
impl PointLight {
pub const DEFAULT_SHADOW_BIAS_MIN: f32 = 0.00005;
pub const DEFAULT_SHADOW_BIAS_MAX: f32 = 0.002;
}
/// 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.050.3 | Full moon on a clear night |
/// | 3.4 | Dark limit of civil twilight under a clear sky |
/// | 2050 | 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 |
/// | 320500 | Office lighting |
/// | 400 | Sunrise or sunset on a clear day. |
/// | 1000 | Overcast day; typical TV studio lighting |
/// | 10,00025,000 | Full daylight (not direct sun) |
/// | 32,000100,000 | Direct sunlight |
///
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux)
#[derive(Debug, Clone)]
pub struct DirectionalLight {
pub color: Color,
pub illuminance: f32,
pub shadow_projection: OrthographicProjection,
pub shadow_bias_min: f32,
pub shadow_bias_max: f32,
}
impl Default for DirectionalLight {
fn default() -> Self {
let size = 100.0;
DirectionalLight {
color: Color::rgb(1.0, 1.0, 1.0),
illuminance: 100000.0,
shadow_projection: OrthographicProjection {
left: -size,
right: size,
bottom: -size,
top: size,
near: -size,
far: size,
..Default::default()
},
shadow_bias_min: Self::DEFAULT_SHADOW_BIAS_MIN,
shadow_bias_max: Self::DEFAULT_SHADOW_BIAS_MAX,
}
}
}
impl DirectionalLight {
pub const DEFAULT_SHADOW_BIAS_MIN: f32 = 0.00005;
pub const DEFAULT_SHADOW_BIAS_MAX: f32 = 0.002;
}
// Ambient light color.
#[derive(Debug)]
pub struct AmbientLight {

View file

@ -1,7 +1,8 @@
use crate::{AmbientLight, ExtractedMeshes, MeshMeta, PbrShaders, PointLight};
use crate::{AmbientLight, DirectionalLight, ExtractedMeshes, MeshMeta, PbrShaders, PointLight};
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_math::{const_vec3, Mat4, Vec3, Vec4};
use bevy_render2::{
camera::CameraProjection,
color::Color,
core_pipeline::Transparent3dPhase,
mesh::Mesh,
@ -28,11 +29,22 @@ pub struct ExtractedPointLight {
range: f32,
radius: f32,
transform: GlobalTransform,
shadow_bias_min: f32,
shadow_bias_max: f32,
}
pub struct ExtractedDirectionalLight {
color: Color,
illuminance: f32,
direction: Vec3,
projection: Mat4,
shadow_bias_min: f32,
shadow_bias_max: f32,
}
#[repr(C)]
#[derive(Copy, Clone, AsStd140, Default, Debug)]
pub struct GpuLight {
pub struct GpuPointLight {
color: Vec4,
// proj: Mat4,
position: Vec3,
@ -40,30 +52,51 @@ pub struct GpuLight {
radius: f32,
near: f32,
far: f32,
shadow_bias_min: f32,
shadow_bias_max: f32,
}
#[repr(C)]
#[derive(Copy, Clone, AsStd140, Default, Debug)]
pub struct GpuDirectionalLight {
view_projection: Mat4,
color: Vec4,
dir_to_light: Vec3,
shadow_bias_min: f32,
shadow_bias_max: f32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, AsStd140)]
pub struct GpuLights {
// TODO: this comes first to work around a WGSL alignment issue. We need to solve this issue before releasing the renderer rework
lights: [GpuLight; MAX_POINT_LIGHTS],
point_lights: [GpuPointLight; MAX_POINT_LIGHTS],
directional_lights: [GpuDirectionalLight; MAX_DIRECTIONAL_LIGHTS],
ambient_color: Vec4,
len: u32,
n_point_lights: u32,
n_directional_lights: u32,
}
// NOTE: this must be kept in sync MAX_POINT_LIGHTS in pbr.frag
// NOTE: this must be kept in sync with the same constants in pbr.frag
pub const MAX_POINT_LIGHTS: usize = 10;
pub const SHADOW_SIZE: Extent3d = Extent3d {
pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
pub const POINT_SHADOW_SIZE: Extent3d = Extent3d {
width: 1024,
height: 1024,
depth_or_array_layers: 6 * MAX_POINT_LIGHTS as u32,
depth_or_array_layers: (6 * MAX_POINT_LIGHTS) as u32,
};
pub const DIRECTIONAL_SHADOW_SIZE: Extent3d = Extent3d {
width: 4096,
height: 4096,
depth_or_array_layers: MAX_DIRECTIONAL_LIGHTS as u32,
};
pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
pub struct ShadowShaders {
pub pipeline: RenderPipeline,
pub view_layout: BindGroupLayout,
pub light_sampler: Sampler,
pub point_light_sampler: Sampler,
pub directional_light_sampler: Sampler,
}
// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
@ -160,7 +193,17 @@ impl FromWorld for ShadowShaders {
ShadowShaders {
pipeline,
view_layout,
light_sampler: render_device.create_sampler(&SamplerDescriptor {
point_light_sampler: render_device.create_sampler(&SamplerDescriptor {
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
address_mode_w: AddressMode::ClampToEdge,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
mipmap_filter: FilterMode::Nearest,
compare: Some(CompareFunction::LessEqual),
..Default::default()
}),
directional_light_sampler: render_device.create_sampler(&SamplerDescriptor {
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
address_mode_w: AddressMode::ClampToEdge,
@ -178,21 +221,36 @@ impl FromWorld for ShadowShaders {
pub fn extract_lights(
mut commands: Commands,
ambient_light: Res<AmbientLight>,
lights: Query<(Entity, &PointLight, &GlobalTransform)>,
point_lights: Query<(Entity, &PointLight, &GlobalTransform)>,
directional_lights: Query<(Entity, &DirectionalLight, &GlobalTransform)>,
) {
commands.insert_resource(ExtractedAmbientLight {
color: ambient_light.color,
brightness: ambient_light.brightness,
});
for (entity, light, transform) in lights.iter() {
for (entity, point_light, transform) in point_lights.iter() {
commands.get_or_spawn(entity).insert(ExtractedPointLight {
color: light.color,
intensity: light.intensity,
range: light.range,
radius: light.radius,
color: point_light.color,
intensity: point_light.intensity,
range: point_light.range,
radius: point_light.radius,
transform: *transform,
shadow_bias_min: point_light.shadow_bias_min,
shadow_bias_max: point_light.shadow_bias_max,
});
}
for (entity, directional_light, transform) in directional_lights.iter() {
commands
.get_or_spawn(entity)
.insert(ExtractedDirectionalLight {
color: directional_light.color,
illuminance: directional_light.illuminance,
direction: transform.forward(),
projection: directional_light.shadow_projection.get_projection_matrix(),
shadow_bias_min: directional_light.shadow_bias_min,
shadow_bias_max: directional_light.shadow_bias_max,
});
}
}
// Can't do `Vec3::Y * -1.0` because mul isn't const
@ -239,13 +297,28 @@ const CUBE_MAP_FACES: [CubeMapFace; 6] = [
},
];
fn face_index_to_name(face_index: usize) -> &'static str {
match face_index {
0 => "+x",
1 => "-x",
2 => "+y",
3 => "-y",
4 => "+z",
5 => "-z",
_ => "invalid",
}
}
pub struct ViewLight {
pub depth_texture_view: TextureView,
pub pass_name: String,
}
pub struct ViewLights {
pub light_depth_texture: Texture,
pub light_depth_texture_view: TextureView,
pub point_light_depth_texture: Texture,
pub point_light_depth_texture_view: TextureView,
pub directional_light_depth_texture: Texture,
pub directional_light_depth_texture_view: TextureView,
pub lights: Vec<Entity>,
pub gpu_light_binding_index: u32,
}
@ -263,7 +336,8 @@ pub fn prepare_lights(
mut light_meta: ResMut<LightMeta>,
views: Query<Entity, With<RenderPhase<Transparent3dPhase>>>,
ambient_light: Res<ExtractedAmbientLight>,
lights: Query<&ExtractedPointLight>,
point_lights: Query<&ExtractedPointLight>,
directional_lights: Query<&ExtractedDirectionalLight>,
) {
// PERF: view.iter().count() could be views.iter().len() if we implemented ExactSizeIterator for archetype-only filters
light_meta
@ -273,10 +347,22 @@ pub fn prepare_lights(
let ambient_color = ambient_light.color.as_rgba_linear() * ambient_light.brightness;
// set up light data for each view
for entity in views.iter() {
let light_depth_texture = texture_cache.get(
let point_light_depth_texture = texture_cache.get(
&render_device,
TextureDescriptor {
size: SHADOW_SIZE,
size: POINT_SHADOW_SIZE,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: SHADOW_FORMAT,
usage: TextureUsage::RENDER_ATTACHMENT | TextureUsage::SAMPLED,
label: None,
},
);
let directional_light_depth_texture = texture_cache.get(
&render_device,
TextureDescriptor {
size: DIRECTIONAL_SHADOW_SIZE,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
@ -289,12 +375,14 @@ pub fn prepare_lights(
let mut gpu_lights = GpuLights {
ambient_color: ambient_color.into(),
len: lights.iter().len() as u32,
lights: [GpuLight::default(); MAX_POINT_LIGHTS],
n_point_lights: point_lights.iter().len() as u32,
n_directional_lights: directional_lights.iter().len() as u32,
point_lights: [GpuPointLight::default(); MAX_POINT_LIGHTS],
directional_lights: [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS],
};
// TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
for (light_index, light) in lights.iter().enumerate().take(MAX_POINT_LIGHTS) {
for (light_index, light) in point_lights.iter().enumerate().take(MAX_POINT_LIGHTS) {
let projection =
Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, light.range);
@ -308,7 +396,7 @@ pub fn prepare_lights(
let view_rotation = GlobalTransform::identity().looking_at(*target, *up);
let depth_texture_view =
light_depth_texture
point_light_depth_texture
.texture
.create_view(&TextureViewDescriptor {
label: None,
@ -324,10 +412,17 @@ pub fn prepare_lights(
let view_light_entity = commands
.spawn()
.insert_bundle((
ViewLight { depth_texture_view },
ViewLight {
depth_texture_view,
pass_name: format!(
"shadow pass point light {} {}",
light_index,
face_index_to_name(face_index)
),
},
ExtractedView {
width: SHADOW_SIZE.width,
height: SHADOW_SIZE.height,
width: POINT_SHADOW_SIZE.width,
height: POINT_SHADOW_SIZE.height,
transform: view_translation * view_rotation,
projection,
},
@ -337,7 +432,7 @@ pub fn prepare_lights(
view_lights.push(view_light_entity);
}
gpu_lights.lights[light_index] = GpuLight {
gpu_lights.point_lights[light_index] = GpuPointLight {
// premultiply color by intensity
// we don't use the alpha at all, so no reason to multiply only [0..3]
color: (light.color.as_rgba_linear() * light.intensity).into(),
@ -347,11 +442,83 @@ pub fn prepare_lights(
near: 0.1,
far: light.range,
// proj: projection,
shadow_bias_min: light.shadow_bias_min,
shadow_bias_max: light.shadow_bias_max,
};
}
let light_depth_texture_view =
light_depth_texture
for (i, light) in directional_lights
.iter()
.enumerate()
.take(MAX_DIRECTIONAL_LIGHTS)
{
// direction is negated to be ready for N.L
let dir_to_light = -light.direction;
// 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;
// NOTE: A directional light seems to have to have an eye position on the line along the direction of the light
// through the world origin. I (Rob Swain) do not yet understand why it cannot be translated away from this.
let view = Mat4::look_at_rh(Vec3::ZERO, light.direction, Vec3::Y);
// NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast
let projection = light.projection;
gpu_lights.directional_lights[i] = GpuDirectionalLight {
// premultiply color by intensity
// we don't use the alpha at all, so no reason to multiply only [0..3]
color: (light.color.as_rgba_linear() * intensity).into(),
dir_to_light,
// NOTE: * view is correct, it should not be view.inverse() here
view_projection: projection * view,
shadow_bias_min: light.shadow_bias_min,
shadow_bias_max: light.shadow_bias_max,
};
let depth_texture_view =
directional_light_depth_texture
.texture
.create_view(&TextureViewDescriptor {
label: None,
format: None,
dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: i as u32,
array_layer_count: NonZeroU32::new(1),
});
let view_light_entity = commands
.spawn()
.insert_bundle((
ViewLight {
depth_texture_view,
pass_name: format!("shadow pass directional light {}", i),
},
ExtractedView {
width: DIRECTIONAL_SHADOW_SIZE.width,
height: DIRECTIONAL_SHADOW_SIZE.height,
transform: GlobalTransform::from_matrix(view.inverse()),
projection,
},
RenderPhase::<ShadowPhase>::default(),
))
.id();
view_lights.push(view_light_entity);
}
let point_light_depth_texture_view =
point_light_depth_texture
.texture
.create_view(&TextureViewDescriptor {
label: None,
@ -363,10 +530,24 @@ pub fn prepare_lights(
base_array_layer: 0,
array_layer_count: None,
});
let directional_light_depth_texture_view = directional_light_depth_texture
.texture
.create_view(&TextureViewDescriptor {
label: None,
format: None,
dimension: Some(TextureViewDimension::D2Array),
aspect: TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
});
commands.entity(entity).insert(ViewLights {
light_depth_texture: light_depth_texture.texture,
light_depth_texture_view,
point_light_depth_texture: point_light_depth_texture.texture,
point_light_depth_texture_view,
directional_light_depth_texture: directional_light_depth_texture.texture,
directional_light_depth_texture_view,
lights: view_lights,
gpu_light_binding_index: light_meta.view_gpu_lights.push(gpu_lights),
});
@ -419,7 +600,7 @@ impl Node for ShadowPassNode {
.get_manual(world, view_light_entity)
.unwrap();
let pass_descriptor = RenderPassDescriptor {
label: Some("shadow_pass"),
label: Some(&view_light.pass_name),
color_attachments: &[],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &view_light.depth_texture_view,

View file

@ -66,13 +66,13 @@ impl FromWorld for PbrShaders {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
// TODO: change this to ViewUniform::std140_size_static once crevice fixes this!
// TODO: change this to GpuLights::std140_size_static once crevice fixes this!
// Context: https://github.com/LPGhatguy/crevice/issues/29
min_binding_size: BufferSize::new(512),
min_binding_size: BufferSize::new(1024),
},
count: None,
},
// Shadow Texture Array
// Point Shadow Texture Cube Array
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStage::FRAGMENT,
@ -83,7 +83,7 @@ impl FromWorld for PbrShaders {
},
count: None,
},
// Shadow Texture Array Sampler
// Point Shadow Texture Array Sampler
BindGroupLayoutEntry {
binding: 3,
visibility: ShaderStage::FRAGMENT,
@ -93,6 +93,27 @@ impl FromWorld for PbrShaders {
},
count: None,
},
// Directional Shadow Texture Array
BindGroupLayoutEntry {
binding: 4,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Depth,
view_dimension: TextureViewDimension::D2Array,
},
count: None,
},
// Directional Shadow Texture Array Sampler
BindGroupLayoutEntry {
binding: 5,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Sampler {
comparison: true,
filtering: true,
},
count: None,
},
],
label: None,
});
@ -379,7 +400,6 @@ pub fn extract_meshes(
continue;
}
}
if let Some(ref image) = material.emissive_texture {
if !images.contains(image) {
continue;
@ -537,11 +557,23 @@ pub fn queue_meshes(
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(&view_lights.light_depth_texture_view),
resource: BindingResource::TextureView(
&view_lights.point_light_depth_texture_view,
),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Sampler(&shadow_shaders.light_sampler),
resource: BindingResource::Sampler(&shadow_shaders.point_light_sampler),
},
BindGroupEntry {
binding: 4,
resource: BindingResource::TextureView(
&view_lights.directional_light_depth_texture_view,
),
},
BindGroupEntry {
binding: 5,
resource: BindingResource::Sampler(&shadow_shaders.directional_light_sampler),
},
],
label: None,

View file

@ -95,6 +95,16 @@ struct PointLight {
radius: f32;
near: f32;
far: f32;
shadow_bias_min: f32;
shadow_bias_max: f32;
};
struct DirectionalLight {
view_projection: mat4x4<f32>;
color: vec4<f32>;
direction_to_light: vec3<f32>;
shadow_bias_min: f32;
shadow_bias_max: f32;
};
[[block]]
@ -102,8 +112,10 @@ struct Lights {
// NOTE: this array size must be kept in sync with the constants defined bevy_pbr2/src/render/light.rs
// TODO: this can be removed if we move to storage buffers for light arrays
point_lights: array<PointLight, 10>;
directional_lights: array<DirectionalLight, 1>;
ambient_color: vec4<f32>;
num_lights: u32;
n_point_lights: u32;
n_directional_lights: u32;
};
let FLAGS_BASE_COLOR_TEXTURE_BIT: u32 = 1u;
@ -117,9 +129,13 @@ let FLAGS_UNLIT_BIT: u32 = 32u;
[[group(0), binding(1)]]
var lights: Lights;
[[group(0), binding(2)]]
var shadow_textures: texture_depth_cube_array;
var point_shadow_textures: texture_depth_cube_array;
[[group(0), binding(3)]]
var shadow_textures_sampler: sampler_comparison;
var point_shadow_textures_sampler: sampler_comparison;
[[group(0), binding(4)]]
var directional_shadow_textures: texture_depth_2d_array;
[[group(0), binding(5)]]
var directional_shadow_textures_sampler: sampler_comparison;
[[group(2), binding(0)]]
var material: StandardMaterial;
@ -348,7 +364,22 @@ fn point_light(
return ((diffuse + specular_light) * light.color.rgb) * (rangeAttenuation * NoL);
}
fn fetch_shadow(light_id: i32, frag_position: vec4<f32>) -> f32 {
fn directional_light(light: DirectionalLight, roughness: f32, NdotV: f32, normal: vec3<f32>, view: vec3<f32>, R: vec3<f32>, F0: vec3<f32>, diffuseColor: vec3<f32>) -> vec3<f32> {
let incident_light = light.direction_to_light.xyz;
let half_vector = normalize(incident_light + view);
let NoL = saturate(dot(normal, incident_light));
let NoH = saturate(dot(normal, half_vector));
let LoH = saturate(dot(incident_light, half_vector));
let diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH);
let specularIntensity = 1.0;
let specular_light = specular(F0, roughness, half_vector, NdotV, NoL, NoH, LoH, specularIntensity);
return (specular_light + diffuse) * light.color.rgb * NoL;
}
fn fetch_point_shadow(light_id: i32, frag_position: vec4<f32>, shadow_bias: f32) -> f32 {
let light = lights.point_lights[light_id];
// because the shadow maps align with the axes and the frustum planes are at 45 degrees
@ -382,7 +413,22 @@ fn fetch_shadow(light_id: i32, frag_position: vec4<f32>) -> f32 {
// mip-mapping functionality. The shadow maps have no mipmaps so Level just samples
// from LOD 0.
let bias = 0.0001;
return textureSampleCompareLevel(shadow_textures, shadow_textures_sampler, frag_ls, i32(light_id), depth - bias);
return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls, i32(light_id), depth - shadow_bias);
}
fn fetch_directional_shadow(light_id: i32, homogeneous_coords: vec4<f32>, shadow_bias: f32) -> f32 {
if (homogeneous_coords.w <= 0.0) {
return 1.0;
}
// compensate for the Y-flip difference between the NDC and texture coordinates
let flip_correction = vec2<f32>(0.5, -0.5);
let proj_correction = 1.0 / homogeneous_coords.w;
// compute texture coordinates for shadow lookup
let light_local = homogeneous_coords.xy * flip_correction * proj_correction + vec2<f32>(0.5, 0.5);
// do the lookup, using HW PCF and comparison
// NOTE: Due to non-uniform control flow above, we must use the level variant of the texture
// sampler to avoid use of implicit derivatives causing possible undefined behavior.
return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, i32(light_id), homogeneous_coords.z * proj_correction - shadow_bias);
}
struct FragmentInput {
@ -471,10 +517,27 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
// accumulate color
var light_accum: vec3<f32> = vec3<f32>(0.0);
for (var i: i32 = 0; i < i32(lights.num_lights); i = i + 1) {
let n_point_lights = i32(lights.n_point_lights);
let n_directional_lights = i32(lights.n_directional_lights);
for (var i: i32 = 0; i < n_point_lights; i = i + 1) {
let light = lights.point_lights[i];
let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, N, V, R, F0, diffuse_color);
let shadow = fetch_shadow(i, in.world_position);
let dir_to_light = normalize(light.position.xyz - in.world_position.xyz);
let shadow_bias = max(
light.shadow_bias_max * (1.0 - dot(in.world_normal, dir_to_light)),
light.shadow_bias_min
);
let shadow = fetch_point_shadow(i, in.world_position, shadow_bias);
light_accum = light_accum + light_contrib * shadow;
}
for (var i: i32 = 0; i < n_directional_lights; i = i + 1) {
let light = lights.directional_lights[i];
let light_contrib = directional_light(light, roughness, NdotV, N, V, R, F0, diffuse_color);
let shadow_bias = max(
light.shadow_bias_max * (1.0 - dot(in.world_normal, light.direction_to_light.xyz)),
light.shadow_bias_min
);
let shadow = fetch_directional_shadow(i, light.view_projection * in.world_position, shadow_bias);
light_accum = light_accum + light_contrib * shadow;
}