-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bevy_pbr2: Add support for not casting/receiving shadows
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
Showing
6 changed files
with
411 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.