Skip to content

Commit

Permalink
bevy_pbr2: Add support for not casting/receiving shadows
Browse files Browse the repository at this point in the history
Having to add a zero-sized type component to every entity to mark it as a
shadow caster or shadow receiver by default feels quite unnecessary. As such, I
have added NotShadowCaster and NotShadowReceiver components. This introduces
double-negatives like Without<NotShadowReceiver>, etc but it means that only
the exceptions have to be specified in user code.

I also added an example to illustrate the effect.
  • Loading branch information
superdump committed Aug 25, 2021
1 parent f368bf7 commit a1a5b01
Show file tree
Hide file tree
Showing 6 changed files with 411 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
307 changes: 307 additions & 0 deletions examples/3d/shadow_caster_receiver_pipelined.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
use bevy::{
core::Time,
ecs::prelude::*,
input::{mouse::MouseMotion, Input},
math::{EulerRot, Mat4, Quat, Vec2, Vec3},
pbr2::{
DirectionalLight, DirectionalLightBundle, NotShadowCaster, NotShadowReceiver, PbrBundle,
PointLight, PointLightBundle, StandardMaterial,
},
prelude::{App, Assets, Handle, KeyCode, Transform},
render2::{
camera::{Camera, OrthographicProjection, PerspectiveCameraBundle},
color::Color,
mesh::{shape, Mesh},
},
PipelinedDefaultPlugins,
};

fn main() {
println!(
"Controls:
WSAD - forward/back/strafe left/right
LShift - 'run'
E - up
Q - down
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)
.add_system(camera_controller)
.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.clone(),
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()
})
.insert(CameraController::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);
}
}
}

struct CameraController {
pub enabled: bool,
pub sensitivity: f32,
pub key_forward: KeyCode,
pub key_back: KeyCode,
pub key_left: KeyCode,
pub key_right: KeyCode,
pub key_up: KeyCode,
pub key_down: KeyCode,
pub key_run: KeyCode,
pub walk_speed: f32,
pub run_speed: f32,
pub friction: f32,
pub pitch: f32,
pub yaw: f32,
pub velocity: Vec3,
}

impl Default for CameraController {
fn default() -> Self {
Self {
enabled: true,
sensitivity: 0.5,
key_forward: KeyCode::W,
key_back: KeyCode::S,
key_left: KeyCode::A,
key_right: KeyCode::D,
key_up: KeyCode::E,
key_down: KeyCode::Q,
key_run: KeyCode::LShift,
walk_speed: 10.0,
run_speed: 30.0,
friction: 0.5,
pitch: 0.0,
yaw: 0.0,
velocity: Vec3::ZERO,
}
}
}

fn camera_controller(
time: Res<Time>,
mut mouse_events: EventReader<MouseMotion>,
key_input: Res<Input<KeyCode>>,
mut query: Query<(&mut Transform, &mut CameraController), With<Camera>>,
) {
let dt = time.delta_seconds();

// Handle mouse input
let mut mouse_delta = Vec2::ZERO;
for mouse_event in mouse_events.iter() {
mouse_delta += mouse_event.delta;
}

for (mut transform, mut options) in query.iter_mut() {
if !options.enabled {
continue;
}

// Handle key input
let mut axis_input = Vec3::ZERO;
if key_input.pressed(options.key_forward) {
axis_input.z += 1.0;
}
if key_input.pressed(options.key_back) {
axis_input.z -= 1.0;
}
if key_input.pressed(options.key_right) {
axis_input.x += 1.0;
}
if key_input.pressed(options.key_left) {
axis_input.x -= 1.0;
}
if key_input.pressed(options.key_up) {
axis_input.y += 1.0;
}
if key_input.pressed(options.key_down) {
axis_input.y -= 1.0;
}

// Apply movement update
if axis_input != Vec3::ZERO {
let max_speed = if key_input.pressed(options.key_run) {
options.run_speed
} else {
options.walk_speed
};
options.velocity = axis_input.normalize() * max_speed;
} else {
let friction = options.friction.clamp(0.0, 1.0);
options.velocity *= 1.0 - friction;
if options.velocity.length_squared() < 1e-6 {
options.velocity = Vec3::ZERO;
}
}
let forward = transform.forward();
let right = transform.right();
transform.translation += options.velocity.x * dt * right
+ options.velocity.y * dt * Vec3::Y
+ options.velocity.z * dt * forward;

if mouse_delta != Vec2::ZERO {
// Apply look update
let (pitch, yaw) = (
(options.pitch - mouse_delta.y * 0.5 * options.sensitivity * dt).clamp(
-0.99 * std::f32::consts::FRAC_PI_2,
0.99 * std::f32::consts::FRAC_PI_2,
),
options.yaw - mouse_delta.x * options.sensitivity * dt,
);
transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, yaw, pitch);
options.pitch = pitch;
options.yaw = yaw;
}
}
}
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;
Loading

0 comments on commit a1a5b01

Please sign in to comment.