diff --git a/crates/common/src/structs.rs b/crates/common/src/structs.rs index 590db87ca..bf1cb21c1 100644 --- a/crates/common/src/structs.rs +++ b/crates/common/src/structs.rs @@ -1,6 +1,7 @@ use std::{f32::consts::PI, num::ParseIntError, ops::Range, str::FromStr, sync::Arc}; use bevy::{ + pbr::{CascadeShadowConfig, CascadeShadowConfigBuilder}, platform::collections::{HashMap, HashSet}, prelude::*, render::view::RenderLayers, @@ -453,9 +454,65 @@ impl AudioSettings { pub enum ShadowSetting { Off, Low, + Middle, High, } +impl ShadowSetting { + /// Creates complete shadow configuration including cascade settings and map resolution. + /// + /// Returns a tuple of (shadows_enabled, cascade_shadow_config, shadow_map_size). + /// + /// # Arguments + /// * `shadow_distance` - Maximum distance for shadow rendering + /// + /// # Returns + /// * `bool` - Whether shadows are enabled + /// * `CascadeShadowConfig` - The cascade shadow configuration + /// * `usize` - The recommended shadow map resolution + pub fn to_shadow_config(&self, shadow_distance: f32) -> (bool, CascadeShadowConfig, usize) { + match self { + ShadowSetting::Off => (false, Default::default(), 512), + ShadowSetting::Low => ( + true, + CascadeShadowConfigBuilder { + num_cascades: 1, + minimum_distance: 0.1, + maximum_distance: shadow_distance, + first_cascade_far_bound: shadow_distance, + overlap_proportion: 0.2, + } + .build(), + 512, // Performance-focused resolution + ), + ShadowSetting::Middle => ( + true, + CascadeShadowConfigBuilder { + num_cascades: 2, + minimum_distance: 0.1, + maximum_distance: shadow_distance, + first_cascade_far_bound: shadow_distance / 8.0, // Optimized for better near-distance quality + overlap_proportion: 0.2, + } + .build(), + 1024, // Balanced quality/performance resolution + ), + ShadowSetting::High => ( + true, + CascadeShadowConfigBuilder { + num_cascades: 4, + minimum_distance: 0.1, + maximum_distance: shadow_distance, + first_cascade_far_bound: shadow_distance / 15.0, + overlap_proportion: 0.2, + } + .build(), + 4096, // Quality-focused resolution + ), + } + } +} + #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)] pub enum AaSetting { Off, diff --git a/crates/system_bridge/src/settings/shadow_settings.rs b/crates/system_bridge/src/settings/shadow_settings.rs index c48244393..db952f2e0 100644 --- a/crates/system_bridge/src/settings/shadow_settings.rs +++ b/crates/system_bridge/src/settings/shadow_settings.rs @@ -1,9 +1,9 @@ use bevy::{ ecs::system::{ - lifetimeless::{SQuery, SRes, Write}, + lifetimeless::{SQuery, SRes, SResMut, Write}, SystemParamItem, }, - pbr::{CascadeShadowConfig, CascadeShadowConfigBuilder, ShadowFilteringMethod}, + pbr::{CascadeShadowConfig, DirectionalLightShadowMap, ShadowFilteringMethod}, platform::collections::HashSet, prelude::*, }; @@ -13,13 +13,14 @@ use super::{AppSetting, EnumAppSetting, IntAppSetting}; impl EnumAppSetting for ShadowSetting { fn variants() -> Vec { - vec![Self::Off, Self::Low, Self::High] + vec![Self::Off, Self::Low, Self::Middle, Self::High] } fn name(&self) -> String { match self { ShadowSetting::Off => "Off", ShadowSetting::Low => "Low", + ShadowSetting::Middle => "Middle", ShadowSetting::High => "High", } .to_owned() @@ -31,6 +32,7 @@ impl AppSetting for ShadowSetting { SRes, SRes, SQuery<(Write, Write)>, + SResMut, ); fn title() -> String { @@ -38,11 +40,12 @@ impl AppSetting for ShadowSetting { } fn description(&self) -> String { - format!("How shadows are rendered in the world.\n\n{}", + format!("How shadows are rendered in the world.\n\n{}", match self { - ShadowSetting::Off => "Off: No shadows are rendered. Fastest", - ShadowSetting::Low => "Low: Low quality shadows. Uses a single pass shadow map and low quality hardward 2x2 filtering. Gives blocky shadow outlines, particularly with high shadow draw distances, but is pretty fast.", - ShadowSetting::High => "High: Higher quality shadows. Uses a set of cascaded shadow maps and higher quality filtering for softer shadow outlines and better quality at higher shadow draw distances, but is more GPU intensive.", + ShadowSetting::Off => "Off: No shadows are rendered. Fastest performance.", + ShadowSetting::Low => "Low: Basic shadows with 512px resolution. Uses a single cascade shadow map and hardware 2x2 filtering. Fast but blocky shadows at distance.", + ShadowSetting::Middle => "Middle: Balanced shadows with 1024px resolution. Uses 2 cascade shadow maps and Gaussian filtering. Good quality/performance balance.", + ShadowSetting::High => "High: Premium shadows with 4096px resolution. Uses 4 cascade shadow maps and Gaussian filtering. Best quality but GPU intensive.", }) } @@ -60,7 +63,7 @@ impl AppSetting for ShadowSetting { fn apply( &self, - (config, cam_res, mut lights): SystemParamItem, + (config, cam_res, mut lights, mut shadow_map): SystemParamItem, mut commands: Commands, cameras: &HashSet, ) { @@ -71,36 +74,15 @@ impl AppSetting for ShadowSetting { }; for (mut light, mut cascades) in lights.iter_mut() { - match value { - ShadowSetting::Off => { - light.shadows_enabled = false; - } - ShadowSetting::Low => { - light.shadows_enabled = true; - *cascades = CascadeShadowConfigBuilder { - num_cascades: 1, - minimum_distance: 0.1, - maximum_distance: config.graphics.shadow_distance, - first_cascade_far_bound: config.graphics.shadow_distance, - overlap_proportion: 0.2, - } - .build() - } - ShadowSetting::High => { - light.shadows_enabled = true; - *cascades = CascadeShadowConfigBuilder { - num_cascades: 4, - minimum_distance: 0.1, - maximum_distance: config.graphics.shadow_distance, - first_cascade_far_bound: config.graphics.shadow_distance / 15.0, - overlap_proportion: 0.2, - } - .build() - } - } + let (shadows_enabled, cascade_config, shadow_map_size) = + value.to_shadow_config(config.graphics.shadow_distance); + light.shadows_enabled = shadows_enabled; + *cascades = cascade_config; + // Update shadow map resolution based on quality setting + shadow_map.size = shadow_map_size; } - let res = &(config, cam_res, lights); + let res = &(config, cam_res, lights, shadow_map); for &cam in cameras { self.apply_to_camera(res, commands.reborrow(), cam); } @@ -120,8 +102,11 @@ impl AppSetting for ShadowSetting { ShadowSetting::Low => { cmds.insert(ShadowFilteringMethod::Hardware2x2); } + ShadowSetting::Middle => { + cmds.insert(ShadowFilteringMethod::Gaussian); // Balanced performance + } ShadowSetting::High => { - cmds.insert(ShadowFilteringMethod::Gaussian); + cmds.insert(ShadowFilteringMethod::Gaussian); // Best quality } } } diff --git a/crates/visuals/src/lib.rs b/crates/visuals/src/lib.rs index 6518005f9..d14555170 100644 --- a/crates/visuals/src/lib.rs +++ b/crates/visuals/src/lib.rs @@ -3,7 +3,7 @@ mod nishita_cloud; use bevy::{ core_pipeline::dof::{DepthOfField, DepthOfFieldMode}, - pbr::{wireframe::WireframePlugin, CascadeShadowConfigBuilder, DirectionalLightShadowMap}, + pbr::{wireframe::WireframePlugin, DirectionalLightShadowMap}, prelude::*, render::{ render_asset::RenderAssetBytesPerFrame, @@ -25,7 +25,7 @@ use common::{ sets::SetupSets, structs::{ AppConfig, DofConfig, FogSetting, PrimaryCamera, PrimaryCameraRes, PrimaryUser, - SceneGlobalLight, SceneLoadDistance, ShadowSetting, TimeOfDay, GROUND_RENDERLAYER, + SceneGlobalLight, SceneLoadDistance, TimeOfDay, GROUND_RENDERLAYER, PRIMARY_AVATAR_LIGHT_LAYER, }, }; @@ -38,7 +38,8 @@ pub struct VisualsPlugin { impl Plugin for VisualsPlugin { fn build(&self, app: &mut App) { - app.insert_resource(DirectionalLightShadowMap { size: 4096 }) + // Initialize with default shadow map size - will be updated by shadow settings + app.insert_resource(DirectionalLightShadowMap { size: 1024 }) .init_resource::() .insert_resource(TimeOfDay { time: 10.0 * 3600.0, @@ -176,6 +177,7 @@ fn apply_global_light( mut prev: Local<(f32, SceneGlobalLight)>, config: Res, mut cloud_dt: Local, + mut shadow_map: ResMut, ) { let next_light = if prev.0 >= TRANSITION_TIME && prev.1.source == scene_global_light.source { scene_global_light.clone() @@ -253,31 +255,13 @@ fn apply_global_light( layer = layer.union(&PRIMARY_AVATAR_LIGHT_LAYER); } - let (shadows_enabled, cascade_shadow_config) = match config.graphics.shadow_settings { - ShadowSetting::Off => (false, Default::default()), - ShadowSetting::Low => ( - true, - CascadeShadowConfigBuilder { - num_cascades: 1, - minimum_distance: 0.1, - maximum_distance: config.graphics.shadow_distance, - first_cascade_far_bound: config.graphics.shadow_distance, - overlap_proportion: 0.2, - } - .build(), - ), - ShadowSetting::High => ( - true, - CascadeShadowConfigBuilder { - num_cascades: 4, - minimum_distance: 0.1, - maximum_distance: config.graphics.shadow_distance, - first_cascade_far_bound: config.graphics.shadow_distance / 15.0, - overlap_proportion: 0.2, - } - .build(), - ), - }; + let (shadows_enabled, cascade_shadow_config, shadow_map_size) = config + .graphics + .shadow_settings + .to_shadow_config(config.graphics.shadow_distance); + + // Update shadow map resolution based on current shadow settings + shadow_map.size = shadow_map_size; commands.spawn(( DirectionalLight {