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..2f43066f76264 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 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]; // 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));