mirror of
https://github.com/bevyengine/bevy
synced 2024-12-23 19:43:07 +00:00
2ae5a21009
[*Percentage-closer soft shadows*] are a technique from 2004 that allow shadows to become blurrier farther from the objects that cast them. It works by introducing a *blocker search* step that runs before the normal shadow map sampling. The blocker search step detects the difference between the depth of the fragment being rasterized and the depth of the nearby samples in the depth buffer. Larger depth differences result in a larger penumbra and therefore a blurrier shadow. To enable PCSS, fill in the `soft_shadow_size` value in `DirectionalLight`, `PointLight`, or `SpotLight`, as appropriate. This shadow size value represents the size of the light and should be tuned as appropriate for your scene. Higher values result in a wider penumbra (i.e. blurrier shadows). When using PCSS, temporal shadow maps (`ShadowFilteringMethod::Temporal`) are recommended. If you don't use `ShadowFilteringMethod::Temporal` and instead use `ShadowFilteringMethod::Gaussian`, Bevy will use the same technique as `Temporal`, but the result won't vary over time. This produces a rather noisy result. Doing better would likely require downsampling the shadow map, which would be complex and slower (and would require PR #13003 to land first). In addition to PCSS, this commit makes the near Z plane for the shadow map configurable on a per-light basis. Previously, it had been hardcoded to 0.1 meters. This change was necessary to make the point light shadow map in the example look reasonable, as otherwise the shadows appeared far too aliased. A new example, `pcss`, has been added. It demonstrates the percentage-closer soft shadow technique with directional lights, point lights, spot lights, non-temporal operation, and temporal operation. The assets are my original work. Both temporal and non-temporal shadows are rather noisy in the example, and, as mentioned before, this is unavoidable without downsampling the depth buffer, which we can't do yet. Note also that the shadows don't look particularly great for point lights; the example simply isn't an ideal scene for them. Nevertheless, I felt that the benefits of the ability to do a side-by-side comparison of directional and point lights outweighed the unsightliness of the point light shadows in that example, so I kept the point light feature in. Fixes #3631. [*Percentage-closer soft shadows*]: https://developer.download.nvidia.com/shaderlibrary/docs/shadow_PCSS.pdf ## Changelog ### Added * Percentage-closer soft shadows (PCSS) are now supported, allowing shadows to become blurrier as they stretch away from objects. To use them, set the `soft_shadow_size` field in `DirectionalLight`, `PointLight`, or `SpotLight`, as applicable. * The near Z value for shadow maps is now customizable via the `shadow_map_near_z` field in `DirectionalLight`, `PointLight`, and `SpotLight`. ## Screenshots PCSS off: ![Screenshot 2024-05-24 120012](https://github.com/bevyengine/bevy/assets/157897/0d35fe98-245b-44fb-8a43-8d0272a73b86) PCSS on: ![Screenshot 2024-05-24 115959](https://github.com/bevyengine/bevy/assets/157897/83397ef8-1317-49dd-bfb3-f8286d7610cd) --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Torstein Grindvik <52322338+torsteingrindvik@users.noreply.github.com>
177 lines
5.6 KiB
Rust
177 lines
5.6 KiB
Rust
//! Simple widgets for example UI.
|
|
|
|
use bevy::{ecs::system::EntityCommands, prelude::*};
|
|
|
|
/// An event that's sent whenever the user changes one of the settings by
|
|
/// clicking a radio button.
|
|
#[derive(Clone, Event, Deref, DerefMut)]
|
|
pub struct WidgetClickEvent<T>(T);
|
|
|
|
/// A marker component that we place on all widgets that send
|
|
/// [`WidgetClickEvent`]s of the given type.
|
|
#[derive(Clone, Component, Deref, DerefMut)]
|
|
pub struct WidgetClickSender<T>(T)
|
|
where
|
|
T: Clone + Send + Sync + 'static;
|
|
|
|
/// A marker component that we place on all radio `Button`s.
|
|
#[derive(Clone, Copy, Component)]
|
|
pub struct RadioButton;
|
|
|
|
/// A marker component that we place on all `Text` inside radio buttons.
|
|
#[derive(Clone, Copy, Component)]
|
|
pub struct RadioButtonText;
|
|
|
|
/// Returns a [`Style`] appropriate for the outer main UI node.
|
|
///
|
|
/// This UI is in the bottom left corner and has flex column support
|
|
pub fn main_ui_style() -> Style {
|
|
Style {
|
|
flex_direction: FlexDirection::Column,
|
|
position_type: PositionType::Absolute,
|
|
row_gap: Val::Px(6.0),
|
|
left: Val::Px(10.0),
|
|
bottom: Val::Px(10.0),
|
|
..default()
|
|
}
|
|
}
|
|
|
|
/// Spawns a single radio button that allows configuration of a setting.
|
|
///
|
|
/// The type parameter specifies the value that will be packaged up and sent in
|
|
/// a [`WidgetClickEvent`] when the radio button is clicked.
|
|
pub fn spawn_option_button<T>(
|
|
parent: &mut ChildBuilder,
|
|
option_value: T,
|
|
option_name: &str,
|
|
is_selected: bool,
|
|
is_first: bool,
|
|
is_last: bool,
|
|
) where
|
|
T: Clone + Send + Sync + 'static,
|
|
{
|
|
let (bg_color, fg_color) = if is_selected {
|
|
(Color::WHITE, Color::BLACK)
|
|
} else {
|
|
(Color::BLACK, Color::WHITE)
|
|
};
|
|
|
|
// Add the button node.
|
|
parent
|
|
.spawn(ButtonBundle {
|
|
style: Style {
|
|
border: UiRect::all(Val::Px(1.0)).with_left(if is_first {
|
|
Val::Px(1.0)
|
|
} else {
|
|
Val::Px(0.0)
|
|
}),
|
|
justify_content: JustifyContent::Center,
|
|
align_items: AlignItems::Center,
|
|
padding: UiRect::axes(Val::Px(12.0), Val::Px(6.0)),
|
|
..default()
|
|
},
|
|
border_color: BorderColor(Color::WHITE),
|
|
border_radius: BorderRadius::ZERO
|
|
.with_left(if is_first { Val::Px(6.0) } else { Val::Px(0.0) })
|
|
.with_right(if is_last { Val::Px(6.0) } else { Val::Px(0.0) }),
|
|
background_color: BackgroundColor(bg_color),
|
|
..default()
|
|
})
|
|
.insert(RadioButton)
|
|
.insert(WidgetClickSender(option_value.clone()))
|
|
.with_children(|parent| {
|
|
spawn_ui_text(parent, option_name, fg_color)
|
|
.insert(RadioButtonText)
|
|
.insert(WidgetClickSender(option_value));
|
|
});
|
|
}
|
|
|
|
/// Spawns the buttons that allow configuration of a setting.
|
|
///
|
|
/// The user may change the setting to any one of the labeled `options`. The
|
|
/// value of the given type parameter will be packaged up and sent as a
|
|
/// [`WidgetClickEvent`] when one of the radio buttons is clicked.
|
|
pub fn spawn_option_buttons<T>(parent: &mut ChildBuilder, title: &str, options: &[(T, &str)])
|
|
where
|
|
T: Clone + Send + Sync + 'static,
|
|
{
|
|
// Add the parent node for the row.
|
|
parent
|
|
.spawn(NodeBundle {
|
|
style: Style {
|
|
align_items: AlignItems::Center,
|
|
..default()
|
|
},
|
|
..default()
|
|
})
|
|
.with_children(|parent| {
|
|
spawn_ui_text(parent, title, Color::BLACK).insert(Style {
|
|
width: Val::Px(125.0),
|
|
..default()
|
|
});
|
|
|
|
for (option_index, (option_value, option_name)) in options.iter().cloned().enumerate() {
|
|
spawn_option_button(
|
|
parent,
|
|
option_value,
|
|
option_name,
|
|
option_index == 0,
|
|
option_index == 0,
|
|
option_index == options.len() - 1,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Spawns text for the UI.
|
|
///
|
|
/// Returns the `EntityCommands`, which allow further customization of the text
|
|
/// style.
|
|
pub fn spawn_ui_text<'a>(
|
|
parent: &'a mut ChildBuilder,
|
|
label: &str,
|
|
color: Color,
|
|
) -> EntityCommands<'a> {
|
|
parent.spawn(TextBundle::from_section(
|
|
label,
|
|
TextStyle {
|
|
font_size: 18.0,
|
|
color,
|
|
..default()
|
|
},
|
|
))
|
|
}
|
|
|
|
/// Checks for clicks on the radio buttons and sends `RadioButtonChangeEvent`s
|
|
/// as necessary.
|
|
pub fn handle_ui_interactions<T>(
|
|
mut interactions: Query<
|
|
(&Interaction, &WidgetClickSender<T>),
|
|
(With<Button>, With<RadioButton>),
|
|
>,
|
|
mut widget_click_events: EventWriter<WidgetClickEvent<T>>,
|
|
) where
|
|
T: Clone + Send + Sync + 'static,
|
|
{
|
|
for (interaction, click_event) in interactions.iter_mut() {
|
|
if *interaction == Interaction::Pressed {
|
|
widget_click_events.send(WidgetClickEvent((**click_event).clone()));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Updates the style of the button part of a radio button to reflect its
|
|
/// selected status.
|
|
pub fn update_ui_radio_button(background_color: &mut BackgroundColor, selected: bool) {
|
|
background_color.0 = if selected { Color::WHITE } else { Color::BLACK };
|
|
}
|
|
|
|
/// Updates the style of the label of a radio button to reflect its selected
|
|
/// status.
|
|
pub fn update_ui_radio_button_text(text: &mut Text, selected: bool) {
|
|
let text_color = if selected { Color::BLACK } else { Color::WHITE };
|
|
|
|
for section in &mut text.sections {
|
|
section.style.color = text_color;
|
|
}
|
|
}
|