render: rewrite PCF for spot lights to somehow fix PCSS directional lights
CI / build (pull_request) Successful in 9m48s Details

This commit is contained in:
SeanOMik 2024-08-09 22:01:57 -04:00
parent a85178eeea
commit 8545e7e27d
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
4 changed files with 206 additions and 34 deletions

View File

@ -8,7 +8,7 @@ use lyra_engine::{
math::{self, Quat, Transform, Vec3}, math::{self, Quat, Transform, Vec3},
render::{ render::{
graph::{ShadowCasterSettings, ShadowFilteringMode}, graph::{ShadowCasterSettings, ShadowFilteringMode},
light::{directional::DirectionalLight, PointLight}, light::{directional::DirectionalLight, PointLight, SpotLight},
}, },
scene::{ scene::{
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
@ -184,12 +184,15 @@ fn setup_scene_plugin(game: &mut Game) {
}, },
ShadowCasterSettings { ShadowCasterSettings {
filtering_mode: ShadowFilteringMode::Pcss, filtering_mode: ShadowFilteringMode::Pcss,
pcf_samples_num: 64,
pcss_blocker_search_samples: 36,
constant_depth_bias_scale: 5.0,
..Default::default() ..Default::default()
}, },
light_tran, light_tran,
)); ));
world.spawn(( /* world.spawn((
cube_mesh.clone(), cube_mesh.clone(),
PointLight { PointLight {
enabled: true, enabled: true,
@ -207,31 +210,40 @@ fn setup_scene_plugin(game: &mut Game) {
Quat::IDENTITY, Quat::IDENTITY,
Vec3::new(0.5, 0.5, 0.5), Vec3::new(0.5, 0.5, 0.5),
), ),
)); )); */
/* world.spawn(( let t = Transform::new(
//cube_mesh.clone(), Vec3::new(4.0 - 1.43, -13.0, 0.0),
PointLight { //Vec3::new(-5.0, 1.0, -0.28),
//Vec3::new(-10.0, 0.94, -0.28),
Quat::from_euler(math::EulerRot::XYZ, 0.0, math::Angle::Degrees(-45.0).to_radians(), 0.0),
Vec3::new(0.15, 0.15, 0.15),
);
world.spawn((
SpotLight {
enabled: true, enabled: true,
color: Vec3::new(0.278, 0.984, 0.0), color: Vec3::new(1.0, 0.0, 0.0),
intensity: 2.0, intensity: 3.0,
range: 9.0, range: 4.5,
//cutoff: math::Angle::Degrees(45.0),
..Default::default() ..Default::default()
}, },
Transform::from_xyz(-0.5, 2.0, -5.0), /* ShadowCasterSettings {
)); */ filtering_mode: ShadowFilteringMode::Pcf,
..Default::default()
}, */
WorldTransform::from(t),
t,
//cube_mesh.clone(),
));
} }
let mut camera = CameraComponent::new_3d(); let mut camera = CameraComponent::new_3d();
//camera.transform.translation += math::Vec3::new(0.0, 2.0, 10.5); camera.transform.translation = math::Vec3::new(-1.0, -10.0, -1.5);
/* camera.transform.translation = math::Vec3::new(-3.0, -8.0, -3.0);
camera.transform.rotate_x(math::Angle::Degrees(-27.0)); camera.transform.rotate_x(math::Angle::Degrees(-27.0));
camera.transform.rotate_y(math::Angle::Degrees(-55.0)); */ camera.transform.rotate_y(math::Angle::Degrees(-90.0));
camera.transform.translation = math::Vec3::new(15.0, -8.0, 1.0);
camera.transform.rotate_x(math::Angle::Degrees(-27.0));
//camera.transform.rotate_y(math::Angle::Degrees(-90.0));
camera.transform.rotate_y(math::Angle::Degrees(90.0));
world.spawn((camera, FreeFlyCamera::default())); world.spawn((camera, FreeFlyCamera::default()));
} }

View File

@ -20,7 +20,7 @@ use wgpu::util::DeviceExt;
use crate::render::{ use crate::render::{
graph::{Node, NodeDesc, NodeType, SlotAttribute, SlotValue}, graph::{Node, NodeDesc, NodeType, SlotAttribute, SlotValue},
light::{directional::DirectionalLight, LightType, PointLight}, light::{directional::DirectionalLight, LightType, PointLight, SpotLight},
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState},
transform_buffer_storage::TransformBuffers, transform_buffer_storage::TransformBuffers,
vertex::Vertex, vertex::Vertex,
@ -186,6 +186,7 @@ impl ShadowMapsPass {
light_type: LightType, light_type: LightType,
entity: Entity, entity: Entity,
light_pos: Transform, light_pos: Transform,
light_half_outer_angle: Option<Angle>,
are_settings_custom: bool, are_settings_custom: bool,
shadow_settings: ShadowCasterSettings, shadow_settings: ShadowCasterSettings,
) -> LightDepthMap { ) -> LightDepthMap {
@ -265,7 +266,58 @@ impl ShadowMapsPass {
indices[0] = uniform_index; indices[0] = uniform_index;
(atlas_index, indices) (atlas_index, indices)
} }
LightType::Spotlight => todo!(), LightType::Spotlight => {
let directional_size = SHADOW_SIZE * 4;
// directional lights require a single map, so allocate that in the atlas.
let atlas_index = atlas
.pack(directional_size.x as _, directional_size.y as _)
.expect("failed to pack new shadow map into texture atlas");
let atlas_frame = atlas.texture_frame(atlas_index).expect("Frame missing");
let aspect = SHADOW_SIZE.x as f32 / SHADOW_SIZE.y as f32;
let projection = glam::Mat4::perspective_rh(
(light_half_outer_angle.unwrap() * 2.0).to_radians(),
aspect,
shadow_settings.near_plane,
shadow_settings.far_plane,
);
// honestly no clue why this works, but I got it from here and the results are good
// https://github.com/asylum2010/Asylum_Tutorials/blob/423e5edfaee7b5ea450a450e65f2eabf641b2482/ShaderTutors/43_ShadowMapFiltering/main.cpp#L323
/* let frustum_size = Vec2::new(0.5 * projection.col(0).x, 0.5 * projection.col(1).y);
// maybe its better to make this a vec2 on the gpu?
let size_avg = (frustum_size.x + frustum_size.y) / 2.0;
let light_size_uv = 0.2 * size_avg; */
let light_trans = light_pos.translation;
let look_view = glam::Mat4::look_at_rh(
light_trans,
light_trans + glam::vec3(1.0, 0.0, 0.0),
glam::vec3(0.0, -1.0, 0.0),
);
let light_proj = projection * look_view;
let u = LightShadowUniform {
space_mat: light_proj,
atlas_frame,
near_plane: shadow_settings.near_plane,
far_plane: shadow_settings.far_plane,
light_size_uv: 0.0,
_padding1: 0,
light_pos: light_pos.translation,
has_shadow_settings,
pcf_samples_num: u.pcf_samples_num,
pcss_blocker_search_samples: u.pcss_blocker_search_samples,
constant_depth_bias: DEFAULT_CONSTANT_DEPTH_BIAS * shadow_settings.constant_depth_bias_scale,
_padding2: 0,
};
let uniform_index = self.light_uniforms_buffer.insert(queue, &u);
let mut indices = [0; 6];
indices[0] = uniform_index;
(atlas_index, indices)
},
LightType::Point => { LightType::Point => {
let aspect = SHADOW_SIZE.x as f32 / SHADOW_SIZE.y as f32; let aspect = SHADOW_SIZE.x as f32 / SHADOW_SIZE.y as f32;
let projection = glam::Mat4::perspective_rh( let projection = glam::Mat4::perspective_rh(
@ -626,6 +678,7 @@ impl Node for ShadowMapsPass {
LightType::Directional, LightType::Directional,
entity, entity,
*pos, *pos,
None,
custom_settings, custom_settings,
shadow_settings, shadow_settings,
); );
@ -649,6 +702,31 @@ impl Node for ShadowMapsPass {
LightType::Point, LightType::Point,
entity, entity,
*pos, *pos,
None,
custom_settings,
shadow_settings,
);
index_components_queue.push_back((entity, atlas_index));
}
}
for (entity, pos, shadow_settings, spot) in world.view_iter::<(
Entities,
&Transform,
Option<&ShadowCasterSettings>,
&SpotLight,
)>() {
if !self.depth_maps.contains_key(&entity) {
let (custom_settings, shadow_settings) = shadow_settings
.map(|ss| (true, ss.clone()))
.unwrap_or((false, settings));
let atlas_index = self.create_depth_map(
&context.queue,
LightType::Spotlight,
entity,
*pos,
Some(spot.outer_cutoff),
custom_settings, custom_settings,
shadow_settings, shadow_settings,
); );
@ -807,7 +885,23 @@ impl Node for ShadowMapsPass {
); );
} }
} }
LightType::Spotlight => todo!(), LightType::Spotlight => {
pass.set_pipeline(&pipeline);
let frame = atlas
.texture_frame(light_depth_map.atlas_index)
.expect("missing atlas frame for light");
light_shadow_pass_impl(
&mut pass,
&self.uniforms_bg,
&render_meshes,
&mesh_buffers,
&transforms,
&frame,
light_depth_map.uniform_index[0] as _,
);
},
} }
} }
} }

View File

@ -190,7 +190,8 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let shadow = calc_shadow_point_light(in.world_position, in.world_normal, light_dir, light, atlas_dimensions); let shadow = calc_shadow_point_light(in.world_position, in.world_normal, light_dir, light, atlas_dimensions);
light_res += blinn_phong_point_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow); light_res += blinn_phong_point_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
} else if (light.light_ty == LIGHT_TY_SPOT) { } else if (light.light_ty == LIGHT_TY_SPOT) {
light_res += blinn_phong_spot_light(in.world_position, in.world_normal, light, u_material, specular_color); let shadow = calc_shadow_spot_light(in.world_position, in.world_normal, light_dir, light, atlas_dimensions);
light_res += blinn_phong_spot_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
} }
} }
@ -482,11 +483,62 @@ fn pcf_point_light(tex_coords: vec3<f32>, test_depth: f32, shadow_us: array<Ligh
return saturate(shadow); return saturate(shadow);
} }
/*fn pcf_point_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, samples_num: u32, uv_radius: f32) -> f32 { fn calc_shadow_spot_light(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light, atlas_dimensions: vec2<i32>) -> f32 {
let map_data: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let frag_pos_light_space = map_data.light_space_matrix * vec4<f32>(world_pos, 1.0);
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
// for some reason the y component is flipped after transforming
proj_coords.y = -proj_coords.y;
// Remap xy to [0.0, 1.0]
let xy_remapped = proj_coords.xy * 0.5 + 0.5;
// use a bias to avoid shadow acne
let current_depth = proj_coords.z - map_data.constant_depth_bias;
// get settings
let settings = get_shadow_settings(map_data);
let pcf_samples_num = settings.x;
let pcss_blocker_search_samples = settings.y;
var shadow = 0.0; var shadow = 0.0;
for (var i = 0; i < i32(samples_num); i++) { // hardware 2x2 PCF via camparison sampler
if pcf_samples_num == 2u {
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
}
// only PCF is supported for spot lights
else if pcf_samples_num > 0u {
let texel_size = 1.0 / f32(map_data.atlas_frame.width);
shadow = pcf_spot_light(xy_remapped, current_depth, map_data, i32(pcf_samples_num), texel_size);
}
// no filtering
else {
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0);
shadow = select(1.0, 0.0, current_depth > closest_depth);
}
// dont cast shadows outside the light's far plane
if (proj_coords.z > 1.0) {
shadow = 1.0;
}
// dont cast shadows if the texture coords would go past the shadow maps
if (xy_remapped.x > 1.0 || xy_remapped.x < 0.0 || xy_remapped.y > 1.0 || xy_remapped.y < 0.0) {
shadow = 1.0;
}
return shadow;
}
/// Calculate the shadow coefficient using PCF of a directional light
fn pcf_spot_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, samples_num: i32, uv_radius: f32) -> f32 {
var shadow = 0.0;
for (var i = 0; i < samples_num; i++) {
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius; let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
let new_coords = to_atlas_frame_coords(shadow_u, offset); let new_coords = to_atlas_frame_coords(shadow_u, offset, false);
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth); shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
} }
@ -494,7 +546,7 @@ fn pcf_point_light(tex_coords: vec3<f32>, test_depth: f32, shadow_us: array<Ligh
// clamp shadow to [0; 1] // clamp shadow to [0; 1]
return saturate(shadow); return saturate(shadow);
}*/ }
fn debug_grid(in: VertexOutput) -> vec4<f32> { fn debug_grid(in: VertexOutput) -> vec4<f32> {
let tile_index_float: vec2<f32> = in.clip_position.xy / 16.0; let tile_index_float: vec2<f32> = in.clip_position.xy / 16.0;
@ -574,7 +626,7 @@ fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_li
return (shadow * (ambient_color + diffuse_color + specular_color)) * point_light.intensity; return (shadow * (ambient_color + diffuse_color + specular_color)) * point_light.intensity;
} }
fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> { fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {
let light_color = spot_light.color; let light_color = spot_light.color;
let light_pos = spot_light.position; let light_pos = spot_light.position;
let camera_view_pos = u_camera.position; let camera_view_pos = u_camera.position;
@ -609,13 +661,13 @@ fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_ligh
let distance = length(light_pos - world_pos); let distance = length(light_pos - world_pos);
let attenuation = calc_attenuation(spot_light, distance); let attenuation = calc_attenuation(spot_light, distance);
ambient_color *= attenuation * spot_light.intensity * cone; ambient_color *= attenuation * cone;
diffuse_color *= attenuation * spot_light.intensity * cone; diffuse_color *= attenuation * cone;
specular_color *= attenuation * spot_light.intensity * cone; specular_color *= attenuation * cone;
//// end of spot light attenuation //// //// end of spot light attenuation ////
//return /*ambient_color +*/ diffuse_color + specular_color;
return /*ambient_color +*/ diffuse_color + specular_color; return (shadow * (diffuse_color + specular_color)) * spot_light.intensity;
} }
fn calc_attenuation(light: Light, distance: f32) -> f32 { fn calc_attenuation(light: Light, distance: f32) -> f32 {

View File

@ -10,7 +10,7 @@ pub fn radians_to_degrees(radians: f32) -> f32 {
radians * 180.0 / PI radians * 180.0 / PI
} }
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Angle { pub enum Angle {
Degrees(f32), Degrees(f32),
Radians(f32), Radians(f32),
@ -69,3 +69,17 @@ impl std::ops::SubAssign for Angle {
} }
} }
} }
impl std::ops::Mul<f32> for Angle {
type Output = Angle;
fn mul(self, rhs: f32) -> Self::Output {
Angle::Radians(self.to_radians() * rhs)
}
}
impl std::ops::MulAssign<f32> for Angle {
fn mul_assign(&mut self, rhs: f32) {
*self = *self * rhs;
}
}