From 7a3f5fce9fe10d051fc320208c716b4eb247a030 Mon Sep 17 00:00:00 2001 From: Franchioping Date: Fri, 28 Nov 2025 20:39:09 +0000 Subject: [PATCH] working shadows somewhat???? --- src/camera.rs | 123 ++++++++++++++++++++ src/main.rs | 120 ++++++------------- src/rendering.rs | 252 ++++++++++++++++++++++++++++++++++++++++ src/shaders/depth.frag | 11 ++ src/shaders/depth.vert | 11 ++ src/shaders/shader.frag | 70 ++++++++++- src/shaders/shader.vert | 18 ++- 7 files changed, 515 insertions(+), 90 deletions(-) create mode 100644 src/camera.rs create mode 100644 src/rendering.rs create mode 100644 src/shaders/depth.frag create mode 100644 src/shaders/depth.vert diff --git a/src/camera.rs b/src/camera.rs new file mode 100644 index 0000000..a44da04 --- /dev/null +++ b/src/camera.rs @@ -0,0 +1,123 @@ +use macroquad::prelude::*; + +const MOVE_SPEED: f32 = 1.0; +const LOOK_SPEED: f32 = 0.01; + +pub struct FirstPersonCamera { + pub position: Vec3, + yaw: f32, + pitch: f32, + + pub front: Vec3, + pub right: Vec3, + pub up: Vec3, + world_up: Vec3, + + grabbed: bool, + last_mouse: Vec2, +} + +impl FirstPersonCamera { + pub fn new(position: Vec3) -> Self { + let yaw: f32 = 1.18; + let pitch: f32 = 0.0; + let world_up = vec3(0.0, 1.0, 0.0); + + let front = vec3( + yaw.cos() * pitch.cos(), + pitch.sin(), + yaw.sin() * pitch.cos(), + ) + .normalize(); + + let right = front.cross(world_up).normalize(); + let up = right.cross(front).normalize(); + + let grabbed = true; + set_cursor_grab(grabbed); + show_mouse(!grabbed); + + Self { + position, + yaw, + pitch, + front, + right, + up, + world_up, + grabbed, + last_mouse: mouse_position().into(), + } + } + + pub fn update_vectors(&mut self) { + self.front = vec3( + self.yaw.cos() * self.pitch.cos(), + self.pitch.sin(), + self.yaw.sin() * self.pitch.cos(), + ) + .normalize(); + + self.right = self.front.cross(self.world_up).normalize(); + self.up = self.right.cross(self.front).normalize(); + } + + pub fn handle_mouse(&mut self, delta: f32) { + let mouse: Vec2 = mouse_position().into(); + let mouse_delta = mouse - self.last_mouse; + self.last_mouse = mouse; + + if !self.grabbed { + return; + } + + self.yaw += mouse_delta.x * delta * LOOK_SPEED; + self.pitch -= mouse_delta.y * delta * LOOK_SPEED; + + // clamp pitch + self.pitch = self.pitch.clamp(-1.5, 1.5); + + self.update_vectors(); + } + + pub fn handle_keyboard(&mut self) { + if is_key_down(KeyCode::W) { + self.position += self.front * MOVE_SPEED; + } + if is_key_down(KeyCode::S) { + self.position -= self.front * MOVE_SPEED; + } + if is_key_down(KeyCode::A) { + self.position -= self.right * MOVE_SPEED; + } + if is_key_down(KeyCode::D) { + self.position += self.right * MOVE_SPEED; + } + if is_key_down(KeyCode::LeftShift) { + self.position += Vec3::Y * MOVE_SPEED; + } + if is_key_down(KeyCode::LeftControl) { + self.position -= Vec3::Y * MOVE_SPEED; + } + } + + pub fn update(&mut self, delta: f32) { + if is_key_pressed(KeyCode::Tab) { + self.grabbed = !self.grabbed; + set_cursor_grab(self.grabbed); + show_mouse(!self.grabbed); + } + + self.handle_keyboard(); + self.handle_mouse(delta); + } + + pub fn apply(&self) { + set_camera(&Camera3D { + position: self.position, + up: self.up, + target: self.position + self.front, + ..Default::default() + }); + } +} diff --git a/src/main.rs b/src/main.rs index dccc323..4b3302d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -use glam::Quat; use macroquad::prelude as mq; use rapier3d::prelude::*; @@ -6,110 +5,65 @@ mod engine; use engine::*; mod camera; +mod rendering; mod graphics_util; use graphics_util::*; +use crate::rendering::Renderer; + #[macroquad::main("3D")] async fn main() { - // Graphics Initialization - let vertex = include_str!("shaders/shader.vert"); - let fragment = include_str!("shaders/shader.frag"); - - let material = mq::load_material( - mq::ShaderSource::Glsl { vertex, fragment }, - mq::MaterialParams { - pipeline_params: mq::PipelineParams { - depth_test: mq::Comparison::LessOrEqual, - depth_write: true, - ..Default::default() - }, - uniforms: vec![mq::UniformDesc::new( - "render_normals_bool", - mq::UniformType::Int1, - )], - ..Default::default() - }, - ) - .unwrap(); - material.set_uniform("render_normals_bool", 0); + let mut world = World::default(); + let mut renderer = Renderer::new(); // Physics Initialization - let mut world = World::default(); world.register_free_collider( - ColliderBuilder::cuboid(50.0, 0.1, 50.0) - .restitution(0.5) + ColliderBuilder::cuboid(50.0, 10.0, 50.0) + .restitution(0.2) .build(), None, ); + add_objects(&mut world); - for i in 0..10 { + loop { + renderer.update_camera(); + if mq::is_key_pressed(mq::KeyCode::L) { + renderer + .light + .set_location(renderer.camera.position, renderer.camera.front); + renderer.update_light(); + println!( + "Light Pos: {}, Light Front Vec{}", + renderer.light.position, renderer.light.front + ); + } + + if mq::is_key_pressed(mq::KeyCode::C) { + add_objects(&mut world); + } + // Physics Simulation + world.step(); + renderer.draw(&mut world); + + mq::next_frame().await + } +} + +fn add_objects(world: &mut World) { + for i in 0..1 { let body = world.register_body( RigidBodyBuilder::dynamic() - .translation(vector![0.0, 10.0 + i as f32, 0.0]) + .translation(vector![0.0, 50.0 + i as f32, 0.0]) .rotation(vector![std::f32::consts::PI / 4.2, i as f32, i as f32]) .build(), ); world.register_collider( - ColliderBuilder::cuboid(0.5, 0.5, 0.5) - .restitution(0.5) + ColliderBuilder::cuboid(3.0, 3.0, 3.0) + .restitution(1.0) .build(), body, None, ); } - - let mut cam: camera::FirstPersonCamera = - camera::FirstPersonCamera::new(mq::vec3(0.0, 10.0, -5.0)); - loop { - // Physics Simulation - world.step(); - - // Graphics Rendering - mq::clear_background(mq::LIGHTGRAY); - cam.update(mq::get_frame_time()); - cam.apply(); - - mq::draw_grid(20, 1., mq::BLACK, mq::GRAY); - - for (handle, coll) in world.colliders.iter() { - let position: glam::Vec3 = world.position_of_collider(handle).unwrap().into(); - let rotation = *coll.rotation(); - unsafe { - let context = mq::get_internal_gl().quad_gl; - - context.push_model_matrix(mq::Mat4::from_rotation_translation( - rotation.into(), - position, - )); - } - let color = world.collider_data.get(&handle).unwrap().color; - match coll.shape().shape_type() { - ShapeType::Ball => { - mq::draw_sphere( - mq::vec3(0.0, 0.0, 0.0), - coll.shape().as_ball().unwrap().radius, - None, - color, - ); - } - ShapeType::Cuboid => { - mq::gl_use_material(&material); - draw_cuboid( - mq::vec3(0.0, 0.0, 0.0), - (coll.shape().as_cuboid().unwrap().half_extents * 2.0).into(), - color, - ); - mq::gl_use_default_material(); - } - _ => println!("Not implemented.. Skipping"), - } - unsafe { - let context = mq::get_internal_gl().quad_gl; - context.pop_model_matrix(); - } - } - - mq::next_frame().await - } } diff --git a/src/rendering.rs b/src/rendering.rs new file mode 100644 index 0000000..c8f462c --- /dev/null +++ b/src/rendering.rs @@ -0,0 +1,252 @@ +use crate::{ + camera::{self as cam, FirstPersonCamera}, + engine::World, + graphics_util::draw_cuboid, +}; +use macroquad::prelude as mq; +use rapier3d::prelude::*; + +const DEPTH_MAP_SIZE: u32 = 512; +const ENABLE_SHADOWS: bool = true; + +const LIGHT_PROJECTION_SIZE: f32 = 20.0; +const LIGHT_NEAR: f32 = 10.0; +const LIGHT_FAR: f32 = 100.0; + +pub struct Light { + projection_matrix: mq::Mat4, + view_matrix: mq::Mat4, + pub position: mq::Vec3, + pub front: mq::Vec3, +} +impl Light { + pub fn new() -> Light { + let mut l = Light { + projection_matrix: mq::Mat4::orthographic_rh( + -LIGHT_PROJECTION_SIZE, + LIGHT_PROJECTION_SIZE, + -LIGHT_PROJECTION_SIZE, + LIGHT_PROJECTION_SIZE, + LIGHT_NEAR, + LIGHT_FAR, + ), + view_matrix: mq::Mat4::NAN, + position: mq::Vec3::NAN, + front: mq::Vec3::NAN, + }; + l.set_location(mq::vec3(5.0, 10.0, 0.0), mq::Vec3::NEG_Y); + return l; + } + pub fn set_location(&mut self, position: mq::Vec3, front: mq::Vec3) { + self.position = position; + self.front = front; + self.view_matrix = + mq::Mat4::look_at_rh(self.position, self.position + self.front, mq::Vec3::Y); + } + pub fn get_space_matrix(&self) -> mq::Mat4 { + return self.projection_matrix * self.view_matrix; + } +} + +pub struct Renderer { + base_material: mq::Material, + depth_material: mq::Material, + depth_target: mq::RenderTarget, + pub light: Light, + pub camera: cam::FirstPersonCamera, + has_light_moved: bool, +} + +impl Renderer { + pub fn new() -> Renderer { + let material = mq::load_material( + mq::ShaderSource::Glsl { + vertex: include_str!("shaders/shader.vert"), + fragment: include_str!("shaders/shader.frag"), + }, + mq::MaterialParams { + pipeline_params: mq::PipelineParams { + cull_face: macroquad::miniquad::CullFace::Front, + depth_test: mq::Comparison::LessOrEqual, + depth_write: true, + ..Default::default() + }, + uniforms: vec![ + mq::UniformDesc::new("render_normals_bool", mq::UniformType::Int1), + mq::UniformDesc::new("render_shadows_bool", mq::UniformType::Int1), + mq::UniformDesc::new("light_position", mq::UniformType::Float3), + mq::UniformDesc::new("light_space_matrix", mq::UniformType::Mat4), + ], + textures: vec![String::from("shadow_map")], + ..Default::default() + }, + ) + .unwrap(); + material.set_uniform("render_normals_bool", 0); + material.set_uniform("render_shadows_bool", ENABLE_SHADOWS); + + let depth_material = mq::load_material( + mq::ShaderSource::Glsl { + vertex: include_str!("shaders/depth.vert"), + fragment: include_str!("shaders/depth.frag"), + }, + mq::MaterialParams { + pipeline_params: mq::PipelineParams { + // cull_face: macroquad::miniquad::CullFace::Back, + depth_test: mq::Comparison::LessOrEqual, + depth_write: true, + ..Default::default() + }, + uniforms: vec![mq::UniformDesc::new( + "light_space_matrix", + mq::UniformType::Mat4, + )], + ..Default::default() + }, + ) + .unwrap(); + + let depth_map_texture = mq::render_target_ex( + DEPTH_MAP_SIZE, + DEPTH_MAP_SIZE, + mq::RenderTargetParams { + sample_count: 1, + depth: true, + }, + ); + depth_map_texture + .texture + .set_filter(mq::FilterMode::Nearest); + + let light = Light::new(); + let camera = FirstPersonCamera::new(mq::vec3(10.0, 50.0, 10.0)); + let mut r = Renderer { + base_material: material, + depth_material, + depth_target: depth_map_texture, + light, + camera, + has_light_moved: true, + }; + r.light + .set_location(mq::vec3(10.0, 40.0, 10.0), mq::Vec3::NEG_Y); + r.update_light(); + return r; + } + pub fn update_camera(&mut self) { + self.camera.update(mq::get_frame_time()); + } + pub fn update_light(&mut self) { + let light_space_matrix = self.light.get_space_matrix(); + self.base_material + .set_uniform("light_position", self.light.position); + self.base_material + .set_uniform("light_space_matrix", light_space_matrix); + self.depth_material + .set_uniform("light_space_matrix", light_space_matrix); + self.has_light_moved = true; + } + fn render_depth(&mut self, mut world: &mut World) { + mq::set_camera(&mq::Camera3D { + position: self.light.position, + target: self.light.position + self.light.front, + up: mq::Vec3::Y, + render_target: Some(self.depth_target.clone()), + ..Default::default() + }); + mq::gl_use_material(&self.depth_material); + mq::clear_background(mq::DARKGRAY); + draw_world(&mut world, None); + mq::gl_use_default_material(); + mq::set_default_camera(); + + self.base_material + .set_texture("shadow_map", self.depth_target.texture.clone()); + } + fn render_default(&mut self, mut world: &mut World) { + mq::clear_background(mq::LIGHTGRAY); + self.camera.apply(); + + draw_world(&mut world, Some(&self.base_material)); + + mq::draw_sphere(self.light.position, 0.25, None, mq::WHITE); + + mq::draw_grid(20, 1., mq::BLACK, mq::GRAY); + mq::gl_use_default_material(); + mq::set_default_camera(); + } + fn render_ui(&mut self) { + mq::set_default_camera(); + mq::draw_texture_ex( + &self.depth_target.texture, + 0., + 0., + mq::WHITE, + mq::DrawTextureParams { + flip_y: true, + dest_size: Some(mq::vec2(200.0, 200.0)), + ..Default::default() + }, + ); + } + pub fn draw(&mut self, mut world: &mut World) { + if ENABLE_SHADOWS { + self.render_depth(&mut world); + self.has_light_moved = false; + } + + self.render_default(&mut world); + + self.render_ui(); + } +} + +fn draw_world(world: &mut World, material: Option<&mq::Material>) { + for (handle, coll) in world.colliders.iter() { + let position: glam::Vec3 = world.position_of_collider(handle).unwrap().into(); + let rotation = *coll.rotation(); + unsafe { + let context = mq::get_internal_gl().quad_gl; + + context.push_model_matrix(mq::Mat4::from_rotation_translation( + rotation.into(), + position, + )); + } + let color = world.collider_data.get(&handle).unwrap().color; + match coll.shape().shape_type() { + ShapeType::Ball => { + mq::draw_sphere( + mq::vec3(0.0, 0.0, 0.0), + coll.shape().as_ball().unwrap().radius, + None, + color, + ); + } + ShapeType::Cuboid => { + match material { + Some(mat) => { + mq::gl_use_material(&mat); + } + None => {} + } + draw_cuboid( + mq::vec3(0.0, 0.0, 0.0), + (coll.shape().as_cuboid().unwrap().half_extents * 2.0).into(), + color, + ); + match material { + Some(_) => { + mq::gl_use_default_material(); + } + None => {} + } + } + _ => println!("Not implemented.. Skipping"), + } + unsafe { + let context = mq::get_internal_gl().quad_gl; + context.pop_model_matrix(); + } + } +} diff --git a/src/shaders/depth.frag b/src/shaders/depth.frag new file mode 100644 index 0000000..9b8e8a9 --- /dev/null +++ b/src/shaders/depth.frag @@ -0,0 +1,11 @@ + +#version 330 core + +in float dist_to_cam; + +out vec4 FragColor; + +void main() { + float c = gl_FragCoord.z * gl_FragCoord.w; + FragColor = vec4(c, c, c, c); +} diff --git a/src/shaders/depth.vert b/src/shaders/depth.vert new file mode 100644 index 0000000..690da03 --- /dev/null +++ b/src/shaders/depth.vert @@ -0,0 +1,11 @@ +#version 330 core +attribute vec3 position; +attribute vec3 normal; // Ensure this attribute exists! + +uniform mat4 Model; +uniform mat4 light_space_matrix; // This will be the Light Space Matrix (LSM) + +void main() { + vec3 pos = position; + gl_Position = light_space_matrix * Model * vec4(pos, 1.0); +} diff --git a/src/shaders/shader.frag b/src/shaders/shader.frag index cb6e50d..b65b56d 100644 --- a/src/shaders/shader.frag +++ b/src/shaders/shader.frag @@ -1,22 +1,86 @@ - #version 330 core in vec4 v_normal; in vec4 color; in vec3 light_direction; +in vec4 v_light_space_position; // New: Position transformed by LightSpaceMatrix + uniform int render_normals_bool; +uniform int render_shadows_bool; +uniform sampler2D shadow_map; // New: Depth map texture out vec4 FragColor; const float ambient_strenght = 0.3; const float diffuse_strenght = 0.7; +/** + * Calculates the shadow factor (0.0 for shadow, 1.0 for lit). + */ +float calculate_shadow() { + float min_bias = 0.003; + float max_bias = 0.01; + float dot_product = dot(normalize(v_normal.xyz), normalize(light_direction)); + float bias = max(max_bias * (1.0 - dot_product), min_bias); + + // float bias = 0; + + // 1. Perspective divide to get Normalized Device Coordinates (NDC) + vec3 proj_coords = v_light_space_position.xyz / v_light_space_position.w; + + // 2. Transform to texture coordinates (0 to 1 range) + vec2 tex_coords = proj_coords.xy * 0.5 + 0.5; + + // 3. Get the closest depth from the light's perspective (shadow map) + // We sample the red channel since Macroquad's depth texture stores depth data there. + float closest_depth = texture(shadow_map, tex_coords).r; + + // 4. Get the current fragment's depth from the light's perspective + // float current_depth = proj_coords.z; + float current_depth = proj_coords.z * 0.5 + 0.5; + // float current_depth = v_light_space_position.z; + // 5. Check if the fragment is outside the light frustum + if (tex_coords.x > 1.0 || tex_coords.x < 0.0 || tex_coords.y > 1.0 || tex_coords.y < 0.0 || current_depth > 1.0) { + return 1.0; + } + + // float shadow = 0.0; + // vec2 texelSize = 1.0 / textureSize(shadow_map, 0); + // int x_ran = 1; + // for (int x = -x_ran; x <= x_ran; x++) + // { + // for (int y = -x_ran; y <= x_ran; y++) + // { + // float pcfDepth = texture(shadow_map, tex_coords + vec2(x, y) * texelSize).x; + // shadow += current_depth - bias > pcfDepth ? 0.0 : 1.0; + // } + // } + // shadow = current_depth - bias > closest_depth ? 0.0 : 1.0; + + // shadow /= ((2 * x_ran) + 1) * ((2 * x_ran) + 1); + + // 6. Compare depths with a bias: if current depth > closest depth, we are in shadow (0.0) + float shadow = current_depth - bias > closest_depth ? 0.0 : 1.0; + + return min(max(shadow, 0), 1); +} + void main() { float diff = max(dot(normalize(vec3(v_normal.x, v_normal.y, v_normal.z)), normalize(light_direction)), 0) * diffuse_strenght; + if (render_normals_bool == 1) { FragColor = vec4(v_normal); + return; } - else { - FragColor = color * (1 / 256.0) * (ambient_strenght + diff); + if (render_shadows_bool == 1) { + float lighting = ambient_strenght + diff; + FragColor = normalize(v_normal) * lighting; + return; } + + float shadow_factor = calculate_shadow(); + + float lighting = ambient_strenght + diff * shadow_factor; + + FragColor = normalize(abs(v_normal)) * lighting; } diff --git a/src/shaders/shader.vert b/src/shaders/shader.vert index a37a54c..3c9f8d9 100644 --- a/src/shaders/shader.vert +++ b/src/shaders/shader.vert @@ -1,4 +1,3 @@ - #version 330 core attribute vec3 position; @@ -7,14 +6,25 @@ attribute vec4 normal; uniform mat4 Model; uniform mat4 Projection; +uniform mat4 light_space_matrix; // New uniform for the light's view-projection matrix +uniform vec3 light_position; out vec4 v_normal; out vec4 color; out vec3 light_direction; +out vec4 v_light_space_position; // New: Output for shadow calculation + void main() { - vec4 light_dir = vec4(1.0, 1.0, 0.0, 0.0) * Model; - light_direction = vec3(light_dir.x, light_dir.y, light_dir.z); + vec4 world_poisition = Model * vec4(position, 1.0); + vec3 direction_to_light = light_position - world_poisition.xyz; + + // Transform the fragment position into the light's clip space + v_light_space_position = light_space_matrix * world_poisition; + + // Original lighting setup + light_direction = normalize(direction_to_light); + color = color0; - v_normal = normal; + v_normal = Model * normal; gl_Position = Projection * Model * vec4(position, 1.0); }