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] - bevy_pbr2: Add support for not casting/receiving shadows #2726

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ path = "examples/3d/render_to_texture.rs"
name = "shadow_biases_pipelined"
path = "examples/3d/shadow_biases_pipelined.rs"

[[example]]
name = "shadow_caster_receiver_pipelined"
path = "examples/3d/shadow_caster_receiver_pipelined.rs"

[[example]]
name = "spawner"
path = "examples/3d/spawner.rs"
Expand Down
182 changes: 182 additions & 0 deletions examples/3d/shadow_caster_receiver_pipelined.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
use bevy::{
ecs::prelude::*,
input::Input,
math::{EulerRot, Mat4, Vec3},
pbr2::{
DirectionalLight, DirectionalLightBundle, NotShadowCaster, NotShadowReceiver, PbrBundle,
PointLight, PointLightBundle, StandardMaterial,
},
prelude::{App, Assets, Handle, KeyCode, Transform},
render2::{
camera::{OrthographicProjection, PerspectiveCameraBundle},
color::Color,
mesh::{shape, Mesh},
},
PipelinedDefaultPlugins,
};

fn main() {
println!(
"Controls:
C - toggle shadow casters (i.e. casters become not, and not casters become casters)
R - toggle shadow receivers (i.e. receivers become not, and not receivers become receivers)
L - switch between directional and point lights"
);
App::new()
.add_plugins(PipelinedDefaultPlugins)
.add_startup_system(setup)
.add_system(toggle_light)
.add_system(toggle_shadows)
.run();
}

/// set up a 3D scene to test shadow biases and perspective projections
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let spawn_plane_depth = 500.0f32;
let spawn_height = 2.0;
let sphere_radius = 0.25;

let white_handle = materials.add(StandardMaterial {
base_color: Color::WHITE,
perceptual_roughness: 1.0,
..Default::default()
});
let sphere_handle = meshes.add(Mesh::from(shape::Icosphere {
radius: sphere_radius,
..Default::default()
}));

// sphere - initially a caster
commands.spawn_bundle(PbrBundle {
mesh: sphere_handle.clone(),
material: materials.add(Color::RED.into()),
transform: Transform::from_xyz(-1.0, spawn_height, 0.0),
..Default::default()
});

// sphere - initially not a caster
commands
.spawn_bundle(PbrBundle {
mesh: sphere_handle,
material: materials.add(Color::BLUE.into()),
transform: Transform::from_xyz(1.0, spawn_height, 0.0),
..Default::default()
})
.insert(NotShadowCaster);

// floating plane - initially not a shadow receiver and not a caster
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 20.0 })),
material: materials.add(Color::GREEN.into()),
transform: Transform::from_xyz(0.0, 1.0, -10.0),
..Default::default()
})
.insert_bundle((NotShadowCaster, NotShadowReceiver));

// lower ground plane - initially a shadow receiver
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 20.0 })),
material: white_handle,
..Default::default()
});

println!("Using DirectionalLight");

commands.spawn_bundle(PointLightBundle {
transform: Transform::from_xyz(5.0, 5.0, 0.0),
point_light: PointLight {
intensity: 0.0,
range: spawn_plane_depth,
color: Color::WHITE,
..Default::default()
},
..Default::default()
});

let theta = std::f32::consts::FRAC_PI_4;
let light_transform = Mat4::from_euler(EulerRot::ZYX, 0.0, std::f32::consts::FRAC_PI_2, -theta);
commands.spawn_bundle(DirectionalLightBundle {
directional_light: DirectionalLight {
illuminance: 100000.0,
shadow_projection: OrthographicProjection {
left: -10.0,
right: 10.0,
bottom: -10.0,
top: 10.0,
near: -50.0,
far: 50.0,
..Default::default()
},
..Default::default()
},
transform: Transform::from_matrix(light_transform),
..Default::default()
});

// camera
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(-5.0, 5.0, 5.0)
.looking_at(Vec3::new(-1.0, 1.0, 0.0), Vec3::Y),
..Default::default()
});
}

fn toggle_light(
input: Res<Input<KeyCode>>,
mut point_lights: Query<&mut PointLight>,
mut directional_lights: Query<&mut DirectionalLight>,
) {
if input.just_pressed(KeyCode::L) {
for mut light in point_lights.iter_mut() {
light.intensity = if light.intensity == 0.0 {
println!("Using PointLight");
100000000.0
} else {
0.0
};
}
for mut light in directional_lights.iter_mut() {
light.illuminance = if light.illuminance == 0.0 {
println!("Using DirectionalLight");
100000.0
} else {
0.0
};
}
}
}

fn toggle_shadows(
mut commands: Commands,
input: Res<Input<KeyCode>>,
queries: QuerySet<(
Query<Entity, (With<Handle<Mesh>>, With<NotShadowCaster>)>,
Query<Entity, (With<Handle<Mesh>>, With<NotShadowReceiver>)>,
Query<Entity, (With<Handle<Mesh>>, Without<NotShadowCaster>)>,
Query<Entity, (With<Handle<Mesh>>, Without<NotShadowReceiver>)>,
)>,
) {
if input.just_pressed(KeyCode::C) {
println!("Toggling casters");
for entity in queries.q0().iter() {
commands.entity(entity).remove::<NotShadowCaster>();
}
for entity in queries.q2().iter() {
commands.entity(entity).insert(NotShadowCaster);
}
}
if input.just_pressed(KeyCode::R) {
println!("Toggling receivers");
for entity in queries.q1().iter() {
commands.entity(entity).remove::<NotShadowReceiver>();
}
for entity in queries.q3().iter() {
commands.entity(entity).insert(NotShadowReceiver);
}
}
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Example | File | Description
`pbr` | [`3d/pbr.rs`](./3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
`pbr_pipelined` | [`3d/pbr_pipelined.rs`](./3d/pbr_pipelined.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
`render_to_texture` | [`3d/render_to_texture.rs`](./3d/render_to_texture.rs) | Shows how to render to texture
`shadow_caster_receiver_pipelined` | [`3d/shadow_caster_receiver_pipelined.rs`](./3d/shadow_caster_receiver_pipelined.rs) | Demonstrates how to prevent meshes from casting/receiving shadows in a 3d scene
`shadow_biases_pipelined` | [`3d/shadow_biases_pipelined.rs`](./3d/shadow_biases_pipelined.rs) | Demonstrates how shadow biases affect shadows in a 3d scene
`spawner` | [`3d/spawner.rs`](./3d/spawner.rs) | Renders a large number of cubes with changing position and material
`texture` | [`3d/texture.rs`](./3d/texture.rs) | Shows configuration of texture materials
Expand Down
5 changes: 5 additions & 0 deletions pipelined/bevy_pbr2/src/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,8 @@ impl Default for AmbientLight {
}
}
}

/// Add this component to make a `Mesh` not cast shadows
pub struct NotShadowCaster;
/// Add this component to make a `Mesh` not receive shadows
pub struct NotShadowReceiver;
72 changes: 58 additions & 14 deletions pipelined/bevy_pbr2/src/render/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod light;
pub use light::*;

use crate::{StandardMaterial, StandardMaterialUniformData};
use crate::{NotShadowCaster, NotShadowReceiver, StandardMaterial, StandardMaterialUniformData};
use bevy_asset::{Assets, Handle};
use bevy_core_pipeline::Transparent3dPhase;
use bevy_ecs::{prelude::*, system::SystemState};
Expand Down Expand Up @@ -120,11 +120,11 @@ impl FromWorld for PbrShaders {
let mesh_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStage::VERTEX,
visibility: ShaderStage::VERTEX | ShaderStage::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(Mat4::std140_size_static() as u64),
min_binding_size: BufferSize::new(80),
},
count: None,
}],
Expand Down Expand Up @@ -374,6 +374,8 @@ struct ExtractedMesh {
mesh: Handle<Mesh>,
transform_binding_offset: u32,
material_handle: Handle<StandardMaterial>,
casts_shadows: bool,
receives_shadows: bool,
}

pub struct ExtractedMeshes {
Expand All @@ -385,10 +387,23 @@ pub fn extract_meshes(
meshes: Res<Assets<Mesh>>,
materials: Res<Assets<StandardMaterial>>,
images: Res<Assets<Image>>,
query: Query<(&GlobalTransform, &Handle<Mesh>, &Handle<StandardMaterial>)>,
query: Query<(
&GlobalTransform,
&Handle<Mesh>,
&Handle<StandardMaterial>,
Option<&NotShadowCaster>,
Option<&NotShadowReceiver>,
)>,
) {
let mut extracted_meshes = Vec::new();
for (transform, mesh_handle, material_handle) in query.iter() {
for (
transform,
mesh_handle,
material_handle,
maybe_not_shadow_caster,
maybe_not_shadow_receiver,
) in query.iter()
{
if !meshes.contains(mesh_handle) {
continue;
}
Expand Down Expand Up @@ -419,6 +434,10 @@ pub fn extract_meshes(
mesh: mesh_handle.clone_weak(),
transform_binding_offset: 0,
material_handle: material_handle.clone_weak(),
// NOTE: Double-negative is so that meshes cast and receive shadows by default
// Not not shadow caster means that this mesh is a shadow caster
casts_shadows: maybe_not_shadow_caster.is_none(),
receives_shadows: maybe_not_shadow_receiver.is_none(),
});
} else {
continue;
Expand All @@ -435,9 +454,25 @@ struct MeshDrawInfo {
material_bind_group_key: FrameSlabMapKey<BufferId, BindGroup>,
}

#[derive(Debug, AsStd140)]
pub struct MeshUniform {
model: Mat4,
flags: u32,
}

// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.wgsl!
bitflags::bitflags! {
#[repr(transparent)]
struct MeshFlags: u32 {
const SHADOW_RECEIVER = (1 << 0);
const NONE = 0;
const UNINITIALIZED = 0xFFFF;
}
}

#[derive(Default)]
pub struct MeshMeta {
transform_uniforms: DynamicUniformVec<Mat4>,
transform_uniforms: DynamicUniformVec<MeshUniform>,
material_bind_groups: FrameSlabMap<BufferId, BindGroup>,
mesh_transform_bind_group: FrameSlabMap<BufferId, BindGroup>,
mesh_transform_bind_group_key: Option<FrameSlabMapKey<BufferId, BindGroup>>,
Expand All @@ -453,8 +488,15 @@ pub fn prepare_meshes(
.transform_uniforms
.reserve_and_clear(extracted_meshes.meshes.len(), &render_device);
for extracted_mesh in extracted_meshes.meshes.iter_mut() {
extracted_mesh.transform_binding_offset =
mesh_meta.transform_uniforms.push(extracted_mesh.transform);
let flags = if extracted_mesh.receives_shadows {
MeshFlags::SHADOW_RECEIVER
} else {
MeshFlags::NONE
};
extracted_mesh.transform_binding_offset = mesh_meta.transform_uniforms.push(MeshUniform {
model: extracted_mesh.transform,
flags: flags.bits,
});
}

mesh_meta
Expand Down Expand Up @@ -694,12 +736,14 @@ pub fn queue_meshes(
for view_light_entity in view_lights.lights.iter().copied() {
let mut shadow_phase = view_light_shadow_phases.get_mut(view_light_entity).unwrap();
// TODO: this should only queue up meshes that are actually visible by each "light view"
for i in 0..extracted_meshes.meshes.len() {
shadow_phase.add(Drawable {
draw_function: draw_shadow_mesh,
draw_key: i,
sort_key: 0, // TODO: sort back-to-front
})
for (i, mesh) in extracted_meshes.meshes.iter().enumerate() {
if mesh.casts_shadows {
shadow_phase.add(Drawable {
draw_function: draw_shadow_mesh,
draw_key: i,
sort_key: 0, // TODO: sort back-to-front
});
}
}
}
}
Expand Down
Loading