@plasius/gpu-lighting 0.1.17 → 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.
- package/CHANGELOG.md +19 -0
- package/README.md +4 -0
- package/dist/techniques/techniques/hybrid/direct-lighting.job.wgsl +69 -2
- package/dist/techniques/techniques/hybrid/final-gather.job.wgsl +62 -2
- package/dist/techniques/techniques/hybrid/prelude.wgsl +23 -0
- package/dist/techniques/techniques/hybrid/radiance-cache.job.wgsl +57 -2
- package/dist/techniques/techniques/hybrid/screen-trace.job.wgsl +219 -2
- package/package.json +1 -1
- package/src/techniques/hybrid/direct-lighting.job.wgsl +69 -2
- package/src/techniques/hybrid/final-gather.job.wgsl +62 -2
- package/src/techniques/hybrid/prelude.wgsl +23 -0
- package/src/techniques/hybrid/radiance-cache.job.wgsl +57 -2
- package/src/techniques/hybrid/screen-trace.job.wgsl +219 -2
package/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,24 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
|
|
|
20
20
|
- **Security**
|
|
21
21
|
- (placeholder)
|
|
22
22
|
|
|
23
|
+
## [0.1.18] - 2026-06-01
|
|
24
|
+
|
|
25
|
+
- **Added**
|
|
26
|
+
- Concrete hybrid realtime WGSL kernels for `directLighting`,
|
|
27
|
+
`screenTrace`, `radianceCache`, and `finalGather`.
|
|
28
|
+
|
|
29
|
+
- **Changed**
|
|
30
|
+
- README now documents the delivered hybrid realtime kernel scope alongside
|
|
31
|
+
the existing pathtracer and reflection-resolve stages.
|
|
32
|
+
|
|
33
|
+
- **Fixed**
|
|
34
|
+
- Hybrid realtime technique exports no longer ship placeholder WGSL bodies
|
|
35
|
+
for the direct-lighting, screen-trace, radiance-cache, and final-gather
|
|
36
|
+
stages.
|
|
37
|
+
|
|
38
|
+
- **Security**
|
|
39
|
+
- (placeholder)
|
|
40
|
+
|
|
23
41
|
## [0.1.17] - 2026-05-13
|
|
24
42
|
|
|
25
43
|
- **Added**
|
|
@@ -308,3 +326,4 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
|
|
|
308
326
|
[0.1.15]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.1.15
|
|
309
327
|
[0.1.16]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.1.16
|
|
310
328
|
[0.1.17]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.1.17
|
|
329
|
+
[0.1.18]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.1.18
|
package/README.md
CHANGED
|
@@ -172,6 +172,10 @@ reference-first mode you described:
|
|
|
172
172
|
|
|
173
173
|
The package now ships concrete WGSL contracts for:
|
|
174
174
|
|
|
175
|
+
- `hybrid.directLighting`: direct sun/sky resolve with roughness-aware specular shaping
|
|
176
|
+
- `hybrid.screenTrace`: first-hit reflection tracing over the shared hybrid scene contracts
|
|
177
|
+
- `hybrid.radianceCache`: irradiance history updates for cache-backed indirect reuse
|
|
178
|
+
- `hybrid.finalGather`: cache + trace composition with temporal reuse for the hybrid GI path
|
|
175
179
|
- `pathtracer.pathTrace`: analytic scene tracing, bounce integration, and sky fallback
|
|
176
180
|
- `pathtracer.accumulate`: progressive history resolve with reset handling
|
|
177
181
|
- `pathtracer.denoise`: spatial-temporal bilateral filtering for reference previews
|
|
@@ -1,3 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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_write> hybridDirectLightingOutput: array<HybridLightingPixel>;
|
|
5
|
+
|
|
6
|
+
fn direct_lighting_index(pixel: vec2<u32>) -> u32 {
|
|
7
|
+
return pixel.y * max(hybridFrameParams.image_width, 1u) + pixel.x;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
fn evaluate_direct_sun(
|
|
11
|
+
normal: vec3<f32>,
|
|
12
|
+
view_direction: vec3<f32>,
|
|
13
|
+
albedo: vec3<f32>,
|
|
14
|
+
roughness: f32,
|
|
15
|
+
metalness: f32
|
|
16
|
+
) -> vec3<f32> {
|
|
17
|
+
let sun_direction = hybrid_safe_normalize(vec3<f32>(0.31, 0.92, 0.22));
|
|
18
|
+
let ndotl = hybrid_saturate(dot(normal, sun_direction));
|
|
19
|
+
if (ndotl <= 0.0) {
|
|
20
|
+
return vec3<f32>(0.0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let halfway = hybrid_safe_normalize(sun_direction + view_direction);
|
|
24
|
+
let f0 = hybrid_surface_f0(albedo, metalness);
|
|
25
|
+
let fresnel = hybrid_fresnel_schlick(
|
|
26
|
+
hybrid_saturate(dot(normal, view_direction)),
|
|
27
|
+
f0
|
|
28
|
+
);
|
|
29
|
+
let diffuse = albedo * ndotl * 0.3183098861837907 * (1.0 - metalness);
|
|
30
|
+
let specular_power = 10.0 + (1.0 - roughness) * 112.0;
|
|
31
|
+
let specular = fresnel * pow(hybrid_saturate(dot(normal, halfway)), specular_power) * ndotl;
|
|
32
|
+
let sun_color = vec3<f32>(6.2, 5.8, 5.1) * max(hybridFrameParams.sky_intensity, 0.0001);
|
|
33
|
+
return (diffuse + specular) * sun_color;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@compute @workgroup_size(8, 8, 1)
|
|
37
|
+
fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
38
|
+
if (
|
|
39
|
+
global_id.x >= hybridFrameParams.image_width ||
|
|
40
|
+
global_id.y >= hybridFrameParams.image_height
|
|
41
|
+
) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let index = direct_lighting_index(global_id.xy);
|
|
46
|
+
let surface = hybridReflectionSurfaces[index];
|
|
47
|
+
let position = surface.position.xyz;
|
|
48
|
+
let normal = hybrid_safe_normalize(surface.normal_roughness.xyz);
|
|
49
|
+
let roughness = clamp(surface.normal_roughness.w + hybridFrameParams.roughness_bias, 0.02, 1.0);
|
|
50
|
+
let albedo = surface.albedo_metalness.xyz;
|
|
51
|
+
let metalness = hybrid_saturate(surface.albedo_metalness.w);
|
|
52
|
+
let emission = surface.emission_occlusion.xyz;
|
|
53
|
+
let occlusion = hybrid_saturate(surface.emission_occlusion.w);
|
|
54
|
+
let view_direction = hybrid_safe_normalize(hybridReflectionCamera.position - position);
|
|
55
|
+
let direct_sun = evaluate_direct_sun(normal, view_direction, albedo, roughness, metalness);
|
|
56
|
+
let sky_fill = hybrid_environment(normal, hybridFrameParams.sky_intensity, hybridFrameParams.sky_mode);
|
|
57
|
+
let grazing = pow(1.0 - hybrid_saturate(dot(normal, view_direction)), 4.0);
|
|
58
|
+
let ambient = sky_fill * (0.08 + grazing * 0.06) * occlusion;
|
|
59
|
+
let radiance = emission + direct_sun * occlusion + ambient;
|
|
60
|
+
let confidence = clamp(
|
|
61
|
+
hybrid_luminance(radiance) * 0.045 + occlusion * 0.35 + (1.0 - roughness) * 0.15,
|
|
62
|
+
0.0,
|
|
63
|
+
1.0
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
hybridDirectLightingOutput[index] = HybridLightingPixel(
|
|
67
|
+
vec4<f32>(radiance * hybridFrameParams.exposure, confidence),
|
|
68
|
+
vec4<f32>(normal, occlusion)
|
|
69
|
+
);
|
|
3
70
|
}
|
|
@@ -1,3 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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> hybridScreenTraceInput: array<HybridScreenTracePixel>;
|
|
5
|
+
@group(0) @binding(4) var<storage, read> hybridRadianceCacheInput: array<HybridRadianceCacheEntry>;
|
|
6
|
+
@group(0) @binding(5) var<storage, read_write> hybridFinalGatherOutput: array<HybridLightingPixel>;
|
|
7
|
+
|
|
8
|
+
fn final_gather_index(pixel: vec2<u32>) -> u32 {
|
|
9
|
+
return pixel.y * max(hybridFrameParams.image_width, 1u) + pixel.x;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@compute @workgroup_size(8, 8, 1)
|
|
13
|
+
fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
14
|
+
if (
|
|
15
|
+
global_id.x >= hybridFrameParams.image_width ||
|
|
16
|
+
global_id.y >= hybridFrameParams.image_height
|
|
17
|
+
) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let index = final_gather_index(global_id.xy);
|
|
22
|
+
let surface = hybridReflectionSurfaces[index];
|
|
23
|
+
let direct = hybridDirectLightingInput[index];
|
|
24
|
+
let trace = hybridScreenTraceInput[index];
|
|
25
|
+
let cache = hybridRadianceCacheInput[index];
|
|
26
|
+
let previous = hybridFinalGatherOutput[index];
|
|
27
|
+
let normal = hybrid_safe_normalize(surface.normal_roughness.xyz);
|
|
28
|
+
let roughness = clamp(surface.normal_roughness.w + hybridFrameParams.roughness_bias, 0.02, 1.0);
|
|
29
|
+
let metalness = hybrid_saturate(surface.albedo_metalness.w);
|
|
30
|
+
let emission = surface.emission_occlusion.xyz;
|
|
31
|
+
let occlusion = hybrid_saturate(surface.emission_occlusion.w);
|
|
32
|
+
let direct_radiance = direct.radiance_confidence.xyz;
|
|
33
|
+
let trace_radiance = trace.radiance_confidence.xyz;
|
|
34
|
+
let cache_irradiance = cache.irradiance_validity.xyz;
|
|
35
|
+
let indirect_gi =
|
|
36
|
+
cache_irradiance * occlusion * (0.32 + (1.0 - roughness) * 0.28);
|
|
37
|
+
let reflection_term =
|
|
38
|
+
trace_radiance *
|
|
39
|
+
trace.radiance_confidence.w *
|
|
40
|
+
(0.18 + (1.0 - roughness) * 0.42 + metalness * 0.25);
|
|
41
|
+
let ambient = hybrid_environment(normal, hybridFrameParams.sky_intensity, hybridFrameParams.sky_mode) * 0.05 * occlusion;
|
|
42
|
+
let current_radiance = direct_radiance + indirect_gi + reflection_term + emission + ambient;
|
|
43
|
+
let history_weight = select(
|
|
44
|
+
0.0,
|
|
45
|
+
encode_history_weight(hybridFrameParams.history_weight),
|
|
46
|
+
hybridFrameParams.reflection_reset == 0u && previous.radiance_confidence.w > 0.0
|
|
47
|
+
);
|
|
48
|
+
let resolved_radiance =
|
|
49
|
+
previous.radiance_confidence.xyz * history_weight +
|
|
50
|
+
current_radiance * (1.0 - history_weight);
|
|
51
|
+
let confidence = clamp(
|
|
52
|
+
direct.radiance_confidence.w * 0.4 +
|
|
53
|
+
cache.irradiance_validity.w * 0.3 +
|
|
54
|
+
trace.radiance_confidence.w * 0.3,
|
|
55
|
+
0.0,
|
|
56
|
+
1.0
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
hybridFinalGatherOutput[index] = HybridLightingPixel(
|
|
60
|
+
vec4<f32>(resolved_radiance, confidence),
|
|
61
|
+
vec4<f32>(normal, occlusion)
|
|
62
|
+
);
|
|
3
63
|
}
|
|
@@ -71,6 +71,21 @@ struct HybridReflectionPixel {
|
|
|
71
71
|
hit_normal_distance: vec4<f32>,
|
|
72
72
|
};
|
|
73
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
|
+
|
|
74
89
|
fn encode_history_weight(value: f32) -> f32 {
|
|
75
90
|
return clamp(value, 0.0, 1.0);
|
|
76
91
|
}
|
|
@@ -92,6 +107,14 @@ fn hybrid_fresnel_schlick(cos_theta: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
|
92
107
|
return f0 + (vec3<f32>(1.0) - f0) * factor;
|
|
93
108
|
}
|
|
94
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
|
+
|
|
95
118
|
fn hybrid_hash_u32(value: u32) -> u32 {
|
|
96
119
|
var x = value + 0x9e3779b9u;
|
|
97
120
|
x = (x ^ (x >> 16u)) * 0x85ebca6bu;
|
|
@@ -1,3 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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,220 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
}
|
package/package.json
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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_write> hybridDirectLightingOutput: array<HybridLightingPixel>;
|
|
5
|
+
|
|
6
|
+
fn direct_lighting_index(pixel: vec2<u32>) -> u32 {
|
|
7
|
+
return pixel.y * max(hybridFrameParams.image_width, 1u) + pixel.x;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
fn evaluate_direct_sun(
|
|
11
|
+
normal: vec3<f32>,
|
|
12
|
+
view_direction: vec3<f32>,
|
|
13
|
+
albedo: vec3<f32>,
|
|
14
|
+
roughness: f32,
|
|
15
|
+
metalness: f32
|
|
16
|
+
) -> vec3<f32> {
|
|
17
|
+
let sun_direction = hybrid_safe_normalize(vec3<f32>(0.31, 0.92, 0.22));
|
|
18
|
+
let ndotl = hybrid_saturate(dot(normal, sun_direction));
|
|
19
|
+
if (ndotl <= 0.0) {
|
|
20
|
+
return vec3<f32>(0.0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let halfway = hybrid_safe_normalize(sun_direction + view_direction);
|
|
24
|
+
let f0 = hybrid_surface_f0(albedo, metalness);
|
|
25
|
+
let fresnel = hybrid_fresnel_schlick(
|
|
26
|
+
hybrid_saturate(dot(normal, view_direction)),
|
|
27
|
+
f0
|
|
28
|
+
);
|
|
29
|
+
let diffuse = albedo * ndotl * 0.3183098861837907 * (1.0 - metalness);
|
|
30
|
+
let specular_power = 10.0 + (1.0 - roughness) * 112.0;
|
|
31
|
+
let specular = fresnel * pow(hybrid_saturate(dot(normal, halfway)), specular_power) * ndotl;
|
|
32
|
+
let sun_color = vec3<f32>(6.2, 5.8, 5.1) * max(hybridFrameParams.sky_intensity, 0.0001);
|
|
33
|
+
return (diffuse + specular) * sun_color;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@compute @workgroup_size(8, 8, 1)
|
|
37
|
+
fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
38
|
+
if (
|
|
39
|
+
global_id.x >= hybridFrameParams.image_width ||
|
|
40
|
+
global_id.y >= hybridFrameParams.image_height
|
|
41
|
+
) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let index = direct_lighting_index(global_id.xy);
|
|
46
|
+
let surface = hybridReflectionSurfaces[index];
|
|
47
|
+
let position = surface.position.xyz;
|
|
48
|
+
let normal = hybrid_safe_normalize(surface.normal_roughness.xyz);
|
|
49
|
+
let roughness = clamp(surface.normal_roughness.w + hybridFrameParams.roughness_bias, 0.02, 1.0);
|
|
50
|
+
let albedo = surface.albedo_metalness.xyz;
|
|
51
|
+
let metalness = hybrid_saturate(surface.albedo_metalness.w);
|
|
52
|
+
let emission = surface.emission_occlusion.xyz;
|
|
53
|
+
let occlusion = hybrid_saturate(surface.emission_occlusion.w);
|
|
54
|
+
let view_direction = hybrid_safe_normalize(hybridReflectionCamera.position - position);
|
|
55
|
+
let direct_sun = evaluate_direct_sun(normal, view_direction, albedo, roughness, metalness);
|
|
56
|
+
let sky_fill = hybrid_environment(normal, hybridFrameParams.sky_intensity, hybridFrameParams.sky_mode);
|
|
57
|
+
let grazing = pow(1.0 - hybrid_saturate(dot(normal, view_direction)), 4.0);
|
|
58
|
+
let ambient = sky_fill * (0.08 + grazing * 0.06) * occlusion;
|
|
59
|
+
let radiance = emission + direct_sun * occlusion + ambient;
|
|
60
|
+
let confidence = clamp(
|
|
61
|
+
hybrid_luminance(radiance) * 0.045 + occlusion * 0.35 + (1.0 - roughness) * 0.15,
|
|
62
|
+
0.0,
|
|
63
|
+
1.0
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
hybridDirectLightingOutput[index] = HybridLightingPixel(
|
|
67
|
+
vec4<f32>(radiance * hybridFrameParams.exposure, confidence),
|
|
68
|
+
vec4<f32>(normal, occlusion)
|
|
69
|
+
);
|
|
3
70
|
}
|
|
@@ -1,3 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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> hybridScreenTraceInput: array<HybridScreenTracePixel>;
|
|
5
|
+
@group(0) @binding(4) var<storage, read> hybridRadianceCacheInput: array<HybridRadianceCacheEntry>;
|
|
6
|
+
@group(0) @binding(5) var<storage, read_write> hybridFinalGatherOutput: array<HybridLightingPixel>;
|
|
7
|
+
|
|
8
|
+
fn final_gather_index(pixel: vec2<u32>) -> u32 {
|
|
9
|
+
return pixel.y * max(hybridFrameParams.image_width, 1u) + pixel.x;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@compute @workgroup_size(8, 8, 1)
|
|
13
|
+
fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
14
|
+
if (
|
|
15
|
+
global_id.x >= hybridFrameParams.image_width ||
|
|
16
|
+
global_id.y >= hybridFrameParams.image_height
|
|
17
|
+
) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let index = final_gather_index(global_id.xy);
|
|
22
|
+
let surface = hybridReflectionSurfaces[index];
|
|
23
|
+
let direct = hybridDirectLightingInput[index];
|
|
24
|
+
let trace = hybridScreenTraceInput[index];
|
|
25
|
+
let cache = hybridRadianceCacheInput[index];
|
|
26
|
+
let previous = hybridFinalGatherOutput[index];
|
|
27
|
+
let normal = hybrid_safe_normalize(surface.normal_roughness.xyz);
|
|
28
|
+
let roughness = clamp(surface.normal_roughness.w + hybridFrameParams.roughness_bias, 0.02, 1.0);
|
|
29
|
+
let metalness = hybrid_saturate(surface.albedo_metalness.w);
|
|
30
|
+
let emission = surface.emission_occlusion.xyz;
|
|
31
|
+
let occlusion = hybrid_saturate(surface.emission_occlusion.w);
|
|
32
|
+
let direct_radiance = direct.radiance_confidence.xyz;
|
|
33
|
+
let trace_radiance = trace.radiance_confidence.xyz;
|
|
34
|
+
let cache_irradiance = cache.irradiance_validity.xyz;
|
|
35
|
+
let indirect_gi =
|
|
36
|
+
cache_irradiance * occlusion * (0.32 + (1.0 - roughness) * 0.28);
|
|
37
|
+
let reflection_term =
|
|
38
|
+
trace_radiance *
|
|
39
|
+
trace.radiance_confidence.w *
|
|
40
|
+
(0.18 + (1.0 - roughness) * 0.42 + metalness * 0.25);
|
|
41
|
+
let ambient = hybrid_environment(normal, hybridFrameParams.sky_intensity, hybridFrameParams.sky_mode) * 0.05 * occlusion;
|
|
42
|
+
let current_radiance = direct_radiance + indirect_gi + reflection_term + emission + ambient;
|
|
43
|
+
let history_weight = select(
|
|
44
|
+
0.0,
|
|
45
|
+
encode_history_weight(hybridFrameParams.history_weight),
|
|
46
|
+
hybridFrameParams.reflection_reset == 0u && previous.radiance_confidence.w > 0.0
|
|
47
|
+
);
|
|
48
|
+
let resolved_radiance =
|
|
49
|
+
previous.radiance_confidence.xyz * history_weight +
|
|
50
|
+
current_radiance * (1.0 - history_weight);
|
|
51
|
+
let confidence = clamp(
|
|
52
|
+
direct.radiance_confidence.w * 0.4 +
|
|
53
|
+
cache.irradiance_validity.w * 0.3 +
|
|
54
|
+
trace.radiance_confidence.w * 0.3,
|
|
55
|
+
0.0,
|
|
56
|
+
1.0
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
hybridFinalGatherOutput[index] = HybridLightingPixel(
|
|
60
|
+
vec4<f32>(resolved_radiance, confidence),
|
|
61
|
+
vec4<f32>(normal, occlusion)
|
|
62
|
+
);
|
|
3
63
|
}
|
|
@@ -71,6 +71,21 @@ struct HybridReflectionPixel {
|
|
|
71
71
|
hit_normal_distance: vec4<f32>,
|
|
72
72
|
};
|
|
73
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
|
+
|
|
74
89
|
fn encode_history_weight(value: f32) -> f32 {
|
|
75
90
|
return clamp(value, 0.0, 1.0);
|
|
76
91
|
}
|
|
@@ -92,6 +107,14 @@ fn hybrid_fresnel_schlick(cos_theta: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
|
92
107
|
return f0 + (vec3<f32>(1.0) - f0) * factor;
|
|
93
108
|
}
|
|
94
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
|
+
|
|
95
118
|
fn hybrid_hash_u32(value: u32) -> u32 {
|
|
96
119
|
var x = value + 0x9e3779b9u;
|
|
97
120
|
x = (x ^ (x >> 16u)) * 0x85ebca6bu;
|
|
@@ -1,3 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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,220 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
}
|