@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.
- package/CHANGELOG.md +43 -0
- package/README.md +54 -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/direct-lighting.job.wgsl +69 -2
- package/dist/techniques/techniques/hybrid/final-gather.job.wgsl +62 -2
- package/dist/techniques/techniques/hybrid/prelude.wgsl +140 -0
- package/dist/techniques/techniques/hybrid/radiance-cache.job.wgsl +57 -2
- package/dist/techniques/techniques/hybrid/reflection-resolve.job.wgsl +235 -2
- package/dist/techniques/techniques/hybrid/screen-trace.job.wgsl +219 -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/direct-lighting.job.wgsl +69 -2
- package/src/techniques/hybrid/final-gather.job.wgsl +62 -2
- package/src/techniques/hybrid/prelude.wgsl +140 -0
- package/src/techniques/hybrid/radiance-cache.job.wgsl +57 -2
- package/src/techniques/hybrid/reflection-resolve.job.wgsl +235 -2
- package/src/techniques/hybrid/screen-trace.job.wgsl +219 -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
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
const PATH_TRACER_PI: f32 = 3.141592653589793;
|
|
2
|
+
const PATH_TRACER_INV_PI: f32 = 0.3183098861837907;
|
|
3
|
+
const PATH_TRACER_EPSILON: f32 = 0.0005;
|
|
4
|
+
|
|
1
5
|
struct PathTracerParams {
|
|
2
6
|
frame_index: u32,
|
|
3
7
|
max_bounces: u32,
|
|
4
8
|
samples_per_pixel: u32,
|
|
5
9
|
enable_next_event_estimation: u32,
|
|
10
|
+
image_width: u32,
|
|
11
|
+
image_height: u32,
|
|
12
|
+
accumulation_reset: u32,
|
|
13
|
+
environment_mode: u32,
|
|
14
|
+
environment_intensity: f32,
|
|
15
|
+
exposure: f32,
|
|
16
|
+
history_blend: f32,
|
|
17
|
+
denoise_strength: f32,
|
|
6
18
|
};
|
|
7
19
|
|
|
8
20
|
struct PathSample {
|
|
@@ -10,6 +22,254 @@ struct PathSample {
|
|
|
10
22
|
throughput: vec3<f32>,
|
|
11
23
|
};
|
|
12
24
|
|
|
25
|
+
struct PathTracerCamera {
|
|
26
|
+
origin: vec3<f32>,
|
|
27
|
+
aspect_ratio: f32,
|
|
28
|
+
forward: vec3<f32>,
|
|
29
|
+
vertical_fov_radians: f32,
|
|
30
|
+
right: vec3<f32>,
|
|
31
|
+
focus_distance: f32,
|
|
32
|
+
up: vec3<f32>,
|
|
33
|
+
aperture_radius: f32,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
struct PathTracerSceneMetadata {
|
|
37
|
+
sphere_count: u32,
|
|
38
|
+
triangle_count: u32,
|
|
39
|
+
material_count: u32,
|
|
40
|
+
max_trace_distance: f32,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
struct PathTracerGroundPlane {
|
|
44
|
+
normal: vec3<f32>,
|
|
45
|
+
height: f32,
|
|
46
|
+
material_index: u32,
|
|
47
|
+
enabled: u32,
|
|
48
|
+
_padding0: vec2<u32>,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
struct PathTracerMaterial {
|
|
52
|
+
albedo_roughness: vec4<f32>,
|
|
53
|
+
emission_metalness: vec4<f32>,
|
|
54
|
+
transmittance_ior: vec4<f32>,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
struct PathTracerSphere {
|
|
58
|
+
center_radius: vec4<f32>,
|
|
59
|
+
material_index: u32,
|
|
60
|
+
flags: u32,
|
|
61
|
+
_padding0: vec2<u32>,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
struct PathTracerTriangle {
|
|
65
|
+
position0: vec4<f32>,
|
|
66
|
+
position1: vec4<f32>,
|
|
67
|
+
position2: vec4<f32>,
|
|
68
|
+
normal0: vec4<f32>,
|
|
69
|
+
normal1: vec4<f32>,
|
|
70
|
+
normal2: vec4<f32>,
|
|
71
|
+
material_index: u32,
|
|
72
|
+
flags: u32,
|
|
73
|
+
_padding0: vec2<u32>,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
struct Ray {
|
|
77
|
+
origin: vec3<f32>,
|
|
78
|
+
t_min: f32,
|
|
79
|
+
direction: vec3<f32>,
|
|
80
|
+
t_max: f32,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
struct PathHit {
|
|
84
|
+
hit: u32,
|
|
85
|
+
distance: f32,
|
|
86
|
+
position: vec3<f32>,
|
|
87
|
+
material_index: u32,
|
|
88
|
+
normal: vec3<f32>,
|
|
89
|
+
primitive_kind: u32,
|
|
90
|
+
barycentric: vec3<f32>,
|
|
91
|
+
_padding1: u32,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
struct MaterialSample {
|
|
95
|
+
albedo: vec3<f32>,
|
|
96
|
+
roughness: f32,
|
|
97
|
+
emission: vec3<f32>,
|
|
98
|
+
metalness: f32,
|
|
99
|
+
transmittance: vec3<f32>,
|
|
100
|
+
refractive_index: f32,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
struct PathScatter {
|
|
104
|
+
direction: vec3<f32>,
|
|
105
|
+
pdf: f32,
|
|
106
|
+
attenuation: vec3<f32>,
|
|
107
|
+
event_kind: f32,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
struct PathState {
|
|
111
|
+
throughput_bounce: vec4<f32>,
|
|
112
|
+
origin_active: vec4<f32>,
|
|
113
|
+
direction_pdf: vec4<f32>,
|
|
114
|
+
random_state: u32,
|
|
115
|
+
last_hit_kind: u32,
|
|
116
|
+
_padding0: vec2<u32>,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
struct PathSamplePixel {
|
|
120
|
+
radiance_opacity: vec4<f32>,
|
|
121
|
+
direction_distance: vec4<f32>,
|
|
122
|
+
normal_roughness: vec4<f32>,
|
|
123
|
+
albedo_sample_count: vec4<f32>,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
struct PathAccumulationPixel {
|
|
127
|
+
integrated_radiance: vec4<f32>,
|
|
128
|
+
moments: vec4<f32>,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
struct PathDenoisePixel {
|
|
132
|
+
filtered_radiance: vec4<f32>,
|
|
133
|
+
normal_depth: vec4<f32>,
|
|
134
|
+
albedo_history: vec4<f32>,
|
|
135
|
+
};
|
|
136
|
+
|
|
13
137
|
fn luminance(value: vec3<f32>) -> f32 {
|
|
14
138
|
return dot(value, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
15
139
|
}
|
|
140
|
+
|
|
141
|
+
fn saturate(value: f32) -> f32 {
|
|
142
|
+
return clamp(value, 0.0, 1.0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fn safe_rcp(value: f32) -> f32 {
|
|
146
|
+
if (abs(value) <= PATH_TRACER_EPSILON) {
|
|
147
|
+
return 0.0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return 1.0 / value;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fn safe_normalize(value: vec3<f32>) -> vec3<f32> {
|
|
154
|
+
if (dot(value, value) <= PATH_TRACER_EPSILON) {
|
|
155
|
+
return vec3<f32>(0.0, 1.0, 0.0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return normalize(value);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn make_default_material() -> PathTracerMaterial {
|
|
162
|
+
return PathTracerMaterial(
|
|
163
|
+
vec4<f32>(0.72, 0.74, 0.76, 0.5),
|
|
164
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.0),
|
|
165
|
+
vec4<f32>(0.0, 0.0, 0.0, 1.45)
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn unpack_material(material: PathTracerMaterial) -> MaterialSample {
|
|
170
|
+
return MaterialSample(
|
|
171
|
+
material.albedo_roughness.xyz,
|
|
172
|
+
clamp(material.albedo_roughness.w, 0.02, 1.0),
|
|
173
|
+
material.emission_metalness.xyz,
|
|
174
|
+
saturate(material.emission_metalness.w),
|
|
175
|
+
material.transmittance_ior.xyz,
|
|
176
|
+
max(material.transmittance_ior.w, 1.0)
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fn hash_u32(value: u32) -> u32 {
|
|
181
|
+
var x = value + 0x9e3779b9u;
|
|
182
|
+
x = (x ^ (x >> 16u)) * 0x85ebca6bu;
|
|
183
|
+
x = (x ^ (x >> 13u)) * 0xc2b2ae35u;
|
|
184
|
+
return x ^ (x >> 16u);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn random_f32(state: ptr<function, u32>) -> f32 {
|
|
188
|
+
let next = hash_u32((*state) ^ 0x68bc21ebu);
|
|
189
|
+
*state = next;
|
|
190
|
+
return f32(next) * 2.3283064365386963e-10;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
fn sample_unit_disk(state: ptr<function, u32>) -> vec2<f32> {
|
|
194
|
+
let radius = sqrt(random_f32(state));
|
|
195
|
+
let angle = 2.0 * PATH_TRACER_PI * random_f32(state);
|
|
196
|
+
return vec2<f32>(cos(angle), sin(angle)) * radius;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
fn sample_unit_sphere(state: ptr<function, u32>) -> vec3<f32> {
|
|
200
|
+
let z = random_f32(state) * 2.0 - 1.0;
|
|
201
|
+
let angle = 2.0 * PATH_TRACER_PI * random_f32(state);
|
|
202
|
+
let radius = sqrt(max(1.0 - z * z, 0.0));
|
|
203
|
+
return vec3<f32>(radius * cos(angle), radius * sin(angle), z);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fn build_tangent(normal: vec3<f32>) -> vec3<f32> {
|
|
207
|
+
let basis = select(
|
|
208
|
+
vec3<f32>(0.0, 1.0, 0.0),
|
|
209
|
+
vec3<f32>(1.0, 0.0, 0.0),
|
|
210
|
+
abs(normal.y) > 0.999
|
|
211
|
+
);
|
|
212
|
+
return safe_normalize(cross(basis, normal));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
fn build_bitangent(normal: vec3<f32>, tangent: vec3<f32>) -> vec3<f32> {
|
|
216
|
+
return safe_normalize(cross(normal, tangent));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
fn sample_cosine_hemisphere(normal: vec3<f32>, state: ptr<function, u32>) -> vec3<f32> {
|
|
220
|
+
let disk = sample_unit_disk(state);
|
|
221
|
+
let z = sqrt(max(1.0 - dot(disk, disk), 0.0));
|
|
222
|
+
let tangent = build_tangent(normal);
|
|
223
|
+
let bitangent = build_bitangent(normal, tangent);
|
|
224
|
+
return safe_normalize(tangent * disk.x + bitangent * disk.y + normal * z);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fn fresnel_schlick(cos_theta: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
228
|
+
let factor = pow(1.0 - saturate(cos_theta), 5.0);
|
|
229
|
+
return f0 + (vec3<f32>(1.0) - f0) * factor;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fn environment_radiance(
|
|
233
|
+
direction: vec3<f32>,
|
|
234
|
+
intensity: f32,
|
|
235
|
+
mode: u32
|
|
236
|
+
) -> vec3<f32> {
|
|
237
|
+
let up_factor = saturate(direction.y * 0.5 + 0.5);
|
|
238
|
+
let horizon_color = vec3<f32>(0.65, 0.74, 0.86);
|
|
239
|
+
let zenith_color = select(
|
|
240
|
+
vec3<f32>(0.05, 0.12, 0.24),
|
|
241
|
+
vec3<f32>(0.11, 0.08, 0.18),
|
|
242
|
+
mode == 1u
|
|
243
|
+
);
|
|
244
|
+
let sunset_color = vec3<f32>(1.1, 0.64, 0.32);
|
|
245
|
+
let sun_direction = safe_normalize(vec3<f32>(0.35, 0.92, 0.18));
|
|
246
|
+
let moon_direction = safe_normalize(vec3<f32>(-0.2, 0.98, -0.1));
|
|
247
|
+
let sun_glow = pow(saturate(dot(direction, sun_direction)), 256.0);
|
|
248
|
+
let moon_glow = pow(saturate(dot(direction, moon_direction)), 512.0);
|
|
249
|
+
var sky = horizon_color * (1.0 - up_factor) + zenith_color * up_factor;
|
|
250
|
+
if (mode == 1u) {
|
|
251
|
+
sky = sky * (1.0 - up_factor * 0.4) + sunset_color * sun_glow * 0.25;
|
|
252
|
+
}
|
|
253
|
+
return (
|
|
254
|
+
sky +
|
|
255
|
+
vec3<f32>(8.0, 7.6, 6.8) * sun_glow +
|
|
256
|
+
vec3<f32>(0.7, 0.76, 0.9) * moon_glow * 0.3
|
|
257
|
+
) * max(intensity, 0.0001);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
fn miss_hit(ray: Ray) -> PathHit {
|
|
261
|
+
return PathHit(
|
|
262
|
+
0u,
|
|
263
|
+
ray.t_max,
|
|
264
|
+
ray.origin + ray.direction * ray.t_max,
|
|
265
|
+
0u,
|
|
266
|
+
vec3<f32>(0.0, 1.0, 0.0),
|
|
267
|
+
0u,
|
|
268
|
+
vec3<f32>(0.0),
|
|
269
|
+
0u
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
fn faceforward_normal(normal: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
274
|
+
return select(normal, -normal, dot(normal, direction) > 0.0);
|
|
275
|
+
}
|
package/package.json
CHANGED
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({
|
|
@@ -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
|
}
|
|
@@ -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
|
+
}
|