diff --git a/src/modules/core/directional_light.rs b/src/modules/core/directional_light.rs index 50e0f285..b99a0650 100644 --- a/src/modules/core/directional_light.rs +++ b/src/modules/core/directional_light.rs @@ -1,11 +1,12 @@ use cgmath::{perspective, Deg, EuclideanSpace, InnerSpace, Matrix4, Point3, SquareMatrix, Transform, Vector3, Vector4, Zero}; -use crate::modules::camera::camera::OPENGL_TO_WGPU_MATRIX; +use crate::modules::{camera::camera::OPENGL_TO_WGPU_MATRIX, environment::sun::Sun, terrain::terrain::Terrain}; use super::texture::Texture; const SHADOW_MAP_SIZE: u32 = 8192; // const SHADOW_MAP_SIZE: u32 = 512; pub struct DirectionalLight { + direction: Vector3, pub position: Point3, pub target: Point3, pub shadow_texture: Texture, @@ -39,6 +40,7 @@ impl DirectionalLight { cascade_splits, cascade_projections, cascade_textures, + direction: Vector3::zero(), } } @@ -55,7 +57,12 @@ impl DirectionalLight { } } - pub fn update(&mut self, camera_view_proj: Matrix4) { + pub fn update(&mut self, camera_view_proj: Matrix4, terrain: &Terrain) { + let [x, y, z, _] = terrain.environment.sun.uniform.sun_position; + let source = Vector3::from([x, y, z]); + let mut center = Vector3::from(terrain.center); + center.y -= 200.0; // this helps to have less stretched shadows when the sun is low + self.direction = (center - source).normalize(); for i in 0..3 { let near = self.cascade_splits[i]; let far = self.cascade_splits[i + 1]; @@ -135,15 +142,19 @@ fn calculate_split_dist(near: f32, far: f32, i: u32, splits: u32, lambda: f32) - } fn calculate_light_view_proj(frustum_corners: &[Point3; 8], directional_light: &DirectionalLight) -> Matrix4 { - let light_direction = Vector3::new(-0.5, -1.0, -0.5).normalize(); // Diagonale depuis la gauche et vers l'arrière + // let light_direction = Vector3::new(-0.5, -1.0, -0.5).normalize(); // Diagonale depuis la gauche et vers l'arrière + let light_direction = directional_light.direction; // Diagonale depuis la gauche et vers l'arrière let frustum_center = get_frustum_center(&frustum_corners); + let stabilized_frustum_center = frustum_center; + // let stabilized_frustum_center = stabilize_cascade_center(frustum_center, directional_light.cascade_projections[0]); + let light_distance = 100.0; - let light_position = frustum_center - light_direction * light_distance; + let light_position = stabilized_frustum_center - light_direction * light_distance; let light_view = Matrix4::look_at_rh( light_position, - frustum_center, + stabilized_frustum_center, Vector3::unit_y() ); @@ -212,4 +223,31 @@ fn get_frustum_center(frustum_corners: &[Point3; 8]) -> Point3 { center.z /= 8.0; center +} + +const TEXEL_SCALE: f32 = 2.0 / SHADOW_MAP_SIZE as f32; // résolution de la shadow map de 1024px +const INV_TEXEL_SCALE: f32 = 1.0 / TEXEL_SCALE; + +fn stabilize_cascade_center(center: Point3, cascade_view_projection: Matrix4) -> Point3 { + // Projeter le nouveau centre en utilisant la projection précédente + let projected_center = cascade_view_projection * center.to_homogeneous(); + + // Diviser par w pour obtenir les coordonnées normalisées + let w = projected_center.w; + + // Arrondir les coordonnées x et y à des valeurs de texels entiers + let x = ((projected_center.x / w) * INV_TEXEL_SCALE).floor() * TEXEL_SCALE; + let y = ((projected_center.y / w) * INV_TEXEL_SCALE).floor() * TEXEL_SCALE; + let z = projected_center.z / w; + + // Re-projeter dans l'espace monde + let inv_cascade_view_projection = cascade_view_projection.invert().unwrap(); + let corrected_center = inv_cascade_view_projection * Vector4::new(x, y, z, 1.0); + + // Diviser par w pour obtenir les coordonnées 3D finales + Point3::new( + corrected_center.x / corrected_center.w, + corrected_center.y / corrected_center.w, + corrected_center.z / corrected_center.w + ) } \ No newline at end of file diff --git a/src/modules/environment/cycle.rs b/src/modules/environment/cycle.rs index 8611c809..b9037193 100644 --- a/src/modules/environment/cycle.rs +++ b/src/modules/environment/cycle.rs @@ -1,6 +1,6 @@ use crate::modules::utils::functions::{denormalize_f32, normalize_f32}; -const REAL_TIME: bool = true; +const REAL_TIME: bool = false; const MS_IN_DAY: u64 = 24 * 3600 * 1000; const UTC: u64 = 2; const DAY_START_HOUR: u64 = 5; @@ -43,7 +43,7 @@ impl Cycle { pub fn update(&mut self, mut delta: f32) { if REAL_TIME == false { - delta *= (86_400_000.0 / 60_000.0) * 2.0; // 24h in 1min + delta *= (86_400_000.0 / 60_000.0) * 4.0; // 24h in 1min // delta *= 86_400_000.0 / 60_000.0; // 24h in 1min } self.delta = delta; diff --git a/src/modules/environment/environment.rs b/src/modules/environment/environment.rs index ffeac627..4c3a6a9e 100644 --- a/src/modules/environment/environment.rs +++ b/src/modules/environment/environment.rs @@ -11,14 +11,14 @@ pub struct Environment { impl Environment { - pub async fn load(name: &str, state: &State<'_>) -> anyhow::Result { + pub async fn load(name: &str, center: [f32; 3], state: &State<'_>) -> anyhow::Result { let cycle = Cycle::new(); let day_msenv = MsEnv::read(name).await?; let night_msenv = MsEnv::read("moonlight04").await?; Ok(Self { cycle, fog: Fog::new(&day_msenv, &night_msenv), - sun: Sun::new(&day_msenv, &night_msenv, state), + sun: Sun::new(&day_msenv, &night_msenv, center, state), sky: Sky::new(&day_msenv, &night_msenv, state), clouds: Clouds::new(&day_msenv, state).await? }) diff --git a/src/modules/environment/sun.rs b/src/modules/environment/sun.rs index 1a27ec9e..adf9bb19 100644 --- a/src/modules/environment/sun.rs +++ b/src/modules/environment/sun.rs @@ -1,4 +1,4 @@ -use cgmath::{Deg, Matrix4, Quaternion, Rotation3, Vector3}; +use cgmath::{Angle, Deg, Matrix4, Quaternion, Rad, Rotation3, Vector3}; use crate::modules::{core::model::{CustomMesh, TransformUniform}, geometry::plane::Plane, state::State}; use super::{cycle::Cycle, environment::MsEnv, Position}; @@ -6,13 +6,14 @@ const DAY_POSITION: [f32; 3] = [2600.0, 000.0, -1000.0]; const NIGHT_POSITION: [f32; 3] = [1400.0, 1400.0, 1400.0]; pub struct Sun { + center: [f32; 3], position: Position, pub uniform: SunUniform, pub mesh: CustomMesh, } impl Sun { - pub fn new(day_msenv: &MsEnv, night_msenv: &MsEnv, state: &State<'_>) -> Self { + pub fn new(day_msenv: &MsEnv, night_msenv: &MsEnv, center: [f32; 3], state: &State<'_>) -> Self { let position = DAY_POSITION; let plane = Plane::new(500.0, 500.0, 1, 1); let mut uniform = SunUniform::default(); @@ -38,6 +39,7 @@ impl Sun { position, uniform, mesh, + center, } } @@ -48,7 +50,8 @@ impl Sun { } if cycle.day_factor > 0.0 { let angle = 180.0 * cycle.day_factor; - self.position = (Quaternion::from_axis_angle(Vector3::unit_z(), Deg(angle)) * Vector3::from(DAY_POSITION)).into(); + // self.position = (Quaternion::from_axis_angle(Vector3::unit_z(), Deg(angle)) * Vector3::from(DAY_POSITION)).into(); + self.position = compute_sun_position(&self.center, 2000.0, angle); self.uniform.sun_position = [self.position[0], self.position[1], self.position[2], 0.0]; } queue.write_buffer( @@ -109,4 +112,12 @@ impl Default for SunUniform { night_character_ambient: Default::default(), } } +} + +fn compute_sun_position(center: &[f32; 3], radius: f32, angle_deg: f32) -> [f32; 3] { + let angle_rad = Rad::from(cgmath::Deg(angle_deg)); + let x = center[0] + radius * angle_rad.cos(); + let z = center[2]; + let y = center[1] + radius * angle_rad.sin(); + [x, y, z] } \ No newline at end of file diff --git a/src/modules/geometry/plane.rs b/src/modules/geometry/plane.rs index ef87f749..edfe8ff3 100644 --- a/src/modules/geometry/plane.rs +++ b/src/modules/geometry/plane.rs @@ -94,7 +94,7 @@ impl Plane { } } - pub fn set_vertices_height(&mut self, vertices_height: Vec) { + pub fn set_vertices_height(&mut self, vertices_height: &Vec) { if vertices_height.len() != self.vertices.len() { panic!("Impossible to set vertices height with incompatible surface vertices count"); } @@ -425,5 +425,7 @@ impl Plane { clouds_buffer ) } + + } diff --git a/src/modules/state.rs b/src/modules/state.rs index f45426ad..ec57d2ce 100644 --- a/src/modules/state.rs +++ b/src/modules/state.rs @@ -104,7 +104,8 @@ impl<'a> State<'a> { .request_device( &wgpu::DeviceDescriptor { label: None, - required_features: wgpu::Features::all_webgpu_mask(), + required_features: wgpu::Features::DEPTH_CLIP_CONTROL, + // required_features: wgpu::Features::all_webgpu_mask(), // required_features: wgpu::Features::empty(), // WebGL doesn't support all of wgpu's features, so if // we're building for the web we'll have to disable some. @@ -453,7 +454,7 @@ impl<'a> State<'a> { } // self.shadow_pipeline.uniforms.directional_light = self.directional_light.uniform(self.projection.aspect); - self.directional_light.update(self.common_pipeline.uniforms.camera.view_proj.into()); + self.directional_light.update(self.common_pipeline.uniforms.camera.view_proj.into(), &self.terrains[0]); self.shadow_pipeline.uniforms.directional_light = self.directional_light.uniform_from_camera(self.common_pipeline.uniforms.camera.view_proj.into()); self.queue.write_buffer(&self.shadow_pipeline.buffers.directional_light, 0, bytemuck::cast_slice(&[self.shadow_pipeline.uniforms.directional_light])); self.queue.write_buffer(&self.common_pipeline.buffers.directional_light, 0, bytemuck::cast_slice(&[self.shadow_pipeline.uniforms.directional_light])); diff --git a/src/modules/terrain/chunk.rs b/src/modules/terrain/chunk.rs index 6bda0f03..1bc81042 100644 --- a/src/modules/terrain/chunk.rs +++ b/src/modules/terrain/chunk.rs @@ -7,6 +7,7 @@ pub struct Chunk { pub terrain_mesh: CustomMesh, pub water_mesh: CustomMesh, pub depth_buffer: wgpu::Buffer, + pub mean_height: f32, water_buffer: wgpu::Buffer, area_data: AreaData, } @@ -24,8 +25,8 @@ impl Chunk { let name = Self::name_from(*x, *y); let chunk_path = &format!("{terrain_path}/{name}"); let height = Height::read(&chunk_path, setting.height_scale).await?; + let mean_height = height.vertices.iter().fold(0.0, |acc, v| acc + *v) / height.vertices.len() as f32; let water = Water::read(&chunk_path).await?; - // dbg!(&water); let textures_set = ChunkTextureSet::read(&chunk_path).await?; let mut alpha_maps = Vec::new(); for i in 0..textures_set.textures.len() { @@ -50,7 +51,7 @@ impl Chunk { (*y as f32 * size) + (size / 2.0) ]; let mut terrain_geometry = Plane::new(size, size, segments, segments); - terrain_geometry.set_vertices_height(height.vertices); + terrain_geometry.set_vertices_height(&height.vertices); let terrain_mesh = terrain_geometry.to_terrain_mesh( &state.device, &state.terrain_pipeline, @@ -79,13 +80,13 @@ impl Chunk { &water_textures, ); let area_data = AreaData::read(&chunk_path).await?; - Ok(Self { terrain_mesh, water_mesh, area_data, water_buffer, depth_buffer, + mean_height, }) } diff --git a/src/modules/terrain/terrain.rs b/src/modules/terrain/terrain.rs index 7c971113..f1b3258d 100644 --- a/src/modules/terrain/terrain.rs +++ b/src/modules/terrain/terrain.rs @@ -6,7 +6,8 @@ pub struct Terrain { water_texture: WaterTexture, pub setting: Setting, pub environment: Environment, - pub chunks: Vec + pub chunks: Vec, + pub center: [f32; 3], } impl Terrain { @@ -16,7 +17,6 @@ impl Terrain { let setting = Setting::read(&path).await?; let texture_set = TextureSet::read(&path).await?; let water_texture = WaterTexture::load(state).await?; - let environment = Environment::load(&setting.environment, state).await?; let textures = texture_set.load_textures(&state.device, &state.queue).await?; let mut chunks = Vec::new(); for x in 0..setting.map_size[0] { @@ -70,11 +70,19 @@ impl Terrain { for chunk in &mut chunks { chunk.load_objects_instances(state).await; } + let center = { + let y = chunks.iter().fold(0.0, |acc, v| acc + v.mean_height) / chunks.len() as f32; + let x = (setting.map_size[0] as f32 * 256.0) / 2.0; + let z = (setting.map_size[1] as f32 * 256.0) / 2.0; + [x, y, z] + }; + let environment = Environment::load(&setting.environment, center, state).await?; Ok(Self { setting, chunks, environment, - water_texture + water_texture, + center, }) } diff --git a/src/shaders/terrain.wgsl b/src/shaders/terrain.wgsl index 1e50e9aa..7d8122b3 100644 --- a/src/shaders/terrain.wgsl +++ b/src/shaders/terrain.wgsl @@ -159,6 +159,9 @@ fn srgb_to_linear(color: vec3) -> vec3 { return vec3(r, g, b); } +const SHADOW_START: f32 = 0.10; +const SHADOW_ZENITH: f32 = 0.5; +const SHADOW_END: f32 = 0.90; @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { @@ -219,7 +222,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { let material_emissive: vec3 = mix(sun.night_material_emissive.rgb, sun.day_material_emissive.rbg, sun_light_factor); let background_diffuse: vec3 = mix(sun.night_background_diffuse.rgb, sun.day_background_diffuse.rbg, sun_light_factor); - let ambient_strength = mix(0.6, 0.3, sun_light_factor); + let ambient_strength = mix(0.7, 0.3, sun_light_factor); let ambient_color = material_ambient * ambient_strength; let sun_light_dir = normalize(sun.sun_position.xyz - in.world_position); @@ -245,29 +248,35 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { // proj_coords.xy, // light_space_position.z * proj_correction, // ); - - // Calculer le biais pour éviter le shadow acne - let bias = 0.0005; - // Appliquer le PCF (Percentage Closer Filtering) + // PCF (Percentage Closer Filtering) var shadow: f32 = 0.0; - let texel_size = 1.0 / 8192.0; // Ajustez cette valeur selon la taille de votre shadow map + + let texel_size = 1.0 / 8192.0; for (var x: i32 = -1; x <= 1; x++) { for (var y: i32 = -1; y <= 1; y++) { let offset = vec2(f32(x), f32(y)) * texel_size; shadow += textureSampleCompare( shadow_texture, shadow_sampler, - // proj_coords.xy, proj_coords.xy + offset, - // proj_coords.z - bias light_space_position.z * proj_correction ); } } shadow /= 9.0; + shadow = denormalize_value_between(shadow, 0.25, 1.0); // attenuate the opacity of the shadow; + + var shadow_strength = 0.0; - final_color *= denormalize_value_between(shadow, 0.2, 1.0); + if cycle.day_factor >= SHADOW_START && cycle.day_factor < SHADOW_ZENITH { + shadow_strength = ease_out_expo(normalize_value_between(cycle.day_factor, SHADOW_START, SHADOW_ZENITH)); + } + else if cycle.day_factor >= SHADOW_ZENITH && cycle.day_factor <= SHADOW_END { + shadow_strength = ease_out_expo(1.0 - normalize_value_between(cycle.day_factor, SHADOW_ZENITH, SHADOW_END)); + } + shadow = mix(1.0, shadow, shadow_strength); + final_color *= shadow; // fog if fog.enabled == 1.0 {