@@ -295,16 +295,27 @@ void main_cs(uint3 dispatch_id : SV_DispatchThreadID)
295295 history_weight = saturate (min (history_weight, 0.992f ));
296296 }
297297
298+ // small 3x3 mean+variance pre blur (schied 2017 §4.2) so isolated bright fireflies do not
299+ // pin the variance estimate to a single pixel, this stabilizes the spatial filter weights
300+ // and gives the firefly clamp below a reliable spatial sigma even when history is young
301+ float3 mean_color, sigma_color, min_color, max_color;
302+ compute_local_statistics (pixel, resolution, mean_color, sigma_color, min_color, max_color);
303+ float spatial_sigma_luma = dot (sigma_color, luminance_weights);
304+ float spatial_var_3x3 = spatial_sigma_luma * spatial_sigma_luma;
305+
298306 // pre-ema firefly soft clamp, lin 2022 §6.4 / production svgf trick
299307 // if the current sample's luma is way above the history band, soft-rescale it down before
300308 // it poisons the moment update, otherwise a single hot pixel can pin the variance estimate
301- // for many frames and force the spatial filter to over-blur the neighborhood, the gate is
302- // applied only once temporal accumulation is established (n_eff > 4) so the bootstrap
303- // window stays unbiased
304- if (history_ok && history_moments.z > 4.0f )
309+ // for many frames and force the spatial filter to over-blur the neighborhood, the gate
310+ // fires as soon as we have a temporal partner so disocclusion fireflies do not boil for
311+ // 4 frames before the clamp kicks in, the band widens via spatial sigma when history is
312+ // young so the clamp does not over-bias on freshly accumulating pixels
313+ if (history_ok)
305314 {
306315 float history_sigma = sqrt (max (history_moments.y - history_moments.x * history_moments.x, 0.0f ));
307- float clamp_high = history_moments.x + 8.0f * max (history_sigma, 0.05f );
316+ float band_sigma = max (history_sigma, spatial_sigma_luma);
317+ float band_widen = lerp (16.0f , 6.0f , saturate (history_moments.z / 4.0f ));
318+ float clamp_high = history_moments.x + band_widen * max (band_sigma, 0.05f );
308319 if (current_luma > clamp_high && current_luma > 1e-3f )
309320 {
310321 float scale = clamp_high / current_luma;
@@ -335,12 +346,6 @@ void main_cs(uint3 dispatch_id : SV_DispatchThreadID)
335346 variance_estimate = max (temporal_var, spatial_var * boost);
336347 }
337348
338- // small 3x3 mean+variance pre blur (schied 2017 §4.2) so isolated bright fireflies do not
339- // pin the variance estimate to a single pixel, this stabilizes the spatial filter weights
340- float3 mean_color, sigma_color, min_color, max_color;
341- compute_local_statistics (pixel, resolution, mean_color, sigma_color, min_color, max_color);
342- float spatial_sigma_luma = dot (sigma_color, luminance_weights);
343- float spatial_var_3x3 = spatial_sigma_luma * spatial_sigma_luma;
344349 variance_estimate = max (variance_estimate, spatial_var_3x3 * 0.25f );
345350
346351 // variance gated history clamp keeps chromatic stability even when the temporal accumulator
0 commit comments