forked from IceSentry/bevy
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathworld_cache_query.wgsl
More file actions
141 lines (119 loc) · 6.17 KB
/
world_cache_query.wgsl
File metadata and controls
141 lines (119 loc) · 6.17 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
#define_import_path bevy_solari::world_cache
#import bevy_pbr::utils::rand_vec2f
#import bevy_render::maths::orthonormalize
/// How responsive the world cache is to changes in lighting (higher is less responsive, lower is more responsive)
const WORLD_CACHE_MAX_TEMPORAL_SAMPLES: f32 = 32.0;
/// How many direct light samples each cell takes when updating each frame
const WORLD_CACHE_DIRECT_LIGHT_SAMPLE_COUNT: u32 = 32u;
/// Maximum amount of distance to trace GI rays between two cache cells
const WORLD_CACHE_MAX_GI_RAY_DISTANCE: f32 = 50.0;
/// Maximum amount of frames a cell can live for without being queried
const WORLD_CACHE_CELL_LIFETIME: u32 = 30u;
/// Maximum amount of attempts to find a cache entry after a hash collision
const WORLD_CACHE_MAX_SEARCH_STEPS: u32 = 3u;
/// Size of a cache cell at the lowest LOD in meters
const WORLD_CACHE_POSITION_BASE_CELL_SIZE: f32 = 0.25;
/// How fast the world cache transitions between LODs as a function of distance to the camera
const WORLD_CACHE_POSITION_LOD_SCALE: f32 = 8.0;
/// Marker value for an empty cell
const WORLD_CACHE_EMPTY_CELL: u32 = 0u;
struct WorldCacheGeometryData {
world_position: vec3<f32>,
padding_a: u32,
world_normal: vec3<f32>,
padding_b: u32
}
@group(1) @binding(14) var<storage, read_write> world_cache_checksums: array<atomic<u32>, #{WORLD_CACHE_SIZE}>;
#ifdef WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER
@group(1) @binding(15) var<storage, read_write> world_cache_life: array<u32, #{WORLD_CACHE_SIZE}>;
#else
@group(1) @binding(15) var<storage, read_write> world_cache_life: array<atomic<u32>, #{WORLD_CACHE_SIZE}>;
#endif
@group(1) @binding(16) var<storage, read_write> world_cache_radiance: array<vec4<f32>, #{WORLD_CACHE_SIZE}>;
@group(1) @binding(17) var<storage, read_write> world_cache_geometry_data: array<WorldCacheGeometryData, #{WORLD_CACHE_SIZE}>;
@group(1) @binding(18) var<storage, read_write> world_cache_luminance_deltas: array<f32, #{WORLD_CACHE_SIZE}>;
@group(1) @binding(19) var<storage, read_write> world_cache_active_cells_new_radiance: array<vec3<f32>, #{WORLD_CACHE_SIZE}>;
@group(1) @binding(20) var<storage, read_write> world_cache_a: array<u32, #{WORLD_CACHE_SIZE}>;
@group(1) @binding(21) var<storage, read_write> world_cache_b: array<u32, 1024u>;
@group(1) @binding(22) var<storage, read_write> world_cache_active_cell_indices: array<u32, #{WORLD_CACHE_SIZE}>;
@group(1) @binding(23) var<storage, read_write> world_cache_active_cells_count: u32;
#ifndef WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER
fn query_world_cache(world_position: vec3<f32>, world_normal: vec3<f32>, view_position: vec3<f32>, cell_lifetime: u32, rng: ptr<function, u32>) -> vec3<f32> {
var cell_size = get_cell_size(world_position, view_position);
// https://tomclabault.github.io/blog/2025/regir, jitter_world_position_tangent_plane
let TBN = orthonormalize(world_normal);
let offset = (rand_vec2f(rng) * 2.0 - 1.0) * cell_size * 0.5;
let jittered_position = world_position + offset.x * TBN[0] + offset.y * TBN[1];
cell_size = get_cell_size(jittered_position, view_position);
let world_position_quantized = bitcast<vec3<u32>>(quantize_position(jittered_position, cell_size));
let world_normal_quantized = bitcast<vec3<u32>>(quantize_normal(world_normal));
var key = compute_key(world_position_quantized, world_normal_quantized);
let checksum = compute_checksum(world_position_quantized, world_normal_quantized);
for (var i = 0u; i < WORLD_CACHE_MAX_SEARCH_STEPS; i++) {
let existing_checksum = atomicCompareExchangeWeak(&world_cache_checksums[key], WORLD_CACHE_EMPTY_CELL, checksum).old_value;
// Cell already exists or is empty - reset lifetime
if existing_checksum == checksum || existing_checksum == WORLD_CACHE_EMPTY_CELL {
#ifndef WORLD_CACHE_QUERY_ATOMIC_MAX_LIFETIME
atomicStore(&world_cache_life[key], cell_lifetime);
#else
atomicMax(&world_cache_life[key], cell_lifetime);
#endif
}
if existing_checksum == checksum {
// Cache entry already exists - get radiance
return world_cache_radiance[key].rgb;
} else if existing_checksum == WORLD_CACHE_EMPTY_CELL {
// Cell is empty - initialize it
world_cache_geometry_data[key].world_position = jittered_position;
world_cache_geometry_data[key].world_normal = world_normal;
return vec3(0.0);
} else {
// Collision - linear probe to next entry
key += 1u;
}
}
return vec3(0.0);
}
#endif
fn get_cell_size(world_position: vec3<f32>, view_position: vec3<f32>) -> f32 {
let camera_distance = distance(view_position, world_position) / WORLD_CACHE_POSITION_LOD_SCALE;
let lod = exp2(floor(log2(1.0 + camera_distance)));
return WORLD_CACHE_POSITION_BASE_CELL_SIZE * lod;
}
fn quantize_position(world_position: vec3<f32>, quantization_factor: f32) -> vec3<f32> {
return floor(world_position / quantization_factor + 0.0001);
}
fn quantize_normal(world_normal: vec3<f32>) -> vec3<f32> {
return floor(world_normal + 0.0001);
}
// TODO: Clustering
fn compute_key(world_position: vec3<u32>, world_normal: vec3<u32>) -> u32 {
var key = pcg_hash(world_position.x);
key = pcg_hash(key + world_position.y);
key = pcg_hash(key + world_position.z);
key = pcg_hash(key + world_normal.x);
key = pcg_hash(key + world_normal.y);
key = pcg_hash(key + world_normal.z);
return wrap_key(key);
}
fn compute_checksum(world_position: vec3<u32>, world_normal: vec3<u32>) -> u32 {
var key = iqint_hash(world_position.x);
key = iqint_hash(key + world_position.y);
key = iqint_hash(key + world_position.z);
key = iqint_hash(key + world_normal.x);
key = iqint_hash(key + world_normal.y);
key = iqint_hash(key + world_normal.z);
return max(key, 1u); // 0u is reserved for WORLD_CACHE_EMPTY_CELL
}
fn pcg_hash(input: u32) -> u32 {
let state = input * 747796405u + 2891336453u;
let word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
return (word >> 22u) ^ word;
}
fn iqint_hash(input: u32) -> u32 {
let n = (input << 13u) ^ input;
return n * (n * n * 15731u + 789221u) + 1376312589u;
}
fn wrap_key(key: u32) -> u32 {
return key & (#{WORLD_CACHE_SIZE} - 1u);
}