bevy/examples/tools/scene_viewer/scene_viewer_plugin.rs
Daniel Chia c3a46822e1 Cascaded shadow maps. (#7064)
Co-authored-by: Robert Swain <robert.swain@gmail.com>

# Objective

Implements cascaded shadow maps for directional lights, which produces better quality shadows without needing excessively large shadow maps.

Fixes #3629

Before
![image](https://user-images.githubusercontent.com/1222141/210061203-bbd965a4-8d11-4cec-9a88-67fc59d0819f.png)

After
![image](https://user-images.githubusercontent.com/1222141/210061334-2ff15334-e6d7-4a31-9314-f34a7805cac6.png)


## Solution

Rather than rendering a single shadow map for directional light, the view frustum is divided into a series of cascades, each of which gets its own shadow map. The correct cascade is then sampled for shadow determination.

---

## Changelog

Directional lights now use cascaded shadow maps for improved shadow quality.


## Migration Guide

You no longer have to manually specify a `shadow_projection` for a directional light, and these settings should be removed. If customization of how cascaded shadow maps work is desired, modify the `CascadeShadowConfig` component instead.
2023-01-25 12:35:39 +00:00

289 lines
9.1 KiB
Rust

//! A glTF scene viewer plugin. Provides controls for animation, directional lighting, and switching between scene cameras.
//! To use in your own application:
//! - Copy the code for the `SceneViewerPlugin` and add the plugin to your App.
//! - Insert an initalized `SceneHandle` resource into your App's `AssetServer`.
use bevy::{asset::LoadState, gltf::Gltf, prelude::*, scene::InstanceId};
use std::f32::consts::*;
use std::fmt;
use super::camera_controller_plugin::*;
#[derive(Resource)]
pub struct SceneHandle {
gltf_handle: Handle<Gltf>,
scene_index: usize,
#[cfg(feature = "animation")]
animations: Vec<Handle<AnimationClip>>,
instance_id: Option<InstanceId>,
pub is_loaded: bool,
pub has_light: bool,
}
impl SceneHandle {
pub fn new(gltf_handle: Handle<Gltf>, scene_index: usize) -> Self {
Self {
gltf_handle,
scene_index,
#[cfg(feature = "animation")]
animations: Vec::new(),
instance_id: None,
is_loaded: false,
has_light: false,
}
}
}
impl fmt::Display for SceneHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"
Scene Controls:
L - animate light direction
U - toggle shadows
C - cycle through the camera controller and any cameras loaded from the scene
Space - Play/Pause animation
Enter - Cycle through animations
"
)
}
}
pub struct SceneViewerPlugin;
impl Plugin for SceneViewerPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<CameraTracker>()
.add_system_to_stage(CoreStage::PreUpdate, scene_load_check)
.add_system(update_lights)
.add_system(camera_tracker);
#[cfg(feature = "animation")]
app.add_system(start_animation)
.add_system(keyboard_animation_control);
}
}
fn scene_load_check(
asset_server: Res<AssetServer>,
mut scenes: ResMut<Assets<Scene>>,
gltf_assets: ResMut<Assets<Gltf>>,
mut scene_handle: ResMut<SceneHandle>,
mut scene_spawner: ResMut<SceneSpawner>,
) {
match scene_handle.instance_id {
None => {
if asset_server.get_load_state(&scene_handle.gltf_handle) == LoadState::Loaded {
let gltf = gltf_assets.get(&scene_handle.gltf_handle).unwrap();
if gltf.scenes.len() > 1 {
info!(
"Displaying scene {} out of {}",
scene_handle.scene_index,
gltf.scenes.len()
);
info!("You can select the scene by adding '#Scene' followed by a number to the end of the file path (e.g '#Scene1' to load the second scene).");
}
let gltf_scene_handle =
gltf.scenes
.get(scene_handle.scene_index)
.unwrap_or_else(|| {
panic!(
"glTF file doesn't contain scene {}!",
scene_handle.scene_index
)
});
let scene = scenes.get_mut(gltf_scene_handle).unwrap();
let mut query = scene
.world
.query::<(Option<&DirectionalLight>, Option<&PointLight>)>();
scene_handle.has_light =
query
.iter(&scene.world)
.any(|(maybe_directional_light, maybe_point_light)| {
maybe_directional_light.is_some() || maybe_point_light.is_some()
});
scene_handle.instance_id =
Some(scene_spawner.spawn(gltf_scene_handle.clone_weak()));
#[cfg(feature = "animation")]
{
scene_handle.animations = gltf.animations.clone();
if !scene_handle.animations.is_empty() {
info!(
"Found {} animation{}",
scene_handle.animations.len(),
if scene_handle.animations.len() == 1 {
""
} else {
"s"
}
);
}
}
info!("Spawning scene...");
}
}
Some(instance_id) if !scene_handle.is_loaded => {
if scene_spawner.instance_is_ready(instance_id) {
info!("...done!");
scene_handle.is_loaded = true;
}
}
Some(_) => {}
}
}
#[cfg(feature = "animation")]
fn start_animation(
mut player: Query<&mut AnimationPlayer>,
mut done: Local<bool>,
scene_handle: Res<SceneHandle>,
) {
if !*done {
if let Ok(mut player) = player.get_single_mut() {
if let Some(animation) = scene_handle.animations.first() {
player.play(animation.clone_weak()).repeat();
*done = true;
}
}
}
}
#[cfg(feature = "animation")]
fn keyboard_animation_control(
keyboard_input: Res<Input<KeyCode>>,
mut animation_player: Query<&mut AnimationPlayer>,
scene_handle: Res<SceneHandle>,
mut current_animation: Local<usize>,
mut changing: Local<bool>,
) {
if scene_handle.animations.is_empty() {
return;
}
if let Ok(mut player) = animation_player.get_single_mut() {
if keyboard_input.just_pressed(KeyCode::Space) {
if player.is_paused() {
player.resume();
} else {
player.pause();
}
}
if *changing {
// change the animation the frame after return was pressed
*current_animation = (*current_animation + 1) % scene_handle.animations.len();
player
.play(scene_handle.animations[*current_animation].clone_weak())
.repeat();
*changing = false;
}
if keyboard_input.just_pressed(KeyCode::Return) {
// delay the animation change for one frame
*changing = true;
// set the current animation to its start and pause it to reset to its starting state
player.set_elapsed(0.0).pause();
}
}
}
fn update_lights(
key_input: Res<Input<KeyCode>>,
time: Res<Time>,
mut query: Query<(&mut Transform, &mut DirectionalLight)>,
mut animate_directional_light: Local<bool>,
) {
for (_, mut light) in &mut query {
if key_input.just_pressed(KeyCode::U) {
light.shadows_enabled = !light.shadows_enabled;
}
}
if key_input.just_pressed(KeyCode::L) {
*animate_directional_light = !*animate_directional_light;
}
if *animate_directional_light {
for (mut transform, _) in &mut query {
transform.rotation = Quat::from_euler(
EulerRot::ZYX,
0.0,
time.elapsed_seconds() * PI / 15.0,
-FRAC_PI_4,
);
}
}
}
#[derive(Resource, Default)]
struct CameraTracker {
active_index: Option<usize>,
cameras: Vec<Entity>,
}
impl CameraTracker {
fn track_camera(&mut self, entity: Entity) -> bool {
self.cameras.push(entity);
if self.active_index.is_none() {
self.active_index = Some(self.cameras.len() - 1);
true
} else {
false
}
}
fn active_camera(&self) -> Option<Entity> {
self.active_index.map(|i| self.cameras[i])
}
fn set_next_active(&mut self) -> Option<Entity> {
let active_index = self.active_index?;
let new_i = (active_index + 1) % self.cameras.len();
self.active_index = Some(new_i);
Some(self.cameras[new_i])
}
}
fn camera_tracker(
mut camera_tracker: ResMut<CameraTracker>,
keyboard_input: Res<Input<KeyCode>>,
mut queries: ParamSet<(
Query<(Entity, &mut Camera), (Added<Camera>, Without<CameraController>)>,
Query<(Entity, &mut Camera), (Added<Camera>, With<CameraController>)>,
Query<&mut Camera>,
)>,
) {
// track added scene camera entities first, to ensure they are preferred for the
// default active camera
for (entity, mut camera) in queries.p0().iter_mut() {
camera.is_active = camera_tracker.track_camera(entity);
}
// iterate added custom camera entities second
for (entity, mut camera) in queries.p1().iter_mut() {
camera.is_active = camera_tracker.track_camera(entity);
}
if keyboard_input.just_pressed(KeyCode::C) {
// disable currently active camera
if let Some(e) = camera_tracker.active_camera() {
if let Ok(mut camera) = queries.p2().get_mut(e) {
camera.is_active = false;
}
}
// enable next active camera
if let Some(e) = camera_tracker.set_next_active() {
if let Ok(mut camera) = queries.p2().get_mut(e) {
camera.is_active = true;
}
}
}
}