33#import bevy_render :: view :: View
44#import bevy_solari :: brdf :: {evaluate_brdf , evaluate_specular_brdf }
55#import bevy_solari :: gbuffer_utils :: gpixel_resolve
6- #import bevy_solari :: sampling :: {sample_ggx_vndf , ggx_vndf_pdf }
7- #import bevy_solari :: scene_bindings :: {trace_ray , resolve_ray_hit_full , RAY_T_MIN , RAY_T_MAX }
6+ #import bevy_solari :: sampling :: {sample_random_light , random_emissive_light_pdf , sample_ggx_vndf , ggx_vndf_pdf , power_heuristic }
7+ #import bevy_solari :: scene_bindings :: {trace_ray , resolve_ray_hit_full , ResolvedRayHitFull , RAY_T_MIN , RAY_T_MAX }
88#import bevy_solari :: world_cache :: {query_world_cache , WORLD_CACHE_CELL_LIFETIME }
99
1010@group (1 ) @binding (0 ) var view_output : texture_storage_2d <rgba16float , read_write >;
1515struct PushConstants { frame_index : u32 , reset : u32 }
1616var <push_constant > constants : PushConstants ;
1717
18+ const DIFFUSE_GI_REUSE_ROUGHNESS_THRESHOLD : f32 = 0 .4 ;
19+ const WORLD_CACHE_TERMINATION_ROUGHNESS_THRESHOLD : f32 = 0 .4 ;
20+
1821@compute @workgroup_size (8 , 8 , 1 )
1922fn specular_gi (@builtin (global_invocation_id ) global_id : vec3 <u32 >) {
2023 if any (global_id . xy >= vec2u (view . main_pass_viewport . zw )) { return ; }
@@ -32,7 +35,7 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3<u32>) {
3235
3336 var radiance : vec3 <f32 >;
3437 var wi : vec3 <f32 >;
35- if surface . material . roughness > 0 .1 {
38+ if surface . material . roughness > DIFFUSE_GI_REUSE_ROUGHNESS_THRESHOLD {
3639 // Surface is very rough, reuse the ReSTIR GI reservoir
3740 let gi_reservoir = gi_reservoirs_a [pixel_index ];
3841 wi = normalize (gi_reservoir . sample_point_world_position - surface . world_position );
@@ -68,6 +71,8 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3<u32>) {
6871fn trace_glossy_path (initial_ray_origin : vec3 <f32 >, initial_wi : vec3 <f32 >, rng : ptr <function , u32 >) -> vec3 <f32 > {
6972 var ray_origin = initial_ray_origin ;
7073 var wi = initial_wi ;
74+ var surface_perfectly_specular = false ;
75+ var p_bounce = 0 .0 ;
7176
7277 // Trace up to three bounces, getting the net throughput from them
7378 var radiance = vec3 (0 .0 );
@@ -78,34 +83,72 @@ fn trace_glossy_path(initial_ray_origin: vec3<f32>, initial_wi: vec3<f32>, rng:
7883 if ray . kind == RAY_QUERY_INTERSECTION_NONE { break ; }
7984 let ray_hit = resolve_ray_hit_full (ray );
8085
81- // Add world cache contribution
82- let diffuse_brdf = ray_hit . material . base_color / PI ;
83- radiance += throughput * diffuse_brdf * query_world_cache (ray_hit . world_position , ray_hit . geometric_world_normal , view . world_position , WORLD_CACHE_CELL_LIFETIME , rng );
84-
85- // Surface is very rough, terminate path in the world cache
86- if ray_hit . material . roughness > 0 .1 && i != 0u { break ; }
87-
88- // Sample new ray direction from the GGX BRDF for next bounce
8986 let TBN = calculate_tbn_mikktspace (ray_hit . world_normal , ray_hit . world_tangent );
9087 let T = TBN [0 ];
9188 let B = TBN [1 ];
9289 let N = TBN [2 ];
90+
9391 let wo = - wi ;
9492 let wo_tangent = vec3 (dot (wo , T ), dot (wo , B ), dot (wo , N ));
93+
94+ // Add emissive contribution (but not on the first bounce, since ReSTIR DI handles that)
95+ if i != 0u {
96+ radiance += throughput * emissive_mis_weight (p_bounce , ray_hit , surface_perfectly_specular ) * ray_hit . material . emissive ;
97+ }
98+
99+ // Should not perform NEE for mirror-like surfaces
100+ surface_perfectly_specular = ray_hit . material . roughness <= 0 .001 && ray_hit . material . metallic > 0 .9999 ;
101+
102+ if ray_hit . material . roughness > WORLD_CACHE_TERMINATION_ROUGHNESS_THRESHOLD && i != 0u {
103+ // Surface is very rough, terminate path in the world cache
104+ let diffuse_brdf = ray_hit . material . base_color / PI ;
105+ radiance += throughput * diffuse_brdf * query_world_cache (ray_hit . world_position , ray_hit . geometric_world_normal , view . world_position , WORLD_CACHE_CELL_LIFETIME , rng );
106+ break ;
107+ } else if ! surface_perfectly_specular {
108+ // Sample direct lighting (NEE)
109+ let direct_lighting = sample_random_light (ray_hit . world_position , ray_hit . world_normal , rng );
110+ let direct_lighting_brdf = evaluate_brdf (ray_hit . world_normal , wo , direct_lighting . wi , ray_hit . material );
111+ let mis_weight = nee_mis_weight (direct_lighting . inverse_pdf , direct_lighting . brdf_rays_can_hit , wo_tangent , direct_lighting . wi , ray_hit , TBN );
112+ radiance += throughput * mis_weight * direct_lighting . radiance * direct_lighting . inverse_pdf * direct_lighting_brdf ;
113+ }
114+
115+ // Sample new ray direction from the GGX BRDF for next bounce
95116 let wi_tangent = sample_ggx_vndf (wo_tangent , ray_hit . material . roughness , rng );
96117 wi = wi_tangent . x * T + wi_tangent . y * B + wi_tangent . z * N ;
97118 ray_origin = ray_hit . world_position ;
98119
99120 // Update throughput for next bounce
100- let pdf = ggx_vndf_pdf (wo_tangent , wi_tangent , ray_hit . material . roughness );
121+ p_bounce = ggx_vndf_pdf (wo_tangent , wi_tangent , ray_hit . material . roughness );
101122 let brdf = evaluate_brdf (N , wo , wi , ray_hit . material );
102123 let cos_theta = saturate (dot (wi , N ));
103- throughput *= (brdf * cos_theta ) / pdf ;
124+ throughput *= (brdf * cos_theta ) / p_bounce ;
104125 }
105126
106127 return radiance ;
107128}
108129
130+ fn emissive_mis_weight (p_bounce : f32 , ray_hit : ResolvedRayHitFull , previous_surface_perfectly_specular : bool ) -> f32 {
131+ if previous_surface_perfectly_specular { return 1 .0 ; }
132+
133+ let p_light = random_emissive_light_pdf (ray_hit );
134+ return power_heuristic (p_bounce , p_light );
135+ }
136+
137+ fn nee_mis_weight (inverse_p_light : f32 , brdf_rays_can_hit : bool , wo_tangent : vec3 <f32 >, wi : vec3 <f32 >, ray_hit : ResolvedRayHitFull , TBN : mat3x3 <f32 >) -> f32 {
138+ if ! brdf_rays_can_hit {
139+ return 1 .0 ;
140+ }
141+
142+ let T = TBN [0 ];
143+ let B = TBN [1 ];
144+ let N = TBN [2 ];
145+ let wi_tangent = vec3 (dot (wi , T ), dot (wi , B ), dot (wi , N ));
146+
147+ let p_light = 1 .0 / inverse_p_light ;
148+ let p_bounce = ggx_vndf_pdf (wo_tangent , wi_tangent , ray_hit . material . roughness );
149+ return max (0 .0 , power_heuristic (p_light , p_bounce ));
150+ }
151+
109152// Don't adjust the size of this struct without also adjusting GI_RESERVOIR_STRUCT_SIZE.
110153struct Reservoir {
111154 sample_point_world_position : vec3 <f32 >,
0 commit comments