forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrestir_gi.wgsl
More file actions
320 lines (263 loc) · 16 KB
/
Copy pathrestir_gi.wgsl
File metadata and controls
320 lines (263 loc) · 16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// https://intro-to-restir.cwyman.org/presentations/2023ReSTIR_Course_Notes.pdf
enable wgpu_ray_query;
#import bevy_core_pipeline::tonemapping::tonemapping_luminance as luminance
#import bevy_pbr::prepass_bindings::PreviousViewUniforms
#import bevy_pbr::utils::{rand_f, sample_uniform_hemisphere, uniform_hemisphere_inverse_pdf, sample_disk}
#import bevy_render::maths::PI
#import bevy_render::view::View
#import bevy_solari::brdf::{evaluate_diffuse_brdf, F_AB}
#import bevy_solari::gbuffer_utils::{gpixel_resolve, pixel_dissimilar, permute_pixel}
#import bevy_solari::sampling::{sample_random_light, trace_point_visibility, balance_heuristic, isnan}
#import bevy_solari::scene_bindings::{trace_ray, resolve_ray_hit_full, RAY_T_MIN, RAY_T_MAX}
#import bevy_solari::world_cache::{query_world_cache, WORLD_CACHE_CELL_LIFETIME}
#import bevy_solari::realtime_bindings::{view_output, gi_reservoirs_a, gi_reservoirs_b, gbuffer, depth_buffer, motion_vectors, previous_gbuffer, previous_depth_buffer, view, previous_view, constants, Reservoir}
#import bevy_solari::specular_gi::DIFFUSE_GI_REUSE_ROUGHNESS_THRESHOLD
const SPATIAL_REUSE_RADIUS_PIXELS = 30.0;
const CONFIDENCE_WEIGHT_CAP = 8.0;
@compute @workgroup_size(8, 8, 1)
fn initial_and_temporal(@builtin(global_invocation_id) global_id: vec3<u32>) {
if any(global_id.xy >= vec2u(view.main_pass_viewport.zw)) { return; }
let pixel_index = global_id.x + global_id.y * u32(view.main_pass_viewport.z);
var rng = pixel_index + constants.frame_index;
let depth = textureLoad(depth_buffer, global_id.xy, 0);
if depth == 0.0 {
gi_reservoirs_b[pixel_index] = empty_reservoir();
return;
}
let surface = gpixel_resolve(textureLoad(gbuffer, global_id.xy, 0), depth, global_id.xy, view.main_pass_viewport.zw, view.world_from_clip);
if surface.material.metallic > 0.9999 && surface.material.roughness <= DIFFUSE_GI_REUSE_ROUGHNESS_THRESHOLD {
gi_reservoirs_b[pixel_index] = empty_reservoir();
return;
}
let initial_reservoir = generate_initial_reservoir(surface.world_position, surface.world_normal, &rng);
let temporal = load_temporal_reservoir(global_id.xy, depth, surface.world_position, surface.world_normal);
let merge_result = merge_reservoirs(initial_reservoir, surface.world_position, surface.world_normal, surface.material.base_color / PI,
temporal.reservoir, temporal.world_position, temporal.world_normal, temporal.diffuse_brdf, &rng);
gi_reservoirs_b[pixel_index] = merge_result.merged_reservoir;
}
@compute @workgroup_size(8, 8, 1)
fn spatial_and_shade(@builtin(global_invocation_id) global_id: vec3<u32>) {
if any(global_id.xy >= vec2u(view.main_pass_viewport.zw)) { return; }
let pixel_index = global_id.x + global_id.y * u32(view.main_pass_viewport.z);
var rng = pixel_index + constants.frame_index;
let depth = textureLoad(depth_buffer, global_id.xy, 0);
if depth == 0.0 {
gi_reservoirs_a[pixel_index] = empty_reservoir();
return;
}
let surface = gpixel_resolve(textureLoad(gbuffer, global_id.xy, 0), depth, global_id.xy, view.main_pass_viewport.zw, view.world_from_clip);
if surface.material.metallic > 0.9999 && surface.material.roughness <= DIFFUSE_GI_REUSE_ROUGHNESS_THRESHOLD {
gi_reservoirs_a[pixel_index] = empty_reservoir();
return;
}
let input_reservoir = gi_reservoirs_b[pixel_index];
let spatial = load_spatial_reservoir(global_id.xy, depth, surface.world_position, surface.world_normal, &rng);
let merge_result = merge_reservoirs(input_reservoir, surface.world_position, surface.world_normal, surface.material.base_color / PI,
spatial.reservoir, spatial.world_position, spatial.world_normal, spatial.diffuse_brdf, &rng);
var combined_reservoir = merge_result.merged_reservoir;
// More accuracy, less stability
#ifndef BIASED_RESAMPLING
gi_reservoirs_a[pixel_index] = combined_reservoir;
#endif
combined_reservoir.unbiased_contribution_weight *= trace_point_visibility(surface.world_position + (surface.world_normal * RAY_T_MIN), combined_reservoir.sample_point_world_position);
// More stability, less accuracy (shadows extend further out than they should)
#ifdef BIASED_RESAMPLING
gi_reservoirs_a[pixel_index] = combined_reservoir;
#endif
let wo = normalize(view.world_position - surface.world_position);
let NdotV = max(dot(surface.world_normal, wo), 0.0001);
let F_ab = F_AB(surface.material.perceptual_roughness, NdotV);
let brdf = evaluate_diffuse_brdf(wo, merge_result.wi, surface.world_normal, surface.material, F_ab);
var pixel_color = textureLoad(view_output, global_id.xy);
pixel_color += vec4(merge_result.selected_sample_radiance * combined_reservoir.unbiased_contribution_weight * view.exposure * brdf, 0.0);
textureStore(view_output, global_id.xy, pixel_color);
}
fn generate_initial_reservoir(world_position: vec3<f32>, world_normal: vec3<f32>, rng: ptr<function, u32>) -> Reservoir {
var reservoir = empty_reservoir();
let ray_direction = sample_uniform_hemisphere(world_normal, rng);
let ray = trace_ray(world_position + (world_normal * RAY_T_MIN), ray_direction, RAY_T_MIN, RAY_T_MAX, RAY_FLAG_NONE);
if ray.kind == RAY_QUERY_INTERSECTION_NONE {
return reservoir;
}
let sample_point = resolve_ray_hit_full(ray);
if any(sample_point.material.emissive != vec3(0.0)) {
return reservoir;
}
reservoir.sample_point_world_position = sample_point.world_position;
reservoir.sample_point_world_normal = sample_point.world_normal;
reservoir.confidence_weight = 1.0;
#ifdef NO_WORLD_CACHE
let direct_lighting = sample_random_light(sample_point.world_position, sample_point.world_normal, rng);
reservoir.radiance = direct_lighting.radiance * saturate(dot(direct_lighting.wi, sample_point.world_normal));
reservoir.unbiased_contribution_weight = direct_lighting.inverse_pdf * uniform_hemisphere_inverse_pdf();
#else
reservoir.radiance = query_world_cache(sample_point.world_position, sample_point.geometric_world_normal, view.world_position, ray.t, WORLD_CACHE_CELL_LIFETIME, rng);
reservoir.unbiased_contribution_weight = uniform_hemisphere_inverse_pdf();
#endif
let sample_point_diffuse_brdf = sample_point.material.base_color / PI;
reservoir.radiance *= sample_point_diffuse_brdf;
return reservoir;
}
fn load_temporal_reservoir(pixel_id: vec2<u32>, depth: f32, world_position: vec3<f32>, world_normal: vec3<f32>) -> NeighborInfo {
let motion_vector = textureLoad(motion_vectors, pixel_id, 0).xy;
let temporal_pixel_id_float = round(vec2<f32>(pixel_id) - (motion_vector * view.main_pass_viewport.zw));
var point_temporal_pixel_id = vec2<u32>(temporal_pixel_id_float);
if bool(constants.reset) {
return NeighborInfo(empty_reservoir(), vec3(0.0), vec3(0.0), vec3(0.0));
}
if any(temporal_pixel_id_float < vec2(0.0)) || any(temporal_pixel_id_float >= view.main_pass_viewport.zw) {
point_temporal_pixel_id = pixel_id;
}
let permuted_temporal_pixel_id = permute_pixel(point_temporal_pixel_id, constants.frame_index, view.main_pass_viewport.zw);
var temporal = load_temporal_reservoir_inner(permuted_temporal_pixel_id, depth, world_position, world_normal);
temporal.reservoir.confidence_weight = min(temporal.reservoir.confidence_weight, CONFIDENCE_WEIGHT_CAP);
return temporal;
}
fn load_temporal_reservoir_inner(temporal_pixel_id: vec2<u32>, depth: f32, world_position: vec3<f32>, world_normal: vec3<f32>) -> NeighborInfo {
// Check if the pixel features have changed heavily between the current and previous frame
let temporal_depth = textureLoad(previous_depth_buffer, temporal_pixel_id, 0);
let temporal_surface = gpixel_resolve(textureLoad(previous_gbuffer, temporal_pixel_id, 0), temporal_depth, temporal_pixel_id, view.main_pass_viewport.zw, previous_view.world_from_clip);
let temporal_diffuse_brdf = temporal_surface.material.base_color / PI;
if pixel_dissimilar(depth, world_position, temporal_surface.world_position, world_normal, temporal_surface.world_normal, view) {
return NeighborInfo(empty_reservoir(), vec3(0.0), vec3(0.0), vec3(0.0));
}
let temporal_pixel_index = temporal_pixel_id.x + temporal_pixel_id.y * u32(view.main_pass_viewport.z);
let temporal_reservoir = gi_reservoirs_a[temporal_pixel_index];
return NeighborInfo(temporal_reservoir, temporal_surface.world_position, temporal_surface.world_normal, temporal_diffuse_brdf);
}
fn load_spatial_reservoir(pixel_id: vec2<u32>, depth: f32, world_position: vec3<f32>, world_normal: vec3<f32>, rng: ptr<function, u32>) -> NeighborInfo {
for (var i = 0u; i < 5u; i++) {
let spatial_pixel_id = get_neighbor_pixel_id(pixel_id, SPATIAL_REUSE_RADIUS_PIXELS, rng);
let spatial_depth = textureLoad(depth_buffer, spatial_pixel_id, 0);
let spatial_surface = gpixel_resolve(textureLoad(gbuffer, spatial_pixel_id, 0), spatial_depth, spatial_pixel_id, view.main_pass_viewport.zw, view.world_from_clip);
let spatial_diffuse_brdf = spatial_surface.material.base_color / PI;
if pixel_dissimilar(depth, world_position, spatial_surface.world_position, world_normal, spatial_surface.world_normal, view) {
continue;
}
let spatial_pixel_index = spatial_pixel_id.x + spatial_pixel_id.y * u32(view.main_pass_viewport.z);
let spatial_reservoir = gi_reservoirs_b[spatial_pixel_index];
return NeighborInfo(spatial_reservoir, spatial_surface.world_position, spatial_surface.world_normal, spatial_diffuse_brdf);
}
return NeighborInfo(empty_reservoir(), world_position, world_normal, vec3(0.0));
}
fn get_neighbor_pixel_id(center_pixel_id: vec2<u32>, search_radius: f32, rng: ptr<function, u32>) -> vec2<u32> {
var spatial_id = vec2<f32>(center_pixel_id) + sample_disk(search_radius, rng);
spatial_id = clamp(spatial_id, vec2(0.0), view.main_pass_viewport.zw - 1.0);
return vec2<u32>(spatial_id);
}
struct NeighborInfo {
reservoir: Reservoir,
world_position: vec3<f32>,
world_normal: vec3<f32>,
diffuse_brdf: vec3<f32>,
}
fn jacobian(
new_world_position: vec3<f32>,
original_world_position: vec3<f32>,
sample_point_world_position: vec3<f32>,
sample_point_world_normal: vec3<f32>,
) -> f32 {
let r = new_world_position - sample_point_world_position;
let q = original_world_position - sample_point_world_position;
let rl = length(r);
let ql = length(q);
let phi_r = saturate(dot(r / rl, sample_point_world_normal));
let phi_q = saturate(dot(q / ql, sample_point_world_normal));
let jacobian = (phi_r * ql * ql) / (phi_q * rl * rl);
return select(jacobian, 0.0, isinf(jacobian) || isnan(jacobian));
}
fn isinf(x: f32) -> bool {
return (bitcast<u32>(x) & 0x7fffffffu) == 0x7f800000u;
}
fn empty_reservoir() -> Reservoir {
return Reservoir(
vec3(0.0),
0.0,
vec3(0.0),
0.0,
vec3(0.0),
0.0,
);
}
struct ReservoirMergeResult {
merged_reservoir: Reservoir,
selected_sample_radiance: vec3<f32>,
wi: vec3<f32>,
}
fn merge_reservoirs(
canonical_reservoir: Reservoir,
canonical_world_position: vec3<f32>,
canonical_world_normal: vec3<f32>,
canonical_diffuse_brdf: vec3<f32>,
other_reservoir: Reservoir,
other_world_position: vec3<f32>,
other_world_normal: vec3<f32>,
other_diffuse_brdf: vec3<f32>,
rng: ptr<function, u32>,
) -> ReservoirMergeResult {
// Radiances for resampling
let canonical_sample_wi = normalize(canonical_reservoir.sample_point_world_position - canonical_world_position);
let other_sample_wi = normalize(other_reservoir.sample_point_world_position - canonical_world_position);
// Target functions for resampling and MIS
let canonical_target_function_canonical_sample = luminance(
canonical_reservoir.radiance * saturate(dot(canonical_sample_wi, canonical_world_normal)) * canonical_diffuse_brdf
);
let canonical_target_function_other_sample = luminance(
other_reservoir.radiance * saturate(dot(other_sample_wi, canonical_world_normal)) * canonical_diffuse_brdf
);
// Extra target functions for MIS
let other_target_function_canonical_sample = luminance(
canonical_reservoir.radiance * saturate(dot(normalize(canonical_reservoir.sample_point_world_position - other_world_position), other_world_normal)) * other_diffuse_brdf
);
let other_target_function_other_sample = luminance(
other_reservoir.radiance * saturate(dot(normalize(other_reservoir.sample_point_world_position - other_world_position), other_world_normal)) * other_diffuse_brdf
);
// Jacobians for resampling and MIS
let canonical_target_function_other_sample_jacobian = jacobian(
canonical_world_position,
other_world_position,
other_reservoir.sample_point_world_position,
other_reservoir.sample_point_world_normal
);
let other_target_function_canonical_sample_jacobian = jacobian(
other_world_position,
canonical_world_position,
canonical_reservoir.sample_point_world_position,
canonical_reservoir.sample_point_world_normal
);
// Don't merge samples with huge jacobians, as it explodes the variance
if canonical_target_function_other_sample_jacobian > 1.2 || other_target_function_canonical_sample_jacobian > 1.2 {
return ReservoirMergeResult(canonical_reservoir, canonical_reservoir.radiance, canonical_sample_wi);
}
// Resampling weight for canonical sample
let canonical_sample_mis_weight = balance_heuristic(
canonical_reservoir.confidence_weight * canonical_target_function_canonical_sample,
other_reservoir.confidence_weight * other_target_function_canonical_sample * other_target_function_canonical_sample_jacobian,
);
let canonical_sample_resampling_weight = canonical_sample_mis_weight * canonical_target_function_canonical_sample * canonical_reservoir.unbiased_contribution_weight;
// Resampling weight for other sample
let other_sample_mis_weight = balance_heuristic(
other_reservoir.confidence_weight * other_target_function_other_sample,
canonical_reservoir.confidence_weight * canonical_target_function_other_sample * canonical_target_function_other_sample_jacobian,
);
let other_sample_resampling_weight = other_sample_mis_weight * canonical_target_function_other_sample * other_reservoir.unbiased_contribution_weight * canonical_target_function_other_sample_jacobian;
// Perform resampling
var combined_reservoir = empty_reservoir();
combined_reservoir.confidence_weight = canonical_reservoir.confidence_weight + other_reservoir.confidence_weight;
combined_reservoir.weight_sum = canonical_sample_resampling_weight + other_sample_resampling_weight;
if rand_f(rng) < other_sample_resampling_weight / combined_reservoir.weight_sum {
combined_reservoir.sample_point_world_position = other_reservoir.sample_point_world_position;
combined_reservoir.sample_point_world_normal = other_reservoir.sample_point_world_normal;
combined_reservoir.radiance = other_reservoir.radiance;
let inverse_target_function = select(0.0, 1.0 / canonical_target_function_other_sample, canonical_target_function_other_sample > 0.0);
combined_reservoir.unbiased_contribution_weight = combined_reservoir.weight_sum * inverse_target_function;
return ReservoirMergeResult(combined_reservoir, other_reservoir.radiance, other_sample_wi);
} else {
combined_reservoir.sample_point_world_position = canonical_reservoir.sample_point_world_position;
combined_reservoir.sample_point_world_normal = canonical_reservoir.sample_point_world_normal;
combined_reservoir.radiance = canonical_reservoir.radiance;
let inverse_target_function = select(0.0, 1.0 / canonical_target_function_canonical_sample, canonical_target_function_canonical_sample > 0.0);
combined_reservoir.unbiased_contribution_weight = combined_reservoir.weight_sum * inverse_target_function;
return ReservoirMergeResult(combined_reservoir, canonical_reservoir.radiance, canonical_sample_wi);
}
}