From 4d993e2667288a201db0824f755feb60a91eb1f7 Mon Sep 17 00:00:00 2001 From: Jeroen <10060956+JeroenHoogers@users.noreply.github.com> Date: Mon, 16 Mar 2026 19:34:16 +0100 Subject: [PATCH] fixed cluster sampling issues for volumetric point and spot lights, fixed raymarching sampling issue when camera is outside the fogvolume --- .../src/volumetric_fog/volumetric_fog.wgsl | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl b/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl index 41018555bc666..4b0b0e46b3e74 100644 --- a/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl +++ b/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl @@ -254,7 +254,7 @@ fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { // Calculate where we are in the ray. let P_world = Ro_world + Rd_world * f32(step) * step_size_world; - let P_view = Rd_view * f32(step) * step_size_world; + let P_view = view_start_pos + Rd_view * f32(step) * step_size_world; var density = density_factor; #ifdef DENSITY_TEXTURE @@ -338,33 +338,35 @@ fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { // Point lights and Spot lights let view_z = view_start_pos.z; let is_orthographic = view.clip_from_view[3].w == 1.0; - let cluster_index = clustering::view_fragment_cluster_index(frag_coord.xy, view_z, is_orthographic); - var clusterable_object_index_ranges = - clustering::unpack_clusterable_object_index_ranges(cluster_index); - for (var i: u32 = clusterable_object_index_ranges.first_point_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); - let light = &clustered_lights.data[light_id]; - if (((*light).flags & POINT_LIGHT_FLAGS_VOLUMETRIC_BIT) == 0) { - continue; - } - // Reset `background_alpha` for a new raymarch. - background_alpha = 1.0; + // Reset `background_alpha` for a new raymarch. + background_alpha = 1.0; - // Start raymarching. - for (var step = 0u; step < step_count; step += 1u) { - // As an optimization, break if we've gotten too dark. - if (background_alpha < 0.001) { - break; - } + // Start raymarching. + for (var step = 0u; step < step_count; step += 1u) { + // As an optimization, break if we've gotten too dark. + if (background_alpha < 0.001) { + break; + } - // Calculate where we are in the ray. - let P_world = Ro_world + Rd_world * f32(step) * step_size_world; - let P_view = Rd_view * f32(step) * step_size_world; + // Calculate where we are in the ray. + let P_world = Ro_world + Rd_world * f32(step) * step_size_world; + let P_view = view_start_pos + Rd_view * f32(step) * step_size_world; - var density = density_factor; + var density = density_factor; + var sample_color = vec3(0.0); + + let cluster_index = clustering::view_fragment_cluster_index(frag_coord.xy, P_view.z, is_orthographic); + var clusterable_object_index_ranges = clustering::unpack_clusterable_object_index_ranges(cluster_index); + for (var i: u32 = clusterable_object_index_ranges.first_point_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); + let light = &clustered_lights.data[light_id]; + if (((*light).flags & POINT_LIGHT_FLAGS_VOLUMETRIC_BIT) == 0) { + continue; + } let light_to_frag = (*light).position_radius.xyz - P_world; let V = Rd_world; @@ -386,7 +388,6 @@ fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { if ((*light).flags & POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u { spot_dir.y = -spot_dir.y; } - let light_to_frag = (*light).position_radius.xyz - P_world; // calculate attenuation based on filament formula https://google.github.io/filament/Filament.html#listing_glslpunctuallight // spot_scale and spot_offset have been precomputed @@ -402,13 +403,6 @@ fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { local_light_attenuation *= spot_attenuation * shadow; } - // Calculate absorption (amount of light absorbed by the fog) and - // out-scattering (amount of light the fog scattered away). - let sample_attenuation = exp(-step_size_world * density * (absorption + scattering)); - - // Process absorption and out-scattering. - background_alpha *= sample_attenuation; - let light_attenuation = exp(-density * bounding_radius * (absorption + scattering)); let light_factors_per_step = fog_color * light_tint * light_attenuation * scattering * density * step_size_world * light_intensity * exposure; @@ -418,9 +412,16 @@ fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { let light_color_per_step = (*light).color_inverse_square_range.rgb * light_factors_per_step; // Accumulate the light. - accumulated_color += light_color_per_step * local_light_attenuation * - background_alpha; + sample_color += light_color_per_step * local_light_attenuation; } + + // Calculate absorption (amount of light absorbed by the fog) and + // out-scattering (amount of light the fog scattered away). + let sample_attenuation = exp(-step_size_world * density * (absorption + scattering)); + + // Process absorption and out-scattering. + background_alpha *= sample_attenuation; + accumulated_color += sample_color * background_alpha; } // We're done! Return the color with alpha so it can be blended onto the @@ -444,7 +445,7 @@ fn fetch_point_shadow_without_normal(light_id: u32, frag_position: vec4, fr let offset_position = frag_position.xyz + 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));