asny / three-d

2D/3D renderer - makes it simple to draw stuff across platforms (including web)
MIT License
1.34k stars 110 forks source link

[bug?] `SpotLight::generate_shadow_map()` doesn't cast shadows? #503

Closed javalsai closed 5 days ago

javalsai commented 1 month ago

To be fair I'm not really familiar with the library, but to my understanding, the Light objects that implement the function generate_shadow_map should cast shadows on things when provided with the appropriate Meshs of the scene. And this is what happens at least with DirectionalLight, but doesn't work at all with SpotLight.

I was able to make this relatively simple demo for the issue:

image

demo code ```rs use three_d::*; fn main() { let window = Window::new(WindowSettings { title: "bug".to_string(), max_size: Some((1200, 700)), ..Default::default() }) .unwrap(); let ctx = window.gl(); let mut camera = Camera::new_perspective( window.viewport(), vec3(5.0, 2.0, 2.5), vec3(0.0, 0.0, -0.5), vec3(0.0, 1.0, 0.0), degrees(45.0), 0.1, 1000.0, ); let mut oc = OrbitControl::new(*camera.target(), 1.0, 10000.0); let s1_mesh = CpuMesh::sphere(16); let s2_mesh = CpuMesh::sphere(16); let mut gm1 = Gm::new( Mesh::new(&ctx, &s1_mesh), PhysicalMaterial::new_opaque( &ctx, &CpuMaterial { albedo: Srgba::RED, ..Default::default() } ), ); gm1.set_transformation(Mat4::from_translation(vec3(2.0, 0.0, 0.0))); let mut gm2 = Gm::new( Mesh::new(&ctx, &s2_mesh), PhysicalMaterial::new_opaque( &ctx, &CpuMaterial { albedo: Srgba::RED, ..Default::default() } ), ); gm2.set_transformation(Mat4::from_translation(vec3(4.0, 0.0, 0.0))); let mut light = SpotLight::new( &ctx, 10.0, Srgba::WHITE, &Vector3::zero(), &vec3(1.0, 0.0, 0.0), degrees(179.9999), Attenuation::default(), ); light.generate_shadow_map(1024, &[&gm1.geometry, &gm2.geometry]); let mut dlight = DirectionalLight::new( &ctx, 1.0, Srgba::WHITE, &vec3(-1.0, 0.0, 0.0) ); dlight.generate_shadow_map(1024, &[&gm1.geometry, &gm2.geometry]); window.render_loop(move |mut frame_input| { camera.set_viewport(frame_input.viewport); oc.handle_events(&mut camera, &mut frame_input.events); frame_input .screen() .clear(ClearState::color_and_depth(42.0 / 255.0, 42.0 / 255.0, 42.0 / 255.0, 1.0, 1.0)) .render(&camera, gm1.into_iter().chain(&gm2), &[&light, &dlight]); FrameOutput::default() }); } ```

I would expect the spotlight to cast a shadow on the right element as it's covered by the left sphere, just like what happens with the directional light from the other side.


Also unrelated, but a few quick thoughts on the shadows thing. They should have a common trait, or implement it in the Light trait, but I don't expect lights like ambient light to have the shadow method. And PointLight should implement shadows too, imo it's just like a spotlight but with a full coverage angle, so there's less variables to mind in the calculation.

javalsai commented 1 month ago

461 could be related to this?

asny commented 5 days ago

degrees(179.9999),

You defined a spotlight with a cutoff at almost 180 degrees, that means the shadow map needs to cover an enormous area and therefore no pixel in the shadow map will actually hit the relatively small sphere. If you change the angle to something more realistic (a spotlight is usually something like a flashlight), then it's working as expected.

Also unrelated, but a few quick thoughts on the shadows thing. They should have a common trait, or implement it in the Light trait, but I don't expect lights like ambient light to have the shadow method.

What is the exact reason why it should be part of a trait? 🤔

And PointLight should implement shadows too, imo it's just like a spotlight but with a full coverage angle, so there's less variables to mind in the calculation.

It's already an issue: https://github.com/asny/three-d/issues/74. However, it's quite expensive for point lights since it requires a shadow map in all directions (a cube map instead of a 2D texture). So it's not used a lot in practice in my experience.

In general, shadow maps are really nice but definitely have their limitations due to performance. Therefore, it's not just plug-and-play, you need to understand the technique before being able to use it effectively.

javalsai commented 5 days ago

You defined a spotlight with a cutoff at almost 180 degrees, that means the shadow map needs to cover an enormous area and therefore no pixel in the shadow map will actually hit the relatively small sphere. If you change the angle to something more realistic (a spotlight is usually something like a flashlight), then it's working as expected.

Well yes, playing around found the cutoff to be somewhere around 179.72-179.73, high enough for any normal use, I was using such angles to try to get a pointlight kind of light but with shadows, guess I wont.

What is the exact reason why it should be part of a trait?

Its just some shared behavior, I simply ran into an issue in my project where I had to take a generic light that implements shadows, so I ended up making my custom trait for it and implementing it for each light element. I just think it makes sense to make it a trait in the library directly.

In general, shadow maps are really nice but definitely have their limitations due to performance. Therefore, it's not just plug-and-play, you need to understand the technique before being able to use it effectively.

Tbh idk much about shadow maps, I just get the basic idea of how they work and using them to make my project look a little nicer, nothing performance critical. But I think they are a little too complex for me, might look into it to optimize things at some point, but it's definitely not important for what I'm doing.

Thanks for the assistance though!

asny commented 2 days ago

Well yes, playing around found the cutoff to be somewhere around 179.72-179.73, high enough for any normal use, I was using such angles to try to get a pointlight kind of light but with shadows, guess I wont.

Well you might be able to if you use 6 spot lights with an angle of 90 degrees. That's basically the point light shadow map setup.

Its just some shared behavior, I simply ran into an issue in my project where I had to take a generic light that implements shadows, so I ended up making my custom trait for it and implementing it for each light element. I just think it makes sense to make it a trait in the library directly.

I'll consider it 👍

Thanks for the assistance though!

No problem 🙂