From 7568d1b585f9d4622ea19f5fe91cb7a597d970a4 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 22 Jan 2025 18:11:16 -0800 Subject: [PATCH 1/2] Combine the shader point and spot light lookup into one loop. Point and spot lights are treated similarly. In fact, the point light calculations are part of the spot light calculations. So we can simplify the code by unifying the two loops. This might make life easier for the shader compiler too. --- crates/bevy_pbr/src/render/pbr_functions.wgsl | 98 +++++++------------ crates/bevy_pbr/src/render/pbr_lighting.wgsl | 19 ++-- crates/bevy_pbr/src/render/shadows.wgsl | 2 +- 3 files changed, 47 insertions(+), 72 deletions(-) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 04b52f8780072..aa77b9b38a351 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -400,9 +400,9 @@ fn apply_pbr_lighting( var clusterable_object_index_ranges = clustering::unpack_clusterable_object_index_ranges(cluster_index); - // Point lights (direct) + // Point lights & spot lights (direct) for (var i: u32 = clusterable_object_index_ranges.first_point_light_index_offset; - i < clusterable_object_index_ranges.first_spot_light_index_offset; + i < clusterable_object_index_ranges.first_reflection_probe_index_offset; i = i + 1u) { let light_id = clustering::get_clusterable_object_id(i); @@ -416,13 +416,31 @@ fn apply_pbr_lighting( let enable_diffuse = true; #endif // LIGHTMAP + // Point lights precede spot lights in the list. + let is_spot_light = i >= clusterable_object_index_ranges.first_spot_light_index_offset; + var shadow: f32 = 1.0; if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - shadow = shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal); + if (is_spot_light) { + shadow = shadows::fetch_spot_shadow( + light_id, + in.world_position, + in.world_normal, + view_bindings::clusterable_objects.data[light_id].shadow_map_near_z + ); + } else { + shadow = shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal); + } + } + + var light_contrib = lighting::point_light(light_id, &lighting_input, enable_diffuse); + + if (is_spot_light) { + // Reuse the point light calculations. + light_contrib *= lighting::spot_light(light_id, &lighting_input); } - let light_contrib = lighting::point_light(light_id, &lighting_input, enable_diffuse); direct_light += light_contrib * shadow; #ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION @@ -438,71 +456,31 @@ fn apply_pbr_lighting( var transmitted_shadow: f32 = 1.0; if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - transmitted_shadow = shadows::fetch_point_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal); + if (is_spot_light) { + transmitted_shadow = shadows::fetch_spot_shadow( + light_id, + diffuse_transmissive_lobe_world_position, + -in.world_normal, + view_bindings::clusterable_objects.data[light_id].shadow_map_near_z, + ); + } else { + transmitted_shadow = shadows::fetch_point_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal); + } } - let transmitted_light_contrib = + var transmitted_light_contrib = lighting::point_light(light_id, &transmissive_lighting_input, enable_diffuse); - transmitted_light += transmitted_light_contrib * transmitted_shadow; -#endif - } - - // Spot lights (direct) - for (var i: u32 = clusterable_object_index_ranges.first_spot_light_index_offset; - i < clusterable_object_index_ranges.first_reflection_probe_index_offset; - i = i + 1u) { - let light_id = clustering::get_clusterable_object_id(i); - - // If we're lightmapped, disable diffuse contribution from the light if - // requested, to avoid double-counting light. -#ifdef LIGHTMAP - let enable_diffuse = - (view_bindings::clusterable_objects.data[light_id].flags & - mesh_view_types::POINT_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT) != 0u; -#else // LIGHTMAP - let enable_diffuse = true; -#endif // LIGHTMAP - var shadow: f32 = 1.0; - if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (view_bindings::clusterable_objects.data[light_id].flags & - mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - shadow = shadows::fetch_spot_shadow( + if (is_spot_light) { + // Reuse the point light calculations. + transmitted_light_contrib *= lighting::spot_light( light_id, - in.world_position, - in.world_normal, - view_bindings::clusterable_objects.data[light_id].shadow_map_near_z, + &transmissive_lighting_input ); } - let light_contrib = lighting::spot_light(light_id, &lighting_input, enable_diffuse); - direct_light += light_contrib * shadow; - -#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION - // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated - // world position, inverted normal and view vectors, and the following simplified - // values for a fully diffuse transmitted light contribution approximation: - // - // roughness = 1.0; - // NdotV = 1.0; - // R = vec3(0.0) // doesn't really matter - // F_ab = vec2(0.1) - // F0 = vec3(0.0) - var transmitted_shadow: f32 = 1.0; - if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) - && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - transmitted_shadow = shadows::fetch_spot_shadow( - light_id, - diffuse_transmissive_lobe_world_position, - -in.world_normal, - view_bindings::clusterable_objects.data[light_id].shadow_map_near_z, - ); - } - - let transmitted_light_contrib = - lighting::spot_light(light_id, &transmissive_lighting_input, enable_diffuse); transmitted_light += transmitted_light_contrib * transmitted_shadow; -#endif +#endif // STANDARD_MATERIAL_DIFFUSE_TRANSMISSION } // directional lights (direct) diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 4497b567e9ff8..4eef7b59dc8dc 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -543,14 +543,13 @@ fn point_light( (rangeAttenuation * derived_input.NdotL); } -fn spot_light( - light_id: u32, - input: ptr, - enable_diffuse: bool -) -> vec3 { - // reuse the point light calculations - let point_light = point_light(light_id, input, enable_diffuse); - +// Returns the additional attenuation that should be applied to the given spot +// light to give it the spot shape. +// +// The resulting value should be multiplied by the luminance that results from +// treating the spot light as thought it were a point light (i.e. the results of +// calling `point_light`). +fn spot_light(light_id: u32, input: ptr) -> f32 { let light = &view_bindings::clusterable_objects.data[light_id]; // reconstruct spot dir from x/z and y-direction flag @@ -566,9 +565,7 @@ fn spot_light( // note we normalize here to get "l" from the filament listing. spot_dir is already normalized let cd = dot(-spot_dir, normalize(light_to_frag)); let attenuation = saturate(cd * (*light).light_custom_data.z + (*light).light_custom_data.w); - let spot_attenuation = attenuation * attenuation; - - return point_light * spot_attenuation; + return attenuation * attenuation; } fn directional_light( diff --git a/crates/bevy_pbr/src/render/shadows.wgsl b/crates/bevy_pbr/src/render/shadows.wgsl index 0e539f00091c5..1623c76a9c466 100644 --- a/crates/bevy_pbr/src/render/shadows.wgsl +++ b/crates/bevy_pbr/src/render/shadows.wgsl @@ -33,7 +33,7 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: v let offset_position = frag_position.xyz + normal_offset + depth_offset; // similar largest-absolute-axis trick as above, but now with the offset fragment position - let frag_ls = offset_position.xyz - (*light).position_radius.xyz ; + let frag_ls = offset_position.xyz - (*light).position_radius.xyz; let abs_position_ls = abs(frag_ls); let major_axis_magnitude = max(abs_position_ls.x, max(abs_position_ls.y, abs_position_ls.z)); From d2f9c523ade1a3b87841a21b85bf58cc41c7b6f2 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 23 Jan 2025 07:51:08 -0800 Subject: [PATCH 2/2] Update crates/bevy_pbr/src/render/pbr_lighting.wgsl Co-authored-by: Anselmo Sampietro --- crates/bevy_pbr/src/render/pbr_lighting.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 4eef7b59dc8dc..2f43066f76264 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -547,7 +547,7 @@ fn point_light( // light to give it the spot shape. // // The resulting value should be multiplied by the luminance that results from -// treating the spot light as thought it were a point light (i.e. the results of +// treating the spot light as though it were a point light (i.e. the results of // calling `point_light`). fn spot_light(light_id: u32, input: ptr) -> f32 { let light = &view_bindings::clusterable_objects.data[light_id];