Skip to content

Commit

Permalink
Size shadow map based on camera view direction
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hasenbanck committed Dec 15, 2024
1 parent 11fb85e commit b7432ca
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 41 deletions.
37 changes: 21 additions & 16 deletions korangar/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
37 changes: 24 additions & 13 deletions korangar/src/world/cameras/directional_shadow.rs
Original file line number Diff line number Diff line change
@@ -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<f32> = Vector3::new(0.0, 1.0, 0.0);

pub struct DirectionalShadowCamera {
focus_point: Point3<f32>,
camera_position: Point3<f32>,
view_direction: Vector3<f32>,
zoom_scale: f32,
main_camera_view_direction: Vector3<f32>,
view_matrix: Matrix4<f32>,
projection_matrix: Matrix4<f32>,
view_projection_matrix: Matrix4<f32>,
Expand All @@ -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(),
Expand All @@ -34,29 +37,37 @@ impl DirectionalShadowCamera {

pub fn set_focus_point(&mut self, focus_point: Point3<f32>) {
// 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,
(focus_point.z / grid_size).floor() * grid_size,
);
}

// 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<f32>, zoom_scale: f32) {
pub fn update(&mut self, direction_to_light: Vector3<f32>, main_camera_view_direction: Vector3<f32>) {
// 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)
}
}

Expand All @@ -70,9 +81,9 @@ impl Camera for DirectionalShadowCamera {
}

fn generate_view_projection(&mut self, _window_size: Vector2<usize>) {
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;
}

Expand Down
2 changes: 1 addition & 1 deletion korangar/src/world/cameras/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 2 additions & 6 deletions korangar/src/world/cameras/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<f32> = Deg(-50.0);
const VERTICAL_FOV: Deg<f32> = Deg(15.0);
const CAMERA_PITCH: Deg<f32> = Deg(-55.0);
const VERTICAL_FOV: Deg<f32> = Deg(15.5);
const THRESHOLD: f32 = 0.01;
const LOOK_UP: Vector3<f32> = Vector3::new(0.0, 1.0, 0.0);

Expand Down Expand Up @@ -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);
Expand Down
5 changes: 0 additions & 5 deletions korangar/src/world/cameras/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions korangar_util/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ pub fn multiply_matrix4_and_point3(matrix: &Matrix4<f32>, vector: Point3<f32>) -
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};
Expand Down

0 comments on commit b7432ca

Please sign in to comment.