From b7432cace1503e2065c2557b7c1660f41bc6f96e Mon Sep 17 00:00:00 2001 From: Nils Hasenbanck Date: Sat, 14 Dec 2024 18:41:06 +0100 Subject: [PATCH] Size shadow map based on camera view direction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This tries to compensate for the top to bottom degree at 55°, where the front facing part needs the most space inside the shadow map. We lerp now the shadow map bounds, so that we can properly draw shadows in the distance. This works for standard sun directions very well. We should re-work the enhanced sun direction, so that extreme low angles when the sun is sinking aren't possible. In reality this is also not the case, since the scattered light that come from the sun behind the horizon isn't throwing extremely large shadows either. We should also aim to also do that, or apply a dynamic scaling based on the sun's height angle in the sky. I also adjusted the FOV/default zoom to be closer to the original client. The maximum zoom also was shrunk a bit, so that we don't need to waste precious shadow map resources. We also now set a default windows size of logical 720p. --- korangar/src/main.rs | 37 +++++++++++-------- .../src/world/cameras/directional_shadow.rs | 37 ++++++++++++------- korangar/src/world/cameras/mod.rs | 2 +- korangar/src/world/cameras/player.rs | 8 +--- korangar/src/world/cameras/start.rs | 5 --- korangar_util/src/math.rs | 5 +++ 6 files changed, 53 insertions(+), 41 deletions(-) diff --git a/korangar/src/main.rs b/korangar/src/main.rs index 64a925cc..abd3a240 100644 --- a/korangar/src/main.rs +++ b/korangar/src/main.rs @@ -78,7 +78,7 @@ use settings::AudioSettings; use wgpu::Queue; use wgpu::{Device, Dx12Compiler, Instance, InstanceFlags, MemoryHints}; use winit::application::ApplicationHandler; -use winit::dpi::PhysicalSize; +use winit::dpi::{LogicalSize, PhysicalSize}; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::keyboard::PhysicalKey; @@ -114,6 +114,11 @@ const MAIN_MENU_CLICK_SOUND_EFFECT: &str = "¹öÆ°¼Ò¸®.wav"; // number that should be playable on most devices. const NUMBER_OF_POINT_LIGHTS_WITH_SHADOWS: usize = 6; +const INITIAL_SCREEN_SIZE: ScreenSize = ScreenSize { + width: 1280.0, + height: 720.0, +}; + static ICON_DATA: &[u8] = include_bytes!("../archive/data/icon.png"); // Create the `threads` module. @@ -271,12 +276,6 @@ struct Client { impl Client { fn init() -> Self { - // We don't know the window size yet, so these values are dummy values. - let initial_screen_size = ScreenSize { - width: 800.0, - height: 600.0, - }; - time_phase!("load settings", { let picker_value = Arc::new(AtomicU64::new(0)); let input_system = InputSystem::new(picker_value.clone()); @@ -401,15 +400,15 @@ impl Client { }); let interface_renderer = InterfaceRenderer::new( - initial_screen_size, + INITIAL_SCREEN_SIZE, font_loader.clone(), &texture_loader, *high_quality_interface.get(), ); - let bottom_interface_renderer = GameInterfaceRenderer::new(initial_screen_size, &texture_loader); + let bottom_interface_renderer = GameInterfaceRenderer::new(INITIAL_SCREEN_SIZE, &texture_loader); let middle_interface_renderer = GameInterfaceRenderer::from_renderer(&bottom_interface_renderer); let top_interface_renderer = GameInterfaceRenderer::from_renderer(&bottom_interface_renderer); - let effect_renderer = EffectRenderer::new(initial_screen_size); + let effect_renderer = EffectRenderer::new(INITIAL_SCREEN_SIZE); #[cfg(feature = "debug")] let debug_marker_renderer = DebugMarkerRenderer::new(); @@ -446,7 +445,7 @@ impl Client { time_phase!("initialize interface", { let application = InterfaceSettings::load_or_default(); - let mut interface = Interface::new(initial_screen_size); + let mut interface = Interface::new(INITIAL_SCREEN_SIZE); let mut focus_state = FocusState::default(); let mouse_cursor = MouseCursor::new(&mut sprite_loader, &mut action_loader); let dialog_system = DialogSystem::default(); @@ -1843,12 +1842,12 @@ impl Client { self.start_camera.update(delta_time); self.player_camera.update(delta_time); - let zoom_scale: f32 = match self.entities.is_empty() { - true => self.start_camera.get_zoom_scale(), - false => self.player_camera.get_zoom_scale(), + let view_direction = match self.entities.is_empty() { + true => self.start_camera.view_direction(), + false => self.player_camera.view_direction(), }; - self.directional_shadow_camera.update(directional_light_direction, zoom_scale); + self.directional_shadow_camera.update(directional_light_direction, view_direction); #[cfg(feature = "debug")] update_cameras_measurement.stop(); @@ -2389,7 +2388,13 @@ impl ApplicationHandler for Client { assert_eq!(image_buffer.width(), image_buffer.height(), "icon must be square"); let icon = Icon::from_rgba(image_data, image_buffer.width(), image_buffer.height()).unwrap(); - let window_attributes = Window::default_attributes().with_title(CLIENT_NAME).with_window_icon(Some(icon)); + let window_attributes = Window::default_attributes() + .with_inner_size(LogicalSize { + width: INITIAL_SCREEN_SIZE.width, + height: INITIAL_SCREEN_SIZE.height, + }) + .with_title(CLIENT_NAME) + .with_window_icon(Some(icon)); let window = Arc::new(event_loop.create_window(window_attributes).unwrap()); let backend_name = self.graphics_engine.get_backend_name(); diff --git a/korangar/src/world/cameras/directional_shadow.rs b/korangar/src/world/cameras/directional_shadow.rs index 0cc38709..2f6c6fca 100644 --- a/korangar/src/world/cameras/directional_shadow.rs +++ b/korangar/src/world/cameras/directional_shadow.rs @@ -1,19 +1,22 @@ use cgmath::{InnerSpace, Matrix4, Point3, Vector2, Vector3, Zero}; +use korangar_util::math::lerp; use super::Camera; use crate::graphics::orthographic_reverse_lh; const FAR_PLANE: f32 = 500.0; const NEAR_PLANE: f32 = -500.0; -const MIN_SCALE: f32 = 0.4; const MAX_BOUNDS: f32 = 300.0; +const FRONT_SCALE: f32 = 1.5; +const SIDE_SCALE: f32 = 1.0; +const BACK_SCALE: f32 = 0.5; const LOOK_UP: Vector3 = Vector3::new(0.0, 1.0, 0.0); pub struct DirectionalShadowCamera { focus_point: Point3, camera_position: Point3, view_direction: Vector3, - zoom_scale: f32, + main_camera_view_direction: Vector3, view_matrix: Matrix4, projection_matrix: Matrix4, view_projection_matrix: Matrix4, @@ -25,7 +28,7 @@ impl DirectionalShadowCamera { focus_point: Point3::new(0.0, 0.0, 0.0), camera_position: Point3::new(0.0, 0.0, 0.0), view_direction: Vector3::zero(), - zoom_scale: 0.0, + main_camera_view_direction: Vector3::zero(), view_matrix: Matrix4::zero(), projection_matrix: Matrix4::zero(), view_projection_matrix: Matrix4::zero(), @@ -34,7 +37,7 @@ impl DirectionalShadowCamera { pub fn set_focus_point(&mut self, focus_point: Point3) { // We need to snap the camera to a grid, or else shadows get too noisy. - let grid_size = 25.0; + let grid_size = 10.0; self.focus_point = Point3::new( (focus_point.x / grid_size).floor() * grid_size, (focus_point.y / grid_size).floor() * grid_size, @@ -42,21 +45,29 @@ impl DirectionalShadowCamera { ); } - // The zoom_scale is used to scale the shadow map and give the best possible - // resolution for objects shadows. - pub fn update(&mut self, direction_to_light: Vector3, zoom_scale: f32) { + pub fn update(&mut self, direction_to_light: Vector3, main_camera_view_direction: Vector3) { // TODO: NHA Currently the directional light is the direction TO the light. // We should change that to make it the direction the light shines. let direction_to_light = direction_to_light.normalize(); let scaled_direction = direction_to_light * 100.0; self.camera_position = self.focus_point + scaled_direction; self.view_direction = -direction_to_light; - self.zoom_scale = zoom_scale; + self.main_camera_view_direction = main_camera_view_direction; } - fn calculate_bounds(&self) -> f32 { - let adjusted_scale = MIN_SCALE + (1.0 - MIN_SCALE) * self.zoom_scale; - MAX_BOUNDS * adjusted_scale + fn calculate_bounds(&self) -> (f32, f32, f32, f32) { + let flat_main_view = Vector3::new(self.main_camera_view_direction.x, 0.0, self.main_camera_view_direction.z).normalize(); + let flat_light_direction = Vector3::new(self.view_direction.x, 0.0, self.view_direction.z).normalize(); + + let angle = flat_main_view.dot(flat_light_direction); + let lerp_factor = (-angle + 1.0) * 0.5; + + let front = lerp(MAX_BOUNDS * FRONT_SCALE, MAX_BOUNDS * SIDE_SCALE, lerp_factor).round(); + let back = lerp(-MAX_BOUNDS * BACK_SCALE, -MAX_BOUNDS * SIDE_SCALE, lerp_factor).round(); + let left = lerp(-MAX_BOUNDS * SIDE_SCALE, -MAX_BOUNDS * BACK_SCALE, lerp_factor).round(); + let right = lerp(MAX_BOUNDS * SIDE_SCALE, MAX_BOUNDS * FRONT_SCALE, lerp_factor).round(); + + (front, back, left, right) } } @@ -70,9 +81,9 @@ impl Camera for DirectionalShadowCamera { } fn generate_view_projection(&mut self, _window_size: Vector2) { - let bound_size = self.calculate_bounds(); + let (front, back, left, right) = self.calculate_bounds(); self.view_matrix = Matrix4::look_to_lh(self.camera_position(), self.view_direction, LOOK_UP); - self.projection_matrix = orthographic_reverse_lh(-bound_size, bound_size, -bound_size, bound_size, NEAR_PLANE, FAR_PLANE); + self.projection_matrix = orthographic_reverse_lh(left, right, back, front, NEAR_PLANE, FAR_PLANE); self.view_projection_matrix = self.projection_matrix * self.view_matrix; } diff --git a/korangar/src/world/cameras/mod.rs b/korangar/src/world/cameras/mod.rs index 07b680bd..952fb154 100644 --- a/korangar/src/world/cameras/mod.rs +++ b/korangar/src/world/cameras/mod.rs @@ -20,7 +20,7 @@ pub use self::start::StartCamera; #[cfg(feature = "debug")] use crate::interface::layout::{ScreenPosition, ScreenSize}; -const MAXIMUM_CAMERA_DISTANCE: f32 = 600.0; +const MAXIMUM_CAMERA_DISTANCE: f32 = 500.0; const MINIMUM_CAMERA_DISTANCE: f32 = 150.0; /// The world space has a left-handed coordinate system where the Y axis is up. diff --git a/korangar/src/world/cameras/player.rs b/korangar/src/world/cameras/player.rs index 61d62853..1fe3f8f2 100644 --- a/korangar/src/world/cameras/player.rs +++ b/korangar/src/world/cameras/player.rs @@ -7,8 +7,8 @@ const ZOOM_SPEED: f32 = 2.0; const ROTATION_SPEED: f32 = 0.01; const DEFAULT_DISTANCE: f32 = 400.0; const DEFAULT_ANGLE: f32 = 180_f32.to_radians(); -const CAMERA_PITCH: Deg = Deg(-50.0); -const VERTICAL_FOV: Deg = Deg(15.0); +const CAMERA_PITCH: Deg = Deg(-55.0); +const VERTICAL_FOV: Deg = Deg(15.5); const THRESHOLD: f32 = 0.01; const LOOK_UP: Vector3 = Vector3::new(0.0, 1.0, 0.0); @@ -49,10 +49,6 @@ impl PlayerCamera { self.focus_point.z.set_desired(position.z); } - pub fn get_zoom_scale(&self) -> f32 { - (self.camera_distance.get_current() - MINIMUM_CAMERA_DISTANCE) / (MAXIMUM_CAMERA_DISTANCE - MINIMUM_CAMERA_DISTANCE) - } - pub fn soft_zoom(&mut self, zoom_factor: f32) { self.camera_distance .move_desired_clamp(zoom_factor * ZOOM_SPEED, MINIMUM_CAMERA_DISTANCE, MAXIMUM_CAMERA_DISTANCE); diff --git a/korangar/src/world/cameras/start.rs b/korangar/src/world/cameras/start.rs index 5fb13da9..91b69663 100644 --- a/korangar/src/world/cameras/start.rs +++ b/korangar/src/world/cameras/start.rs @@ -48,11 +48,6 @@ impl StartCamera { self.camera_position = self.focus_point + rotated_offset; self.view_direction = -rotated_offset.normalize(); } - - pub fn get_zoom_scale(&self) -> f32 { - // The start camera has a fixed zoom. - 1.0 - } } impl Camera for StartCamera { diff --git a/korangar_util/src/math.rs b/korangar_util/src/math.rs index e70927ec..46630cff 100644 --- a/korangar_util/src/math.rs +++ b/korangar_util/src/math.rs @@ -9,6 +9,11 @@ pub fn multiply_matrix4_and_point3(matrix: &Matrix4, vector: Point3) - Point3::from_vec((adjusted_vector / adjusted_vector.w).truncate()) } +/// Simple linear interpolation. +pub fn lerp(a: f32, b: f32, t: f32) -> f32 { + a + (b - a) * t +} + #[cfg(test)] mod tests { use cgmath::{assert_relative_eq, EuclideanSpace, Matrix4, Point3};