Skip to content

Commit

Permalink
bevy_pbr2: Add support for not casting/receiving shadows (#2726)
Browse files Browse the repository at this point in the history
# Objective

Allow marking meshes as not casting / receiving shadows.

## Solution

- Added `NotShadowCaster` and `NotShadowReceiver` zero-sized type components.
- Extract these components into `bool`s in `ExtractedMesh`
- Only generate `DrawShadowMesh` `Drawable`s for meshes _without_ `NotShadowCaster`
- Add a `u32` bit `flags` member to `MeshUniform` with one flag indicating whether the mesh is a shadow receiver
- If a mesh does _not_ have the `NotShadowReceiver` component, then it is a shadow receiver, and so the bit in the `MeshUniform` is set, otherwise it is not set.
- Added an example illustrating the functionality.

NOTE: I wanted to have the default state of a mesh as being a shadow caster and shadow receiver, hence the `Not*` components. However, I am on the fence about this. I don't want to have a negative performance impact, nor have people wondering why their custom meshes don't have shadows because they forgot to add `ShadowCaster` and `ShadowReceiver` components, but I also really don't like the double negatives the `Not*` approach incurs. What do you think?

Co-authored-by: Carter Anderson <[email protected]>
  • Loading branch information
superdump and cart committed Aug 25, 2021
1 parent f368bf7 commit f4aa328
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 35 deletions.
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

0 comments on commit f4aa328

Please sign in to comment.