Skip to content

Commit 98a5aa5

Browse files
SparkyPotatoJMS55
andauthored
Specular GI MIS (#22313)
ReSTIR DI (and other DI algorithms) can't find light samples that align well with very smooth, specular surfaces. However, specular GI does a trace for these surfaces, which we can use to improve DI. This PR uses the specular GI trace to light the specular lobe for smooth surfaces, disabling DI on the specular lobe for them. This PR: <img width="1920" height="1032" alt="image" src="https://github.com/user-attachments/assets/d7768779-6a57-4226-8d36-eefaf2a27df1" /> Previously: <img width="1920" height="1032" alt="image" src="https://github.com/user-attachments/assets/5b408160-a384-4f4b-8ae4-3c89812f75d3" /> Reference: <img width="1920" height="1032" alt="image" src="https://github.com/user-attachments/assets/089fdec5-17a4-4277-87b2-dd6d271ba1f6" /> --------- Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com>
1 parent 2b394aa commit 98a5aa5

3 files changed

Lines changed: 31 additions & 13 deletions

File tree

crates/bevy_solari/src/realtime/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ impl Plugin for SolariLightingPlugin {
3939
load_shader_library!(app, "presample_light_tiles.wgsl");
4040
embedded_asset!(app, "restir_di.wgsl");
4141
embedded_asset!(app, "restir_gi.wgsl");
42-
embedded_asset!(app, "specular_gi.wgsl");
42+
load_shader_library!(app, "specular_gi.wgsl");
4343
load_shader_library!(app, "world_cache_query.wgsl");
4444
embedded_asset!(app, "world_cache_compact.wgsl");
4545
embedded_asset!(app, "world_cache_update.wgsl");

crates/bevy_solari/src/realtime/restir_di.wgsl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
#import bevy_pbr::utils::{rand_f, rand_range_u, sample_disk}
77
#import bevy_render::maths::PI
88
#import bevy_render::view::View
9-
#import bevy_solari::brdf::evaluate_brdf
9+
#import bevy_solari::brdf::{evaluate_brdf, evaluate_diffuse_brdf}
1010
#import bevy_solari::gbuffer_utils::{gpixel_resolve, pixel_dissimilar, permute_pixel}
1111
#import bevy_solari::presample_light_tiles::{ResolvedLightSamplePacked, unpack_resolved_light_sample}
1212
#import bevy_solari::sampling::{LightSample, calculate_resolved_light_contribution, resolve_and_calculate_light_contribution, resolve_light_sample, trace_light_visibility, balance_heuristic}
1313
#import bevy_solari::scene_bindings::{light_sources, previous_frame_light_id_translations, LIGHT_NOT_PRESENT_THIS_FRAME}
14+
#import bevy_solari::specular_gi::SPECULAR_GI_FOR_DI_ROUGHNESS_THRESHOLD
1415

1516
@group(1) @binding(0) var view_output: texture_storage_2d<rgba16float, read_write>;
1617
@group(1) @binding(1) var<storage, read_write> light_tile_samples: array<LightSample>;
@@ -93,7 +94,13 @@ fn spatial_and_shade(@builtin(global_invocation_id) global_id: vec3<u32>) {
9394
#endif
9495

9596
let wo = normalize(view.world_position - surface.world_position);
96-
let brdf = evaluate_brdf(surface.world_normal, wo, merge_result.wi, surface.material);
97+
var brdf: vec3<f32>;
98+
// If the surface is very smooth, let specular GI handle the specular lobe
99+
if surface.material.roughness <= SPECULAR_GI_FOR_DI_ROUGHNESS_THRESHOLD {
100+
brdf = evaluate_diffuse_brdf(surface.material.base_color, surface.material.metallic);
101+
} else {
102+
brdf = evaluate_brdf(surface.world_normal, wo, merge_result.wi, surface.material);
103+
}
97104

98105
var pixel_color = merge_result.selected_sample_radiance * combined_reservoir.unbiased_contribution_weight;
99106
pixel_color *= brdf;

crates/bevy_solari/src/realtime/specular_gi.wgsl

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#define_import_path bevy_solari::specular_gi
2+
13
#import bevy_pbr::pbr_functions::calculate_tbn_mikktspace
24
#import bevy_render::maths::{orthonormalize, PI}
35
#import bevy_render::view::View
@@ -16,6 +18,7 @@ struct PushConstants { frame_index: u32, reset: u32 }
1618
var<push_constant> constants: PushConstants;
1719

1820
const DIFFUSE_GI_REUSE_ROUGHNESS_THRESHOLD: f32 = 0.4;
21+
const SPECULAR_GI_FOR_DI_ROUGHNESS_THRESHOLD: f32 = 0.0225;
1922
const TERMINATE_IN_WORLD_CACHE_THRESHOLD: f32 = 0.03;
2023

2124
@compute @workgroup_size(8, 8, 1)
@@ -57,7 +60,7 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3<u32>) {
5760
var a0 = dot(wo_unnormalized, wo_unnormalized) / (4.0 * PI * cos_theta);
5861
a0 *= TERMINATE_IN_WORLD_CACHE_THRESHOLD;
5962

60-
radiance = trace_glossy_path(surface.world_position, wi, pdf, a0, &rng) / pdf;
63+
radiance = trace_glossy_path(surface.world_position, wi, surface.material.roughness, pdf, a0, &rng) / pdf;
6164
}
6265

6366
let brdf = evaluate_specular_brdf(surface.world_normal, wo, wi, surface.material.base_color, surface.material.metallic,
@@ -74,7 +77,7 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3<u32>) {
7477
#endif
7578
}
7679

77-
fn trace_glossy_path(initial_ray_origin: vec3<f32>, initial_wi: vec3<f32>, initial_p_bounce: f32, a0: f32, rng: ptr<function, u32>) -> vec3<f32> {
80+
fn trace_glossy_path(initial_ray_origin: vec3<f32>, initial_wi: vec3<f32>, initial_roughness: f32, initial_p_bounce: f32, a0: f32, rng: ptr<function, u32>) -> vec3<f32> {
7881
var ray_origin = initial_ray_origin;
7982
var wi = initial_wi;
8083
var p_bounce = initial_p_bounce;
@@ -98,10 +101,9 @@ fn trace_glossy_path(initial_ray_origin: vec3<f32>, initial_wi: vec3<f32>, initi
98101
let wo = -wi;
99102
let wo_tangent = vec3(dot(wo, T), dot(wo, B), dot(wo, N));
100103

101-
// Add emissive contribution (but not on the first bounce, since ReSTIR DI handles that)
102-
if i != 0u {
103-
radiance += throughput * emissive_mis_weight(p_bounce, ray_hit, surface_perfectly_specular) * ray_hit.material.emissive;
104-
}
104+
// Add emissive contribution
105+
let mis_weight = emissive_mis_weight(i, initial_roughness, p_bounce, ray_hit, surface_perfectly_specular);
106+
radiance += throughput * mis_weight * ray_hit.material.emissive;
105107

106108
// Should not perform NEE for mirror-like surfaces
107109
surface_perfectly_specular = ray_hit.material.roughness <= 0.001 && ray_hit.material.metallic > 0.9999;
@@ -137,11 +139,20 @@ fn trace_glossy_path(initial_ray_origin: vec3<f32>, initial_wi: vec3<f32>, initi
137139
return radiance;
138140
}
139141

140-
fn emissive_mis_weight(p_bounce: f32, ray_hit: ResolvedRayHitFull, previous_surface_perfectly_specular: bool) -> f32 {
141-
if previous_surface_perfectly_specular { return 1.0; }
142+
fn emissive_mis_weight(i: u32, initial_roughness: f32, p_bounce: f32, ray_hit: ResolvedRayHitFull, previous_surface_perfectly_specular: bool) -> f32 {
143+
if i != 0u {
144+
if previous_surface_perfectly_specular { return 1.0; }
142145

143-
let p_light = random_emissive_light_pdf(ray_hit);
144-
return power_heuristic(p_bounce, p_light);
146+
let p_light = random_emissive_light_pdf(ray_hit);
147+
return power_heuristic(p_bounce, p_light);
148+
} else {
149+
// The first bounce gets MIS weight 0.0 or 1.0 depending on if ReSTIR DI shaded using the specular lobe or not
150+
if initial_roughness <= SPECULAR_GI_FOR_DI_ROUGHNESS_THRESHOLD {
151+
return 1.0;
152+
} else {
153+
return 0.0;
154+
}
155+
}
145156
}
146157

147158
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 {

0 commit comments

Comments
 (0)