@plasius/gpu-lighting 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +54 -0
  3. package/dist/index.cjs +105 -0
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +102 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/techniques/techniques/hybrid/direct-lighting.job.wgsl +69 -2
  8. package/dist/techniques/techniques/hybrid/final-gather.job.wgsl +62 -2
  9. package/dist/techniques/techniques/hybrid/prelude.wgsl +140 -0
  10. package/dist/techniques/techniques/hybrid/radiance-cache.job.wgsl +57 -2
  11. package/dist/techniques/techniques/hybrid/reflection-resolve.job.wgsl +235 -2
  12. package/dist/techniques/techniques/hybrid/screen-trace.job.wgsl +219 -2
  13. package/dist/techniques/techniques/pathtracer/accumulate.job.wgsl +64 -2
  14. package/dist/techniques/techniques/pathtracer/denoise.job.wgsl +125 -2
  15. package/dist/techniques/techniques/pathtracer/pathtrace.job.wgsl +429 -2
  16. package/dist/techniques/techniques/pathtracer/prelude.wgsl +260 -0
  17. package/package.json +1 -1
  18. package/src/index.js +114 -0
  19. package/src/techniques/hybrid/direct-lighting.job.wgsl +69 -2
  20. package/src/techniques/hybrid/final-gather.job.wgsl +62 -2
  21. package/src/techniques/hybrid/prelude.wgsl +140 -0
  22. package/src/techniques/hybrid/radiance-cache.job.wgsl +57 -2
  23. package/src/techniques/hybrid/reflection-resolve.job.wgsl +235 -2
  24. package/src/techniques/hybrid/screen-trace.job.wgsl +219 -2
  25. package/src/techniques/pathtracer/accumulate.job.wgsl +64 -2
  26. package/src/techniques/pathtracer/denoise.job.wgsl +125 -2
  27. package/src/techniques/pathtracer/pathtrace.job.wgsl +429 -2
  28. package/src/techniques/pathtracer/prelude.wgsl +260 -0
@@ -3,6 +3,14 @@ struct HybridFrameParams {
3
3
  max_trace_steps: u32,
4
4
  history_weight: f32,
5
5
  exposure: f32,
6
+ image_width: u32,
7
+ image_height: u32,
8
+ reflection_reset: u32,
9
+ sky_mode: u32,
10
+ sky_intensity: f32,
11
+ max_reflection_distance: f32,
12
+ roughness_bias: f32,
13
+ thickness: f32,
6
14
  };
7
15
 
8
16
  struct HybridHit {
@@ -10,6 +18,138 @@ struct HybridHit {
10
18
  hit_distance: f32,
11
19
  };
12
20
 
21
+ struct HybridReflectionCamera {
22
+ position: vec3<f32>,
23
+ _padding0: f32,
24
+ };
25
+
26
+ struct HybridReflectionSurface {
27
+ position: vec4<f32>,
28
+ normal_roughness: vec4<f32>,
29
+ albedo_metalness: vec4<f32>,
30
+ emission_occlusion: vec4<f32>,
31
+ };
32
+
33
+ struct HybridGroundPlane {
34
+ normal: vec3<f32>,
35
+ height: f32,
36
+ material_index: u32,
37
+ enabled: u32,
38
+ _padding0: vec2<u32>,
39
+ };
40
+
41
+ struct HybridReflectionSceneMetadata {
42
+ sphere_count: u32,
43
+ material_count: u32,
44
+ _padding0: vec2<u32>,
45
+ max_trace_distance: f32,
46
+ };
47
+
48
+ struct HybridReflectionMaterial {
49
+ albedo_roughness: vec4<f32>,
50
+ emission_metalness: vec4<f32>,
51
+ };
52
+
53
+ struct HybridReflectionSphere {
54
+ center_radius: vec4<f32>,
55
+ material_index: u32,
56
+ flags: u32,
57
+ _padding0: vec2<u32>,
58
+ };
59
+
60
+ struct HybridReflectionTrace {
61
+ hit_mask: u32,
62
+ distance: f32,
63
+ position: vec3<f32>,
64
+ material_index: u32,
65
+ normal: vec3<f32>,
66
+ _padding1: u32,
67
+ };
68
+
69
+ struct HybridReflectionPixel {
70
+ reflection_confidence: vec4<f32>,
71
+ hit_normal_distance: vec4<f32>,
72
+ };
73
+
74
+ struct HybridLightingPixel {
75
+ radiance_confidence: vec4<f32>,
76
+ normal_occlusion: vec4<f32>,
77
+ };
78
+
79
+ struct HybridScreenTracePixel {
80
+ radiance_confidence: vec4<f32>,
81
+ hit_normal_distance: vec4<f32>,
82
+ };
83
+
84
+ struct HybridRadianceCacheEntry {
85
+ irradiance_validity: vec4<f32>,
86
+ bent_normal_depth: vec4<f32>,
87
+ };
88
+
13
89
  fn encode_history_weight(value: f32) -> f32 {
14
90
  return clamp(value, 0.0, 1.0);
15
91
  }
92
+
93
+ fn hybrid_saturate(value: f32) -> f32 {
94
+ return clamp(value, 0.0, 1.0);
95
+ }
96
+
97
+ fn hybrid_safe_normalize(value: vec3<f32>) -> vec3<f32> {
98
+ if (dot(value, value) <= 0.000001) {
99
+ return vec3<f32>(0.0, 1.0, 0.0);
100
+ }
101
+
102
+ return normalize(value);
103
+ }
104
+
105
+ fn hybrid_fresnel_schlick(cos_theta: f32, f0: vec3<f32>) -> vec3<f32> {
106
+ let factor = pow(1.0 - hybrid_saturate(cos_theta), 5.0);
107
+ return f0 + (vec3<f32>(1.0) - f0) * factor;
108
+ }
109
+
110
+ fn hybrid_surface_f0(albedo: vec3<f32>, metalness: f32) -> vec3<f32> {
111
+ return vec3<f32>(0.04) * (1.0 - metalness) + albedo * metalness;
112
+ }
113
+
114
+ fn hybrid_luminance(color: vec3<f32>) -> f32 {
115
+ return dot(color, vec3<f32>(0.2126, 0.7152, 0.0722));
116
+ }
117
+
118
+ fn hybrid_hash_u32(value: u32) -> u32 {
119
+ var x = value + 0x9e3779b9u;
120
+ x = (x ^ (x >> 16u)) * 0x85ebca6bu;
121
+ x = (x ^ (x >> 13u)) * 0xc2b2ae35u;
122
+ return x ^ (x >> 16u);
123
+ }
124
+
125
+ fn hybrid_random_f32(state: ptr<function, u32>) -> f32 {
126
+ let next = hybrid_hash_u32((*state) ^ 0x7f4a7c15u);
127
+ *state = next;
128
+ return f32(next) * 2.3283064365386963e-10;
129
+ }
130
+
131
+ fn hybrid_sample_unit_sphere(state: ptr<function, u32>) -> vec3<f32> {
132
+ let z = hybrid_random_f32(state) * 2.0 - 1.0;
133
+ let angle = 6.283185307179586 * hybrid_random_f32(state);
134
+ let radius = sqrt(max(1.0 - z * z, 0.0));
135
+ return vec3<f32>(radius * cos(angle), radius * sin(angle), z);
136
+ }
137
+
138
+ fn hybrid_environment(direction: vec3<f32>, intensity: f32, mode: u32) -> vec3<f32> {
139
+ let up_factor = hybrid_saturate(direction.y * 0.5 + 0.5);
140
+ let horizon_color = vec3<f32>(0.54, 0.64, 0.77);
141
+ let zenith_color = select(
142
+ vec3<f32>(0.04, 0.09, 0.18),
143
+ vec3<f32>(0.09, 0.07, 0.16),
144
+ mode == 1u
145
+ );
146
+ let sun_direction = hybrid_safe_normalize(vec3<f32>(0.31, 0.92, 0.22));
147
+ let sun_glow = pow(hybrid_saturate(dot(direction, sun_direction)), 192.0);
148
+ let sunset_bias = select(vec3<f32>(0.0), vec3<f32>(0.55, 0.24, 0.08) * (1.0 - up_factor), mode == 1u);
149
+ return (
150
+ horizon_color * (1.0 - up_factor) +
151
+ zenith_color * up_factor +
152
+ sunset_bias +
153
+ vec3<f32>(5.8, 5.5, 5.0) * sun_glow
154
+ ) * max(intensity, 0.0001);
155
+ }
@@ -1,3 +1,58 @@
1
- fn process_job() {
2
- // Placeholder radiance cache update stage for indirect lighting reuse.
1
+ @group(0) @binding(0) var<uniform> hybridFrameParams: HybridFrameParams;
2
+ @group(0) @binding(1) var<storage, read> hybridReflectionSurfaces: array<HybridReflectionSurface>;
3
+ @group(0) @binding(2) var<storage, read> hybridDirectLightingInput: array<HybridLightingPixel>;
4
+ @group(0) @binding(3) var<storage, read> hybridRadianceCacheHistory: array<HybridRadianceCacheEntry>;
5
+ @group(0) @binding(4) var<storage, read_write> hybridRadianceCacheOutput: array<HybridRadianceCacheEntry>;
6
+
7
+ fn radiance_cache_index(pixel: vec2<u32>) -> u32 {
8
+ return pixel.y * max(hybridFrameParams.image_width, 1u) + pixel.x;
9
+ }
10
+
11
+ @compute @workgroup_size(8, 8, 1)
12
+ fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
13
+ if (
14
+ global_id.x >= hybridFrameParams.image_width ||
15
+ global_id.y >= hybridFrameParams.image_height
16
+ ) {
17
+ return;
18
+ }
19
+
20
+ let index = radiance_cache_index(global_id.xy);
21
+ let surface = hybridReflectionSurfaces[index];
22
+ let direct = hybridDirectLightingInput[index];
23
+ let previous = hybridRadianceCacheHistory[index];
24
+ let normal = hybrid_safe_normalize(surface.normal_roughness.xyz);
25
+ let roughness = clamp(surface.normal_roughness.w + hybridFrameParams.roughness_bias, 0.02, 1.0);
26
+ let occlusion = hybrid_saturate(surface.emission_occlusion.w);
27
+ let sky_probe = hybrid_environment(normal, hybridFrameParams.sky_intensity, hybridFrameParams.sky_mode);
28
+ let direct_irradiance = direct.radiance_confidence.xyz * (0.28 + (1.0 - roughness) * 0.12);
29
+ let cache_fill = sky_probe * (0.08 + occlusion * 0.12);
30
+ let current_irradiance = direct_irradiance + cache_fill;
31
+ let bent_normal = hybrid_safe_normalize(
32
+ normal * (0.75 + occlusion * 0.25) +
33
+ previous.bent_normal_depth.xyz * previous.irradiance_validity.w * 0.2
34
+ );
35
+ let history_weight = select(
36
+ 0.0,
37
+ encode_history_weight(hybridFrameParams.history_weight) * previous.irradiance_validity.w,
38
+ hybridFrameParams.reflection_reset == 0u && previous.irradiance_validity.w > 0.0
39
+ );
40
+ let resolved_irradiance =
41
+ previous.irradiance_validity.xyz * history_weight +
42
+ current_irradiance * (1.0 - history_weight);
43
+ let validity = clamp(
44
+ hybrid_luminance(resolved_irradiance) * 0.05 +
45
+ occlusion * 0.35 +
46
+ (1.0 - roughness) * 0.15,
47
+ 0.0,
48
+ 1.0
49
+ );
50
+ let probe_depth =
51
+ previous.bent_normal_depth.w * history_weight +
52
+ length(surface.position.xyz) * (1.0 - history_weight);
53
+
54
+ hybridRadianceCacheOutput[index] = HybridRadianceCacheEntry(
55
+ vec4<f32>(resolved_irradiance, validity),
56
+ vec4<f32>(bent_normal, probe_depth)
57
+ );
3
58
  }
@@ -1,3 +1,236 @@
1
- fn process_job() {
2
- // Placeholder reflection resolve stage for rough/specular response.
1
+ @group(0) @binding(0) var<uniform> hybridFrameParams: HybridFrameParams;
2
+ @group(0) @binding(1) var<uniform> hybridReflectionCamera: HybridReflectionCamera;
3
+ @group(0) @binding(2) var<storage, read> hybridReflectionSurfaces: array<HybridReflectionSurface>;
4
+ @group(0) @binding(3) var<storage, read> hybridReflectionHistory: array<HybridReflectionPixel>;
5
+ @group(0) @binding(4) var<storage, read> hybridReflectionScene: HybridReflectionSceneMetadata;
6
+ @group(0) @binding(5) var<uniform> hybridGroundPlane: HybridGroundPlane;
7
+ @group(0) @binding(6) var<storage, read> hybridReflectionMaterials: array<HybridReflectionMaterial>;
8
+ @group(0) @binding(7) var<storage, read> hybridReflectionSpheres: array<HybridReflectionSphere>;
9
+ @group(0) @binding(8) var<storage, read_write> hybridReflectionOutput: array<HybridReflectionPixel>;
10
+
11
+ fn reflection_index(pixel: vec2<u32>) -> u32 {
12
+ return pixel.y * max(hybridFrameParams.image_width, 1u) + pixel.x;
13
+ }
14
+
15
+ fn unpack_reflection_material(index: u32) -> HybridReflectionMaterial {
16
+ if (hybridReflectionScene.material_count == 0u) {
17
+ return HybridReflectionMaterial(
18
+ vec4<f32>(0.7, 0.72, 0.76, 0.4),
19
+ vec4<f32>(0.0, 0.0, 0.0, 0.0)
20
+ );
21
+ }
22
+
23
+ let safe_index = min(index, hybridReflectionScene.material_count - 1u);
24
+ return hybridReflectionMaterials[safe_index];
25
+ }
26
+
27
+ fn miss_reflection() -> HybridReflectionTrace {
28
+ return HybridReflectionTrace(
29
+ 0u,
30
+ hybridReflectionScene.max_trace_distance,
31
+ vec3<f32>(0.0),
32
+ 0u,
33
+ vec3<f32>(0.0, 1.0, 0.0),
34
+ 0u
35
+ );
36
+ }
37
+
38
+ fn set_best_reflection(best: ptr<function, HybridReflectionTrace>, candidate: HybridReflectionTrace) {
39
+ if (candidate.hit_mask == 1u && candidate.distance < (*best).distance) {
40
+ *best = candidate;
41
+ }
42
+ }
43
+
44
+ fn trace_reflection_ground(
45
+ origin: vec3<f32>,
46
+ direction: vec3<f32>,
47
+ best: ptr<function, HybridReflectionTrace>
48
+ ) {
49
+ if (hybridGroundPlane.enabled == 0u) {
50
+ return;
51
+ }
52
+
53
+ let plane_normal = hybrid_safe_normalize(hybridGroundPlane.normal);
54
+ let denominator = dot(plane_normal, direction);
55
+ if (abs(denominator) <= 0.0005) {
56
+ return;
57
+ }
58
+
59
+ let distance = -(dot(plane_normal, origin) + hybridGroundPlane.height) / denominator;
60
+ if (distance <= 0.0005 || distance >= (*best).distance || distance >= hybridReflectionScene.max_trace_distance) {
61
+ return;
62
+ }
63
+
64
+ set_best_reflection(
65
+ best,
66
+ HybridReflectionTrace(
67
+ 1u,
68
+ distance,
69
+ origin + direction * distance,
70
+ hybridGroundPlane.material_index,
71
+ plane_normal,
72
+ 0u
73
+ )
74
+ );
75
+ }
76
+
77
+ fn trace_reflection_spheres(
78
+ origin: vec3<f32>,
79
+ direction: vec3<f32>,
80
+ best: ptr<function, HybridReflectionTrace>
81
+ ) {
82
+ var index = 0u;
83
+ loop {
84
+ if (index >= hybridReflectionScene.sphere_count) {
85
+ break;
86
+ }
87
+
88
+ let sphere = hybridReflectionSpheres[index];
89
+ let offset = origin - sphere.center_radius.xyz;
90
+ let a = dot(direction, direction);
91
+ let half_b = dot(offset, direction);
92
+ let c = dot(offset, offset) - sphere.center_radius.w * sphere.center_radius.w;
93
+ let discriminant = half_b * half_b - a * c;
94
+ if (discriminant >= 0.0) {
95
+ let root = sqrt(discriminant);
96
+ var distance = (-half_b - root) / max(a, 0.0005);
97
+ if (distance <= 0.0005) {
98
+ distance = (-half_b + root) / max(a, 0.0005);
99
+ }
100
+ if (distance > 0.0005 && distance < (*best).distance && distance < hybridReflectionScene.max_trace_distance) {
101
+ let position = origin + direction * distance;
102
+ let normal = hybrid_safe_normalize(position - sphere.center_radius.xyz);
103
+ set_best_reflection(
104
+ best,
105
+ HybridReflectionTrace(
106
+ 1u,
107
+ distance,
108
+ position,
109
+ sphere.material_index,
110
+ normal,
111
+ 0u
112
+ )
113
+ );
114
+ }
115
+ }
116
+
117
+ index = index + 1u;
118
+ }
119
+ }
120
+
121
+ fn trace_reflection_scene(origin: vec3<f32>, direction: vec3<f32>) -> HybridReflectionTrace {
122
+ var best = miss_reflection();
123
+ trace_reflection_ground(origin, direction, &best);
124
+ trace_reflection_spheres(origin, direction, &best);
125
+ return best;
126
+ }
127
+
128
+ fn evaluate_hit_lighting(trace: HybridReflectionTrace, view_direction: vec3<f32>) -> vec3<f32> {
129
+ let material = unpack_reflection_material(trace.material_index);
130
+ let surface_normal = hybrid_safe_normalize(trace.normal);
131
+ let light_direction = hybrid_safe_normalize(vec3<f32>(0.31, 0.92, 0.22));
132
+ let ndotl = hybrid_saturate(dot(surface_normal, light_direction));
133
+ let halfway = hybrid_safe_normalize(light_direction + view_direction);
134
+ let roughness = clamp(material.albedo_roughness.w, 0.02, 1.0);
135
+ let metalness = hybrid_saturate(material.emission_metalness.w);
136
+ let albedo = material.albedo_roughness.xyz;
137
+ let f0 = vec3<f32>(0.04) * (1.0 - metalness) + albedo * metalness;
138
+ let fresnel = hybrid_fresnel_schlick(
139
+ hybrid_saturate(dot(surface_normal, view_direction)),
140
+ f0
141
+ );
142
+ let diffuse = albedo * ndotl * 0.3183098861837907 * (1.0 - metalness);
143
+ let specular_power = 12.0 + (1.0 - roughness) * 96.0;
144
+ let specular = fresnel * pow(hybrid_saturate(dot(surface_normal, halfway)), specular_power) * ndotl;
145
+ let direct_light = vec3<f32>(5.4, 5.0, 4.6) * ndotl;
146
+ let environment = hybrid_environment(surface_normal, hybridFrameParams.sky_intensity, hybridFrameParams.sky_mode);
147
+ return material.emission_metalness.xyz + (diffuse + specular) * direct_light + environment * 0.08;
148
+ }
149
+
150
+ @compute @workgroup_size(8, 8, 1)
151
+ fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
152
+ if (
153
+ global_id.x >= hybridFrameParams.image_width ||
154
+ global_id.y >= hybridFrameParams.image_height
155
+ ) {
156
+ return;
157
+ }
158
+
159
+ let index = reflection_index(global_id.xy);
160
+ let surface = hybridReflectionSurfaces[index];
161
+ let previous = hybridReflectionHistory[index];
162
+ let position = surface.position.xyz;
163
+ let normal = hybrid_safe_normalize(surface.normal_roughness.xyz);
164
+ let roughness = clamp(surface.normal_roughness.w + hybridFrameParams.roughness_bias, 0.02, 1.0);
165
+ let albedo = surface.albedo_metalness.xyz;
166
+ let metalness = hybrid_saturate(surface.albedo_metalness.w);
167
+ let occlusion = hybrid_saturate(surface.emission_occlusion.w);
168
+ let view_direction = hybrid_safe_normalize(hybridReflectionCamera.position - position);
169
+ let f0 = vec3<f32>(0.04) * (1.0 - metalness) + albedo * metalness;
170
+ let fresnel = hybrid_fresnel_schlick(hybrid_saturate(dot(normal, view_direction)), f0);
171
+ let grazing_boost = pow(1.0 - hybrid_saturate(dot(normal, view_direction)), 5.0);
172
+ let reflection_budget = clamp(
173
+ (metalness * 0.85 + max(fresnel.x, max(fresnel.y, fresnel.z))) *
174
+ (1.0 - roughness * 0.65) +
175
+ grazing_boost * 0.2,
176
+ 0.0,
177
+ 1.0
178
+ );
179
+ let trace_needed = reflection_budget > 0.02;
180
+ var random_state = hybrid_hash_u32(
181
+ hybridFrameParams.frame_index * 747796405u +
182
+ index * 2891336453u +
183
+ 0x27d4eb2du
184
+ );
185
+ let reflection_direction = hybrid_safe_normalize(
186
+ reflect(-view_direction, normal) +
187
+ hybrid_sample_unit_sphere(&random_state) * roughness * roughness * 0.35
188
+ );
189
+ var trace = miss_reflection();
190
+ if (trace_needed) {
191
+ trace = trace_reflection_scene(
192
+ position + normal * max(hybridFrameParams.thickness, 0.0005),
193
+ reflection_direction
194
+ );
195
+ }
196
+ let hit_radiance = select(
197
+ hybrid_environment(reflection_direction, hybridFrameParams.sky_intensity, hybridFrameParams.sky_mode),
198
+ evaluate_hit_lighting(trace, -reflection_direction),
199
+ trace.hit_mask == 1u
200
+ );
201
+ let roughness_mix = roughness * roughness;
202
+ let distant_fade = exp(
203
+ -trace.distance /
204
+ max(hybridFrameParams.max_reflection_distance * 0.65, 0.0005)
205
+ );
206
+ let reflection_color =
207
+ hit_radiance * reflection_budget * distant_fade * occlusion;
208
+ let sky_lobe = hybrid_environment(normal, hybridFrameParams.sky_intensity, hybridFrameParams.sky_mode);
209
+ let shaped_reflection =
210
+ reflection_color * (1.0 - roughness_mix * 0.35) +
211
+ sky_lobe * roughness_mix * 0.18 * reflection_budget;
212
+ let history_weight = select(
213
+ 0.0,
214
+ encode_history_weight(hybridFrameParams.history_weight),
215
+ hybridFrameParams.reflection_reset == 0u && previous.reflection_confidence.w > 0.0
216
+ );
217
+ let resolved =
218
+ previous.reflection_confidence.xyz * history_weight +
219
+ shaped_reflection * (1.0 - history_weight);
220
+ let confidence = clamp(
221
+ reflection_budget * (0.45 + distant_fade * 0.55) * (1.0 - roughness * 0.35),
222
+ 0.0,
223
+ 1.0
224
+ );
225
+ let resolved_normal = select(normal, hybrid_safe_normalize(trace.normal), trace.hit_mask == 1u);
226
+ let resolved_distance = select(
227
+ hybridFrameParams.max_reflection_distance,
228
+ trace.distance,
229
+ trace.hit_mask == 1u
230
+ );
231
+
232
+ hybridReflectionOutput[index] = HybridReflectionPixel(
233
+ vec4<f32>(resolved * hybridFrameParams.exposure, confidence),
234
+ vec4<f32>(resolved_normal, resolved_distance)
235
+ );
3
236
  }
@@ -1,3 +1,220 @@
1
- fn process_job() {
2
- // Placeholder screen-space tracing stage for first-hit reuse.
1
+ @group(0) @binding(0) var<uniform> hybridFrameParams: HybridFrameParams;
2
+ @group(0) @binding(1) var<uniform> hybridReflectionCamera: HybridReflectionCamera;
3
+ @group(0) @binding(2) var<storage, read> hybridReflectionSurfaces: array<HybridReflectionSurface>;
4
+ @group(0) @binding(3) var<storage, read> hybridScreenTraceHistory: array<HybridScreenTracePixel>;
5
+ @group(0) @binding(4) var<storage, read> hybridReflectionScene: HybridReflectionSceneMetadata;
6
+ @group(0) @binding(5) var<uniform> hybridGroundPlane: HybridGroundPlane;
7
+ @group(0) @binding(6) var<storage, read> hybridReflectionMaterials: array<HybridReflectionMaterial>;
8
+ @group(0) @binding(7) var<storage, read> hybridReflectionSpheres: array<HybridReflectionSphere>;
9
+ @group(0) @binding(8) var<storage, read_write> hybridScreenTraceOutput: array<HybridScreenTracePixel>;
10
+
11
+ fn screen_trace_index(pixel: vec2<u32>) -> u32 {
12
+ return pixel.y * max(hybridFrameParams.image_width, 1u) + pixel.x;
13
+ }
14
+
15
+ fn unpack_reflection_material(index: u32) -> HybridReflectionMaterial {
16
+ if (hybridReflectionScene.material_count == 0u) {
17
+ return HybridReflectionMaterial(
18
+ vec4<f32>(0.7, 0.72, 0.76, 0.4),
19
+ vec4<f32>(0.0, 0.0, 0.0, 0.0)
20
+ );
21
+ }
22
+
23
+ let safe_index = min(index, hybridReflectionScene.material_count - 1u);
24
+ return hybridReflectionMaterials[safe_index];
25
+ }
26
+
27
+ fn miss_trace() -> HybridReflectionTrace {
28
+ return HybridReflectionTrace(
29
+ 0u,
30
+ hybridReflectionScene.max_trace_distance,
31
+ vec3<f32>(0.0),
32
+ 0u,
33
+ vec3<f32>(0.0, 1.0, 0.0),
34
+ 0u
35
+ );
36
+ }
37
+
38
+ fn set_best_trace(best: ptr<function, HybridReflectionTrace>, candidate: HybridReflectionTrace) {
39
+ if (candidate.hit_mask == 1u && candidate.distance < (*best).distance) {
40
+ *best = candidate;
41
+ }
42
+ }
43
+
44
+ fn trace_ground(
45
+ origin: vec3<f32>,
46
+ direction: vec3<f32>,
47
+ best: ptr<function, HybridReflectionTrace>
48
+ ) {
49
+ if (hybridGroundPlane.enabled == 0u) {
50
+ return;
51
+ }
52
+
53
+ let plane_normal = hybrid_safe_normalize(hybridGroundPlane.normal);
54
+ let denominator = dot(plane_normal, direction);
55
+ if (abs(denominator) <= 0.0005) {
56
+ return;
57
+ }
58
+
59
+ let distance = -(dot(plane_normal, origin) + hybridGroundPlane.height) / denominator;
60
+ if (distance <= 0.0005 || distance >= (*best).distance || distance >= hybridReflectionScene.max_trace_distance) {
61
+ return;
62
+ }
63
+
64
+ set_best_trace(
65
+ best,
66
+ HybridReflectionTrace(
67
+ 1u,
68
+ distance,
69
+ origin + direction * distance,
70
+ hybridGroundPlane.material_index,
71
+ plane_normal,
72
+ 0u
73
+ )
74
+ );
75
+ }
76
+
77
+ fn trace_spheres(
78
+ origin: vec3<f32>,
79
+ direction: vec3<f32>,
80
+ best: ptr<function, HybridReflectionTrace>
81
+ ) {
82
+ var index = 0u;
83
+ loop {
84
+ if (index >= hybridReflectionScene.sphere_count) {
85
+ break;
86
+ }
87
+
88
+ let sphere = hybridReflectionSpheres[index];
89
+ let offset = origin - sphere.center_radius.xyz;
90
+ let a = dot(direction, direction);
91
+ let half_b = dot(offset, direction);
92
+ let c = dot(offset, offset) - sphere.center_radius.w * sphere.center_radius.w;
93
+ let discriminant = half_b * half_b - a * c;
94
+ if (discriminant >= 0.0) {
95
+ let root = sqrt(discriminant);
96
+ var distance = (-half_b - root) / max(a, 0.0005);
97
+ if (distance <= 0.0005) {
98
+ distance = (-half_b + root) / max(a, 0.0005);
99
+ }
100
+ if (distance > 0.0005 && distance < (*best).distance && distance < hybridReflectionScene.max_trace_distance) {
101
+ let position = origin + direction * distance;
102
+ let normal = hybrid_safe_normalize(position - sphere.center_radius.xyz);
103
+ set_best_trace(
104
+ best,
105
+ HybridReflectionTrace(
106
+ 1u,
107
+ distance,
108
+ position,
109
+ sphere.material_index,
110
+ normal,
111
+ 0u
112
+ )
113
+ );
114
+ }
115
+ }
116
+
117
+ index = index + 1u;
118
+ }
119
+ }
120
+
121
+ fn trace_screen_scene(origin: vec3<f32>, direction: vec3<f32>) -> HybridReflectionTrace {
122
+ var best = miss_trace();
123
+ trace_ground(origin, direction, &best);
124
+ trace_spheres(origin, direction, &best);
125
+ return best;
126
+ }
127
+
128
+ fn evaluate_hit_radiance(trace: HybridReflectionTrace, reflection_direction: vec3<f32>) -> vec3<f32> {
129
+ let material = unpack_reflection_material(trace.material_index);
130
+ let normal = hybrid_safe_normalize(trace.normal);
131
+ let view_direction = -reflection_direction;
132
+ let light_direction = hybrid_safe_normalize(vec3<f32>(0.31, 0.92, 0.22));
133
+ let ndotl = hybrid_saturate(dot(normal, light_direction));
134
+ let halfway = hybrid_safe_normalize(light_direction + view_direction);
135
+ let roughness = clamp(material.albedo_roughness.w, 0.02, 1.0);
136
+ let metalness = hybrid_saturate(material.emission_metalness.w);
137
+ let albedo = material.albedo_roughness.xyz;
138
+ let fresnel = hybrid_fresnel_schlick(
139
+ hybrid_saturate(dot(normal, view_direction)),
140
+ hybrid_surface_f0(albedo, metalness)
141
+ );
142
+ let diffuse = albedo * ndotl * 0.3183098861837907 * (1.0 - metalness);
143
+ let specular = fresnel * pow(hybrid_saturate(dot(normal, halfway)), 12.0 + (1.0 - roughness) * 84.0) * ndotl;
144
+ let sky_fill = hybrid_environment(normal, hybridFrameParams.sky_intensity, hybridFrameParams.sky_mode);
145
+ return material.emission_metalness.xyz + (diffuse + specular) * vec3<f32>(4.8, 4.5, 4.1) + sky_fill * 0.1;
146
+ }
147
+
148
+ @compute @workgroup_size(8, 8, 1)
149
+ fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
150
+ if (
151
+ global_id.x >= hybridFrameParams.image_width ||
152
+ global_id.y >= hybridFrameParams.image_height
153
+ ) {
154
+ return;
155
+ }
156
+
157
+ let index = screen_trace_index(global_id.xy);
158
+ let surface = hybridReflectionSurfaces[index];
159
+ let previous = hybridScreenTraceHistory[index];
160
+ let position = surface.position.xyz;
161
+ let normal = hybrid_safe_normalize(surface.normal_roughness.xyz);
162
+ let roughness = clamp(surface.normal_roughness.w + hybridFrameParams.roughness_bias, 0.02, 1.0);
163
+ let albedo = surface.albedo_metalness.xyz;
164
+ let metalness = hybrid_saturate(surface.albedo_metalness.w);
165
+ let occlusion = hybrid_saturate(surface.emission_occlusion.w);
166
+ let view_direction = hybrid_safe_normalize(hybridReflectionCamera.position - position);
167
+ var random_state = hybrid_hash_u32(
168
+ hybridFrameParams.frame_index * 1664525u +
169
+ index * 1013904223u +
170
+ 0x68bc21ebu
171
+ );
172
+ let reflected_direction = hybrid_safe_normalize(
173
+ reflect(-view_direction, normal) +
174
+ hybrid_sample_unit_sphere(&random_state) * roughness * roughness * 0.25
175
+ );
176
+ let trace = trace_screen_scene(
177
+ position + normal * max(hybridFrameParams.thickness, 0.0005),
178
+ reflected_direction
179
+ );
180
+ let sky_fallback = hybrid_environment(
181
+ reflected_direction,
182
+ hybridFrameParams.sky_intensity,
183
+ hybridFrameParams.sky_mode
184
+ );
185
+ let hit_radiance = select(
186
+ sky_fallback,
187
+ evaluate_hit_radiance(trace, reflected_direction),
188
+ trace.hit_mask == 1u
189
+ );
190
+ let reflection_budget = clamp(
191
+ max(hybrid_surface_f0(albedo, metalness).x, max(albedo.y * metalness, albedo.z * metalness)) +
192
+ (1.0 - roughness) * 0.45,
193
+ 0.0,
194
+ 1.0
195
+ );
196
+ let trace_confidence = clamp(
197
+ reflection_budget * occlusion * select(0.35, 0.9, trace.hit_mask == 1u),
198
+ 0.0,
199
+ 1.0
200
+ );
201
+ let history_weight = select(
202
+ 0.0,
203
+ encode_history_weight(hybridFrameParams.history_weight),
204
+ hybridFrameParams.reflection_reset == 0u && previous.radiance_confidence.w > 0.0
205
+ );
206
+ let resolved_radiance =
207
+ previous.radiance_confidence.xyz * history_weight +
208
+ hit_radiance * trace_confidence * (1.0 - history_weight);
209
+ let resolved_normal = select(normal, hybrid_safe_normalize(trace.normal), trace.hit_mask == 1u);
210
+ let resolved_distance = select(
211
+ hybridFrameParams.max_reflection_distance,
212
+ trace.distance,
213
+ trace.hit_mask == 1u
214
+ );
215
+
216
+ hybridScreenTraceOutput[index] = HybridScreenTracePixel(
217
+ vec4<f32>(resolved_radiance * hybridFrameParams.exposure, trace_confidence),
218
+ vec4<f32>(resolved_normal, resolved_distance)
219
+ );
3
220
  }