@plasius/gpu-lighting 0.1.16 → 0.1.17
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 +24 -0
- package/README.md +50 -0
- package/dist/index.cjs +105 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -1
- package/dist/techniques/techniques/hybrid/prelude.wgsl +117 -0
- package/dist/techniques/techniques/hybrid/reflection-resolve.job.wgsl +235 -2
- package/dist/techniques/techniques/pathtracer/accumulate.job.wgsl +64 -2
- package/dist/techniques/techniques/pathtracer/denoise.job.wgsl +125 -2
- package/dist/techniques/techniques/pathtracer/pathtrace.job.wgsl +429 -2
- package/dist/techniques/techniques/pathtracer/prelude.wgsl +260 -0
- package/package.json +1 -1
- package/src/index.js +114 -0
- package/src/techniques/hybrid/prelude.wgsl +117 -0
- package/src/techniques/hybrid/reflection-resolve.job.wgsl +235 -2
- package/src/techniques/pathtracer/accumulate.job.wgsl +64 -2
- package/src/techniques/pathtracer/denoise.job.wgsl +125 -2
- package/src/techniques/pathtracer/pathtrace.job.wgsl +429 -2
- package/src/techniques/pathtracer/prelude.wgsl +260 -0
package/src/index.js
CHANGED
|
@@ -131,6 +131,16 @@ export const lightingProfiles = Object.freeze(
|
|
|
131
131
|
export const lightingProfileNames = Object.freeze(Object.keys(lightingProfiles));
|
|
132
132
|
|
|
133
133
|
export const defaultLightingProfile = "realtime";
|
|
134
|
+
export const lightingProfileModeOrder = Object.freeze([
|
|
135
|
+
"realtime",
|
|
136
|
+
"hybrid",
|
|
137
|
+
"reference",
|
|
138
|
+
]);
|
|
139
|
+
export const defaultAdaptiveLightingProfilePolicy = Object.freeze({
|
|
140
|
+
preferredProfile: "reference",
|
|
141
|
+
minimumFrameRate: 30,
|
|
142
|
+
sampleWindowSize: 4,
|
|
143
|
+
});
|
|
134
144
|
export const lightingDistanceBands = Object.freeze([
|
|
135
145
|
"near",
|
|
136
146
|
"mid",
|
|
@@ -199,6 +209,23 @@ function assertLightingImportance(name, value) {
|
|
|
199
209
|
return value;
|
|
200
210
|
}
|
|
201
211
|
|
|
212
|
+
function readPositiveIntegerOption(name, value, fallback) {
|
|
213
|
+
if (value === undefined) {
|
|
214
|
+
return fallback;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (
|
|
218
|
+
typeof value !== "number" ||
|
|
219
|
+
!Number.isFinite(value) ||
|
|
220
|
+
value <= 0 ||
|
|
221
|
+
Math.round(value) !== value
|
|
222
|
+
) {
|
|
223
|
+
throw new Error(`${name} must be a positive integer.`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
|
|
202
229
|
function resolveBandParticipation(profileName, band, importance) {
|
|
203
230
|
const referenceProfile = profileName === "reference";
|
|
204
231
|
const premiumImportance =
|
|
@@ -276,6 +303,93 @@ export function createLightingBandPlan(options = {}) {
|
|
|
276
303
|
});
|
|
277
304
|
}
|
|
278
305
|
|
|
306
|
+
const lightingProfileModeEstimatedCostMs = Object.freeze({
|
|
307
|
+
realtime: 4.5,
|
|
308
|
+
hybrid: 7.5,
|
|
309
|
+
reference: 12.5,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
export function createLightingProfileModeLadder(options = {}) {
|
|
313
|
+
const moduleId =
|
|
314
|
+
typeof options.id === "string" && options.id.trim().length > 0
|
|
315
|
+
? options.id.trim()
|
|
316
|
+
: "lighting-profile-mode";
|
|
317
|
+
const preferredProfile = getLightingProfile(
|
|
318
|
+
options.preferredProfile ?? defaultAdaptiveLightingProfilePolicy.preferredProfile
|
|
319
|
+
).name;
|
|
320
|
+
const initialProfile = getLightingProfile(
|
|
321
|
+
options.initialProfile ?? preferredProfile
|
|
322
|
+
).name;
|
|
323
|
+
const minimumFrameRate = readPositiveIntegerOption(
|
|
324
|
+
"minimumFrameRate",
|
|
325
|
+
options.minimumFrameRate,
|
|
326
|
+
defaultAdaptiveLightingProfilePolicy.minimumFrameRate
|
|
327
|
+
);
|
|
328
|
+
const sampleWindowSize = readPositiveIntegerOption(
|
|
329
|
+
"sampleWindowSize",
|
|
330
|
+
options.sampleWindowSize,
|
|
331
|
+
defaultAdaptiveLightingProfilePolicy.sampleWindowSize
|
|
332
|
+
);
|
|
333
|
+
const importance = assertLightingImportance(
|
|
334
|
+
"importance",
|
|
335
|
+
options.importance ?? "high"
|
|
336
|
+
);
|
|
337
|
+
const moduleImportance = assertLightingImportance(
|
|
338
|
+
"moduleImportance",
|
|
339
|
+
options.moduleImportance ?? "critical"
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const levels = Object.freeze(
|
|
343
|
+
lightingProfileModeOrder.map((profileName) => {
|
|
344
|
+
const profile = getLightingProfile(profileName);
|
|
345
|
+
return Object.freeze({
|
|
346
|
+
id: profile.name,
|
|
347
|
+
estimatedCostMs: lightingProfileModeEstimatedCostMs[profile.name],
|
|
348
|
+
config: Object.freeze({
|
|
349
|
+
profile: profile.name,
|
|
350
|
+
description: profile.description,
|
|
351
|
+
techniques: Object.freeze([...profile.techniques]),
|
|
352
|
+
lightingBandPlan: createLightingBandPlan({
|
|
353
|
+
profile: profile.name,
|
|
354
|
+
importance,
|
|
355
|
+
}),
|
|
356
|
+
policy: Object.freeze({
|
|
357
|
+
preferredProfile,
|
|
358
|
+
minimumFrameRate,
|
|
359
|
+
sampleWindowSize,
|
|
360
|
+
}),
|
|
361
|
+
}),
|
|
362
|
+
});
|
|
363
|
+
})
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
return Object.freeze({
|
|
367
|
+
id: moduleId,
|
|
368
|
+
domain: "lighting",
|
|
369
|
+
authority: "visual",
|
|
370
|
+
importance: moduleImportance,
|
|
371
|
+
initialLevel: initialProfile,
|
|
372
|
+
levels,
|
|
373
|
+
target: Object.freeze({
|
|
374
|
+
minimumFrameRate,
|
|
375
|
+
maximumFrameRate: minimumFrameRate,
|
|
376
|
+
preferredFrameRates: Object.freeze([minimumFrameRate]),
|
|
377
|
+
}),
|
|
378
|
+
adaptation: Object.freeze({
|
|
379
|
+
sampleWindowSize,
|
|
380
|
+
minimumSamplesBeforeAdjustment: sampleWindowSize,
|
|
381
|
+
degradeCooldownFrames: 1,
|
|
382
|
+
upgradeCooldownFrames: sampleWindowSize,
|
|
383
|
+
minStableFramesForRecovery: sampleWindowSize,
|
|
384
|
+
}),
|
|
385
|
+
policy: Object.freeze({
|
|
386
|
+
preferredProfile,
|
|
387
|
+
minimumFrameRate,
|
|
388
|
+
sampleWindowSize,
|
|
389
|
+
}),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
279
393
|
function buildWorkerBudgetLevels(jobType, queueClass, presets) {
|
|
280
394
|
return Object.freeze([
|
|
281
395
|
Object.freeze({
|
|
@@ -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,115 @@ 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
|
+
|
|
13
74
|
fn encode_history_weight(value: f32) -> f32 {
|
|
14
75
|
return clamp(value, 0.0, 1.0);
|
|
15
76
|
}
|
|
77
|
+
|
|
78
|
+
fn hybrid_saturate(value: f32) -> f32 {
|
|
79
|
+
return clamp(value, 0.0, 1.0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn hybrid_safe_normalize(value: vec3<f32>) -> vec3<f32> {
|
|
83
|
+
if (dot(value, value) <= 0.000001) {
|
|
84
|
+
return vec3<f32>(0.0, 1.0, 0.0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return normalize(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn hybrid_fresnel_schlick(cos_theta: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
91
|
+
let factor = pow(1.0 - hybrid_saturate(cos_theta), 5.0);
|
|
92
|
+
return f0 + (vec3<f32>(1.0) - f0) * factor;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fn hybrid_hash_u32(value: u32) -> u32 {
|
|
96
|
+
var x = value + 0x9e3779b9u;
|
|
97
|
+
x = (x ^ (x >> 16u)) * 0x85ebca6bu;
|
|
98
|
+
x = (x ^ (x >> 13u)) * 0xc2b2ae35u;
|
|
99
|
+
return x ^ (x >> 16u);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn hybrid_random_f32(state: ptr<function, u32>) -> f32 {
|
|
103
|
+
let next = hybrid_hash_u32((*state) ^ 0x7f4a7c15u);
|
|
104
|
+
*state = next;
|
|
105
|
+
return f32(next) * 2.3283064365386963e-10;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn hybrid_sample_unit_sphere(state: ptr<function, u32>) -> vec3<f32> {
|
|
109
|
+
let z = hybrid_random_f32(state) * 2.0 - 1.0;
|
|
110
|
+
let angle = 6.283185307179586 * hybrid_random_f32(state);
|
|
111
|
+
let radius = sqrt(max(1.0 - z * z, 0.0));
|
|
112
|
+
return vec3<f32>(radius * cos(angle), radius * sin(angle), z);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fn hybrid_environment(direction: vec3<f32>, intensity: f32, mode: u32) -> vec3<f32> {
|
|
116
|
+
let up_factor = hybrid_saturate(direction.y * 0.5 + 0.5);
|
|
117
|
+
let horizon_color = vec3<f32>(0.54, 0.64, 0.77);
|
|
118
|
+
let zenith_color = select(
|
|
119
|
+
vec3<f32>(0.04, 0.09, 0.18),
|
|
120
|
+
vec3<f32>(0.09, 0.07, 0.16),
|
|
121
|
+
mode == 1u
|
|
122
|
+
);
|
|
123
|
+
let sun_direction = hybrid_safe_normalize(vec3<f32>(0.31, 0.92, 0.22));
|
|
124
|
+
let sun_glow = pow(hybrid_saturate(dot(direction, sun_direction)), 192.0);
|
|
125
|
+
let sunset_bias = select(vec3<f32>(0.0), vec3<f32>(0.55, 0.24, 0.08) * (1.0 - up_factor), mode == 1u);
|
|
126
|
+
return (
|
|
127
|
+
horizon_color * (1.0 - up_factor) +
|
|
128
|
+
zenith_color * up_factor +
|
|
129
|
+
sunset_bias +
|
|
130
|
+
vec3<f32>(5.8, 5.5, 5.0) * sun_glow
|
|
131
|
+
) * max(intensity, 0.0001);
|
|
132
|
+
}
|
|
@@ -1,3 +1,236 @@
|
|
|
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> 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,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
@group(0) @binding(0) var<uniform> pathTracerParams: PathTracerParams;
|
|
2
|
+
@group(0) @binding(1) var<storage, read> pathSampleBuffer: array<PathSamplePixel>;
|
|
3
|
+
@group(0) @binding(2) var<storage, read_write> pathAccumulationBuffer: array<PathAccumulationPixel>;
|
|
4
|
+
|
|
5
|
+
fn accumulation_index(pixel: vec2<u32>) -> u32 {
|
|
6
|
+
return pixel.y * max(pathTracerParams.image_width, 1u) + pixel.x;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
fn reset_accumulation(sample: PathSamplePixel) -> PathAccumulationPixel {
|
|
10
|
+
let sample_luminance = luminance(sample.radiance_opacity.xyz);
|
|
11
|
+
return PathAccumulationPixel(
|
|
12
|
+
vec4<f32>(sample.radiance_opacity.xyz, max(sample.albedo_sample_count.w, 1.0)),
|
|
13
|
+
vec4<f32>(sample_luminance, 0.0, 0.0, f32(pathTracerParams.frame_index))
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@compute @workgroup_size(8, 8, 1)
|
|
18
|
+
fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
19
|
+
if (
|
|
20
|
+
global_id.x >= pathTracerParams.image_width ||
|
|
21
|
+
global_id.y >= pathTracerParams.image_height
|
|
22
|
+
) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let index = accumulation_index(global_id.xy);
|
|
27
|
+
let sample = pathSampleBuffer[index];
|
|
28
|
+
let history = pathAccumulationBuffer[index];
|
|
29
|
+
let sample_luminance = luminance(sample.radiance_opacity.xyz);
|
|
30
|
+
let current_samples = max(sample.albedo_sample_count.w, 1.0);
|
|
31
|
+
let history_samples = max(history.integrated_radiance.w, 0.0);
|
|
32
|
+
let should_reset =
|
|
33
|
+
pathTracerParams.accumulation_reset != 0u ||
|
|
34
|
+
history_samples <= 0.0 ||
|
|
35
|
+
history.moments.w <= 0.0;
|
|
36
|
+
|
|
37
|
+
if (should_reset) {
|
|
38
|
+
pathAccumulationBuffer[index] = reset_accumulation(sample);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let combined_samples = min(history_samples + current_samples, 4096.0);
|
|
43
|
+
let progressive_weight = current_samples / max(combined_samples, 1.0);
|
|
44
|
+
let minimum_refresh = 1.0 - clamp(pathTracerParams.history_blend, 0.0, 0.98);
|
|
45
|
+
let blend = clamp(max(progressive_weight, minimum_refresh), 0.02, 1.0);
|
|
46
|
+
let integrated =
|
|
47
|
+
history.integrated_radiance.xyz * (1.0 - blend) +
|
|
48
|
+
sample.radiance_opacity.xyz * blend;
|
|
49
|
+
let mean_luminance =
|
|
50
|
+
history.moments.x * (1.0 - blend) +
|
|
51
|
+
sample_luminance * blend;
|
|
52
|
+
let variance_sample = sample_luminance - mean_luminance;
|
|
53
|
+
let variance =
|
|
54
|
+
max(0.0, history.moments.y * (1.0 - blend) + variance_sample * variance_sample * blend);
|
|
55
|
+
|
|
56
|
+
pathAccumulationBuffer[index] = PathAccumulationPixel(
|
|
57
|
+
vec4<f32>(integrated, combined_samples),
|
|
58
|
+
vec4<f32>(
|
|
59
|
+
mean_luminance,
|
|
60
|
+
variance,
|
|
61
|
+
1.0 - blend,
|
|
62
|
+
f32(pathTracerParams.frame_index)
|
|
63
|
+
)
|
|
64
|
+
);
|
|
3
65
|
}
|
|
@@ -1,3 +1,126 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
@group(0) @binding(0) var<uniform> pathTracerParams: PathTracerParams;
|
|
2
|
+
@group(0) @binding(1) var<storage, read> pathAccumulationBuffer: array<PathAccumulationPixel>;
|
|
3
|
+
@group(0) @binding(2) var<storage, read> pathSampleBuffer: array<PathSamplePixel>;
|
|
4
|
+
@group(0) @binding(3) var<storage, read_write> pathDenoiseHistoryBuffer: array<PathDenoisePixel>;
|
|
5
|
+
|
|
6
|
+
fn denoise_index(pixel: vec2<u32>) -> u32 {
|
|
7
|
+
return pixel.y * max(pathTracerParams.image_width, 1u) + pixel.x;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
fn in_bounds(pixel: vec2<i32>) -> bool {
|
|
11
|
+
return
|
|
12
|
+
pixel.x >= 0 &&
|
|
13
|
+
pixel.y >= 0 &&
|
|
14
|
+
pixel.x < i32(pathTracerParams.image_width) &&
|
|
15
|
+
pixel.y < i32(pathTracerParams.image_height);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fn spatial_kernel(offset: vec2<i32>) -> f32 {
|
|
19
|
+
if (offset.x == 0 && offset.y == 0) {
|
|
20
|
+
return 4.0;
|
|
21
|
+
}
|
|
22
|
+
if (offset.x == 0 || offset.y == 0) {
|
|
23
|
+
return 2.0;
|
|
24
|
+
}
|
|
25
|
+
return 1.0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn bilateral_weight(
|
|
29
|
+
center_sample: PathSamplePixel,
|
|
30
|
+
center_accumulation: PathAccumulationPixel,
|
|
31
|
+
neighbor_sample: PathSamplePixel,
|
|
32
|
+
offset: vec2<i32>
|
|
33
|
+
) -> f32 {
|
|
34
|
+
let strength = clamp(pathTracerParams.denoise_strength, 0.0, 1.0);
|
|
35
|
+
let center_normal = safe_normalize(center_sample.normal_roughness.xyz);
|
|
36
|
+
let neighbor_normal = safe_normalize(neighbor_sample.normal_roughness.xyz);
|
|
37
|
+
let normal_alignment = pow(
|
|
38
|
+
saturate(dot(center_normal, neighbor_normal)),
|
|
39
|
+
6.0 + (1.0 - strength) * 24.0
|
|
40
|
+
);
|
|
41
|
+
let depth_delta = abs(center_sample.direction_distance.w - neighbor_sample.direction_distance.w);
|
|
42
|
+
let depth_scale =
|
|
43
|
+
max(center_sample.direction_distance.w, 1.0) *
|
|
44
|
+
(0.01 + strength * 0.04);
|
|
45
|
+
let depth_weight = exp(-depth_delta * safe_rcp(depth_scale));
|
|
46
|
+
let albedo_delta = length(
|
|
47
|
+
center_sample.albedo_sample_count.xyz - neighbor_sample.albedo_sample_count.xyz
|
|
48
|
+
);
|
|
49
|
+
let albedo_weight = exp(-albedo_delta * (2.0 + strength * 2.0));
|
|
50
|
+
let sample_weight = clamp(
|
|
51
|
+
neighbor_sample.albedo_sample_count.w /
|
|
52
|
+
max(center_sample.albedo_sample_count.w, 1.0),
|
|
53
|
+
0.25,
|
|
54
|
+
1.5
|
|
55
|
+
);
|
|
56
|
+
let variance_penalty = 1.0 / (1.0 + center_accumulation.moments.y * 0.5);
|
|
57
|
+
return spatial_kernel(offset) *
|
|
58
|
+
max(normal_alignment * depth_weight * albedo_weight * sample_weight * variance_penalty, 0.0001);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@compute @workgroup_size(8, 8, 1)
|
|
62
|
+
fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
63
|
+
if (
|
|
64
|
+
global_id.x >= pathTracerParams.image_width ||
|
|
65
|
+
global_id.y >= pathTracerParams.image_height
|
|
66
|
+
) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let pixel = vec2<i32>(i32(global_id.x), i32(global_id.y));
|
|
71
|
+
let index = denoise_index(global_id.xy);
|
|
72
|
+
let center_accumulation = pathAccumulationBuffer[index];
|
|
73
|
+
let center_sample = pathSampleBuffer[index];
|
|
74
|
+
let center_history = pathDenoiseHistoryBuffer[index];
|
|
75
|
+
var filtered = vec3<f32>(0.0);
|
|
76
|
+
var total_weight = 0.0;
|
|
77
|
+
|
|
78
|
+
for (var y = -1; y <= 1; y = y + 1) {
|
|
79
|
+
for (var x = -1; x <= 1; x = x + 1) {
|
|
80
|
+
let neighbor_pixel = pixel + vec2<i32>(x, y);
|
|
81
|
+
if (!in_bounds(neighbor_pixel)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let neighbor_index = denoise_index(vec2<u32>(u32(neighbor_pixel.x), u32(neighbor_pixel.y)));
|
|
86
|
+
let neighbor_accumulation = pathAccumulationBuffer[neighbor_index];
|
|
87
|
+
let neighbor_sample = pathSampleBuffer[neighbor_index];
|
|
88
|
+
let weight = bilateral_weight(
|
|
89
|
+
center_sample,
|
|
90
|
+
center_accumulation,
|
|
91
|
+
neighbor_sample,
|
|
92
|
+
vec2<i32>(x, y)
|
|
93
|
+
);
|
|
94
|
+
filtered = filtered + neighbor_accumulation.integrated_radiance.xyz * weight;
|
|
95
|
+
total_weight = total_weight + weight;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let filtered_color = filtered / max(total_weight, PATH_TRACER_EPSILON);
|
|
100
|
+
let sample_confidence = clamp(
|
|
101
|
+
log2(max(center_accumulation.integrated_radiance.w, 1.0) + 1.0) / 6.0,
|
|
102
|
+
0.0,
|
|
103
|
+
1.0
|
|
104
|
+
);
|
|
105
|
+
let variance_penalty = 1.0 / (1.0 + center_accumulation.moments.y * 0.75);
|
|
106
|
+
let confidence = clamp(sample_confidence * variance_penalty, 0.0, 1.0);
|
|
107
|
+
let use_history =
|
|
108
|
+
pathTracerParams.accumulation_reset == 0u &&
|
|
109
|
+
center_history.filtered_radiance.w > 0.0;
|
|
110
|
+
let temporal_blend = clamp(0.2 + confidence * 0.6, 0.2, 0.9);
|
|
111
|
+
let resolved_color = select(
|
|
112
|
+
filtered_color,
|
|
113
|
+
center_history.filtered_radiance.xyz * (1.0 - temporal_blend) +
|
|
114
|
+
filtered_color * temporal_blend,
|
|
115
|
+
use_history
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
pathDenoiseHistoryBuffer[index] = PathDenoisePixel(
|
|
119
|
+
vec4<f32>(resolved_color, confidence),
|
|
120
|
+
vec4<f32>(safe_normalize(center_sample.normal_roughness.xyz), center_sample.direction_distance.w),
|
|
121
|
+
vec4<f32>(
|
|
122
|
+
center_sample.albedo_sample_count.xyz,
|
|
123
|
+
center_accumulation.integrated_radiance.w
|
|
124
|
+
)
|
|
125
|
+
);
|
|
3
126
|
}
|