Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions crates/common/src/structs.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
Expand Down
59 changes: 22 additions & 37 deletions crates/system_bridge/src/settings/shadow_settings.rs
Original file line number Diff line number Diff line change
@@ -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::*,
};
Expand All @@ -13,13 +13,14 @@ use super::{AppSetting, EnumAppSetting, IntAppSetting};

impl EnumAppSetting for ShadowSetting {
fn variants() -> Vec<Self> {
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()
Expand All @@ -31,18 +32,20 @@ impl AppSetting for ShadowSetting {
SRes<AppConfig>,
SRes<PrimaryCameraRes>,
SQuery<(Write<DirectionalLight>, Write<CascadeShadowConfig>)>,
SResMut<DirectionalLightShadowMap>,
);

fn title() -> String {
"Shadow settings".to_owned()
}

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.",
})
}

Expand All @@ -60,7 +63,7 @@ impl AppSetting for ShadowSetting {

fn apply(
&self,
(config, cam_res, mut lights): SystemParamItem<Self::Param>,
(config, cam_res, mut lights, mut shadow_map): SystemParamItem<Self::Param>,
mut commands: Commands,
cameras: &HashSet<Entity>,
) {
Expand All @@ -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);
}
Expand All @@ -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
}
}
}
Expand Down
40 changes: 12 additions & 28 deletions crates/visuals/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
},
};
Expand All @@ -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::<SceneGlobalLight>()
.insert_resource(TimeOfDay {
time: 10.0 * 3600.0,
Expand Down Expand Up @@ -176,6 +177,7 @@ fn apply_global_light(
mut prev: Local<(f32, SceneGlobalLight)>,
config: Res<AppConfig>,
mut cloud_dt: Local<f32>,
mut shadow_map: ResMut<DirectionalLightShadowMap>,
) {
let next_light = if prev.0 >= TRANSITION_TIME && prev.1.source == scene_global_light.source {
scene_global_light.clone()
Expand Down Expand Up @@ -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 {
Expand Down