1import bevy_core_pipeline::tonemapping::tonemapping_luminance as luminance; 2import bevy_pbr::render::utils::{rand_f, rand_range_u}; 3import bevy_render::maths::PI; 4import bevy_render::utils::octahedral_encode; 5import package::scene::brdf::{brdf_pdf, evaluate_and_sample_brdf, evaluate_brdf, EvaluateAndSampleBrdfResult, F_AB}; 6import package::realtime::presample_light_tiles::unpack_resolved_light_sample; 7import package::realtime::bindings::{empty_reservoir, light_tile_resolved_samples, light_tile_samples, Reservoir, constants, view}; 8import package::scene::sampling::{calculate_resolved_light_contribution, isinf, LightSample, NULL_LIGHT_ID, power_heuristic, trace_visibility, sample_environment_map_light}; 9import package::scene::bindings::{light_sources, RAY_T_MAX, RAY_T_MIN, resolve_ray_hit_full, ResolvedMaterial, ResolvedRayHitFull, trace_ray}; 10import package::realtime::world_cache_query::{get_cell_size, query_world_cache, WORLD_CACHE_CELL_LIFETIME}; 12@if(DLSS_RR_GUIDE_BUFFERS) 13import package::realtime::resolve_dlss_rr_textures::{is_delta_mirror, primary_surface_glossy, psr_bounce_reflectance, psr_chain_mirror, psr_environment_miss, psr_init, replace_primary_surface}; 17const RECONNECTION_FOOTPRINT_KAPPA = 0.02; 18const RECONNECTION_ROUGHNESS_MIN = 0.6; 19const RECONNECTION_RELAX_DISTANCE = 1.0; 21const CACHE_TERMINATION_MIN_SOLID_ANGLE = PI; 28struct InitialSamplingResult { 29 @if(RESTIR) reservoir: Reservoir, 36 ray_origin: vec3<f32>, 39 material: ResolvedMaterial, 41 throughput_past_first_hit: vec3<f32>, 43 @if(RESTIR) x2_position: vec3<f32>, 44 @if(RESTIR) x2_normal: vec3<f32>, 47 @if(RESTIR) x2_reusable: bool, 49 @if(RESTIR) x1_brdf: vec3<f32>, 52 @if(RESTIR) reservoir: Reservoir, 53 @if(RESTIR) weight_sum: f32, 54 @if(RESTIR) selected_target_function: f32, 57fn new_path_state(world_position: vec3<f32>, world_normal: vec3<f32>, wo: vec3<f32>, material: ResolvedMaterial) -> PathState { 59 path.ray_origin = world_position + (world_normal * RAY_T_MIN); 60 path.normal = world_normal; 62 path.material = material; 63 path.throughput_past_first_hit = vec3(1.0); 64 path.radiance = vec3(0.0); 66 path.x1_brdf = vec3(0.0); 67 path.x2_position = vec3(0.0); 68 path.x2_normal = vec3(0.0); 69 path.x2_reusable = false; 70 path.reservoir = empty_reservoir(); 71 path.reservoir.confidence_weight = 1.0; 72 path.weight_sum = 0.0; 73 path.selected_target_function = 0.0; 78fn generate_initial_path(world_position: vec3<f32>, world_normal: vec3<f32>, material: ResolvedMaterial, workgroup_id: vec2<u32>, pixel_id: vec2<u32>, rng: ptr<function, u32>) -> InitialSamplingResult { 79 let wo = normalize(view.world_position - world_position); 80 let primary_NdotV = max(dot(world_normal, wo), 0.0001); 81 let primary_F_ab = F_AB(material.perceptual_roughness, primary_NdotV); 83 var path = new_path_state(world_position, world_normal, wo, material); 85 @if(DLSS_RR_GUIDE_BUFFERS) 86 var psr = psr_init(world_normal, material); 88 for (var bounce = 0u; bounce < constants.max_bounces; bounce++) { 89 let NdotV = max(dot(path.normal, path.wo), 0.0001); 90 let F_ab = F_AB(path.material.perceptual_roughness, NdotV); 95 let p_nee = mix(1.0, path.material.perceptual_roughness, path.material.metallic); 96 let di_samples = select(constants.secondary_di_samples, constants.primary_di_samples, bounce == 0u); 97 generate_nee_candidate(&path, F_ab, p_nee, di_samples, workgroup_id, bounce, rng); 100 let next_bounce = evaluate_and_sample_brdf(path.wo, path.normal, path.material, F_ab, rng); 101 if next_bounce.pdf == 0.0 { break; } 102 let ray = trace_ray(path.ray_origin, next_bounce.wi, RAY_T_MIN, RAY_T_MAX, RAY_FLAG_NONE); 104 if ray.kind == RAY_QUERY_INTERSECTION_NONE { 105 @if(DLSS_RR_GUIDE_BUFFERS) 106 if !psr.finished && !next_bounce.diffuse_selected { 107 psr_environment_miss(pixel_id, wo); 110 let environment_map_light_radiance = sample_environment_map_light(next_bounce.wi); 112 generate_environment_map_light_candidate(&path, next_bounce, environment_map_light_radiance, primary_F_ab, bounce, rng); 114 path.radiance += path.throughput_past_first_hit * next_bounce.throughput * environment_map_light_radiance; 120 let ray_hit = resolve_ray_hit_full(ray); 121 let p_brdf = next_bounce.pdf; 123 @if(DLSS_RR_GUIDE_BUFFERS) 125 if next_bounce.diffuse_selected { 129 psr.throughput *= psr_bounce_reflectance(path.wo, next_bounce.wi, path.normal, path.material, F_ab, isinf(p_brdf)); 130 psr.reflection_length += ray.t; 132 if !primary_surface_glossy(material) && is_delta_mirror(ray_hit.material) { 134 psr = psr_chain_mirror(psr, ray_hit.world_normal); 137 replace_primary_surface(pixel_id, psr, ray_hit, material, world_position, world_normal, wo, primary_F_ab); 145 path.x2_position = ray_hit.world_position; 146 path.x2_normal = ray_hit.world_normal; 148 path.x1_brdf = evaluate_brdf(wo, next_bounce.wi, world_normal, material, primary_F_ab); 150 path.x2_reusable = reconnection_reusable(ray.t, p_brdf, next_bounce.wi, next_bounce.diffuse_selected, ray_hit, world_position, material.perceptual_roughness, primary_NdotV); 155 path.throughput_past_first_hit *= next_bounce.throughput / max(path.x1_brdf, vec3(0.0001)); 159 path.throughput_past_first_hit *= next_bounce.throughput; 163 path.throughput_past_first_hit *= next_bounce.throughput; 167 if any(ray_hit.material.emissive > vec3(0.0)) && dot(ray_hit.world_normal, -next_bounce.wi) > 0.0 { 168 generate_emissive_candidate(&path, ray_hit, next_bounce.wi, p_brdf, ray.t, p_nee, di_samples, bounce, rng); 172 if terminate_into_cache(&path, ray_hit, ray.t, p_brdf, bounce, rng) { 177 path.ray_origin = ray_hit.world_position + (ray_hit.geometric_world_normal * RAY_T_MIN); 178 path.normal = ray_hit.world_normal; 179 path.wo = -next_bounce.wi; 180 path.material = ray_hit.material; 188 var full_throughput = path.throughput_past_first_hit; 190 full_throughput *= max(path.x1_brdf, vec3(0.0001)); 191 let rr = saturate(luminance(full_throughput)); 192 if rand_f(rng) >= rr { break; } 193 path.throughput_past_first_hit /= rr; 197 var result: InitialSamplingResult; 198 result.radiance = path.radiance; 200 if path.selected_target_function > 0.0 { 201 path.reservoir.unbiased_contribution_weight = path.weight_sum / path.selected_target_function; 203 result.reservoir = path.reservoir; 208fn generate_nee_candidate( 209 path: ptr<function, PathState>, 213 workgroup_id: vec2<u32>, 215 rng: ptr<function, u32>, 217 if rand_f(rng) >= p_nee { return; } 219 let di = sample_light_ris((*path).ray_origin, (*path).normal, (*path).wo, (*path).material, F_ab, di_samples, workgroup_id, bounce, rng); 220 let di_target_function = luminance(di.brdf_radiance); 221 if di_target_function <= 0.0 { return; } 225 var nee_mis_weight = 1.0; 226 if di.brdf_rays_can_hit && di.inverse_solid_angle_pdf > 0.0 { 227 let p_nee_strategy = f32(di_samples) * (1.0 / di.inverse_solid_angle_pdf) * p_nee; 228 let p_brdf_at_nee = brdf_pdf((*path).wo, di.wi, (*path).normal, (*path).material, F_ab); 229 nee_mis_weight = power_heuristic(p_nee_strategy, p_brdf_at_nee); 236 let target_function = di_target_function * nee_mis_weight; 237 let resampling_weight = target_function * di.unbiased_contribution_weight / p_nee; 239 (*path).weight_sum += resampling_weight; 240 if rand_f(rng) * (*path).weight_sum < resampling_weight { 241 (*path).reservoir.light_sample = di.light_sample; 242 (*path).selected_target_function = target_function; 245 (*path).radiance += di.brdf_radiance * di.unbiased_contribution_weight * nee_mis_weight / p_nee; 249 let L_at_reconnection = (*path).throughput_past_first_hit * di.brdf_radiance * di.unbiased_contribution_weight * nee_mis_weight / p_nee; 252 resample_reconnection_candidate(path, L_at_reconnection, rng); 254 (*path).radiance += L_at_reconnection; 260 unbiased_contribution_weight: f32, 261 light_sample: LightSample, 263 brdf_radiance: vec3<f32>, 264 inverse_solid_angle_pdf: f32, 265 brdf_rays_can_hit: bool, 268fn sample_light_ris(ray_origin: vec3<f32>, normal: vec3<f32>, wo: vec3<f32>, material: ResolvedMaterial, F_ab: vec2<f32>, di_samples: u32, workgroup_id: vec2<u32>, bounce: u32, rng: ptr<function, u32>) -> DiSample { 269 var workgroup_rng = (workgroup_id.x * 5782582u) + workgroup_id.y + bounce; 270 let light_tile_start = rand_range_u(128u, &workgroup_rng) * 1024u; 272 var weight_sum = 0.0; 273 var selected_target_function = 0.0; 274 var selected_tile_sample = 0u; 275 var selected_world_position = vec4(0.0); 276 var selected_wi = vec3(0.0); 277 var selected_brdf_radiance = vec3(0.0); 278 var selected_inverse_solid_angle_pdf = 0.0; 279 var selected_brdf_rays_can_hit = false; 280 let mis_weight = 1.0 / f32(di_samples); 281 for (var i = 0u; i < di_samples; i++) { 282 let tile_sample = light_tile_start + rand_range_u(1024u, rng); 283 let resolved_light_sample = unpack_resolved_light_sample(light_tile_resolved_samples[tile_sample], view.exposure); 284 let light_contribution = calculate_resolved_light_contribution(resolved_light_sample, ray_origin, normal); 285 let brdf_current = evaluate_brdf(wo, light_contribution.wi, normal, material, F_ab); 286 let brdf_radiance = brdf_current * light_contribution.radiance; 288 let target_function = luminance(brdf_radiance); 289 let resampling_weight = mis_weight * (target_function * light_contribution.inverse_pdf); 291 weight_sum += resampling_weight; 293 if rand_f(rng) * weight_sum < resampling_weight { 294 selected_target_function = target_function; 295 selected_tile_sample = tile_sample; 296 selected_world_position = resolved_light_sample.world_position; 297 selected_wi = light_contribution.wi; 298 selected_inverse_solid_angle_pdf = light_contribution.inverse_solid_angle_pdf; 299 selected_brdf_rays_can_hit = light_contribution.brdf_rays_can_hit; 300 selected_brdf_radiance = brdf_radiance; 304 var unbiased_contribution_weight = 0.0; 305 if selected_target_function > 0.0 { 306 unbiased_contribution_weight = weight_sum / selected_target_function; 307 unbiased_contribution_weight *= trace_visibility(ray_origin, selected_world_position); 310 return DiSample(unbiased_contribution_weight, light_tile_samples[selected_tile_sample], selected_wi, selected_brdf_radiance, selected_inverse_solid_angle_pdf, selected_brdf_rays_can_hit); 313fn generate_emissive_candidate( 314 path: ptr<function, PathState>, 315 ray_hit: ResolvedRayHitFull, 322 rng: ptr<function, u32>, 324 let NdotV_hit = max(dot(ray_hit.world_normal, -wi), 0.0001); 325 let light_count = arrayLength(&light_sources); 326 let area_pdf = 1.0 / (f32(light_count) * f32(ray_hit.triangle_count) * ray_hit.triangle_area); 327 let p_light = area_pdf * ray_t * ray_t / NdotV_hit; 328 let emissive_mis_weight = power_heuristic(p_brdf, p_light * p_nee * f32(di_samples)); 331 if !(*path).x2_reusable { 335 (*path).radiance += (*path).x1_brdf * (*path).throughput_past_first_hit * ray_hit.material.emissive * emissive_mis_weight; 336 } else if bounce == 0u { 338 let target_function = luminance((*path).x1_brdf * ray_hit.material.emissive) * emissive_mis_weight; 339 let resampling_weight = luminance((*path).x1_brdf * (*path).throughput_past_first_hit * ray_hit.material.emissive) * emissive_mis_weight; 341 (*path).weight_sum += resampling_weight; 342 if rand_f(rng) * (*path).weight_sum < resampling_weight { 343 (*path).reservoir.light_sample = LightSample(NULL_LIGHT_ID, bitcast<u32>(area_pdf)); 344 (*path).reservoir.sample_point_world_position = (*path).x2_position; 345 (*path).reservoir.sample_point_world_normal = octahedral_encode((*path).x2_normal); 346 (*path).reservoir.radiance = ray_hit.material.emissive; 347 (*path).selected_target_function = target_function; 351 let emissive_L_at_reconnection = (*path).throughput_past_first_hit * ray_hit.material.emissive * emissive_mis_weight; 352 resample_reconnection_candidate(path, emissive_L_at_reconnection, rng); 355 (*path).radiance += (*path).throughput_past_first_hit * ray_hit.material.emissive * emissive_mis_weight; 360fn generate_environment_map_light_candidate( 361 path: ptr<function, PathState>, 362 next_bounce: EvaluateAndSampleBrdfResult, 363 environment_map_light_radiance: vec3<f32>, 364 primary_F_ab: vec2<f32>, 366 rng: ptr<function, u32>, 368 if all(environment_map_light_radiance == vec3(0.0)) { return; } 372 if !x1_lobe_reusable(next_bounce.pdf, next_bounce.diffuse_selected, (*path).material.perceptual_roughness) { 373 (*path).radiance += next_bounce.throughput * environment_map_light_radiance; 382 let x1_brdf = evaluate_brdf((*path).wo, next_bounce.wi, (*path).normal, (*path).material, primary_F_ab); 383 let target_function = luminance(x1_brdf * environment_map_light_radiance); 384 let resampling_weight = luminance(next_bounce.throughput * environment_map_light_radiance); 386 (*path).weight_sum += resampling_weight; 387 if rand_f(rng) * (*path).weight_sum < resampling_weight { 388 (*path).reservoir.light_sample = LightSample(NULL_LIGHT_ID, 0u); 389 (*path).reservoir.sample_point_world_position = (*path).ray_origin + next_bounce.wi * RAY_T_MAX; 390 (*path).reservoir.sample_point_world_normal = octahedral_encode(-next_bounce.wi); 391 (*path).reservoir.radiance = environment_map_light_radiance; 392 (*path).selected_target_function = target_function; 396 let L_at_reconnection = (*path).throughput_past_first_hit * next_bounce.throughput * environment_map_light_radiance; 397 resample_reconnection_candidate(path, L_at_reconnection, rng); 401fn terminate_into_cache( 402 path: ptr<function, PathState>, 403 ray_hit: ResolvedRayHitFull, 407 rng: ptr<function, u32>, 412 let lobe_solid_angle = 1.0 / p_brdf; 413 let broad_enough_to_terminate = lobe_solid_angle >= CACHE_TERMINATION_MIN_SOLID_ANGLE; 414 let forced_terminate = bounce == constants.max_bounces - 1u; 415 if !(broad_enough_to_terminate || forced_terminate) { return false; } 420 let world_cache_cell_size = get_cell_size(ray_hit.world_position, view.world_position, ray_t, &rng_copy); 421 if ray_t <= sqrt(3.0) * world_cache_cell_size { return false; } 423 let cached_radiance = query_world_cache(ray_hit.world_position, ray_hit.geometric_world_normal, view.world_position, ray_t, WORLD_CACHE_CELL_LIFETIME, rng); 425 let cache_outgoing = (ray_hit.material.base_color / PI) * cached_radiance; 426 let cache_L_at_reconnection = (*path).throughput_past_first_hit * cache_outgoing; 429 resample_reconnection_candidate(path, cache_L_at_reconnection, rng); 431 (*path).radiance += cache_L_at_reconnection; 440fn resample_reconnection_candidate(path: ptr<function, PathState>, L_at_reconnection: vec3<f32>, rng: ptr<function, u32>) { 441 if !(*path).x2_reusable { 443 (*path).radiance += (*path).x1_brdf * L_at_reconnection; 447 let target_function = luminance((*path).x1_brdf * L_at_reconnection); 448 let resampling_weight = target_function; 450 (*path).weight_sum += resampling_weight; 451 if rand_f(rng) * (*path).weight_sum < resampling_weight { 452 (*path).reservoir.light_sample = LightSample(NULL_LIGHT_ID, 0u); 453 (*path).reservoir.sample_point_world_position = (*path).x2_position; 454 (*path).reservoir.sample_point_world_normal = octahedral_encode((*path).x2_normal); 455 (*path).reservoir.radiance = L_at_reconnection; 456 (*path).selected_target_function = target_function; 464fn reconnection_reusable(ray_t: f32, p_brdf: f32, wi: vec3<f32>, diffuse_selected: bool, ray_hit: ResolvedRayHitFull, world_position: vec3<f32>, x1_perceptual_roughness: f32, primary_NdotV: f32) -> bool { 468 let cos_x2 = max(dot(ray_hit.world_normal, -wi), 0.0001); 469 let ray_footprint = (ray_t * ray_t) / (p_brdf * cos_x2); 470 let primary_dist = length(view.world_position - world_position); 471 let primary_footprint = 4.0 * PI * primary_dist * primary_dist / primary_NdotV; 472 let footprint_ok = ray_footprint >= (RECONNECTION_FOOTPRINT_KAPPA / 100.0) * primary_footprint; 474 let x1_lobe_ok = x1_lobe_reusable(p_brdf, diffuse_selected, x1_perceptual_roughness); 480 let x2_is_light = any(ray_hit.material.emissive > vec3(0.0)); 481 let x2_roughness = mix(1.0, ray_hit.material.perceptual_roughness, ray_hit.material.metallic); 482 let x2_roughness_floor = RECONNECTION_ROUGHNESS_MIN * saturate(RECONNECTION_RELAX_DISTANCE / ray_t); 483 let x2_end_ok = x2_is_light || x2_roughness >= x2_roughness_floor; 485 return footprint_ok && x1_lobe_ok && x2_end_ok; 490fn x1_lobe_reusable(p_brdf: f32, diffuse_selected: bool, x1_perceptual_roughness: f32) -> bool { 491 return !isinf(p_brdf) && (diffuse_selected || x1_perceptual_roughness >= RECONNECTION_ROUGHNESS_MIN);