mirror of
https://github.com/bevyengine/bevy.git
synced 2026-05-06 06:06:42 -04:00
ec254ded7d
# Objective - Partially addresses #23688. - Prevents use of `Skybox::default()` from causing errors. ## Solution `Skybox::default()` is problematic because it contains an `Image` that is not a valid skybox. ~~This change removes the `Default` implementation and instead provides a `new()` function which takes the image as a parameter (and also the brightness, which is practically required).~~ This change makes the `image` field optional so that the default `None` renders nothing. Things we could do instead of this: * Make `Skybox` not implement `Default`. I am informed this is a bad idea. * Create a default cubemap image for `default()` to use. ## Testing Ran the `skybox` and `irradiance_volumes` examples.
458 lines
15 KiB
Rust
458 lines
15 KiB
Rust
//! Demonstrates contact shadows, also known as screen-space shadows.
|
|
|
|
use crate::widgets::{RadioButton, RadioButtonText, WidgetClickEvent, WidgetClickSender};
|
|
use bevy::anti_alias::taa::TemporalAntiAliasing;
|
|
use bevy::core_pipeline::tonemapping::Tonemapping;
|
|
use bevy::light::Skybox;
|
|
use bevy::pbr::ScreenSpaceAmbientOcclusion;
|
|
use bevy::post_process::motion_blur::MotionBlur;
|
|
use bevy::window::{CursorIcon, PrimaryWindow, SystemCursorIcon};
|
|
use bevy::{
|
|
camera::Hdr, ecs::message::MessageReader, light::NotShadowReceiver, pbr::ContactShadows,
|
|
post_process::bloom::Bloom, prelude::*,
|
|
};
|
|
|
|
#[path = "../helpers/widgets.rs"]
|
|
mod widgets;
|
|
|
|
#[derive(Clone, Copy, PartialEq, Default, Debug)]
|
|
enum ContactShadowState {
|
|
#[default]
|
|
Enabled,
|
|
Disabled,
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Default, Debug)]
|
|
enum ShadowMaps {
|
|
#[default]
|
|
Enabled,
|
|
Disabled,
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Default, Debug)]
|
|
enum LightRotation {
|
|
Stationary,
|
|
#[default]
|
|
Rotating,
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Default, Debug)]
|
|
enum LightType {
|
|
Directional,
|
|
#[default]
|
|
Point,
|
|
Spot,
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Default, Debug)]
|
|
enum ReceiveShadows {
|
|
#[default]
|
|
Enabled,
|
|
Disabled,
|
|
}
|
|
|
|
/// Each example setting that can be toggled in the UI.
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
enum ExampleSetting {
|
|
ContactShadows(ContactShadowState),
|
|
ShadowMaps(ShadowMaps),
|
|
LightRotation(LightRotation),
|
|
LightType(LightType),
|
|
ReceiveShadows(ReceiveShadows),
|
|
}
|
|
|
|
const LIGHT_ROTATION_SPEED: f32 = 0.002;
|
|
|
|
#[derive(Resource, Default)]
|
|
struct AppStatus {
|
|
contact_shadows: ContactShadowState,
|
|
shadow_maps: ShadowMaps,
|
|
light_rotation: LightRotation,
|
|
light_type: LightType,
|
|
receive_shadows: ReceiveShadows,
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct LightContainer;
|
|
|
|
#[derive(Component)]
|
|
struct GroundPlane;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins((
|
|
DefaultPlugins.set(WindowPlugin {
|
|
primary_window: Some(Window {
|
|
title: "Bevy Contact Shadows Example".into(),
|
|
..default()
|
|
}),
|
|
..default()
|
|
}),
|
|
MeshPickingPlugin,
|
|
))
|
|
.init_resource::<AppStatus>()
|
|
.insert_resource(GlobalAmbientLight::NONE)
|
|
.add_message::<WidgetClickEvent<ExampleSetting>>()
|
|
.add_systems(Startup, setup)
|
|
.add_systems(Update, rotate_light)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
widgets::handle_ui_interactions::<ExampleSetting>,
|
|
update_radio_buttons.after(widgets::handle_ui_interactions::<ExampleSetting>),
|
|
handle_setting_change.after(widgets::handle_ui_interactions::<ExampleSetting>),
|
|
),
|
|
)
|
|
.run();
|
|
}
|
|
|
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
commands.spawn((
|
|
Camera3d::default(),
|
|
Transform::from_xyz(-0.8, 0.6, -0.8).looking_at(Vec3::new(0.0, 0.35, 0.0), Vec3::Y),
|
|
ContactShadows::default(),
|
|
TemporalAntiAliasing::default(), // Contact shadows and AO benefit from TAA
|
|
// Everything past this point is extra to look pretty.
|
|
Bloom::default(),
|
|
Hdr,
|
|
Skybox {
|
|
brightness: 1000.0,
|
|
image: Some(asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2")),
|
|
..default()
|
|
},
|
|
EnvironmentMapLight {
|
|
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
|
|
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
|
|
intensity: 1000.0,
|
|
..default()
|
|
},
|
|
ScreenSpaceAmbientOcclusion::default(),
|
|
Msaa::Off,
|
|
Tonemapping::AcesFitted,
|
|
MotionBlur {
|
|
shutter_angle: 2.0, // This is really just for fun when spinning the model
|
|
..default()
|
|
},
|
|
));
|
|
|
|
let directional_light = commands
|
|
.spawn((
|
|
DirectionalLight {
|
|
shadow_maps_enabled: true,
|
|
contact_shadows_enabled: true,
|
|
..default()
|
|
},
|
|
Visibility::Hidden,
|
|
))
|
|
.id();
|
|
|
|
let point_light = commands
|
|
.spawn((
|
|
PointLight {
|
|
intensity: light_consts::lumens::VERY_LARGE_CINEMA_LIGHT * 0.4,
|
|
shadow_maps_enabled: true,
|
|
contact_shadows_enabled: true,
|
|
..default()
|
|
},
|
|
Visibility::Visible,
|
|
))
|
|
.id();
|
|
|
|
let spot_light = commands
|
|
.spawn((
|
|
SpotLight {
|
|
intensity: light_consts::lumens::VERY_LARGE_CINEMA_LIGHT * 0.4,
|
|
shadow_maps_enabled: true,
|
|
contact_shadows_enabled: true,
|
|
..default()
|
|
},
|
|
Visibility::Hidden,
|
|
))
|
|
.id();
|
|
|
|
commands
|
|
.spawn((
|
|
Transform::from_xyz(-0.8, 1.5, 1.2).looking_at(Vec3::ZERO, Vec3::Y),
|
|
Visibility::default(),
|
|
LightContainer,
|
|
))
|
|
.add_child(directional_light)
|
|
.add_child(point_light)
|
|
.add_child(spot_light);
|
|
|
|
commands
|
|
.spawn((
|
|
WorldAssetRoot(asset_server.load(
|
|
GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
|
|
)),
|
|
Transform::from_rotation(Quat::from_rotation_y(std::f32::consts::PI)),
|
|
))
|
|
.observe(
|
|
|event: On<Pointer<Drag>>,
|
|
mut query: Query<&mut Transform, With<WorldAssetRoot>>,
|
|
mut commands: Commands,
|
|
mut window: Query<Entity, With<PrimaryWindow>>| {
|
|
for mut transform in query.iter_mut() {
|
|
transform.rotate_y(event.delta.x * 0.01);
|
|
}
|
|
commands
|
|
.entity(window.single_mut().unwrap())
|
|
.insert(CursorIcon::System(SystemCursorIcon::Grabbing));
|
|
},
|
|
)
|
|
.observe(
|
|
|_: On<Pointer<Over>>,
|
|
mut commands: Commands,
|
|
mut window: Query<Entity, With<PrimaryWindow>>| {
|
|
commands
|
|
.entity(window.single_mut().unwrap())
|
|
.insert(CursorIcon::System(SystemCursorIcon::Grab));
|
|
},
|
|
)
|
|
.observe(
|
|
|_: On<Pointer<Out>>,
|
|
mut commands: Commands,
|
|
mut window: Query<Entity, With<PrimaryWindow>>| {
|
|
commands
|
|
.entity(window.single_mut().unwrap())
|
|
.insert(CursorIcon::System(SystemCursorIcon::Default));
|
|
},
|
|
)
|
|
.observe(
|
|
|_: On<Pointer<DragEnd>>,
|
|
mut commands: Commands,
|
|
mut window: Query<Entity, With<PrimaryWindow>>| {
|
|
commands
|
|
.entity(window.single_mut().unwrap())
|
|
.insert(CursorIcon::System(SystemCursorIcon::Default));
|
|
},
|
|
);
|
|
|
|
commands.spawn((
|
|
Mesh3d(asset_server.add(Circle::default().mesh().into())),
|
|
MeshMaterial3d(asset_server.add(StandardMaterial {
|
|
base_color: Color::srgb(0.06, 0.06, 0.06),
|
|
..default()
|
|
})),
|
|
Transform::from_rotation(Quat::from_axis_angle(Vec3::X, -std::f32::consts::FRAC_PI_2)),
|
|
GroundPlane,
|
|
));
|
|
|
|
spawn_buttons(&mut commands);
|
|
|
|
commands.spawn((
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
top: px(12.0),
|
|
left: px(0.0),
|
|
right: px(0.0),
|
|
justify_content: JustifyContent::Center,
|
|
..default()
|
|
},
|
|
children![(
|
|
Text::new("Drag model to spin"),
|
|
TextFont {
|
|
font_size: FontSize::Px(18.0),
|
|
..default()
|
|
},
|
|
)],
|
|
));
|
|
}
|
|
|
|
fn rotate_light(
|
|
mut lights: Query<&mut Transform, With<LightContainer>>,
|
|
app_status: Res<AppStatus>,
|
|
) {
|
|
if app_status.light_rotation != LightRotation::Rotating {
|
|
return;
|
|
}
|
|
|
|
for mut transform in lights.iter_mut() {
|
|
transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(LIGHT_ROTATION_SPEED));
|
|
}
|
|
}
|
|
|
|
fn spawn_buttons(commands: &mut Commands) {
|
|
commands.spawn((
|
|
widgets::main_ui_node(),
|
|
children![
|
|
widgets::option_buttons(
|
|
"Contact Shadows",
|
|
&[
|
|
(
|
|
ExampleSetting::ContactShadows(ContactShadowState::Enabled),
|
|
"On"
|
|
),
|
|
(
|
|
ExampleSetting::ContactShadows(ContactShadowState::Disabled),
|
|
"Off"
|
|
),
|
|
],
|
|
),
|
|
widgets::option_buttons(
|
|
"Shadow Maps",
|
|
&[
|
|
(ExampleSetting::ShadowMaps(ShadowMaps::Enabled), "On"),
|
|
(ExampleSetting::ShadowMaps(ShadowMaps::Disabled), "Off"),
|
|
],
|
|
),
|
|
widgets::option_buttons(
|
|
"Light Rotation",
|
|
&[
|
|
(ExampleSetting::LightRotation(LightRotation::Rotating), "On"),
|
|
(
|
|
ExampleSetting::LightRotation(LightRotation::Stationary),
|
|
"Off"
|
|
),
|
|
],
|
|
),
|
|
widgets::option_buttons(
|
|
"Light Type",
|
|
&[
|
|
(
|
|
ExampleSetting::LightType(LightType::Directional),
|
|
"Directional"
|
|
),
|
|
(ExampleSetting::LightType(LightType::Point), "Point"),
|
|
(ExampleSetting::LightType(LightType::Spot), "Spot"),
|
|
],
|
|
),
|
|
widgets::option_buttons(
|
|
"Receive Shadows",
|
|
&[
|
|
(
|
|
ExampleSetting::ReceiveShadows(ReceiveShadows::Enabled),
|
|
"On"
|
|
),
|
|
(
|
|
ExampleSetting::ReceiveShadows(ReceiveShadows::Disabled),
|
|
"Off"
|
|
),
|
|
],
|
|
),
|
|
],
|
|
));
|
|
}
|
|
|
|
fn update_radio_buttons(
|
|
mut widgets: Query<
|
|
(
|
|
Entity,
|
|
Option<&mut BackgroundColor>,
|
|
Has<Text>,
|
|
&WidgetClickSender<ExampleSetting>,
|
|
),
|
|
Or<(With<RadioButton>, With<RadioButtonText>)>,
|
|
>,
|
|
app_status: Res<AppStatus>,
|
|
mut writer: TextUiWriter,
|
|
) {
|
|
for (entity, background_color, has_text, sender) in widgets.iter_mut() {
|
|
let selected = match **sender {
|
|
ExampleSetting::ContactShadows(value) => value == app_status.contact_shadows,
|
|
ExampleSetting::ShadowMaps(value) => value == app_status.shadow_maps,
|
|
ExampleSetting::LightRotation(value) => value == app_status.light_rotation,
|
|
ExampleSetting::LightType(value) => value == app_status.light_type,
|
|
ExampleSetting::ReceiveShadows(value) => value == app_status.receive_shadows,
|
|
};
|
|
|
|
if let Some(mut background_color) = background_color {
|
|
widgets::update_ui_radio_button(&mut background_color, selected);
|
|
}
|
|
if has_text {
|
|
widgets::update_ui_radio_button_text(entity, &mut writer, selected);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_setting_change(
|
|
mut lights: Query<
|
|
(
|
|
&mut Visibility,
|
|
Option<&mut DirectionalLight>,
|
|
Option<&mut PointLight>,
|
|
Option<&mut SpotLight>,
|
|
),
|
|
Or<(With<DirectionalLight>, With<PointLight>, With<SpotLight>)>,
|
|
>,
|
|
mut ground_plane: Query<Entity, With<GroundPlane>>,
|
|
mut events: MessageReader<WidgetClickEvent<ExampleSetting>>,
|
|
mut app_status: ResMut<AppStatus>,
|
|
mut commands: Commands,
|
|
) {
|
|
for event in events.read() {
|
|
match **event {
|
|
ExampleSetting::ContactShadows(value) => {
|
|
app_status.contact_shadows = value;
|
|
for (_, maybe_directional_light, maybe_point_light, maybe_spot_light) in
|
|
lights.iter_mut()
|
|
{
|
|
if let Some(mut directional_light) = maybe_directional_light {
|
|
directional_light.contact_shadows_enabled =
|
|
value == ContactShadowState::Enabled;
|
|
}
|
|
if let Some(mut point_light) = maybe_point_light {
|
|
point_light.contact_shadows_enabled = value == ContactShadowState::Enabled;
|
|
}
|
|
if let Some(mut spot_light) = maybe_spot_light {
|
|
spot_light.contact_shadows_enabled = value == ContactShadowState::Enabled;
|
|
}
|
|
}
|
|
}
|
|
ExampleSetting::ShadowMaps(value) => {
|
|
app_status.shadow_maps = value;
|
|
for (_, maybe_directional_light, maybe_point_light, maybe_spot_light) in
|
|
lights.iter_mut()
|
|
{
|
|
if let Some(mut directional_light) = maybe_directional_light {
|
|
directional_light.shadow_maps_enabled = value == ShadowMaps::Enabled;
|
|
}
|
|
if let Some(mut point_light) = maybe_point_light {
|
|
point_light.shadow_maps_enabled = value == ShadowMaps::Enabled;
|
|
}
|
|
if let Some(mut spot_light) = maybe_spot_light {
|
|
spot_light.shadow_maps_enabled = value == ShadowMaps::Enabled;
|
|
}
|
|
}
|
|
}
|
|
ExampleSetting::LightRotation(value) => {
|
|
app_status.light_rotation = value;
|
|
}
|
|
ExampleSetting::LightType(value) => {
|
|
app_status.light_type = value;
|
|
for (
|
|
mut visibility,
|
|
maybe_directional_light,
|
|
maybe_point_light,
|
|
maybe_spot_light,
|
|
) in lights.iter_mut()
|
|
{
|
|
let is_visible = match value {
|
|
LightType::Directional => maybe_directional_light.is_some(),
|
|
LightType::Point => maybe_point_light.is_some(),
|
|
LightType::Spot => maybe_spot_light.is_some(),
|
|
};
|
|
*visibility = if is_visible {
|
|
Visibility::Visible
|
|
} else {
|
|
Visibility::Hidden
|
|
};
|
|
}
|
|
}
|
|
ExampleSetting::ReceiveShadows(value) => {
|
|
app_status.receive_shadows = value;
|
|
for entity in ground_plane.iter_mut() {
|
|
match value {
|
|
ReceiveShadows::Enabled => {
|
|
commands.entity(entity).remove::<NotShadowReceiver>();
|
|
}
|
|
ReceiveShadows::Disabled => {
|
|
commands.entity(entity).insert(NotShadowReceiver);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|