use glam::Vec3;
use crate::{
frustum::Frustum,
lights::{Light, LightKey},
shadows::{
light_shadow::{CubeFaceUpdateRate, LightShadowParams},
quality_tier::ShadowQualityTier,
},
AwsmRenderer,
};
#[derive(Clone, Copy, Debug)]
pub struct LightImportanceDecision {
pub tier: ShadowQualityTier,
pub resolution: u32,
pub cube_face_update_rate: CubeFaceUpdateRate,
}
impl LightImportanceDecision {
pub fn resolution_for_tier(tier: ShadowQualityTier) -> u32 {
match tier {
ShadowQualityTier::Low => 256,
ShadowQualityTier::Medium => 512,
ShadowQualityTier::High => 1024,
ShadowQualityTier::Ultra => 2048,
ShadowQualityTier::Custom => 1024,
}
}
}
impl AwsmRenderer {
pub fn refresh_light_importance_budgets(&mut self) {
let Some(matrices) = self.camera.last_matrices.as_ref() else {
return;
};
let camera_pos = matrices.view.inverse().w_axis.truncate();
let frustum = Frustum::from_view_projection(matrices.view_projection());
let snapshot: Vec<(LightKey, Light)> =
self.lights.iter().map(|(k, l)| (k, l.clone())).collect();
for (light_key, light) in snapshot {
let casts = self
.shadows
.light_params(light_key)
.map(|p| p.cast)
.unwrap_or(false);
if !casts {
continue;
}
let decision = light_importance_decision(&light, camera_pos, &frustum);
if let Some(params) = self.shadows.params.get_mut(light_key) {
apply_decision(params, decision);
}
self.lights.mark_punctual_dirty();
}
}
}
fn apply_decision(params: &mut LightShadowParams, decision: LightImportanceDecision) {
params.resolution = decision.resolution;
params.cube_face_update_rate = decision.cube_face_update_rate;
if let Some(preset) = decision.tier.preset() {
preset.apply_to_light_params(params);
}
}
fn light_importance_decision(
light: &Light,
camera_pos: Vec3,
camera_frustum: &Frustum,
) -> LightImportanceDecision {
if matches!(light, Light::Directional { .. }) {
return LightImportanceDecision {
tier: ShadowQualityTier::High,
resolution: 2048,
cube_face_update_rate: CubeFaceUpdateRate::EveryFrame,
};
}
let Some(aabb) = light.world_aabb() else {
return LightImportanceDecision {
tier: ShadowQualityTier::Low,
resolution: LightImportanceDecision::resolution_for_tier(ShadowQualityTier::Low),
cube_face_update_rate: CubeFaceUpdateRate::Every2Frames,
};
};
let in_frustum = camera_frustum.intersects_aabb(&aabb);
if !in_frustum {
return LightImportanceDecision {
tier: ShadowQualityTier::Low,
resolution: LightImportanceDecision::resolution_for_tier(ShadowQualityTier::Low),
cube_face_update_rate: CubeFaceUpdateRate::Every2Frames,
};
}
let (position, intensity) = match light {
Light::Point {
position,
intensity,
..
} => (Vec3::from(*position), *intensity),
Light::Spot {
position,
intensity,
..
} => (Vec3::from(*position), *intensity),
Light::Directional { .. } => unreachable!("directional handled above"),
};
let dist_sq = (position - camera_pos).length_squared().max(0.001);
let score = intensity / (1.0 + dist_sq);
let tier = if score > 10.0 {
ShadowQualityTier::Ultra
} else if score > 1.0 {
ShadowQualityTier::High
} else if score > 0.05 {
ShadowQualityTier::Medium
} else {
ShadowQualityTier::Low
};
let resolution = LightImportanceDecision::resolution_for_tier(tier);
let cube_face_update_rate = match tier {
ShadowQualityTier::Low => CubeFaceUpdateRate::Every2Frames,
_ => CubeFaceUpdateRate::EveryFrame,
};
LightImportanceDecision {
tier,
resolution,
cube_face_update_rate,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn directional_pins_to_high() {
let light = Light::Directional {
color: [1.0, 1.0, 1.0],
intensity: 1.0,
direction: [0.0, -1.0, 0.0],
};
let frustum =
Frustum::from_view_projection(glam::Mat4::perspective_rh(1.0, 1.0, 0.1, 100.0));
let d = light_importance_decision(&light, Vec3::ZERO, &frustum);
assert_eq!(d.tier, ShadowQualityTier::High);
}
#[test]
fn out_of_frustum_point_drops_to_low() {
let view = glam::Mat4::look_at_rh(Vec3::ZERO, Vec3::new(0.0, 0.0, 1.0), Vec3::Y);
let proj = glam::Mat4::perspective_rh(60.0_f32.to_radians(), 1.0, 0.1, 50.0);
let frustum = Frustum::from_view_projection(proj * view);
let light = Light::Point {
color: [1.0; 3],
intensity: 10.0,
position: [0.0, 0.0, -100.0],
range: 1.0,
};
let d = light_importance_decision(&light, Vec3::ZERO, &frustum);
assert_eq!(d.tier, ShadowQualityTier::Low);
}
#[test]
fn close_strong_point_climbs_to_ultra() {
let view = glam::Mat4::look_at_rh(Vec3::ZERO, Vec3::new(0.0, 0.0, 1.0), Vec3::Y);
let proj = glam::Mat4::perspective_rh(60.0_f32.to_radians(), 1.0, 0.1, 50.0);
let frustum = Frustum::from_view_projection(proj * view);
let light = Light::Point {
color: [1.0; 3],
intensity: 100.0,
position: [0.0, 0.0, 1.0],
range: 5.0,
};
let d = light_importance_decision(&light, Vec3::ZERO, &frustum);
assert_eq!(d.tier, ShadowQualityTier::Ultra);
}
}