@plasius/gpu-renderer 0.2.5 → 0.2.7
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 +73 -20
- package/README.md +49 -7
- package/dist/index.cjs +3071 -454
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3069 -454
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.d.ts +228 -8
- package/src/index.js +52 -0
- package/src/wavefront-compute.js +3106 -462
- package/src/wavefront-frame-runtime.js +167 -0
package/src/wavefront-compute.js
CHANGED
|
@@ -1,32 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createGpuParallelismCounters,
|
|
3
|
+
createGpuParallelismDiagnostics,
|
|
4
|
+
createGpuSubmissionBatcher,
|
|
5
|
+
createGpuWorkerJobDiagnostics,
|
|
6
|
+
recordDirectDispatch,
|
|
7
|
+
recordIndirectDispatch,
|
|
8
|
+
} from "./wavefront-frame-runtime.js"
|
|
9
|
+
|
|
1
10
|
const DEFAULT_WIDTH = 1280;
|
|
2
11
|
const DEFAULT_HEIGHT = 720;
|
|
3
12
|
const DEFAULT_MAX_DEPTH = 6;
|
|
4
13
|
const DEFAULT_TILE_SIZE = 128;
|
|
5
14
|
const DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
15
|
+
const MAX_SAMPLES_PER_PIXEL = 256;
|
|
16
|
+
const DEFAULT_BRDF_LUT_SIZE = 128;
|
|
17
|
+
const DEFAULT_BRDF_LUT_SAMPLE_COUNT = 256;
|
|
6
18
|
const DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
7
19
|
const DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
8
20
|
const DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
21
|
+
const DEFAULT_MEDIUM_PHASE_MODEL = 0;
|
|
9
22
|
const WORKGROUP_SIZE = 64;
|
|
10
23
|
export const rendererWavefrontComputeMode = "webgpu-compute";
|
|
11
24
|
export const rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
12
25
|
export const rendererWavefrontComputeStatsStride = 8;
|
|
13
26
|
const RAY_RECORD_BYTES = 80;
|
|
14
|
-
const HIT_RECORD_BYTES =
|
|
15
|
-
const SCENE_OBJECT_RECORD_BYTES =
|
|
27
|
+
const HIT_RECORD_BYTES = 256;
|
|
28
|
+
const SCENE_OBJECT_RECORD_BYTES = 160;
|
|
16
29
|
const MESH_VERTEX_RECORD_BYTES = 48;
|
|
17
|
-
const MESH_RANGE_RECORD_BYTES =
|
|
18
|
-
const TRIANGLE_RECORD_BYTES =
|
|
30
|
+
const MESH_RANGE_RECORD_BYTES = 240;
|
|
31
|
+
const TRIANGLE_RECORD_BYTES = 352;
|
|
32
|
+
const GPU_MATERIAL_RECORD_BYTES = 192;
|
|
19
33
|
const BVH_NODE_RECORD_BYTES = 48;
|
|
20
34
|
const BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
21
35
|
const EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
22
36
|
const ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
37
|
+
const MEDIUM_TABLE_ROWS = 2;
|
|
23
38
|
const ACCUMULATION_RECORD_BYTES = 16;
|
|
24
39
|
const PATH_VERTEX_RECORD_BYTES = 16;
|
|
25
|
-
const
|
|
40
|
+
const GPU_SUBMITTED_WORK_TIMEOUT_MS = 5_000;
|
|
41
|
+
const GPU_READBACK_COMPLETION_TIMEOUT_MS = 60_000;
|
|
42
|
+
const GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 60_000;
|
|
43
|
+
const CONFIG_BUFFER_BYTES = 320;
|
|
26
44
|
const COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
27
45
|
const INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
28
46
|
const COUNTER_BUFFER_BYTES = 32;
|
|
29
47
|
const TRACE_STORAGE_BUFFER_BINDINGS = 10;
|
|
48
|
+
const BRDF_LUT_UPLOAD_CACHE = new Map();
|
|
30
49
|
const HIT_TYPE_SURFACE = 0;
|
|
31
50
|
const HIT_TYPE_EMISSIVE = 1;
|
|
32
51
|
const MATERIAL_DIFFUSE = 0;
|
|
@@ -66,6 +85,7 @@ export const wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
66
85
|
meshVertexRecordBytes: MESH_VERTEX_RECORD_BYTES,
|
|
67
86
|
meshRangeRecordBytes: MESH_RANGE_RECORD_BYTES,
|
|
68
87
|
triangleRecordBytes: TRIANGLE_RECORD_BYTES,
|
|
88
|
+
materialRecordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
69
89
|
bvhNodeRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
70
90
|
bvhLeafReferenceRecordBytes: BVH_LEAF_REF_RECORD_BYTES,
|
|
71
91
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
@@ -164,6 +184,38 @@ function asColor(value, fallback = [1, 1, 1, 1]) {
|
|
|
164
184
|
];
|
|
165
185
|
}
|
|
166
186
|
|
|
187
|
+
function maxComponent(value) {
|
|
188
|
+
return Math.max(value?.[0] ?? 0, value?.[1] ?? 0, value?.[2] ?? 0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function deriveLegacySheenColor(baseColor, sheen, sheenTint) {
|
|
192
|
+
const sheenStrength = clamp(Number(sheen) || 0, 0, 1);
|
|
193
|
+
if (sheenStrength <= 0) {
|
|
194
|
+
return [0, 0, 0, 1];
|
|
195
|
+
}
|
|
196
|
+
const tint = clamp(Number(sheenTint) || 0, 0, 1);
|
|
197
|
+
const base = asColor(baseColor, [1, 1, 1, 1]);
|
|
198
|
+
return [
|
|
199
|
+
clamp((1 - tint) * sheenStrength + base[0] * tint * sheenStrength, 0, 1),
|
|
200
|
+
clamp((1 - tint) * sheenStrength + base[1] * tint * sheenStrength, 0, 1),
|
|
201
|
+
clamp((1 - tint) * sheenStrength + base[2] * tint * sheenStrength, 0, 1),
|
|
202
|
+
1,
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function resolveSheenColor(input, fallbackBaseColor) {
|
|
207
|
+
if (input?.sheenColor || input?.material?.sheenColor) {
|
|
208
|
+
return asColor(input.sheenColor ?? input.material?.sheenColor, [0, 0, 0, 1]).map((value, index) =>
|
|
209
|
+
index < 3 ? clamp(value, 0, 1) : 1
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return deriveLegacySheenColor(
|
|
213
|
+
fallbackBaseColor,
|
|
214
|
+
input?.sheen ?? input?.material?.sheen,
|
|
215
|
+
input?.sheenTint ?? input?.material?.sheenTint
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
167
219
|
function resolveEnvironmentMap(input = null) {
|
|
168
220
|
const source = input && typeof input === "object" ? input : null;
|
|
169
221
|
const hasTexture = Boolean(source?.view || source?.texture || source?.data);
|
|
@@ -173,6 +225,11 @@ function resolveEnvironmentMap(input = null) {
|
|
|
173
225
|
enabled: hasTexture && source?.enabled !== false,
|
|
174
226
|
width,
|
|
175
227
|
height,
|
|
228
|
+
mipLevelCount: readPositiveInteger(
|
|
229
|
+
"environmentMap.mipLevelCount",
|
|
230
|
+
source?.mipLevelCount,
|
|
231
|
+
1
|
|
232
|
+
),
|
|
176
233
|
format: typeof source?.format === "string" ? source.format : "rgba16float",
|
|
177
234
|
projection: typeof source?.projection === "string" ? source.projection : "equirectangular",
|
|
178
235
|
texture: source?.texture ?? null,
|
|
@@ -185,6 +242,7 @@ function resolveEnvironmentMap(input = null) {
|
|
|
185
242
|
0,
|
|
186
243
|
readFiniteNumber("environmentMap.ambientStrength", source?.ambientStrength, 0.32)
|
|
187
244
|
),
|
|
245
|
+
hasImportanceData: source?.hasImportanceData === true,
|
|
188
246
|
});
|
|
189
247
|
}
|
|
190
248
|
|
|
@@ -382,6 +440,183 @@ function deriveBounds(input) {
|
|
|
382
440
|
return null;
|
|
383
441
|
}
|
|
384
442
|
|
|
443
|
+
function deriveBeerLambertAbsorptionFromAttenuationColor(
|
|
444
|
+
attenuationColor,
|
|
445
|
+
attenuationDistance,
|
|
446
|
+
density = 1
|
|
447
|
+
) {
|
|
448
|
+
const distance = Number(attenuationDistance);
|
|
449
|
+
const densityScale = Math.max(0, Number(density) || 0);
|
|
450
|
+
if (!Number.isFinite(distance) || distance <= 0 || densityScale <= 0) {
|
|
451
|
+
return [0, 0, 0];
|
|
452
|
+
}
|
|
453
|
+
return attenuationColor.slice(0, 3).map((channel) => {
|
|
454
|
+
const clamped = clamp(Number(channel) || 0, 0.0001, 1);
|
|
455
|
+
return Math.max(0, (-Math.log(clamped) / distance) * densityScale);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function readMediumPhaseModel(value) {
|
|
460
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
461
|
+
return Math.max(0, Math.trunc(value));
|
|
462
|
+
}
|
|
463
|
+
switch (String(value ?? "").trim().toLowerCase()) {
|
|
464
|
+
case "isotropic":
|
|
465
|
+
default:
|
|
466
|
+
return DEFAULT_MEDIUM_PHASE_MODEL;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function resolveWavefrontVolumeInput(input) {
|
|
471
|
+
return input?.volume ?? input?.material?.volume ?? null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function normalizeWavefrontThickness(input, label) {
|
|
475
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
476
|
+
return Math.max(
|
|
477
|
+
0,
|
|
478
|
+
readFiniteNumber(
|
|
479
|
+
label,
|
|
480
|
+
input?.thickness ?? volume?.thickness ?? input?.material?.thickness,
|
|
481
|
+
0
|
|
482
|
+
)
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function resolveWavefrontMediumId(input, fallbackId = 1) {
|
|
487
|
+
return (
|
|
488
|
+
input?.mediumRefId ??
|
|
489
|
+
input?.mediumId ??
|
|
490
|
+
input?.material?.mediumId ??
|
|
491
|
+
input?.materialRefId ??
|
|
492
|
+
input?.material?.id ??
|
|
493
|
+
input?.materialId ??
|
|
494
|
+
input?.id ??
|
|
495
|
+
fallbackId
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function deriveWavefrontTransportMedium(input, fallbackId = 1) {
|
|
500
|
+
const resolvedId = resolveWavefrontMediumId(input, fallbackId);
|
|
501
|
+
if (input?.medium) {
|
|
502
|
+
return normalizeWavefrontMedium(
|
|
503
|
+
{
|
|
504
|
+
...input.medium,
|
|
505
|
+
id: input.medium.id ?? input.medium.mediumId ?? resolvedId,
|
|
506
|
+
},
|
|
507
|
+
fallbackId
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
511
|
+
if (!volume) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
return normalizeWavefrontMedium(
|
|
515
|
+
{
|
|
516
|
+
id: resolvedId,
|
|
517
|
+
phaseModel: volume.phaseModel,
|
|
518
|
+
density: volume.density,
|
|
519
|
+
attenuationColor: volume.attenuationColor,
|
|
520
|
+
attenuationDistance: volume.attenuationDistance,
|
|
521
|
+
absorption: volume.absorption,
|
|
522
|
+
scattering: volume.scattering,
|
|
523
|
+
},
|
|
524
|
+
fallbackId
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function normalizeWavefrontMedium(input = {}, index = 0) {
|
|
529
|
+
const id = readNonNegativeInteger("medium id", input.id ?? input.mediumId, index);
|
|
530
|
+
const density = Math.max(0, readFiniteNumber("medium density", input.density, 1));
|
|
531
|
+
const attenuationColor = asColor(
|
|
532
|
+
input.attenuationColor ?? input.color ?? input.medium?.attenuationColor,
|
|
533
|
+
[1, 1, 1, 1]
|
|
534
|
+
);
|
|
535
|
+
const attenuationDistance = readFiniteNumber(
|
|
536
|
+
"medium attenuationDistance",
|
|
537
|
+
input.attenuationDistance ?? input.distance ?? input.medium?.attenuationDistance,
|
|
538
|
+
0
|
|
539
|
+
);
|
|
540
|
+
const absorption =
|
|
541
|
+
Array.isArray(input.absorption) || Array.isArray(input.medium?.absorption)
|
|
542
|
+
? asVec3(input.absorption ?? input.medium?.absorption, [0, 0, 0]).map((value) =>
|
|
543
|
+
Math.max(0, Number(value) || 0)
|
|
544
|
+
)
|
|
545
|
+
: deriveBeerLambertAbsorptionFromAttenuationColor(
|
|
546
|
+
attenuationColor,
|
|
547
|
+
attenuationDistance,
|
|
548
|
+
density
|
|
549
|
+
);
|
|
550
|
+
const scattering = asVec3(
|
|
551
|
+
input.scattering ?? input.medium?.scattering,
|
|
552
|
+
[0, 0, 0]
|
|
553
|
+
).map((value) => Math.max(0, Number(value) || 0));
|
|
554
|
+
return Object.freeze({
|
|
555
|
+
id,
|
|
556
|
+
phaseModel: readMediumPhaseModel(input.phaseModel ?? input.medium?.phaseModel),
|
|
557
|
+
density,
|
|
558
|
+
attenuationColor: Object.freeze(attenuationColor),
|
|
559
|
+
attenuationDistance,
|
|
560
|
+
absorption: Object.freeze(absorption),
|
|
561
|
+
scattering: Object.freeze(scattering),
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function collectWavefrontMediums(options, meshes, sceneObjects = []) {
|
|
566
|
+
const mediumsById = new Map();
|
|
567
|
+
mediumsById.set(
|
|
568
|
+
0,
|
|
569
|
+
Object.freeze({
|
|
570
|
+
id: 0,
|
|
571
|
+
phaseModel: DEFAULT_MEDIUM_PHASE_MODEL,
|
|
572
|
+
density: 0,
|
|
573
|
+
attenuationColor: Object.freeze([1, 1, 1, 1]),
|
|
574
|
+
attenuationDistance: 0,
|
|
575
|
+
absorption: Object.freeze([0, 0, 0]),
|
|
576
|
+
scattering: Object.freeze([0, 0, 0]),
|
|
577
|
+
})
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
const register = (input, fallbackId = mediumsById.size) => {
|
|
581
|
+
if (!input) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const normalized = normalizeWavefrontMedium(
|
|
585
|
+
typeof input === "object" ? { id: fallbackId, ...input } : { id: fallbackId },
|
|
586
|
+
fallbackId
|
|
587
|
+
);
|
|
588
|
+
const existing = mediumsById.get(normalized.id);
|
|
589
|
+
if (existing && JSON.stringify(existing) !== JSON.stringify(normalized)) {
|
|
590
|
+
throw new Error(`Medium id ${normalized.id} is defined more than once with different values.`);
|
|
591
|
+
}
|
|
592
|
+
mediumsById.set(normalized.id, normalized);
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
for (const medium of options.mediums ?? []) {
|
|
596
|
+
register(medium);
|
|
597
|
+
}
|
|
598
|
+
for (const mesh of meshes) {
|
|
599
|
+
register(mesh.medium, mesh.mediumRefId ?? mesh.medium?.id ?? 0);
|
|
600
|
+
}
|
|
601
|
+
for (const mesh of meshes) {
|
|
602
|
+
if ((mesh.mediumRefId ?? 0) > 0 && !mediumsById.has(mesh.mediumRefId)) {
|
|
603
|
+
register({ id: mesh.mediumRefId });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
for (const object of sceneObjects) {
|
|
607
|
+
register(object.medium, object.mediumRefId ?? object.medium?.id ?? 0);
|
|
608
|
+
}
|
|
609
|
+
for (const object of sceneObjects) {
|
|
610
|
+
if ((object.mediumRefId ?? 0) > 0 && !mediumsById.has(object.mediumRefId)) {
|
|
611
|
+
register({ id: object.mediumRefId });
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return Object.freeze(
|
|
616
|
+
Array.from(mediumsById.values()).sort((left, right) => left.id - right.id)
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
385
620
|
export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
386
621
|
const bounds = deriveBounds(input);
|
|
387
622
|
const kind = readObjectKind(input.kind ?? input.type ?? (bounds ? "box" : "sphere"));
|
|
@@ -394,7 +629,8 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
394
629
|
input.halfExtent ?? input.halfExtents ?? input.extents ?? bounds?.halfExtent,
|
|
395
630
|
[0.5, 0.5, 0.5]
|
|
396
631
|
).map((value) => Math.max(value, 0.001));
|
|
397
|
-
const
|
|
632
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
633
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
398
634
|
const color = asColor(
|
|
399
635
|
input.color ??
|
|
400
636
|
input.baseColor ??
|
|
@@ -407,23 +643,63 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
407
643
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
408
644
|
[0, 0, 0, 1]
|
|
409
645
|
);
|
|
646
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
647
|
+
const transmission = clamp(
|
|
648
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
649
|
+
0,
|
|
650
|
+
1
|
|
651
|
+
);
|
|
652
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
653
|
+
const specularColor = asColor(
|
|
654
|
+
input.specularColor ?? input.material?.specularColor,
|
|
655
|
+
[1, 1, 1, 1]
|
|
656
|
+
).map((value, componentIndex) => (componentIndex < 3 ? clamp(value, 0, 1) : 1));
|
|
657
|
+
const medium = deriveWavefrontTransportMedium(input, index + 1);
|
|
658
|
+
const resolvedMaterialKind =
|
|
659
|
+
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
660
|
+
? MATERIAL_EMISSIVE
|
|
661
|
+
: materialKindInput === undefined || materialKindInput === null
|
|
662
|
+
? transmission > 0.001 || opacity < 0.999
|
|
663
|
+
? MATERIAL_TRANSPARENT
|
|
664
|
+
: materialKind
|
|
665
|
+
: materialKind;
|
|
410
666
|
|
|
411
667
|
return Object.freeze({
|
|
412
668
|
id: readNonNegativeInteger("id", input.id, index + 1),
|
|
413
669
|
kind,
|
|
414
|
-
materialKind:
|
|
415
|
-
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
416
|
-
? MATERIAL_EMISSIVE
|
|
417
|
-
: materialKind,
|
|
670
|
+
materialKind: resolvedMaterialKind,
|
|
418
671
|
flags: readNonNegativeInteger("flags", input.flags, 0),
|
|
672
|
+
mediumRefId: readNonNegativeInteger(
|
|
673
|
+
"mediumRefId",
|
|
674
|
+
input.mediumRefId ?? medium?.id ?? input.medium?.id ?? input.mediumId,
|
|
675
|
+
0
|
|
676
|
+
),
|
|
677
|
+
medium,
|
|
419
678
|
center: Object.freeze(center),
|
|
420
679
|
halfExtent: Object.freeze(halfExtent),
|
|
421
680
|
color: Object.freeze(color),
|
|
422
681
|
emission: Object.freeze(emission),
|
|
423
682
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
424
683
|
metallic: clamp(readFiniteNumber("metallic", input.metallic ?? input.material?.metallic, 0), 0, 1),
|
|
425
|
-
opacity
|
|
684
|
+
opacity,
|
|
426
685
|
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
686
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
687
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
688
|
+
sheenColor: Object.freeze(sheenColor),
|
|
689
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
690
|
+
clearcoatRoughness: clamp(
|
|
691
|
+
readFiniteNumber(
|
|
692
|
+
"clearcoatRoughness",
|
|
693
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
694
|
+
0.08
|
|
695
|
+
),
|
|
696
|
+
0,
|
|
697
|
+
1
|
|
698
|
+
),
|
|
699
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
700
|
+
specularColor: Object.freeze(specularColor),
|
|
701
|
+
thickness: normalizeWavefrontThickness(input, "thickness"),
|
|
702
|
+
transmission,
|
|
427
703
|
});
|
|
428
704
|
}
|
|
429
705
|
|
|
@@ -507,7 +783,8 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
507
783
|
readFiniteNumber("mesh uv", value, 0)
|
|
508
784
|
)
|
|
509
785
|
: null;
|
|
510
|
-
const
|
|
786
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
787
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
511
788
|
const color = asColor(
|
|
512
789
|
input.color ??
|
|
513
790
|
input.baseColor ??
|
|
@@ -520,6 +797,26 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
520
797
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
521
798
|
[0, 0, 0, 1]
|
|
522
799
|
);
|
|
800
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
801
|
+
const transmission = clamp(
|
|
802
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
803
|
+
0,
|
|
804
|
+
1
|
|
805
|
+
);
|
|
806
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
807
|
+
const specularColor = asColor(
|
|
808
|
+
input.specularColor ?? input.material?.specularColor,
|
|
809
|
+
[1, 1, 1, 1]
|
|
810
|
+
).map((value, componentIndex) => (componentIndex < 3 ? clamp(value, 0, 1) : 1));
|
|
811
|
+
const medium = deriveWavefrontTransportMedium(input, meshIndex + 1);
|
|
812
|
+
const resolvedMaterialKind =
|
|
813
|
+
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
814
|
+
? MATERIAL_EMISSIVE
|
|
815
|
+
: materialKindInput === undefined || materialKindInput === null
|
|
816
|
+
? transmission > 0.001 || opacity < 0.999
|
|
817
|
+
? MATERIAL_TRANSPARENT
|
|
818
|
+
: materialKind
|
|
819
|
+
: materialKind;
|
|
523
820
|
|
|
524
821
|
return Object.freeze({
|
|
525
822
|
id: readNonNegativeInteger("mesh id", input.id, meshIndex + 1),
|
|
@@ -527,10 +824,7 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
527
824
|
indices: Object.freeze(indices),
|
|
528
825
|
normals: normals ? Object.freeze(normals) : null,
|
|
529
826
|
uvs: uvs ? Object.freeze(uvs) : null,
|
|
530
|
-
materialKind:
|
|
531
|
-
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
532
|
-
? MATERIAL_EMISSIVE
|
|
533
|
-
: materialKind,
|
|
827
|
+
materialKind: resolvedMaterialKind,
|
|
534
828
|
flags: readNonNegativeInteger("mesh flags", input.flags, 0),
|
|
535
829
|
materialRefId: readNonNegativeInteger(
|
|
536
830
|
"mesh materialRefId",
|
|
@@ -539,18 +833,202 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
539
833
|
),
|
|
540
834
|
mediumRefId: readNonNegativeInteger(
|
|
541
835
|
"mesh mediumRefId",
|
|
542
|
-
input.mediumRefId ??
|
|
836
|
+
input.mediumRefId ??
|
|
837
|
+
medium?.id ??
|
|
838
|
+
input.medium?.id ??
|
|
839
|
+
input.mediumId ??
|
|
840
|
+
input.material?.mediumId,
|
|
543
841
|
0
|
|
544
842
|
),
|
|
843
|
+
medium,
|
|
545
844
|
color: Object.freeze(color),
|
|
546
845
|
emission: Object.freeze(emission),
|
|
547
846
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
548
847
|
metallic: clamp(readFiniteNumber("metallic", input.metallic ?? input.material?.metallic, 0), 0, 1),
|
|
549
|
-
opacity
|
|
848
|
+
opacity,
|
|
550
849
|
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
850
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
851
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
852
|
+
sheenColor: Object.freeze(sheenColor),
|
|
853
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
854
|
+
clearcoatRoughness: clamp(
|
|
855
|
+
readFiniteNumber(
|
|
856
|
+
"clearcoatRoughness",
|
|
857
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
858
|
+
0.08
|
|
859
|
+
),
|
|
860
|
+
0,
|
|
861
|
+
1
|
|
862
|
+
),
|
|
863
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
864
|
+
specularColor: Object.freeze(specularColor),
|
|
865
|
+
thickness: normalizeWavefrontThickness(input, "mesh thickness"),
|
|
866
|
+
transmission,
|
|
867
|
+
baseColorTexture: input.baseColorTexture ?? input.material?.baseColorTexture ?? null,
|
|
868
|
+
metallicRoughnessTexture:
|
|
869
|
+
input.metallicRoughnessTexture ?? input.material?.metallicRoughnessTexture ?? null,
|
|
870
|
+
normalTexture: input.normalTexture ?? input.material?.normalTexture ?? null,
|
|
871
|
+
occlusionTexture: input.occlusionTexture ?? input.material?.occlusionTexture ?? null,
|
|
872
|
+
emissiveTexture: input.emissiveTexture ?? input.material?.emissiveTexture ?? null,
|
|
551
873
|
});
|
|
552
874
|
}
|
|
553
875
|
|
|
876
|
+
function clampUnit(value) {
|
|
877
|
+
return clamp(Number(value) || 0, 0, 1);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function srgbToLinear(value) {
|
|
881
|
+
const channel = clampUnit(value);
|
|
882
|
+
if (channel <= 0.04045) {
|
|
883
|
+
return channel / 12.92;
|
|
884
|
+
}
|
|
885
|
+
return ((channel + 0.055) / 1.055) ** 2.4;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function sampleTextureRgba(texture, uv = [0, 0], colorSpace = "linear") {
|
|
889
|
+
if (
|
|
890
|
+
!texture ||
|
|
891
|
+
!Number.isFinite(texture.width) ||
|
|
892
|
+
!Number.isFinite(texture.height) ||
|
|
893
|
+
!texture.data ||
|
|
894
|
+
texture.width <= 0 ||
|
|
895
|
+
texture.height <= 0
|
|
896
|
+
) {
|
|
897
|
+
return [1, 1, 1, 1];
|
|
898
|
+
}
|
|
899
|
+
const u = ((uv[0] % 1) + 1) % 1;
|
|
900
|
+
const v = ((uv[1] % 1) + 1) % 1;
|
|
901
|
+
const x = Math.min(texture.width - 1, Math.max(0, Math.round(u * (texture.width - 1))));
|
|
902
|
+
const y = Math.min(texture.height - 1, Math.max(0, Math.round((1 - v) * (texture.height - 1))));
|
|
903
|
+
const offset = (y * texture.width + x) * 4;
|
|
904
|
+
const data = texture.data;
|
|
905
|
+
const scale = resolveTextureSampleScale(data);
|
|
906
|
+
const defaultChannel = scale === 1 ? 1 : Math.round(1 / scale);
|
|
907
|
+
const color = [
|
|
908
|
+
(data[offset] ?? defaultChannel) * scale,
|
|
909
|
+
(data[offset + 1] ?? defaultChannel) * scale,
|
|
910
|
+
(data[offset + 2] ?? defaultChannel) * scale,
|
|
911
|
+
(data[offset + 3] ?? defaultChannel) * scale,
|
|
912
|
+
];
|
|
913
|
+
if (colorSpace === "srgb") {
|
|
914
|
+
return [srgbToLinear(color[0]), srgbToLinear(color[1]), srgbToLinear(color[2]), color[3]];
|
|
915
|
+
}
|
|
916
|
+
return color;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function resolveTextureSampleScale(data) {
|
|
920
|
+
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
|
|
921
|
+
return 1 / 255;
|
|
922
|
+
}
|
|
923
|
+
if (data instanceof Uint16Array) {
|
|
924
|
+
return 1 / 65535;
|
|
925
|
+
}
|
|
926
|
+
if (Array.isArray(data) && data.some((value) => Number(value) > 1)) {
|
|
927
|
+
return 1 / 255;
|
|
928
|
+
}
|
|
929
|
+
return 1;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function normalizeVectorOrFallback(vector, fallback) {
|
|
933
|
+
return normalize(Array.isArray(vector) ? vector : fallback, fallback);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, fallbackNormal) {
|
|
937
|
+
const edge1 = subtract(v1, v0);
|
|
938
|
+
const edge2 = subtract(v2, v0);
|
|
939
|
+
const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
|
|
940
|
+
const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
|
|
941
|
+
const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
|
|
942
|
+
if (Math.abs(determinant) < 1e-6) {
|
|
943
|
+
const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
944
|
+
const tangent = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
|
|
945
|
+
const bitangent = normalize(cross(fallbackNormal, tangent), [0, 0, 1]);
|
|
946
|
+
return { tangent, bitangent };
|
|
947
|
+
}
|
|
948
|
+
const inverse = 1 / determinant;
|
|
949
|
+
const tangent = normalize(
|
|
950
|
+
[
|
|
951
|
+
inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
|
|
952
|
+
inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
|
|
953
|
+
inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1]),
|
|
954
|
+
],
|
|
955
|
+
[1, 0, 0]
|
|
956
|
+
);
|
|
957
|
+
const bitangent = normalize(
|
|
958
|
+
[
|
|
959
|
+
inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
|
|
960
|
+
inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
|
|
961
|
+
inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0]),
|
|
962
|
+
],
|
|
963
|
+
[0, 0, 1]
|
|
964
|
+
);
|
|
965
|
+
return { tangent, bitangent };
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
|
|
969
|
+
if (!normalTexture) {
|
|
970
|
+
return normalizeVectorOrFallback(normal, [0, 1, 0]);
|
|
971
|
+
}
|
|
972
|
+
const sample = sampleTextureRgba(normalTexture, uv, "linear");
|
|
973
|
+
const strength = clampUnit(normalTexture.scale ?? 1);
|
|
974
|
+
const tangentNormal = normalize(
|
|
975
|
+
[
|
|
976
|
+
(sample[0] * 2 - 1) * strength,
|
|
977
|
+
(sample[1] * 2 - 1) * strength,
|
|
978
|
+
1 + (sample[2] * 2 - 1 - 1) * strength,
|
|
979
|
+
],
|
|
980
|
+
[0, 0, 1]
|
|
981
|
+
);
|
|
982
|
+
return normalize(
|
|
983
|
+
[
|
|
984
|
+
tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
|
|
985
|
+
tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
|
|
986
|
+
tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2],
|
|
987
|
+
],
|
|
988
|
+
normal
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function sampleBaseColor(mesh, uv) {
|
|
993
|
+
const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
|
|
994
|
+
return [
|
|
995
|
+
clampUnit(mesh.color[0] * sample[0]),
|
|
996
|
+
clampUnit(mesh.color[1] * sample[1]),
|
|
997
|
+
clampUnit(mesh.color[2] * sample[2]),
|
|
998
|
+
clampUnit((mesh.color[3] ?? 1) * sample[3]),
|
|
999
|
+
];
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function sampleSurfaceMaterial(mesh, uv) {
|
|
1003
|
+
const textureSample = mesh.metallicRoughnessTexture
|
|
1004
|
+
? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear")
|
|
1005
|
+
: [1, 1, 1, 1];
|
|
1006
|
+
return {
|
|
1007
|
+
roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
|
|
1008
|
+
metallic: clamp(mesh.metallic * textureSample[2], 0, 1),
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function averageColors(colors) {
|
|
1013
|
+
const count = Math.max(colors.length, 1);
|
|
1014
|
+
return colors.reduce(
|
|
1015
|
+
(accumulator, color) => [
|
|
1016
|
+
accumulator[0] + color[0] / count,
|
|
1017
|
+
accumulator[1] + color[1] / count,
|
|
1018
|
+
accumulator[2] + color[2] / count,
|
|
1019
|
+
accumulator[3] + color[3] / count,
|
|
1020
|
+
],
|
|
1021
|
+
[0, 0, 0, 0]
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function averageNumbers(values, fallback = 0) {
|
|
1026
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
1027
|
+
return fallback;
|
|
1028
|
+
}
|
|
1029
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
554
1032
|
function createMeshTriangleRecords(meshes) {
|
|
555
1033
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
556
1034
|
let nextTriangleId = 0;
|
|
@@ -571,6 +1049,16 @@ function createMeshTriangleRecords(meshes) {
|
|
|
571
1049
|
const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
|
|
572
1050
|
const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
|
|
573
1051
|
const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
|
|
1052
|
+
const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
|
|
1053
|
+
const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
|
|
1054
|
+
const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
|
|
1055
|
+
const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
|
|
1056
|
+
const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
|
|
1057
|
+
const sampledMaterials = [
|
|
1058
|
+
sampleSurfaceMaterial(mesh, uv0),
|
|
1059
|
+
sampleSurfaceMaterial(mesh, uv1),
|
|
1060
|
+
sampleSurfaceMaterial(mesh, uv2),
|
|
1061
|
+
];
|
|
574
1062
|
const bounds = triangleBounds(v0, v1, v2);
|
|
575
1063
|
|
|
576
1064
|
triangles.push(
|
|
@@ -581,18 +1069,42 @@ function createMeshTriangleRecords(meshes) {
|
|
|
581
1069
|
flags: mesh.flags,
|
|
582
1070
|
materialRefId: mesh.materialRefId,
|
|
583
1071
|
mediumRefId: mesh.mediumRefId,
|
|
1072
|
+
materialSlot: meshIndex,
|
|
584
1073
|
v0: Object.freeze(v0),
|
|
585
1074
|
v1: Object.freeze(v1),
|
|
586
1075
|
v2: Object.freeze(v2),
|
|
587
|
-
n0: Object.freeze(
|
|
588
|
-
n1: Object.freeze(
|
|
589
|
-
n2: Object.freeze(
|
|
1076
|
+
n0: Object.freeze(shadedN0),
|
|
1077
|
+
n1: Object.freeze(shadedN1),
|
|
1078
|
+
n2: Object.freeze(shadedN2),
|
|
590
1079
|
uv0: Object.freeze(uv0),
|
|
591
1080
|
uv1: Object.freeze(uv1),
|
|
592
1081
|
uv2: Object.freeze(uv2),
|
|
593
|
-
color:
|
|
1082
|
+
color: Object.freeze(averageColors(sampledColors)),
|
|
594
1083
|
emission: mesh.emission,
|
|
595
|
-
material: Object.freeze([
|
|
1084
|
+
material: Object.freeze([
|
|
1085
|
+
averageNumbers(sampledMaterials.map((sample) => sample.roughness), mesh.roughness),
|
|
1086
|
+
averageNumbers(sampledMaterials.map((sample) => sample.metallic), mesh.metallic),
|
|
1087
|
+
mesh.opacity,
|
|
1088
|
+
mesh.ior,
|
|
1089
|
+
]),
|
|
1090
|
+
materialResponse: Object.freeze([
|
|
1091
|
+
mesh.sheenColor[0] ?? 0,
|
|
1092
|
+
mesh.sheenColor[1] ?? 0,
|
|
1093
|
+
mesh.sheenColor[2] ?? 0,
|
|
1094
|
+
mesh.clearcoat,
|
|
1095
|
+
]),
|
|
1096
|
+
materialExtension: Object.freeze([
|
|
1097
|
+
mesh.clearcoatRoughness,
|
|
1098
|
+
mesh.specular,
|
|
1099
|
+
mesh.transmission,
|
|
1100
|
+
mesh.thickness,
|
|
1101
|
+
]),
|
|
1102
|
+
specularColor: Object.freeze([
|
|
1103
|
+
mesh.specularColor[0] ?? 1,
|
|
1104
|
+
mesh.specularColor[1] ?? 1,
|
|
1105
|
+
mesh.specularColor[2] ?? 1,
|
|
1106
|
+
1,
|
|
1107
|
+
]),
|
|
596
1108
|
bounds: Object.freeze({
|
|
597
1109
|
min: Object.freeze(bounds.min),
|
|
598
1110
|
max: Object.freeze(bounds.max),
|
|
@@ -713,6 +1225,245 @@ function nextPowerOfTwo(value) {
|
|
|
713
1225
|
return 2 ** Math.ceil(Math.log2(value));
|
|
714
1226
|
}
|
|
715
1227
|
|
|
1228
|
+
function textureComponentToByte(value, fallback) {
|
|
1229
|
+
const numeric = Number(value);
|
|
1230
|
+
if (!Number.isFinite(numeric)) {
|
|
1231
|
+
return fallback;
|
|
1232
|
+
}
|
|
1233
|
+
if (numeric >= 0 && numeric <= 1) {
|
|
1234
|
+
return Math.max(0, Math.min(255, Math.round(numeric * 255)));
|
|
1235
|
+
}
|
|
1236
|
+
return Math.max(0, Math.min(255, Math.round(numeric)));
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function createSolidTextureSample(width, height, rgba) {
|
|
1240
|
+
const data = new Uint8Array(width * height * 4);
|
|
1241
|
+
for (let offset = 0; offset < data.length; offset += 4) {
|
|
1242
|
+
data[offset] = rgba[0];
|
|
1243
|
+
data[offset + 1] = rgba[1];
|
|
1244
|
+
data[offset + 2] = rgba[2];
|
|
1245
|
+
data[offset + 3] = rgba[3];
|
|
1246
|
+
}
|
|
1247
|
+
return Object.freeze({
|
|
1248
|
+
width,
|
|
1249
|
+
height,
|
|
1250
|
+
data,
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function normalizeTextureSampleInput(texture, fallbackColor) {
|
|
1255
|
+
if (
|
|
1256
|
+
!texture ||
|
|
1257
|
+
!Number.isFinite(texture.width) ||
|
|
1258
|
+
!Number.isFinite(texture.height) ||
|
|
1259
|
+
texture.width <= 0 ||
|
|
1260
|
+
texture.height <= 0
|
|
1261
|
+
) {
|
|
1262
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
const pixelCount = Math.trunc(texture.width) * Math.trunc(texture.height) * 4;
|
|
1266
|
+
const source =
|
|
1267
|
+
ArrayBuffer.isView(texture.data) || Array.isArray(texture.data) ? texture.data : null;
|
|
1268
|
+
if (!source || source.length < pixelCount) {
|
|
1269
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
const data = new Uint8Array(pixelCount);
|
|
1273
|
+
for (let index = 0; index < pixelCount; index += 1) {
|
|
1274
|
+
data[index] = textureComponentToByte(source[index], fallbackColor[index % 4]);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
return Object.freeze({
|
|
1278
|
+
width: Math.trunc(texture.width),
|
|
1279
|
+
height: Math.trunc(texture.height),
|
|
1280
|
+
data,
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function buildTextureAtlas(textures, fallbackColor) {
|
|
1285
|
+
const padding = 1;
|
|
1286
|
+
const defaultTexture = createSolidTextureSample(1, 1, fallbackColor);
|
|
1287
|
+
const uniqueEntries = [{ source: null, texture: defaultTexture }];
|
|
1288
|
+
const bySource = new Map();
|
|
1289
|
+
|
|
1290
|
+
for (const texture of Array.isArray(textures) ? textures : []) {
|
|
1291
|
+
if (!texture || bySource.has(texture)) {
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
const normalized = normalizeTextureSampleInput(texture, fallbackColor);
|
|
1295
|
+
bySource.set(texture, uniqueEntries.length);
|
|
1296
|
+
uniqueEntries.push({ source: texture, texture: normalized });
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
const totalArea = uniqueEntries.reduce((sum, entry) => {
|
|
1300
|
+
return sum + (entry.texture.width + padding * 2) * (entry.texture.height + padding * 2);
|
|
1301
|
+
}, 0);
|
|
1302
|
+
const maxTileWidth = uniqueEntries.reduce((maxWidth, entry) => {
|
|
1303
|
+
return Math.max(maxWidth, entry.texture.width + padding * 2);
|
|
1304
|
+
}, 1);
|
|
1305
|
+
const targetWidth = Math.max(
|
|
1306
|
+
maxTileWidth,
|
|
1307
|
+
nextPowerOfTwo(Math.max(maxTileWidth, Math.ceil(Math.sqrt(totalArea))))
|
|
1308
|
+
);
|
|
1309
|
+
|
|
1310
|
+
let cursorX = 0;
|
|
1311
|
+
let cursorY = 0;
|
|
1312
|
+
let rowHeight = 0;
|
|
1313
|
+
let atlasWidth = 0;
|
|
1314
|
+
const placements = uniqueEntries.map((entry) => {
|
|
1315
|
+
const tileWidth = entry.texture.width + padding * 2;
|
|
1316
|
+
const tileHeight = entry.texture.height + padding * 2;
|
|
1317
|
+
if (cursorX > 0 && cursorX + tileWidth > targetWidth) {
|
|
1318
|
+
cursorX = 0;
|
|
1319
|
+
cursorY += rowHeight;
|
|
1320
|
+
rowHeight = 0;
|
|
1321
|
+
}
|
|
1322
|
+
const placement = Object.freeze({
|
|
1323
|
+
x: cursorX,
|
|
1324
|
+
y: cursorY,
|
|
1325
|
+
tileWidth,
|
|
1326
|
+
tileHeight,
|
|
1327
|
+
width: entry.texture.width,
|
|
1328
|
+
height: entry.texture.height,
|
|
1329
|
+
});
|
|
1330
|
+
cursorX += tileWidth;
|
|
1331
|
+
atlasWidth = Math.max(atlasWidth, cursorX);
|
|
1332
|
+
rowHeight = Math.max(rowHeight, tileHeight);
|
|
1333
|
+
return placement;
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
const atlasHeight = Math.max(1, cursorY + rowHeight);
|
|
1337
|
+
const atlasData = new Uint8Array(Math.max(1, atlasWidth * atlasHeight * 4));
|
|
1338
|
+
|
|
1339
|
+
const writePixel = (x, y, rgba) => {
|
|
1340
|
+
const offset = (y * atlasWidth + x) * 4;
|
|
1341
|
+
atlasData[offset] = rgba[0];
|
|
1342
|
+
atlasData[offset + 1] = rgba[1];
|
|
1343
|
+
atlasData[offset + 2] = rgba[2];
|
|
1344
|
+
atlasData[offset + 3] = rgba[3];
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1347
|
+
const rects = placements.map((placement, entryIndex) => {
|
|
1348
|
+
const { texture } = uniqueEntries[entryIndex];
|
|
1349
|
+
for (let y = 0; y < placement.tileHeight; y += 1) {
|
|
1350
|
+
for (let x = 0; x < placement.tileWidth; x += 1) {
|
|
1351
|
+
const sampleX = Math.max(0, Math.min(texture.width - 1, x - padding));
|
|
1352
|
+
const sampleY = Math.max(0, Math.min(texture.height - 1, y - padding));
|
|
1353
|
+
const sourceOffset = (sampleY * texture.width + sampleX) * 4;
|
|
1354
|
+
writePixel(placement.x + x, placement.y + y, texture.data.slice(sourceOffset, sourceOffset + 4));
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return Object.freeze([
|
|
1358
|
+
(placement.x + padding) / Math.max(1, atlasWidth),
|
|
1359
|
+
(placement.y + padding) / Math.max(1, atlasHeight),
|
|
1360
|
+
placement.width / Math.max(1, atlasWidth),
|
|
1361
|
+
placement.height / Math.max(1, atlasHeight),
|
|
1362
|
+
]);
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
const rectBySource = new Map();
|
|
1366
|
+
uniqueEntries.forEach((entry, index) => {
|
|
1367
|
+
if (entry.source) {
|
|
1368
|
+
rectBySource.set(entry.source, rects[index]);
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
return Object.freeze({
|
|
1373
|
+
width: Math.max(1, atlasWidth),
|
|
1374
|
+
height: Math.max(1, atlasHeight),
|
|
1375
|
+
data: atlasData,
|
|
1376
|
+
defaultRect: rects[0],
|
|
1377
|
+
resolveRect(texture) {
|
|
1378
|
+
return rectBySource.get(texture) ?? rects[0];
|
|
1379
|
+
},
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
export function createWavefrontGpuMaterialSource(meshes = []) {
|
|
1384
|
+
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
1385
|
+
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1386
|
+
const baseColorAtlas = buildTextureAtlas(
|
|
1387
|
+
normalized.map((mesh) => mesh.baseColorTexture),
|
|
1388
|
+
[255, 255, 255, 255]
|
|
1389
|
+
);
|
|
1390
|
+
const metallicRoughnessAtlas = buildTextureAtlas(
|
|
1391
|
+
normalized.map((mesh) => mesh.metallicRoughnessTexture),
|
|
1392
|
+
[255, 255, 255, 255]
|
|
1393
|
+
);
|
|
1394
|
+
const normalAtlas = buildTextureAtlas(
|
|
1395
|
+
normalized.map((mesh) => mesh.normalTexture),
|
|
1396
|
+
[128, 128, 255, 255]
|
|
1397
|
+
);
|
|
1398
|
+
const occlusionAtlas = buildTextureAtlas(
|
|
1399
|
+
normalized.map((mesh) => mesh.occlusionTexture),
|
|
1400
|
+
[255, 255, 255, 255]
|
|
1401
|
+
);
|
|
1402
|
+
const emissiveAtlas = buildTextureAtlas(
|
|
1403
|
+
normalized.map((mesh) => mesh.emissiveTexture),
|
|
1404
|
+
[255, 255, 255, 255]
|
|
1405
|
+
);
|
|
1406
|
+
const bytes = new ArrayBuffer(Math.max(1, normalized.length) * GPU_MATERIAL_RECORD_BYTES);
|
|
1407
|
+
const floatView = new Float32Array(bytes);
|
|
1408
|
+
|
|
1409
|
+
normalized.forEach((mesh, meshIndex) => {
|
|
1410
|
+
const byteOffset = meshIndex * GPU_MATERIAL_RECORD_BYTES;
|
|
1411
|
+
writeVec4(floatView, byteOffset, mesh.color);
|
|
1412
|
+
writeVec4(floatView, byteOffset + 16, mesh.emission);
|
|
1413
|
+
writeVec4(floatView, byteOffset + 32, [
|
|
1414
|
+
mesh.roughness,
|
|
1415
|
+
mesh.metallic,
|
|
1416
|
+
mesh.opacity,
|
|
1417
|
+
mesh.ior,
|
|
1418
|
+
]);
|
|
1419
|
+
writeVec4(floatView, byteOffset + 48, [
|
|
1420
|
+
mesh.sheenColor[0] ?? 0,
|
|
1421
|
+
mesh.sheenColor[1] ?? 0,
|
|
1422
|
+
mesh.sheenColor[2] ?? 0,
|
|
1423
|
+
mesh.clearcoat,
|
|
1424
|
+
]);
|
|
1425
|
+
writeVec4(floatView, byteOffset + 64, [
|
|
1426
|
+
mesh.clearcoatRoughness,
|
|
1427
|
+
mesh.specular,
|
|
1428
|
+
mesh.transmission,
|
|
1429
|
+
mesh.thickness,
|
|
1430
|
+
]);
|
|
1431
|
+
writeVec4(floatView, byteOffset + 80, [
|
|
1432
|
+
mesh.specularColor[0] ?? 1,
|
|
1433
|
+
mesh.specularColor[1] ?? 1,
|
|
1434
|
+
mesh.specularColor[2] ?? 1,
|
|
1435
|
+
1,
|
|
1436
|
+
]);
|
|
1437
|
+
writeVec4(floatView, byteOffset + 96, baseColorAtlas.resolveRect(mesh.baseColorTexture));
|
|
1438
|
+
writeVec4(
|
|
1439
|
+
floatView,
|
|
1440
|
+
byteOffset + 112,
|
|
1441
|
+
metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1442
|
+
);
|
|
1443
|
+
writeVec4(floatView, byteOffset + 128, normalAtlas.resolveRect(mesh.normalTexture));
|
|
1444
|
+
writeVec4(floatView, byteOffset + 144, occlusionAtlas.resolveRect(mesh.occlusionTexture));
|
|
1445
|
+
writeVec4(floatView, byteOffset + 160, emissiveAtlas.resolveRect(mesh.emissiveTexture));
|
|
1446
|
+
writeVec4(floatView, byteOffset + 176, [
|
|
1447
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1448
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1449
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1450
|
+
0,
|
|
1451
|
+
]);
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
return Object.freeze({
|
|
1455
|
+
buffer: bytes,
|
|
1456
|
+
count: normalized.length,
|
|
1457
|
+
recordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
1458
|
+
records: Object.freeze(normalized),
|
|
1459
|
+
baseColorAtlas,
|
|
1460
|
+
metallicRoughnessAtlas,
|
|
1461
|
+
normalAtlas,
|
|
1462
|
+
occlusionAtlas,
|
|
1463
|
+
emissiveAtlas,
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
|
|
716
1467
|
function estimateBvhLeafSortCapacity(triangleCount) {
|
|
717
1468
|
return triangleCount <= 0 ? 0 : nextPowerOfTwo(triangleCount);
|
|
718
1469
|
}
|
|
@@ -783,9 +1534,10 @@ function resolveAccelerationBuildMode(options = {}) {
|
|
|
783
1534
|
return mode;
|
|
784
1535
|
}
|
|
785
1536
|
|
|
786
|
-
export function createWavefrontGpuMeshSource(meshes = []) {
|
|
1537
|
+
export function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput = null) {
|
|
787
1538
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
788
1539
|
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1540
|
+
const gpuMaterialSource = gpuMaterialSourceInput ?? createWavefrontGpuMaterialSource(normalized);
|
|
789
1541
|
const vertexCount = normalized.reduce((count, mesh) => count + mesh.positions.length / 3, 0);
|
|
790
1542
|
const indexCount = normalized.reduce((count, mesh) => count + mesh.indices.length, 0);
|
|
791
1543
|
const triangleCount = Math.floor(indexCount / 3);
|
|
@@ -842,7 +1594,7 @@ export function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
842
1594
|
meshUints[meshOffset + 8] = mesh.indices.length / 3;
|
|
843
1595
|
meshUints[meshOffset + 9] = meshVertexBase;
|
|
844
1596
|
meshUints[meshOffset + 10] = meshVertexCount;
|
|
845
|
-
meshUints[meshOffset + 11] =
|
|
1597
|
+
meshUints[meshOffset + 11] = meshIndex;
|
|
846
1598
|
const floatOffset = meshOffset;
|
|
847
1599
|
writeVec4(meshFloats, floatOffset * 4 + 48, mesh.color);
|
|
848
1600
|
writeVec4(meshFloats, floatOffset * 4 + 64, mesh.emission);
|
|
@@ -852,6 +1604,55 @@ export function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
852
1604
|
mesh.opacity,
|
|
853
1605
|
mesh.ior,
|
|
854
1606
|
]);
|
|
1607
|
+
writeVec4(meshFloats, floatOffset * 4 + 96, [
|
|
1608
|
+
mesh.sheenColor[0] ?? 0,
|
|
1609
|
+
mesh.sheenColor[1] ?? 0,
|
|
1610
|
+
mesh.sheenColor[2] ?? 0,
|
|
1611
|
+
mesh.clearcoat,
|
|
1612
|
+
]);
|
|
1613
|
+
writeVec4(meshFloats, floatOffset * 4 + 112, [
|
|
1614
|
+
mesh.clearcoatRoughness,
|
|
1615
|
+
mesh.specular,
|
|
1616
|
+
mesh.transmission,
|
|
1617
|
+
mesh.thickness,
|
|
1618
|
+
]);
|
|
1619
|
+
writeVec4(meshFloats, floatOffset * 4 + 128, [
|
|
1620
|
+
mesh.specularColor[0] ?? 1,
|
|
1621
|
+
mesh.specularColor[1] ?? 1,
|
|
1622
|
+
mesh.specularColor[2] ?? 1,
|
|
1623
|
+
1,
|
|
1624
|
+
]);
|
|
1625
|
+
writeVec4(
|
|
1626
|
+
meshFloats,
|
|
1627
|
+
floatOffset * 4 + 144,
|
|
1628
|
+
gpuMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
|
|
1629
|
+
);
|
|
1630
|
+
writeVec4(
|
|
1631
|
+
meshFloats,
|
|
1632
|
+
floatOffset * 4 + 160,
|
|
1633
|
+
gpuMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1634
|
+
);
|
|
1635
|
+
writeVec4(
|
|
1636
|
+
meshFloats,
|
|
1637
|
+
floatOffset * 4 + 176,
|
|
1638
|
+
gpuMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
|
|
1639
|
+
);
|
|
1640
|
+
writeVec4(
|
|
1641
|
+
meshFloats,
|
|
1642
|
+
floatOffset * 4 + 192,
|
|
1643
|
+
gpuMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
|
|
1644
|
+
);
|
|
1645
|
+
writeVec4(
|
|
1646
|
+
meshFloats,
|
|
1647
|
+
floatOffset * 4 + 208,
|
|
1648
|
+
gpuMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
|
|
1649
|
+
);
|
|
1650
|
+
writeVec4(meshFloats, floatOffset * 4 + 224, [
|
|
1651
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1652
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1653
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1654
|
+
0,
|
|
1655
|
+
]);
|
|
855
1656
|
|
|
856
1657
|
vertexCursor += meshVertexCount;
|
|
857
1658
|
indexCursor += mesh.indices.length;
|
|
@@ -928,12 +1729,17 @@ function normalizeSceneObjects(sceneObjects, useDefaultScene = true) {
|
|
|
928
1729
|
return source.map((object, index) => normalizeWavefrontSceneObject(object, index));
|
|
929
1730
|
}
|
|
930
1731
|
|
|
1732
|
+
function normalizeWavefrontMeshes(meshes) {
|
|
1733
|
+
const source = Array.isArray(meshes) ? meshes : [];
|
|
1734
|
+
return source.map((mesh, index) => normalizeWavefrontMesh(mesh, index));
|
|
1735
|
+
}
|
|
1736
|
+
|
|
931
1737
|
function normalizeMeshes(options = {}) {
|
|
932
1738
|
if (Array.isArray(options.meshes)) {
|
|
933
|
-
return options.meshes;
|
|
1739
|
+
return normalizeWavefrontMeshes(options.meshes);
|
|
934
1740
|
}
|
|
935
1741
|
if (options.mesh) {
|
|
936
|
-
return [options.mesh];
|
|
1742
|
+
return normalizeWavefrontMeshes([options.mesh]);
|
|
937
1743
|
}
|
|
938
1744
|
return [];
|
|
939
1745
|
}
|
|
@@ -1188,12 +1994,14 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1188
1994
|
options.environmentPortalCapacity,
|
|
1189
1995
|
0
|
|
1190
1996
|
);
|
|
1997
|
+
const materialCapacity = readNonNegativeInteger("materialCapacity", options.materialCapacity, 0);
|
|
1191
1998
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
1192
1999
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
1193
2000
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
1194
2001
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
1195
2002
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
1196
2003
|
const triangleBytes = triangleCapacity * TRIANGLE_RECORD_BYTES;
|
|
2004
|
+
const materialTableBytes = materialCapacity * GPU_MATERIAL_RECORD_BYTES;
|
|
1197
2005
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
1198
2006
|
const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
|
|
1199
2007
|
const emissiveTriangleMetadataBytes =
|
|
@@ -1209,6 +2017,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1209
2017
|
pathVertexBytes,
|
|
1210
2018
|
sceneObjectBytes,
|
|
1211
2019
|
triangleBytes,
|
|
2020
|
+
materialTableBytes,
|
|
1212
2021
|
bvhNodeBytes,
|
|
1213
2022
|
bvhLeafReferenceBytes,
|
|
1214
2023
|
emissiveTriangleMetadataBytes,
|
|
@@ -1223,6 +2032,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1223
2032
|
pathVertexBytes +
|
|
1224
2033
|
sceneObjectBytes +
|
|
1225
2034
|
triangleBytes +
|
|
2035
|
+
materialTableBytes +
|
|
1226
2036
|
bvhNodeBytes +
|
|
1227
2037
|
bvhLeafReferenceBytes +
|
|
1228
2038
|
emissiveTriangleMetadataBytes +
|
|
@@ -1244,7 +2054,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1244
2054
|
const samplesPerPixel = clamp(
|
|
1245
2055
|
readPositiveInteger("samplesPerPixel", options.samplesPerPixel, DEFAULT_SAMPLES_PER_PIXEL),
|
|
1246
2056
|
1,
|
|
1247
|
-
|
|
2057
|
+
MAX_SAMPLES_PER_PIXEL
|
|
1248
2058
|
);
|
|
1249
2059
|
const maxFramePassesPerSubmission = clamp(
|
|
1250
2060
|
readPositiveInteger(
|
|
@@ -1262,9 +2072,13 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1262
2072
|
);
|
|
1263
2073
|
const meshes = normalizeMeshes(options);
|
|
1264
2074
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
2075
|
+
const gpuMaterialSource =
|
|
2076
|
+
meshes.length > 0
|
|
2077
|
+
? createWavefrontGpuMaterialSource(meshes)
|
|
2078
|
+
: createWavefrontGpuMaterialSource([]);
|
|
1265
2079
|
const gpuMeshSource =
|
|
1266
2080
|
meshes.length > 0
|
|
1267
|
-
? createWavefrontGpuMeshSource(meshes)
|
|
2081
|
+
? createWavefrontGpuMeshSource(meshes, gpuMaterialSource)
|
|
1268
2082
|
: createWavefrontGpuMeshSource([]);
|
|
1269
2083
|
const meshAcceleration =
|
|
1270
2084
|
accelerationBuildMode === "cpu-debug"
|
|
@@ -1285,6 +2099,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1285
2099
|
const sceneObjects = Object.freeze(
|
|
1286
2100
|
normalizeSceneObjects(options.sceneObjects, meshes.length === 0)
|
|
1287
2101
|
);
|
|
2102
|
+
const mediums = collectWavefrontMediums(options, meshes, sceneObjects);
|
|
1288
2103
|
const sceneObjectCapacity = Math.max(
|
|
1289
2104
|
sceneObjects.length,
|
|
1290
2105
|
readPositiveInteger("sceneObjectCapacity", options.sceneObjectCapacity, DEFAULT_SCENE_OBJECT_CAPACITY)
|
|
@@ -1357,9 +2172,12 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1357
2172
|
sceneObjects,
|
|
1358
2173
|
sceneObjectCount: sceneObjects.length,
|
|
1359
2174
|
sceneObjectCapacity,
|
|
2175
|
+
mediums,
|
|
2176
|
+
mediumCount: mediums.length,
|
|
1360
2177
|
accelerationBuildMode,
|
|
1361
2178
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
1362
2179
|
gpuMeshSource,
|
|
2180
|
+
gpuMaterialSource,
|
|
1363
2181
|
meshAcceleration,
|
|
1364
2182
|
emissiveTriangleIndices,
|
|
1365
2183
|
emissiveTriangleCount: emissiveTriangleIndices.count,
|
|
@@ -1390,6 +2208,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1390
2208
|
maxDepth,
|
|
1391
2209
|
sceneObjectCapacity,
|
|
1392
2210
|
triangleCapacity,
|
|
2211
|
+
materialCapacity: gpuMaterialSource.count,
|
|
1393
2212
|
bvhNodeCapacity,
|
|
1394
2213
|
bvhLeafSortCapacity,
|
|
1395
2214
|
emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
|
|
@@ -1473,16 +2292,35 @@ export function packWavefrontSceneObjects(sceneObjects, capacity = sceneObjects.
|
|
|
1473
2292
|
uintView[u32 + 1] = object.id;
|
|
1474
2293
|
uintView[u32 + 2] = object.materialKind;
|
|
1475
2294
|
uintView[u32 + 3] = object.flags;
|
|
1476
|
-
|
|
1477
|
-
writeVec4(floatView, byteOffset + 32, [...object.
|
|
1478
|
-
writeVec4(floatView, byteOffset + 48, object.
|
|
1479
|
-
writeVec4(floatView, byteOffset + 64, object.
|
|
1480
|
-
writeVec4(floatView, byteOffset + 80,
|
|
2295
|
+
uintView[u32 + 4] = object.mediumRefId;
|
|
2296
|
+
writeVec4(floatView, byteOffset + 32, [...object.center, 0]);
|
|
2297
|
+
writeVec4(floatView, byteOffset + 48, [...object.halfExtent, 0]);
|
|
2298
|
+
writeVec4(floatView, byteOffset + 64, object.color);
|
|
2299
|
+
writeVec4(floatView, byteOffset + 80, object.emission);
|
|
2300
|
+
writeVec4(floatView, byteOffset + 96, [
|
|
1481
2301
|
object.roughness,
|
|
1482
2302
|
object.metallic,
|
|
1483
2303
|
object.opacity,
|
|
1484
2304
|
object.ior,
|
|
1485
2305
|
]);
|
|
2306
|
+
writeVec4(floatView, byteOffset + 112, [
|
|
2307
|
+
object.sheenColor[0] ?? 0,
|
|
2308
|
+
object.sheenColor[1] ?? 0,
|
|
2309
|
+
object.sheenColor[2] ?? 0,
|
|
2310
|
+
object.clearcoat,
|
|
2311
|
+
]);
|
|
2312
|
+
writeVec4(floatView, byteOffset + 128, [
|
|
2313
|
+
object.clearcoatRoughness,
|
|
2314
|
+
object.specular,
|
|
2315
|
+
object.transmission,
|
|
2316
|
+
object.thickness,
|
|
2317
|
+
]);
|
|
2318
|
+
writeVec4(floatView, byteOffset + 144, [
|
|
2319
|
+
object.specularColor[0] ?? 1,
|
|
2320
|
+
object.specularColor[1] ?? 1,
|
|
2321
|
+
object.specularColor[2] ?? 1,
|
|
2322
|
+
1,
|
|
2323
|
+
]);
|
|
1486
2324
|
});
|
|
1487
2325
|
|
|
1488
2326
|
return Object.freeze({
|
|
@@ -1511,7 +2349,7 @@ export function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1511
2349
|
uintView[u32 + 3] = triangle.flags;
|
|
1512
2350
|
uintView[u32 + 4] = triangle.materialRefId;
|
|
1513
2351
|
uintView[u32 + 5] = triangle.mediumRefId;
|
|
1514
|
-
uintView[u32 + 6] = 0;
|
|
2352
|
+
uintView[u32 + 6] = triangle.materialSlot ?? 0;
|
|
1515
2353
|
uintView[u32 + 7] = 0;
|
|
1516
2354
|
writeVec4(floatView, byteOffset + 32, [...triangle.v0, 0]);
|
|
1517
2355
|
writeVec4(floatView, byteOffset + 48, [...triangle.v1, 0]);
|
|
@@ -1524,6 +2362,15 @@ export function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1524
2362
|
writeVec4(floatView, byteOffset + 160, triangle.color);
|
|
1525
2363
|
writeVec4(floatView, byteOffset + 176, triangle.emission);
|
|
1526
2364
|
writeVec4(floatView, byteOffset + 192, triangle.material);
|
|
2365
|
+
writeVec4(floatView, byteOffset + 208, triangle.materialResponse);
|
|
2366
|
+
writeVec4(floatView, byteOffset + 224, triangle.materialExtension ?? [0.08, 1, 0, 0]);
|
|
2367
|
+
writeVec4(floatView, byteOffset + 240, triangle.specularColor ?? [1, 1, 1, 1]);
|
|
2368
|
+
writeVec4(floatView, byteOffset + 256, triangle.baseColorAtlas ?? [0, 0, 1, 1]);
|
|
2369
|
+
writeVec4(floatView, byteOffset + 272, triangle.metallicRoughnessAtlas ?? [0, 0, 1, 1]);
|
|
2370
|
+
writeVec4(floatView, byteOffset + 288, triangle.normalAtlas ?? [0, 0, 1, 1]);
|
|
2371
|
+
writeVec4(floatView, byteOffset + 304, triangle.occlusionAtlas ?? [0, 0, 1, 1]);
|
|
2372
|
+
writeVec4(floatView, byteOffset + 320, triangle.emissiveAtlas ?? [0, 0, 1, 1]);
|
|
2373
|
+
writeVec4(floatView, byteOffset + 336, triangle.textureSettings ?? [1, 1, 1, 0]);
|
|
1527
2374
|
});
|
|
1528
2375
|
|
|
1529
2376
|
return Object.freeze({
|
|
@@ -1623,6 +2470,12 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1623
2470
|
0,
|
|
1624
2471
|
0,
|
|
1625
2472
|
]);
|
|
2473
|
+
writeVec4(floatView, 304, [
|
|
2474
|
+
config.environmentMap.width ?? 1,
|
|
2475
|
+
config.environmentMap.height ?? 1,
|
|
2476
|
+
config.environmentMap.mipLevelCount ?? 1,
|
|
2477
|
+
config.environmentMap.hasImportanceData ? 1 : 0,
|
|
2478
|
+
]);
|
|
1626
2479
|
return bytes;
|
|
1627
2480
|
}
|
|
1628
2481
|
|
|
@@ -1813,6 +2666,7 @@ export function intersectWavefrontReferenceTriangle(ray, triangle, options = {})
|
|
|
1813
2666
|
color: triangle.color,
|
|
1814
2667
|
emission: triangle.emission,
|
|
1815
2668
|
material: triangle.material,
|
|
2669
|
+
materialResponse: triangle.materialResponse,
|
|
1816
2670
|
});
|
|
1817
2671
|
}
|
|
1818
2672
|
|
|
@@ -1840,6 +2694,7 @@ function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
|
1840
2694
|
color: Object.freeze([0, 0, 0, 0]),
|
|
1841
2695
|
emission: radiance,
|
|
1842
2696
|
material: Object.freeze([1, 0, 1, 1]),
|
|
2697
|
+
materialResponse: Object.freeze([0, 0, 0, 0]),
|
|
1843
2698
|
});
|
|
1844
2699
|
}
|
|
1845
2700
|
|
|
@@ -1932,12 +2787,378 @@ function environmentMapIntegerScale(data) {
|
|
|
1932
2787
|
return 1;
|
|
1933
2788
|
}
|
|
1934
2789
|
|
|
1935
|
-
function
|
|
1936
|
-
if (!
|
|
1937
|
-
return
|
|
2790
|
+
function environmentMapHasSamplingData(environmentMap) {
|
|
2791
|
+
if (!environmentMap || !environmentMap.data) {
|
|
2792
|
+
return false;
|
|
1938
2793
|
}
|
|
1939
|
-
const
|
|
1940
|
-
|
|
2794
|
+
const width = Math.max(1, environmentMap.width ?? 1);
|
|
2795
|
+
const height = Math.max(1, environmentMap.height ?? 1);
|
|
2796
|
+
return environmentMap.data.length >= width * height * 4;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
function createRgba8TextureUpload(source) {
|
|
2800
|
+
const width = Math.max(1, Math.trunc(source.width));
|
|
2801
|
+
const height = Math.max(1, Math.trunc(source.height));
|
|
2802
|
+
const bytesPerRow = alignTo(width * 4, 256);
|
|
2803
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
2804
|
+
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
2805
|
+
for (let y = 0; y < height; y += 1) {
|
|
2806
|
+
const sourceOffset = y * width * 4;
|
|
2807
|
+
const targetOffset = y * bytesPerRow;
|
|
2808
|
+
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
2809
|
+
}
|
|
2810
|
+
return Object.freeze({
|
|
2811
|
+
bytes,
|
|
2812
|
+
bytesPerRow,
|
|
2813
|
+
width,
|
|
2814
|
+
height,
|
|
2815
|
+
});
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
2819
|
+
if (!data || index >= data.length) {
|
|
2820
|
+
return fallback;
|
|
2821
|
+
}
|
|
2822
|
+
const value = Number(data[index]);
|
|
2823
|
+
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
function reflectVector(direction, normal) {
|
|
2827
|
+
return subtract(direction, scale(normal, 2 * dot(direction, normal)));
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
function buildOrthonormalBasis(normal) {
|
|
2831
|
+
const tangentFallback = Math.abs(normal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
2832
|
+
const tangent = normalize(cross(tangentFallback, normal), [1, 0, 0]);
|
|
2833
|
+
const bitangent = normalize(cross(normal, tangent), [0, 0, 1]);
|
|
2834
|
+
return { tangent, bitangent };
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
function localToWorld(local, normal) {
|
|
2838
|
+
const basis = buildOrthonormalBasis(normal);
|
|
2839
|
+
return normalize(
|
|
2840
|
+
add(
|
|
2841
|
+
add(scale(basis.tangent, local[0]), scale(basis.bitangent, local[1])),
|
|
2842
|
+
scale(normal, local[2])
|
|
2843
|
+
),
|
|
2844
|
+
normal
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
function radicalInverseVdc(bits) {
|
|
2849
|
+
let value = bits >>> 0;
|
|
2850
|
+
value = ((value << 16) | (value >>> 16)) >>> 0;
|
|
2851
|
+
value = (((value & 0x55555555) << 1) | ((value & 0xaaaaaaaa) >>> 1)) >>> 0;
|
|
2852
|
+
value = (((value & 0x33333333) << 2) | ((value & 0xcccccccc) >>> 2)) >>> 0;
|
|
2853
|
+
value = (((value & 0x0f0f0f0f) << 4) | ((value & 0xf0f0f0f0) >>> 4)) >>> 0;
|
|
2854
|
+
value = (((value & 0x00ff00ff) << 8) | ((value & 0xff00ff00) >>> 8)) >>> 0;
|
|
2855
|
+
return value * 2.3283064365386963e-10;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
function hammersley(index, count) {
|
|
2859
|
+
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
function importanceSampleGgx(sample, roughness, normal) {
|
|
2863
|
+
const alpha = Math.max(roughness * roughness, 0.0001);
|
|
2864
|
+
const phi = 2 * Math.PI * sample[0];
|
|
2865
|
+
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
2866
|
+
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
2867
|
+
const halfVector = localToWorld(
|
|
2868
|
+
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
2869
|
+
normal
|
|
2870
|
+
);
|
|
2871
|
+
return normalize(halfVector, normal);
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
function distributionGgx(nDotH, roughness) {
|
|
2875
|
+
const alpha = Math.max(roughness * roughness, 0.0001);
|
|
2876
|
+
const alpha2 = alpha * alpha;
|
|
2877
|
+
const denom = (nDotH * nDotH) * (alpha2 - 1) + 1;
|
|
2878
|
+
return alpha2 / Math.max(Math.PI * denom * denom, 0.000001);
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
function geometrySchlickGgx(nDotV, roughness) {
|
|
2882
|
+
const k = ((roughness + 1) * (roughness + 1)) / 8;
|
|
2883
|
+
return nDotV / Math.max(nDotV * (1 - k) + k, 0.000001);
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
function geometrySmith(nDotV, nDotL, roughness) {
|
|
2887
|
+
return geometrySchlickGgx(nDotV, roughness) * geometrySchlickGgx(nDotL, roughness);
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
2891
|
+
const viewDirection = [Math.sqrt(Math.max(0, 1 - nDotV * nDotV)), 0, nDotV];
|
|
2892
|
+
const normal = [0, 0, 1];
|
|
2893
|
+
let scaleTerm = 0;
|
|
2894
|
+
let biasTerm = 0;
|
|
2895
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
2896
|
+
const xi = hammersley(index, sampleCount);
|
|
2897
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2898
|
+
const vDotH = Math.max(dot(viewDirection, halfVector), 0);
|
|
2899
|
+
const lightDirection = normalize(
|
|
2900
|
+
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
2901
|
+
normal
|
|
2902
|
+
);
|
|
2903
|
+
const nDotL = Math.max(lightDirection[2], 0);
|
|
2904
|
+
const nDotH = Math.max(halfVector[2], 0);
|
|
2905
|
+
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
2909
|
+
const visibility = (geometry * vDotH) / Math.max(nDotH * nDotV, 0.000001);
|
|
2910
|
+
const fresnel = (1 - vDotH) ** 5;
|
|
2911
|
+
scaleTerm += (1 - fresnel) * visibility;
|
|
2912
|
+
biasTerm += fresnel * visibility;
|
|
2913
|
+
}
|
|
2914
|
+
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
function createBrdfLutUploadBytes(
|
|
2918
|
+
size = DEFAULT_BRDF_LUT_SIZE,
|
|
2919
|
+
sampleCount = DEFAULT_BRDF_LUT_SAMPLE_COUNT
|
|
2920
|
+
) {
|
|
2921
|
+
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
2922
|
+
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
2923
|
+
if (cached) {
|
|
2924
|
+
return cached;
|
|
2925
|
+
}
|
|
2926
|
+
const width = Math.max(1, Math.trunc(size));
|
|
2927
|
+
const height = Math.max(1, Math.trunc(size));
|
|
2928
|
+
const rowBytes = width * 8;
|
|
2929
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2930
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
2931
|
+
const view = new DataView(bytes.buffer);
|
|
2932
|
+
for (let y = 0; y < height; y += 1) {
|
|
2933
|
+
const roughness = (y + 0.5) / height;
|
|
2934
|
+
for (let x = 0; x < width; x += 1) {
|
|
2935
|
+
const nDotV = Math.max((x + 0.5) / width, 0.0001);
|
|
2936
|
+
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
2937
|
+
const offset = y * bytesPerRow + x * 8;
|
|
2938
|
+
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
2939
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
2940
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
2941
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
2945
|
+
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
2946
|
+
return upload;
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
2950
|
+
const width = Math.max(1, environmentMap.width);
|
|
2951
|
+
const height = Math.max(1, environmentMap.height);
|
|
2952
|
+
const pixels = new Float32Array(width * height * 4);
|
|
2953
|
+
const data = environmentMap.data;
|
|
2954
|
+
const integerScale = environmentMapIntegerScale(data);
|
|
2955
|
+
for (let index = 0; index < width * height; index += 1) {
|
|
2956
|
+
const sourceOffset = index * 4;
|
|
2957
|
+
const targetOffset = index * 4;
|
|
2958
|
+
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
2959
|
+
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
2960
|
+
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
2961
|
+
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
2962
|
+
}
|
|
2963
|
+
return pixels;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
2967
|
+
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
2968
|
+
const theta = v * Math.PI;
|
|
2969
|
+
const sinTheta = Math.sin(theta);
|
|
2970
|
+
return [
|
|
2971
|
+
Math.cos(angle) * sinTheta,
|
|
2972
|
+
Math.cos(theta),
|
|
2973
|
+
Math.sin(angle) * sinTheta,
|
|
2974
|
+
];
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
function sampleEnvironmentPixelsBilinear(pixels, width, height, u, v) {
|
|
2978
|
+
const wrappedU = ((u % 1) + 1) % 1;
|
|
2979
|
+
const clampedV = clamp(v, 0, 1);
|
|
2980
|
+
const x = wrappedU * width - 0.5;
|
|
2981
|
+
const y = clampedV * height - 0.5;
|
|
2982
|
+
const x0 = ((Math.floor(x) % width) + width) % width;
|
|
2983
|
+
const y0 = clamp(Math.floor(y), 0, height - 1);
|
|
2984
|
+
const x1 = (x0 + 1) % width;
|
|
2985
|
+
const y1 = clamp(y0 + 1, 0, height - 1);
|
|
2986
|
+
const tx = x - Math.floor(x);
|
|
2987
|
+
const ty = y - Math.floor(y);
|
|
2988
|
+
const read = (px, py) => {
|
|
2989
|
+
const offset = (py * width + px) * 4;
|
|
2990
|
+
return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]];
|
|
2991
|
+
};
|
|
2992
|
+
const a = read(x0, y0);
|
|
2993
|
+
const b = read(x1, y0);
|
|
2994
|
+
const c = read(x0, y1);
|
|
2995
|
+
const d = read(x1, y1);
|
|
2996
|
+
const mixPair = (first, second, factor) => first * (1 - factor) + second * factor;
|
|
2997
|
+
return [
|
|
2998
|
+
mixPair(mixPair(a[0], b[0], tx), mixPair(c[0], d[0], tx), ty),
|
|
2999
|
+
mixPair(mixPair(a[1], b[1], tx), mixPair(c[1], d[1], tx), ty),
|
|
3000
|
+
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
3001
|
+
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty),
|
|
3002
|
+
];
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
3006
|
+
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
3007
|
+
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
3008
|
+
const u = ((((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI)) + 0.5 + rotationTurns) % 1) + 1) % 1;
|
|
3009
|
+
const v = Math.acos(clamp(unitDirection[1], -1, 1)) / Math.PI;
|
|
3010
|
+
return [u, clamp(v, 0, 1)];
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
3014
|
+
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
3015
|
+
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
function createFloat16RgbaUploadFromLevels(levels) {
|
|
3019
|
+
return levels.map((level) => {
|
|
3020
|
+
const rowBytes = level.width * 8;
|
|
3021
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
3022
|
+
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
3023
|
+
const view = new DataView(bytes.buffer);
|
|
3024
|
+
for (let y = 0; y < level.height; y += 1) {
|
|
3025
|
+
for (let x = 0; x < level.width; x += 1) {
|
|
3026
|
+
const sourceOffset = (y * level.width + x) * 4;
|
|
3027
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
3028
|
+
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
3029
|
+
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
3030
|
+
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
3031
|
+
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
return Object.freeze({ bytes, bytesPerRow, width: level.width, height: level.height });
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
function createPrefilteredEnvironmentLevels(environmentMap, fallbackColor) {
|
|
3039
|
+
const sourcePixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
3040
|
+
const sourceWidth = Math.max(1, environmentMap.width);
|
|
3041
|
+
const sourceHeight = Math.max(1, environmentMap.height);
|
|
3042
|
+
const mipLevelCount = Math.max(1, Math.floor(Math.log2(Math.max(sourceWidth, sourceHeight))) + 1);
|
|
3043
|
+
const levels = [
|
|
3044
|
+
Object.freeze({
|
|
3045
|
+
width: sourceWidth,
|
|
3046
|
+
height: sourceHeight,
|
|
3047
|
+
data: sourcePixels,
|
|
3048
|
+
}),
|
|
3049
|
+
];
|
|
3050
|
+
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
3051
|
+
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
3052
|
+
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
3053
|
+
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
3054
|
+
const data = new Float32Array(width * height * 4);
|
|
3055
|
+
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
3056
|
+
for (let y = 0; y < height; y += 1) {
|
|
3057
|
+
for (let x = 0; x < width; x += 1) {
|
|
3058
|
+
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
3059
|
+
const normal = normalize(direction, [0, 1, 0]);
|
|
3060
|
+
const viewDirection = normal;
|
|
3061
|
+
let totalWeight = 0;
|
|
3062
|
+
const accum = [0, 0, 0];
|
|
3063
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
3064
|
+
const xi = hammersley(sampleIndex, sampleCount);
|
|
3065
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
3066
|
+
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
3067
|
+
const lightDirection = normalize(
|
|
3068
|
+
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
3069
|
+
normal
|
|
3070
|
+
);
|
|
3071
|
+
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
3072
|
+
if (nDotL <= 0.000001) {
|
|
3073
|
+
continue;
|
|
3074
|
+
}
|
|
3075
|
+
const radiance = sampleEnvironmentRadiance(
|
|
3076
|
+
sourcePixels,
|
|
3077
|
+
sourceWidth,
|
|
3078
|
+
sourceHeight,
|
|
3079
|
+
lightDirection,
|
|
3080
|
+
environmentMap.rotationRadians
|
|
3081
|
+
);
|
|
3082
|
+
accum[0] += radiance[0] * nDotL;
|
|
3083
|
+
accum[1] += radiance[1] * nDotL;
|
|
3084
|
+
accum[2] += radiance[2] * nDotL;
|
|
3085
|
+
totalWeight += nDotL;
|
|
3086
|
+
}
|
|
3087
|
+
const offset = (y * width + x) * 4;
|
|
3088
|
+
data[offset] = accum[0] / Math.max(totalWeight, 0.000001);
|
|
3089
|
+
data[offset + 1] = accum[1] / Math.max(totalWeight, 0.000001);
|
|
3090
|
+
data[offset + 2] = accum[2] / Math.max(totalWeight, 0.000001);
|
|
3091
|
+
data[offset + 3] = 1;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
levels.push(Object.freeze({ width, height, data }));
|
|
3095
|
+
}
|
|
3096
|
+
return Object.freeze({
|
|
3097
|
+
levels,
|
|
3098
|
+
mipLevelCount,
|
|
3099
|
+
width: sourceWidth,
|
|
3100
|
+
height: sourceHeight,
|
|
3101
|
+
});
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
function createEnvironmentSamplingTables(environmentMap, fallbackColor) {
|
|
3105
|
+
if (!environmentMapHasSamplingData(environmentMap)) {
|
|
3106
|
+
return Object.freeze({
|
|
3107
|
+
width: 1,
|
|
3108
|
+
height: 1,
|
|
3109
|
+
pdf: new Float32Array([1]),
|
|
3110
|
+
marginalCdf: new Float32Array([1]),
|
|
3111
|
+
conditionalCdf: new Float32Array([1]),
|
|
3112
|
+
hasImportanceData: false,
|
|
3113
|
+
});
|
|
3114
|
+
}
|
|
3115
|
+
const pixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
3116
|
+
const width = Math.max(1, environmentMap.width);
|
|
3117
|
+
const height = Math.max(1, environmentMap.height);
|
|
3118
|
+
const pdf = new Float32Array(width * height);
|
|
3119
|
+
const marginalCdf = new Float32Array(height);
|
|
3120
|
+
const conditionalCdf = new Float32Array(width * height);
|
|
3121
|
+
const rowSums = new Float32Array(height);
|
|
3122
|
+
let totalWeight = 0;
|
|
3123
|
+
for (let y = 0; y < height; y += 1) {
|
|
3124
|
+
const theta = ((y + 0.5) / height) * Math.PI;
|
|
3125
|
+
const sinTheta = Math.max(Math.sin(theta), 0.0001);
|
|
3126
|
+
let rowWeight = 0;
|
|
3127
|
+
for (let x = 0; x < width; x += 1) {
|
|
3128
|
+
const offset = (y * width + x) * 4;
|
|
3129
|
+
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
3130
|
+
const weight = Math.max(luminance * sinTheta, 0.000001);
|
|
3131
|
+
pdf[y * width + x] = weight;
|
|
3132
|
+
rowWeight += weight;
|
|
3133
|
+
conditionalCdf[y * width + x] = rowWeight;
|
|
3134
|
+
}
|
|
3135
|
+
rowSums[y] = rowWeight;
|
|
3136
|
+
totalWeight += rowWeight;
|
|
3137
|
+
if (rowWeight > 0) {
|
|
3138
|
+
for (let x = 0; x < width; x += 1) {
|
|
3139
|
+
conditionalCdf[y * width + x] /= rowWeight;
|
|
3140
|
+
}
|
|
3141
|
+
} else {
|
|
3142
|
+
for (let x = 0; x < width; x += 1) {
|
|
3143
|
+
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
marginalCdf[y] = totalWeight;
|
|
3147
|
+
}
|
|
3148
|
+
for (let y = 0; y < height; y += 1) {
|
|
3149
|
+
marginalCdf[y] /= Math.max(totalWeight, 0.000001);
|
|
3150
|
+
}
|
|
3151
|
+
for (let index = 0; index < pdf.length; index += 1) {
|
|
3152
|
+
pdf[index] /= Math.max(totalWeight, 0.000001);
|
|
3153
|
+
}
|
|
3154
|
+
return Object.freeze({
|
|
3155
|
+
width,
|
|
3156
|
+
height,
|
|
3157
|
+
pdf,
|
|
3158
|
+
marginalCdf,
|
|
3159
|
+
conditionalCdf,
|
|
3160
|
+
hasImportanceData: true,
|
|
3161
|
+
});
|
|
1941
3162
|
}
|
|
1942
3163
|
|
|
1943
3164
|
function createEnvironmentMapUploadBytes(environmentMap, fallbackColor) {
|
|
@@ -1970,12 +3191,13 @@ function createEnvironmentMapUploadBytes(environmentMap, fallbackColor) {
|
|
|
1970
3191
|
}
|
|
1971
3192
|
}
|
|
1972
3193
|
|
|
1973
|
-
|
|
3194
|
+
const upload = Object.freeze({
|
|
1974
3195
|
bytes,
|
|
1975
3196
|
bytesPerRow,
|
|
1976
3197
|
width,
|
|
1977
3198
|
height,
|
|
1978
3199
|
});
|
|
3200
|
+
return upload;
|
|
1979
3201
|
}
|
|
1980
3202
|
|
|
1981
3203
|
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
@@ -1988,9 +3210,13 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1988
3210
|
addressModeV: "clamp-to-edge",
|
|
1989
3211
|
magFilter: "linear",
|
|
1990
3212
|
minFilter: "linear",
|
|
3213
|
+
mipmapFilter: "linear",
|
|
1991
3214
|
}),
|
|
1992
3215
|
texture: null,
|
|
1993
3216
|
ownsTexture: false,
|
|
3217
|
+
width: Math.max(1, environmentMap.width),
|
|
3218
|
+
height: Math.max(1, environmentMap.height),
|
|
3219
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1),
|
|
1994
3220
|
});
|
|
1995
3221
|
}
|
|
1996
3222
|
|
|
@@ -2003,17 +3229,95 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
2003
3229
|
addressModeV: "clamp-to-edge",
|
|
2004
3230
|
magFilter: "linear",
|
|
2005
3231
|
minFilter: "linear",
|
|
3232
|
+
mipmapFilter: "linear",
|
|
2006
3233
|
}),
|
|
2007
3234
|
texture: environmentMap.texture,
|
|
2008
3235
|
ownsTexture: false,
|
|
3236
|
+
width: Math.max(1, environmentMap.width),
|
|
3237
|
+
height: Math.max(1, environmentMap.height),
|
|
3238
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1),
|
|
2009
3239
|
});
|
|
2010
3240
|
}
|
|
2011
3241
|
|
|
2012
|
-
const
|
|
3242
|
+
const prefiltered = createPrefilteredEnvironmentLevels(environmentMap, fallbackColor);
|
|
3243
|
+
const uploads = createFloat16RgbaUploadFromLevels(prefiltered.levels);
|
|
2013
3244
|
const texture = device.createTexture({
|
|
2014
3245
|
label: environmentMap.enabled
|
|
2015
3246
|
? "plasius.wavefront.environmentMap"
|
|
2016
3247
|
: "plasius.wavefront.environmentMapFallback",
|
|
3248
|
+
size: { width: prefiltered.width, height: prefiltered.height },
|
|
3249
|
+
format: "rgba16float",
|
|
3250
|
+
mipLevelCount: prefiltered.mipLevelCount,
|
|
3251
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST,
|
|
3252
|
+
});
|
|
3253
|
+
uploads.forEach((upload, mipLevel) => {
|
|
3254
|
+
device.queue.writeTexture(
|
|
3255
|
+
{ texture, mipLevel },
|
|
3256
|
+
upload.bytes,
|
|
3257
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3258
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
3259
|
+
);
|
|
3260
|
+
});
|
|
3261
|
+
return Object.freeze({
|
|
3262
|
+
view: texture.createView(),
|
|
3263
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
3264
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
3265
|
+
addressModeU: "repeat",
|
|
3266
|
+
addressModeV: "clamp-to-edge",
|
|
3267
|
+
magFilter: "linear",
|
|
3268
|
+
minFilter: "linear",
|
|
3269
|
+
mipmapFilter: "linear",
|
|
3270
|
+
}),
|
|
3271
|
+
texture,
|
|
3272
|
+
ownsTexture: true,
|
|
3273
|
+
width: prefiltered.width,
|
|
3274
|
+
height: prefiltered.height,
|
|
3275
|
+
mipLevelCount: prefiltered.mipLevelCount,
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
function createEnvironmentSamplingTextureResource(device, constants, environmentMap, fallbackColor) {
|
|
3280
|
+
const tables = createEnvironmentSamplingTables(environmentMap, fallbackColor);
|
|
3281
|
+
const rowBytes = tables.width * 8;
|
|
3282
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
3283
|
+
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
3284
|
+
const view = new DataView(bytes.buffer);
|
|
3285
|
+
for (let y = 0; y < tables.height; y += 1) {
|
|
3286
|
+
for (let x = 0; x < tables.width; x += 1) {
|
|
3287
|
+
const probability = tables.pdf[y * tables.width + x];
|
|
3288
|
+
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
3289
|
+
const marginal = tables.marginalCdf[y];
|
|
3290
|
+
const offset = y * bytesPerRow + x * 8;
|
|
3291
|
+
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
3292
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
3293
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
3294
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
const texture = device.createTexture({
|
|
3298
|
+
label: "plasius.wavefront.environmentSampling",
|
|
3299
|
+
size: { width: tables.width, height: tables.height },
|
|
3300
|
+
format: "rgba16float",
|
|
3301
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST,
|
|
3302
|
+
});
|
|
3303
|
+
device.queue.writeTexture(
|
|
3304
|
+
{ texture },
|
|
3305
|
+
bytes,
|
|
3306
|
+
{ bytesPerRow, rowsPerImage: tables.height },
|
|
3307
|
+
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
3308
|
+
);
|
|
3309
|
+
return Object.freeze({
|
|
3310
|
+
view: texture.createView(),
|
|
3311
|
+
texture,
|
|
3312
|
+
ownsTexture: true,
|
|
3313
|
+
hasImportanceData: tables.hasImportanceData,
|
|
3314
|
+
});
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE) {
|
|
3318
|
+
const upload = createBrdfLutUploadBytes(size);
|
|
3319
|
+
const texture = device.createTexture({
|
|
3320
|
+
label: "plasius.wavefront.brdfLut",
|
|
2017
3321
|
size: { width: upload.width, height: upload.height },
|
|
2018
3322
|
format: "rgba16float",
|
|
2019
3323
|
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST,
|
|
@@ -2026,15 +3330,117 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
2026
3330
|
);
|
|
2027
3331
|
return Object.freeze({
|
|
2028
3332
|
view: texture.createView(),
|
|
2029
|
-
sampler:
|
|
2030
|
-
label: "plasius.wavefront.
|
|
2031
|
-
addressModeU: "
|
|
3333
|
+
sampler: device.createSampler({
|
|
3334
|
+
label: "plasius.wavefront.brdfLutSampler",
|
|
3335
|
+
addressModeU: "clamp-to-edge",
|
|
2032
3336
|
addressModeV: "clamp-to-edge",
|
|
2033
3337
|
magFilter: "linear",
|
|
2034
3338
|
minFilter: "linear",
|
|
2035
3339
|
}),
|
|
2036
3340
|
texture,
|
|
2037
3341
|
ownsTexture: true,
|
|
3342
|
+
width: upload.width,
|
|
3343
|
+
height: upload.height,
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
function createMediumTextureResource(device, constants, mediums) {
|
|
3348
|
+
const normalized = Array.isArray(mediums) && mediums.length > 0 ? mediums : [{ id: 0 }];
|
|
3349
|
+
const width = Math.max(
|
|
3350
|
+
1,
|
|
3351
|
+
normalized.reduce((maximum, medium) => Math.max(maximum, medium.id ?? 0), 0) + 1
|
|
3352
|
+
);
|
|
3353
|
+
const level = {
|
|
3354
|
+
width,
|
|
3355
|
+
height: MEDIUM_TABLE_ROWS,
|
|
3356
|
+
data: new Float32Array(width * MEDIUM_TABLE_ROWS * 4),
|
|
3357
|
+
};
|
|
3358
|
+
|
|
3359
|
+
for (const medium of normalized) {
|
|
3360
|
+
const mediumId = Math.max(0, Math.trunc(Number(medium.id) || 0));
|
|
3361
|
+
const absorptionOffset = mediumId * 4;
|
|
3362
|
+
level.data[absorptionOffset] = Math.max(0, medium.absorption?.[0] ?? 0);
|
|
3363
|
+
level.data[absorptionOffset + 1] = Math.max(0, medium.absorption?.[1] ?? 0);
|
|
3364
|
+
level.data[absorptionOffset + 2] = Math.max(0, medium.absorption?.[2] ?? 0);
|
|
3365
|
+
level.data[absorptionOffset + 3] = Math.max(0, medium.phaseModel ?? 0);
|
|
3366
|
+
|
|
3367
|
+
const scatteringOffset = (width + mediumId) * 4;
|
|
3368
|
+
level.data[scatteringOffset] = Math.max(0, medium.scattering?.[0] ?? 0);
|
|
3369
|
+
level.data[scatteringOffset + 1] = Math.max(0, medium.scattering?.[1] ?? 0);
|
|
3370
|
+
level.data[scatteringOffset + 2] = Math.max(0, medium.scattering?.[2] ?? 0);
|
|
3371
|
+
level.data[scatteringOffset + 3] = Math.max(0, medium.density ?? 0);
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
const upload = createFloat16RgbaUploadFromLevels([level])[0];
|
|
3375
|
+
const texture = device.createTexture({
|
|
3376
|
+
label: "plasius.wavefront.mediumTable",
|
|
3377
|
+
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
3378
|
+
format: "rgba16float",
|
|
3379
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST,
|
|
3380
|
+
});
|
|
3381
|
+
device.queue.writeTexture(
|
|
3382
|
+
{ texture },
|
|
3383
|
+
upload.bytes,
|
|
3384
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3385
|
+
{ width, height: MEDIUM_TABLE_ROWS, depthOrArrayLayers: 1 }
|
|
3386
|
+
);
|
|
3387
|
+
return Object.freeze({
|
|
3388
|
+
texture,
|
|
3389
|
+
view: texture.createView(),
|
|
3390
|
+
ownsTexture: true,
|
|
3391
|
+
count: normalized.length,
|
|
3392
|
+
width,
|
|
3393
|
+
});
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
function mediumTablesEqual(left, right) {
|
|
3397
|
+
const leftMediums = Array.isArray(left) ? left : [];
|
|
3398
|
+
const rightMediums = Array.isArray(right) ? right : [];
|
|
3399
|
+
if (leftMediums.length !== rightMediums.length) {
|
|
3400
|
+
return false;
|
|
3401
|
+
}
|
|
3402
|
+
for (let index = 0; index < leftMediums.length; index += 1) {
|
|
3403
|
+
const leftMedium = leftMediums[index];
|
|
3404
|
+
const rightMedium = rightMediums[index];
|
|
3405
|
+
if ((leftMedium?.id ?? 0) !== (rightMedium?.id ?? 0)) {
|
|
3406
|
+
return false;
|
|
3407
|
+
}
|
|
3408
|
+
if ((leftMedium?.phaseModel ?? 0) !== (rightMedium?.phaseModel ?? 0)) {
|
|
3409
|
+
return false;
|
|
3410
|
+
}
|
|
3411
|
+
if ((leftMedium?.density ?? 0) !== (rightMedium?.density ?? 0)) {
|
|
3412
|
+
return false;
|
|
3413
|
+
}
|
|
3414
|
+
for (let component = 0; component < 3; component += 1) {
|
|
3415
|
+
if ((leftMedium?.absorption?.[component] ?? 0) !== (rightMedium?.absorption?.[component] ?? 0)) {
|
|
3416
|
+
return false;
|
|
3417
|
+
}
|
|
3418
|
+
if ((leftMedium?.scattering?.[component] ?? 0) !== (rightMedium?.scattering?.[component] ?? 0)) {
|
|
3419
|
+
return false;
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
return true;
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
3427
|
+
const upload = createRgba8TextureUpload(atlas);
|
|
3428
|
+
const texture = device.createTexture({
|
|
3429
|
+
label,
|
|
3430
|
+
size: { width: upload.width, height: upload.height },
|
|
3431
|
+
format: "rgba8unorm",
|
|
3432
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST,
|
|
3433
|
+
});
|
|
3434
|
+
device.queue.writeTexture(
|
|
3435
|
+
{ texture },
|
|
3436
|
+
upload.bytes,
|
|
3437
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3438
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
3439
|
+
);
|
|
3440
|
+
return Object.freeze({
|
|
3441
|
+
texture,
|
|
3442
|
+
view: texture.createView(),
|
|
3443
|
+
ownsTexture: true,
|
|
2038
3444
|
});
|
|
2039
3445
|
}
|
|
2040
3446
|
|
|
@@ -2084,6 +3490,26 @@ async function createComputePipeline(device, shaderModule, layout, entryPoint, l
|
|
|
2084
3490
|
}
|
|
2085
3491
|
}
|
|
2086
3492
|
|
|
3493
|
+
async function assertShaderModuleCompiles(shaderModule, label) {
|
|
3494
|
+
if (typeof shaderModule?.compilationInfo !== "function") {
|
|
3495
|
+
return;
|
|
3496
|
+
}
|
|
3497
|
+
const info = await shaderModule.compilationInfo();
|
|
3498
|
+
const messages = Array.isArray(info?.messages) ? info.messages : [];
|
|
3499
|
+
const errors = messages.filter((message) => message?.type === "error");
|
|
3500
|
+
if (errors.length <= 0) {
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
const diagnostics = errors
|
|
3504
|
+
.map((message) => {
|
|
3505
|
+
const line = Number.isFinite(message.lineNum) ? message.lineNum : "?";
|
|
3506
|
+
const column = Number.isFinite(message.linePos) ? message.linePos : "?";
|
|
3507
|
+
return `line ${line}:${column} ${message.message}`;
|
|
3508
|
+
})
|
|
3509
|
+
.join("\n");
|
|
3510
|
+
throw new Error(`WGSL compilation preflight failed for ${label}:\n${diagnostics}`);
|
|
3511
|
+
}
|
|
3512
|
+
|
|
2087
3513
|
async function createRenderPipeline(device, descriptor) {
|
|
2088
3514
|
if (typeof device.createRenderPipelineAsync === "function") {
|
|
2089
3515
|
return device.createRenderPipelineAsync(descriptor);
|
|
@@ -2093,6 +3519,7 @@ async function createRenderPipeline(device, descriptor) {
|
|
|
2093
3519
|
|
|
2094
3520
|
const WAVEFRONT_COMPUTE_WGSL = `
|
|
2095
3521
|
const RAY_FLAG_GUIDED_EMISSIVE: u32 = 1u;
|
|
3522
|
+
const RAY_FLAG_DELTA_SAMPLE: u32 = 2u;
|
|
2096
3523
|
|
|
2097
3524
|
struct RayRecord {
|
|
2098
3525
|
rayId: u32,
|
|
@@ -2118,11 +3545,12 @@ struct HitRecord {
|
|
|
2118
3545
|
primitiveId: u32,
|
|
2119
3546
|
materialRefId: u32,
|
|
2120
3547
|
mediumRefId: u32,
|
|
3548
|
+
materialSlot: u32,
|
|
2121
3549
|
pad0: u32,
|
|
2122
3550
|
pad1: u32,
|
|
2123
|
-
pad2: u32,
|
|
2124
3551
|
distance: f32,
|
|
2125
|
-
|
|
3552
|
+
occlusion: f32,
|
|
3553
|
+
pad2: vec2<f32>,
|
|
2126
3554
|
position: vec4<f32>,
|
|
2127
3555
|
geometricNormal: vec4<f32>,
|
|
2128
3556
|
shadingNormal: vec4<f32>,
|
|
@@ -2131,6 +3559,9 @@ struct HitRecord {
|
|
|
2131
3559
|
color: vec4<f32>,
|
|
2132
3560
|
emission: vec4<f32>,
|
|
2133
3561
|
material: vec4<f32>,
|
|
3562
|
+
materialResponse: vec4<f32>,
|
|
3563
|
+
materialExtension: vec4<f32>,
|
|
3564
|
+
specularColor: vec4<f32>,
|
|
2134
3565
|
};
|
|
2135
3566
|
|
|
2136
3567
|
struct SceneObject {
|
|
@@ -2138,11 +3569,18 @@ struct SceneObject {
|
|
|
2138
3569
|
objectId: u32,
|
|
2139
3570
|
materialKind: u32,
|
|
2140
3571
|
flags: u32,
|
|
3572
|
+
mediumRefId: u32,
|
|
3573
|
+
pad0: u32,
|
|
3574
|
+
pad1: u32,
|
|
3575
|
+
pad2: u32,
|
|
2141
3576
|
center: vec4<f32>,
|
|
2142
3577
|
halfExtent: vec4<f32>,
|
|
2143
3578
|
color: vec4<f32>,
|
|
2144
3579
|
emission: vec4<f32>,
|
|
2145
3580
|
material: vec4<f32>,
|
|
3581
|
+
materialResponse: vec4<f32>,
|
|
3582
|
+
materialExtension: vec4<f32>,
|
|
3583
|
+
specularColor: vec4<f32>,
|
|
2146
3584
|
};
|
|
2147
3585
|
|
|
2148
3586
|
struct TriangleRecord {
|
|
@@ -2152,7 +3590,7 @@ struct TriangleRecord {
|
|
|
2152
3590
|
flags: u32,
|
|
2153
3591
|
materialRefId: u32,
|
|
2154
3592
|
mediumRefId: u32,
|
|
2155
|
-
|
|
3593
|
+
materialSlot: u32,
|
|
2156
3594
|
pad1: u32,
|
|
2157
3595
|
v0: vec4<f32>,
|
|
2158
3596
|
v1: vec4<f32>,
|
|
@@ -2165,6 +3603,15 @@ struct TriangleRecord {
|
|
|
2165
3603
|
color: vec4<f32>,
|
|
2166
3604
|
emission: vec4<f32>,
|
|
2167
3605
|
material: vec4<f32>,
|
|
3606
|
+
materialResponse: vec4<f32>,
|
|
3607
|
+
materialExtension: vec4<f32>,
|
|
3608
|
+
specularColor: vec4<f32>,
|
|
3609
|
+
baseColorAtlas: vec4<f32>,
|
|
3610
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3611
|
+
normalAtlas: vec4<f32>,
|
|
3612
|
+
occlusionAtlas: vec4<f32>,
|
|
3613
|
+
emissiveAtlas: vec4<f32>,
|
|
3614
|
+
textureSettings: vec4<f32>,
|
|
2168
3615
|
};
|
|
2169
3616
|
|
|
2170
3617
|
struct BvhNode {
|
|
@@ -2185,10 +3632,10 @@ struct BvhLeafRef {
|
|
|
2185
3632
|
|
|
2186
3633
|
struct ScatterResult {
|
|
2187
3634
|
direction: vec4<f32>,
|
|
3635
|
+
pdf: f32,
|
|
3636
|
+
mediumRefId: u32,
|
|
2188
3637
|
flags: u32,
|
|
2189
3638
|
pad0: u32,
|
|
2190
|
-
pad1: u32,
|
|
2191
|
-
pad2: u32,
|
|
2192
3639
|
};
|
|
2193
3640
|
|
|
2194
3641
|
struct MeshVertex {
|
|
@@ -2209,10 +3656,19 @@ struct MeshRange {
|
|
|
2209
3656
|
triangleCount: u32,
|
|
2210
3657
|
firstVertex: u32,
|
|
2211
3658
|
vertexCount: u32,
|
|
2212
|
-
|
|
3659
|
+
materialSlot: u32,
|
|
2213
3660
|
color: vec4<f32>,
|
|
2214
3661
|
emission: vec4<f32>,
|
|
2215
3662
|
material: vec4<f32>,
|
|
3663
|
+
materialResponse: vec4<f32>,
|
|
3664
|
+
materialExtension: vec4<f32>,
|
|
3665
|
+
specularColor: vec4<f32>,
|
|
3666
|
+
baseColorAtlas: vec4<f32>,
|
|
3667
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3668
|
+
normalAtlas: vec4<f32>,
|
|
3669
|
+
occlusionAtlas: vec4<f32>,
|
|
3670
|
+
emissiveAtlas: vec4<f32>,
|
|
3671
|
+
textureSettings: vec4<f32>,
|
|
2216
3672
|
};
|
|
2217
3673
|
|
|
2218
3674
|
struct FrameConfig {
|
|
@@ -2253,6 +3709,7 @@ struct FrameConfig {
|
|
|
2253
3709
|
_portalPad1: u32,
|
|
2254
3710
|
environmentMapSettings: vec4<f32>,
|
|
2255
3711
|
pathResolveSettings: vec4<f32>,
|
|
3712
|
+
environmentMapMeta: vec4<f32>,
|
|
2256
3713
|
};
|
|
2257
3714
|
|
|
2258
3715
|
struct Counters {
|
|
@@ -2315,6 +3772,16 @@ struct EnvironmentPortal {
|
|
|
2315
3772
|
@group(0) @binding(20) var environmentMapTexture: texture_2d<f32>;
|
|
2316
3773
|
@group(0) @binding(21) var environmentMapSampler: sampler;
|
|
2317
3774
|
@group(0) @binding(22) var<storage, read_write> pathVertices: array<vec4<f32>>;
|
|
3775
|
+
@group(0) @binding(23) var baseColorAtlasTexture: texture_2d<f32>;
|
|
3776
|
+
@group(0) @binding(24) var metallicRoughnessAtlasTexture: texture_2d<f32>;
|
|
3777
|
+
@group(0) @binding(25) var normalAtlasTexture: texture_2d<f32>;
|
|
3778
|
+
@group(0) @binding(26) var occlusionAtlasTexture: texture_2d<f32>;
|
|
3779
|
+
@group(0) @binding(27) var emissiveAtlasTexture: texture_2d<f32>;
|
|
3780
|
+
@group(0) @binding(28) var materialAtlasSampler: sampler;
|
|
3781
|
+
@group(0) @binding(29) var brdfLutTexture: texture_2d<f32>;
|
|
3782
|
+
@group(0) @binding(30) var brdfLutSampler: sampler;
|
|
3783
|
+
@group(0) @binding(31) var environmentSamplingTexture: texture_2d<f32>;
|
|
3784
|
+
@group(0) @binding(32) var mediumTableTexture: texture_2d<f32>;
|
|
2318
3785
|
|
|
2319
3786
|
fn hash_u32(value: u32) -> u32 {
|
|
2320
3787
|
var x = value;
|
|
@@ -2351,6 +3818,146 @@ fn safe_normalize(value: vec3<f32>, fallback: vec3<f32>) -> vec3<f32> {
|
|
|
2351
3818
|
return value / len;
|
|
2352
3819
|
}
|
|
2353
3820
|
|
|
3821
|
+
struct TangentBasis {
|
|
3822
|
+
tangent: vec3<f32>,
|
|
3823
|
+
bitangent: vec3<f32>,
|
|
3824
|
+
};
|
|
3825
|
+
|
|
3826
|
+
struct SurfaceMaterialSample {
|
|
3827
|
+
color: vec4<f32>,
|
|
3828
|
+
emission: vec4<f32>,
|
|
3829
|
+
material: vec4<f32>,
|
|
3830
|
+
materialResponse: vec4<f32>,
|
|
3831
|
+
materialExtension: vec4<f32>,
|
|
3832
|
+
specularColor: vec4<f32>,
|
|
3833
|
+
shadingNormal: vec3<f32>,
|
|
3834
|
+
occlusion: f32,
|
|
3835
|
+
};
|
|
3836
|
+
|
|
3837
|
+
fn srgb_to_linear_channel(value: f32) -> f32 {
|
|
3838
|
+
if (value <= 0.04045) {
|
|
3839
|
+
return value / 12.92;
|
|
3840
|
+
}
|
|
3841
|
+
return pow((value + 0.055) / 1.055, 2.4);
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
fn srgb_to_linear_vec3(value: vec3<f32>) -> vec3<f32> {
|
|
3845
|
+
return vec3<f32>(
|
|
3846
|
+
srgb_to_linear_channel(value.x),
|
|
3847
|
+
srgb_to_linear_channel(value.y),
|
|
3848
|
+
srgb_to_linear_channel(value.z)
|
|
3849
|
+
);
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
fn wrap_uv(uv: vec2<f32>) -> vec2<f32> {
|
|
3853
|
+
return fract(fract(uv) + vec2<f32>(1.0));
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
fn atlas_sample_uv(rect: vec4<f32>, uv: vec2<f32>) -> vec2<f32> {
|
|
3857
|
+
let local = wrap_uv(uv);
|
|
3858
|
+
let clamped = clamp(local, vec2<f32>(0.001), vec2<f32>(0.999));
|
|
3859
|
+
return rect.xy + clamped * rect.zw;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
fn sample_atlas(textureRef: texture_2d<f32>, rect: vec4<f32>, uv: vec2<f32>) -> vec4<f32> {
|
|
3863
|
+
return textureSampleLevel(textureRef, materialAtlasSampler, atlas_sample_uv(rect, uv), 0.0);
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
fn build_triangle_tangent_basis(
|
|
3867
|
+
triangle: TriangleRecord,
|
|
3868
|
+
fallbackNormal: vec3<f32>
|
|
3869
|
+
) -> TangentBasis {
|
|
3870
|
+
let edge1 = triangle.v1.xyz - triangle.v0.xyz;
|
|
3871
|
+
let edge2 = triangle.v2.xyz - triangle.v0.xyz;
|
|
3872
|
+
let uv0 = triangle.uv0uv1.xy;
|
|
3873
|
+
let uv1 = triangle.uv0uv1.zw;
|
|
3874
|
+
let uv2 = triangle.uv2Pad.xy;
|
|
3875
|
+
let deltaUv1 = uv1 - uv0;
|
|
3876
|
+
let deltaUv2 = uv2 - uv0;
|
|
3877
|
+
let determinant = deltaUv1.x * deltaUv2.y - deltaUv1.y * deltaUv2.x;
|
|
3878
|
+
if (abs(determinant) <= 0.000001) {
|
|
3879
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(fallbackNormal.y) >= 0.999);
|
|
3880
|
+
let tangent = safe_normalize(cross(tangentFallback, fallbackNormal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3881
|
+
let bitangent = safe_normalize(cross(fallbackNormal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3882
|
+
return TangentBasis(tangent, bitangent);
|
|
3883
|
+
}
|
|
3884
|
+
let inverse = 1.0 / determinant;
|
|
3885
|
+
let tangent = safe_normalize(
|
|
3886
|
+
inverse * (edge1 * deltaUv2.y - edge2 * deltaUv1.y),
|
|
3887
|
+
vec3<f32>(1.0, 0.0, 0.0)
|
|
3888
|
+
);
|
|
3889
|
+
let bitangent = safe_normalize(
|
|
3890
|
+
inverse * (-edge1 * deltaUv2.x + edge2 * deltaUv1.x),
|
|
3891
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3892
|
+
);
|
|
3893
|
+
return TangentBasis(tangent, bitangent);
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3896
|
+
fn sample_surface_material(
|
|
3897
|
+
triangle: TriangleRecord,
|
|
3898
|
+
uv: vec2<f32>,
|
|
3899
|
+
geometricNormal: vec3<f32>,
|
|
3900
|
+
shadingNormal: vec3<f32>
|
|
3901
|
+
) -> SurfaceMaterialSample {
|
|
3902
|
+
let baseColorTexel = sample_atlas(baseColorAtlasTexture, triangle.baseColorAtlas, uv);
|
|
3903
|
+
let baseColor = vec4<f32>(
|
|
3904
|
+
clamp(triangle.color.rgb * srgb_to_linear_vec3(baseColorTexel.rgb), vec3<f32>(0.0), vec3<f32>(1.0)),
|
|
3905
|
+
clamp(triangle.color.a * baseColorTexel.a, 0.0, 1.0)
|
|
3906
|
+
);
|
|
3907
|
+
let metallicRoughnessTexel = sample_atlas(
|
|
3908
|
+
metallicRoughnessAtlasTexture,
|
|
3909
|
+
triangle.metallicRoughnessAtlas,
|
|
3910
|
+
uv
|
|
3911
|
+
);
|
|
3912
|
+
let normalTexel = sample_atlas(normalAtlasTexture, triangle.normalAtlas, uv);
|
|
3913
|
+
let occlusionTexel = sample_atlas(occlusionAtlasTexture, triangle.occlusionAtlas, uv);
|
|
3914
|
+
let emissiveTexel = sample_atlas(emissiveAtlasTexture, triangle.emissiveAtlas, uv);
|
|
3915
|
+
let normalScale = clamp(triangle.textureSettings.x, 0.0, 1.0);
|
|
3916
|
+
let tangentBasis = build_triangle_tangent_basis(triangle, geometricNormal);
|
|
3917
|
+
let tangentNormal = safe_normalize(
|
|
3918
|
+
vec3<f32>(
|
|
3919
|
+
(normalTexel.x * 2.0 - 1.0) * normalScale,
|
|
3920
|
+
(normalTexel.y * 2.0 - 1.0) * normalScale,
|
|
3921
|
+
1.0 + ((normalTexel.z * 2.0 - 1.0) - 1.0) * normalScale
|
|
3922
|
+
),
|
|
3923
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3924
|
+
);
|
|
3925
|
+
let mappedNormal = safe_normalize(
|
|
3926
|
+
tangentBasis.tangent * tangentNormal.x +
|
|
3927
|
+
tangentBasis.bitangent * tangentNormal.y +
|
|
3928
|
+
shadingNormal * tangentNormal.z,
|
|
3929
|
+
shadingNormal
|
|
3930
|
+
);
|
|
3931
|
+
let emission = vec4<f32>(
|
|
3932
|
+
max(
|
|
3933
|
+
triangle.emission.rgb *
|
|
3934
|
+
srgb_to_linear_vec3(emissiveTexel.rgb) *
|
|
3935
|
+
max(triangle.textureSettings.z, 0.0),
|
|
3936
|
+
vec3<f32>(0.0)
|
|
3937
|
+
),
|
|
3938
|
+
clamp(triangle.emission.a * emissiveTexel.a, 0.0, 1.0)
|
|
3939
|
+
);
|
|
3940
|
+
return SurfaceMaterialSample(
|
|
3941
|
+
baseColor,
|
|
3942
|
+
emission,
|
|
3943
|
+
vec4<f32>(
|
|
3944
|
+
clamp(triangle.material.x * metallicRoughnessTexel.y, 0.0, 1.0),
|
|
3945
|
+
clamp(triangle.material.y * metallicRoughnessTexel.z, 0.0, 1.0),
|
|
3946
|
+
clamp(triangle.material.z * baseColor.a, 0.0, 1.0),
|
|
3947
|
+
clamp(triangle.material.w, 1.0, 3.0)
|
|
3948
|
+
),
|
|
3949
|
+
triangle.materialResponse,
|
|
3950
|
+
triangle.materialExtension,
|
|
3951
|
+
triangle.specularColor,
|
|
3952
|
+
repair_shading_normal(geometricNormal, mappedNormal),
|
|
3953
|
+
clamp(
|
|
3954
|
+
mix(1.0, occlusionTexel.x, clamp(triangle.textureSettings.y, 0.0, 1.0)),
|
|
3955
|
+
0.0,
|
|
3956
|
+
1.0
|
|
3957
|
+
)
|
|
3958
|
+
);
|
|
3959
|
+
}
|
|
3960
|
+
|
|
2354
3961
|
fn saturate(value: f32) -> f32 {
|
|
2355
3962
|
return clamp(value, 0.0, 1.0);
|
|
2356
3963
|
}
|
|
@@ -2359,6 +3966,10 @@ fn max_component(value: vec3<f32>) -> f32 {
|
|
|
2359
3966
|
return max(max(value.x, value.y), value.z);
|
|
2360
3967
|
}
|
|
2361
3968
|
|
|
3969
|
+
fn radiance_luminance(value: vec3<f32>) -> f32 {
|
|
3970
|
+
return dot(value, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
3971
|
+
}
|
|
3972
|
+
|
|
2362
3973
|
fn environment_map_enabled() -> bool {
|
|
2363
3974
|
return config.environmentMapSettings.x > 0.5;
|
|
2364
3975
|
}
|
|
@@ -2388,103 +3999,440 @@ fn clear_deferred_path(rayId: u32) {
|
|
|
2388
3999
|
}
|
|
2389
4000
|
}
|
|
2390
4001
|
|
|
2391
|
-
fn record_deferred_path_response(ray: RayRecord, response: vec3<f32>) {
|
|
2392
|
-
if (!deferred_path_resolve_enabled() || ray.rayId >= config.tilePixelCount || ray.bounce >= config.maxDepth) {
|
|
2393
|
-
return;
|
|
2394
|
-
}
|
|
2395
|
-
pathVertices[path_vertex_index(ray.rayId, ray.bounce)] =
|
|
2396
|
-
vec4<f32>(max(response, vec3<f32>(0.0)), 1.0);
|
|
4002
|
+
fn record_deferred_path_response(ray: RayRecord, response: vec3<f32>) {
|
|
4003
|
+
if (!deferred_path_resolve_enabled() || ray.rayId >= config.tilePixelCount || ray.bounce >= config.maxDepth) {
|
|
4004
|
+
return;
|
|
4005
|
+
}
|
|
4006
|
+
pathVertices[path_vertex_index(ray.rayId, ray.bounce)] =
|
|
4007
|
+
vec4<f32>(max(response, vec3<f32>(0.0)), 1.0);
|
|
4008
|
+
}
|
|
4009
|
+
|
|
4010
|
+
fn record_deferred_terminal_source(ray: RayRecord, sourceRadiance: vec3<f32>) {
|
|
4011
|
+
if (!deferred_path_resolve_enabled() || ray.rayId >= config.tilePixelCount) {
|
|
4012
|
+
return;
|
|
4013
|
+
}
|
|
4014
|
+
pathVertices[path_vertex_index(ray.rayId, config.maxDepth)] =
|
|
4015
|
+
vec4<f32>(clamp_sample_radiance(sourceRadiance), 1.0);
|
|
4016
|
+
}
|
|
4017
|
+
|
|
4018
|
+
fn environment_map_uv(direction: vec3<f32>) -> vec2<f32> {
|
|
4019
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4020
|
+
let rotationTurns = config.environmentMapSettings.z / 6.28318530718;
|
|
4021
|
+
let u = fract(atan2(rayDirection.z, rayDirection.x) / 6.28318530718 + 0.5 + rotationTurns);
|
|
4022
|
+
let v = acos(clamp(rayDirection.y, -1.0, 1.0)) / 3.14159265359;
|
|
4023
|
+
return vec2<f32>(u, clamp(v, 0.0, 1.0));
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
fn environment_map_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
4027
|
+
let uv = environment_map_uv(direction);
|
|
4028
|
+
let texel = max(textureSampleLevel(environmentMapTexture, environmentMapSampler, uv, 0.0).rgb, vec3<f32>(0.0));
|
|
4029
|
+
return texel * max(config.environmentMapSettings.y, 0.0);
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
fn procedural_environment_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
4033
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4034
|
+
let upFactor = saturate(rayDirection.y * 0.5 + 0.5);
|
|
4035
|
+
let sunDirection = safe_normalize(
|
|
4036
|
+
config.environmentSunDirectionIntensity.xyz,
|
|
4037
|
+
vec3<f32>(0.0, 1.0, 0.0)
|
|
4038
|
+
);
|
|
4039
|
+
let sunGlow = pow(saturate(dot(rayDirection, sunDirection)), 192.0);
|
|
4040
|
+
let gradient =
|
|
4041
|
+
config.environmentHorizonColor.xyz * (1.0 - upFactor) +
|
|
4042
|
+
config.environmentZenithColor.xyz * upFactor;
|
|
4043
|
+
return (
|
|
4044
|
+
gradient +
|
|
4045
|
+
config.environmentSunColor.xyz * sunGlow
|
|
4046
|
+
) * max(config.environmentSunDirectionIntensity.w, 0.0001);
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
fn base_environment_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
4050
|
+
if (environment_map_enabled()) {
|
|
4051
|
+
return environment_map_radiance(direction);
|
|
4052
|
+
}
|
|
4053
|
+
return procedural_environment_radiance(direction);
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
4057
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
4058
|
+
return vec3<f32>(1.0);
|
|
4059
|
+
}
|
|
4060
|
+
var scale = vec3<f32>(0.0);
|
|
4061
|
+
for (var portalIndex = 0u; portalIndex < config.environmentPortalCount; portalIndex = portalIndex + 1u) {
|
|
4062
|
+
let portal = environmentPortals[portalIndex];
|
|
4063
|
+
if (portal.kind == 1u) {
|
|
4064
|
+
let portalNormal = safe_normalize(portal.normal.xyz, vec3<f32>(0.0, 0.0, 1.0));
|
|
4065
|
+
let denominator = dot(direction, portalNormal);
|
|
4066
|
+
let twoSided = (portal.flags & 1u) != 0u;
|
|
4067
|
+
var facing = abs(denominator) > 0.0001;
|
|
4068
|
+
if (!twoSided && denominator <= 0.0001) {
|
|
4069
|
+
facing = false;
|
|
4070
|
+
}
|
|
4071
|
+
if (facing) {
|
|
4072
|
+
let distance = dot(portal.position.xyz - origin, portalNormal) / denominator;
|
|
4073
|
+
if (distance > 0.001) {
|
|
4074
|
+
let hitPosition = origin + direction * distance;
|
|
4075
|
+
let local = hitPosition - portal.position.xyz;
|
|
4076
|
+
let tangent = safe_normalize(portal.tangent.xyz, vec3<f32>(1.0, 0.0, 0.0));
|
|
4077
|
+
let bitangent = safe_normalize(portal.bitangent.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4078
|
+
let u = dot(local, tangent);
|
|
4079
|
+
let v = dot(local, bitangent);
|
|
4080
|
+
if (abs(u) <= portal.tangent.w && abs(v) <= portal.bitangent.w) {
|
|
4081
|
+
let areaWeight = clamp(sqrt(max(portal.position.w, 0.0001)), 0.25, 4.0);
|
|
4082
|
+
let angleWeight = max(abs(denominator), 0.08);
|
|
4083
|
+
let portalScale = portal.color.rgb * portal.normal.w * portal.color.a * areaWeight * angleWeight;
|
|
4084
|
+
scale = max(scale, portalScale);
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
return scale;
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
4094
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4095
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
4096
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
4097
|
+
return base_environment_radiance(rayDirection) *
|
|
4098
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
4099
|
+
}
|
|
4100
|
+
|
|
4101
|
+
fn direct_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
4102
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4103
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
4104
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
4105
|
+
if (
|
|
4106
|
+
config.environmentPortalCount > 0u &&
|
|
4107
|
+
config.environmentPortalMode == 2u &&
|
|
4108
|
+
!portalHit
|
|
4109
|
+
) {
|
|
4110
|
+
return vec3<f32>(0.0);
|
|
4111
|
+
}
|
|
4112
|
+
return base_environment_radiance(rayDirection) *
|
|
4113
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
fn radical_inverse_vdc(bitsValue: u32) -> f32 {
|
|
4117
|
+
var bits = bitsValue;
|
|
4118
|
+
bits = (bits << 16u) | (bits >> 16u);
|
|
4119
|
+
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xaaaaaaaau) >> 1u);
|
|
4120
|
+
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xccccccccu) >> 2u);
|
|
4121
|
+
bits = ((bits & 0x0f0f0f0fu) << 4u) | ((bits & 0xf0f0f0f0u) >> 4u);
|
|
4122
|
+
bits = ((bits & 0x00ff00ffu) << 8u) | ((bits & 0xff00ff00u) >> 8u);
|
|
4123
|
+
return f32(bits) * 2.3283064365386963e-10;
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
fn hammersley_2d(index: u32, count: u32) -> vec2<f32> {
|
|
4127
|
+
return vec2<f32>(f32(index) / max(f32(count), 1.0), radical_inverse_vdc(index));
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
fn build_basis_tangent(normal: vec3<f32>) -> vec3<f32> {
|
|
4131
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(normal.y) >= 0.999);
|
|
4132
|
+
return safe_normalize(cross(tangentFallback, normal), vec3<f32>(1.0, 0.0, 0.0));
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
fn local_to_world(local: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
4136
|
+
let tangent = build_basis_tangent(normal);
|
|
4137
|
+
let bitangent = safe_normalize(cross(normal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
4138
|
+
return safe_normalize(tangent * local.x + bitangent * local.y + normal * local.z, normal);
|
|
4139
|
+
}
|
|
4140
|
+
|
|
4141
|
+
fn cosine_sample_hemisphere(sample: vec2<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
4142
|
+
let phi = 6.28318530718 * sample.x;
|
|
4143
|
+
let radius = sqrt(sample.y);
|
|
4144
|
+
let x = cos(phi) * radius;
|
|
4145
|
+
let y = sin(phi) * radius;
|
|
4146
|
+
let z = sqrt(max(0.0, 1.0 - sample.y));
|
|
4147
|
+
return local_to_world(vec3<f32>(x, y, z), normal);
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
fn importance_sample_ggx(sample: vec2<f32>, roughness: f32, normal: vec3<f32>) -> vec3<f32> {
|
|
4151
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
4152
|
+
let phi = 6.28318530718 * sample.x;
|
|
4153
|
+
let cosTheta = sqrt((1.0 - sample.y) / max(1.0 + (alpha * alpha - 1.0) * sample.y, 0.0001));
|
|
4154
|
+
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
4155
|
+
let localHalf = vec3<f32>(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
|
4156
|
+
return local_to_world(localHalf, normal);
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
fn distribution_ggx(normal: vec3<f32>, halfVector: vec3<f32>, roughness: f32) -> f32 {
|
|
4160
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
4161
|
+
let alpha2 = alpha * alpha;
|
|
4162
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
4163
|
+
let denominator = nDotH * nDotH * (alpha2 - 1.0) + 1.0;
|
|
4164
|
+
return alpha2 / max(3.14159265359 * denominator * denominator, 0.000001);
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
fn geometry_schlick_ggx(nDotValue: f32, roughness: f32) -> f32 {
|
|
4168
|
+
let k = ((roughness + 1.0) * (roughness + 1.0)) / 8.0;
|
|
4169
|
+
return nDotValue / max(nDotValue * (1.0 - k) + k, 0.000001);
|
|
4170
|
+
}
|
|
4171
|
+
|
|
4172
|
+
fn geometry_smith(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
4173
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
4174
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4175
|
+
return geometry_schlick_ggx(nDotV, roughness) * geometry_schlick_ggx(nDotL, roughness);
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4178
|
+
fn fresnel_schlick(cosine: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
4179
|
+
return f0 + (vec3<f32>(1.0) - f0) * pow(1.0 - cosine, 5.0);
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
fn sample_brdf_lut(nDotV: f32, roughness: f32) -> vec2<f32> {
|
|
4183
|
+
let uv = vec2<f32>(clamp(nDotV, 0.0, 1.0), clamp(roughness, 0.0, 1.0));
|
|
4184
|
+
return textureSampleLevel(brdfLutTexture, brdfLutSampler, uv, 0.0).xy;
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4187
|
+
fn prefiltered_environment_radiance(direction: vec3<f32>, roughness: f32) -> vec3<f32> {
|
|
4188
|
+
let uv = environment_map_uv(direction);
|
|
4189
|
+
let maxLevel = max(config.environmentMapMeta.z - 1.0, 0.0);
|
|
4190
|
+
let lod = clamp(roughness, 0.0, 1.0) * maxLevel;
|
|
4191
|
+
let texel = max(textureSampleLevel(environmentMapTexture, environmentMapSampler, uv, lod).rgb, vec3<f32>(0.0));
|
|
4192
|
+
return texel * max(config.environmentMapSettings.y, 0.0);
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
fn environment_pdf_dimensions() -> vec2<u32> {
|
|
4196
|
+
return vec2<u32>(
|
|
4197
|
+
max(u32(config.environmentMapMeta.x), 1u),
|
|
4198
|
+
max(u32(config.environmentMapMeta.y), 1u)
|
|
4199
|
+
);
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
fn environment_importance_sampling_enabled() -> bool {
|
|
4203
|
+
return config.environmentMapMeta.w > 0.5;
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
fn uniform_sphere_pdf() -> f32 {
|
|
4207
|
+
return 1.0 / (4.0 * 3.14159265359);
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4210
|
+
fn sample_uniform_sphere_direction(sample: vec2<f32>) -> vec3<f32> {
|
|
4211
|
+
let z = 1.0 - 2.0 * sample.y;
|
|
4212
|
+
let radial = sqrt(max(1.0 - z * z, 0.0));
|
|
4213
|
+
let phi = sample.x * 6.28318530718;
|
|
4214
|
+
return vec3<f32>(cos(phi) * radial, z, sin(phi) * radial);
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
fn environment_sampling_texel(x: u32, y: u32) -> vec4<f32> {
|
|
4218
|
+
return textureLoad(environmentSamplingTexture, vec2<i32>(i32(x), i32(y)), 0);
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
fn environment_pdf_texel(x: u32, y: u32) -> f32 {
|
|
4222
|
+
return environment_sampling_texel(x, y).x;
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
fn environment_row_cdf_texel(y: u32) -> f32 {
|
|
4226
|
+
return environment_sampling_texel(0u, y).z;
|
|
4227
|
+
}
|
|
4228
|
+
|
|
4229
|
+
fn environment_column_cdf_texel(x: u32, y: u32) -> f32 {
|
|
4230
|
+
return environment_sampling_texel(x, y).y;
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
fn environment_direction_pdf(direction: vec3<f32>) -> f32 {
|
|
4234
|
+
if (!environment_importance_sampling_enabled()) {
|
|
4235
|
+
return uniform_sphere_pdf();
|
|
4236
|
+
}
|
|
4237
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4238
|
+
let uv = environment_map_uv(rayDirection);
|
|
4239
|
+
let dimensions = environment_pdf_dimensions();
|
|
4240
|
+
let width = max(f32(dimensions.x), 1.0);
|
|
4241
|
+
let height = max(f32(dimensions.y), 1.0);
|
|
4242
|
+
let x = min(u32(uv.x * width), dimensions.x - 1u);
|
|
4243
|
+
let y = min(u32(uv.y * height), dimensions.y - 1u);
|
|
4244
|
+
let discretePdf = max(environment_pdf_texel(x, y), 0.0);
|
|
4245
|
+
let sinTheta = sqrt(max(1.0 - rayDirection.y * rayDirection.y, 0.0));
|
|
4246
|
+
let solidAngle = max((2.0 * 3.14159265359 * 3.14159265359 * sinTheta) / (width * height), 0.000001);
|
|
4247
|
+
return discretePdf / solidAngle;
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
fn sample_row_cdf(count: u32, sampleValue: f32) -> u32 {
|
|
4251
|
+
if (count == 0u) {
|
|
4252
|
+
return 0u;
|
|
4253
|
+
}
|
|
4254
|
+
var low = 0u;
|
|
4255
|
+
var high = count - 1u;
|
|
4256
|
+
loop {
|
|
4257
|
+
if (low >= high) {
|
|
4258
|
+
break;
|
|
4259
|
+
}
|
|
4260
|
+
let mid = (low + high) / 2u;
|
|
4261
|
+
let cdfValue = environment_row_cdf_texel(mid);
|
|
4262
|
+
if (sampleValue <= cdfValue) {
|
|
4263
|
+
high = mid;
|
|
4264
|
+
} else {
|
|
4265
|
+
low = mid + 1u;
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
return min(low, count - 1u);
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4271
|
+
fn sample_column_cdf(row: u32, count: u32, sampleValue: f32) -> u32 {
|
|
4272
|
+
if (count == 0u) {
|
|
4273
|
+
return 0u;
|
|
4274
|
+
}
|
|
4275
|
+
var low = 0u;
|
|
4276
|
+
var high = count - 1u;
|
|
4277
|
+
loop {
|
|
4278
|
+
if (low >= high) {
|
|
4279
|
+
break;
|
|
4280
|
+
}
|
|
4281
|
+
let mid = (low + high) / 2u;
|
|
4282
|
+
let cdfValue = environment_column_cdf_texel(mid, row);
|
|
4283
|
+
if (sampleValue <= cdfValue) {
|
|
4284
|
+
high = mid;
|
|
4285
|
+
} else {
|
|
4286
|
+
low = mid + 1u;
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
return min(low, count - 1u);
|
|
4290
|
+
}
|
|
4291
|
+
|
|
4292
|
+
struct EnvironmentSample {
|
|
4293
|
+
direction: vec3<f32>,
|
|
4294
|
+
radiance: vec3<f32>,
|
|
4295
|
+
pdf: f32,
|
|
4296
|
+
};
|
|
4297
|
+
|
|
4298
|
+
fn sample_environment_importance(sample: vec2<f32>) -> EnvironmentSample {
|
|
4299
|
+
if (!environment_importance_sampling_enabled()) {
|
|
4300
|
+
let direction = sample_uniform_sphere_direction(sample);
|
|
4301
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), uniform_sphere_pdf());
|
|
4302
|
+
}
|
|
4303
|
+
let dimensions = environment_pdf_dimensions();
|
|
4304
|
+
let row = sample_row_cdf(dimensions.y, sample.y);
|
|
4305
|
+
let column = sample_column_cdf(row, dimensions.x, sample.x);
|
|
4306
|
+
let uv = vec2<f32>(
|
|
4307
|
+
(f32(column) + 0.5) / max(f32(dimensions.x), 1.0),
|
|
4308
|
+
(f32(row) + 0.5) / max(f32(dimensions.y), 1.0)
|
|
4309
|
+
);
|
|
4310
|
+
let theta = uv.y * 3.14159265359;
|
|
4311
|
+
let phi = (uv.x - 0.5 - config.environmentMapSettings.z / 6.28318530718) * 6.28318530718;
|
|
4312
|
+
let sinTheta = sin(theta);
|
|
4313
|
+
let direction = vec3<f32>(cos(phi) * sinTheta, cos(theta), sin(phi) * sinTheta);
|
|
4314
|
+
let pdf = environment_direction_pdf(direction);
|
|
4315
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), pdf);
|
|
2397
4316
|
}
|
|
2398
4317
|
|
|
2399
|
-
fn
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
pathVertices[path_vertex_index(ray.rayId, config.maxDepth)] =
|
|
2404
|
-
vec4<f32>(clamp_sample_radiance(sourceRadiance), 1.0);
|
|
4318
|
+
fn power_heuristic(pdfA: f32, pdfB: f32) -> f32 {
|
|
4319
|
+
let a2 = pdfA * pdfA;
|
|
4320
|
+
let b2 = pdfB * pdfB;
|
|
4321
|
+
return a2 / max(a2 + b2, 0.000001);
|
|
2405
4322
|
}
|
|
2406
4323
|
|
|
2407
|
-
fn
|
|
4324
|
+
fn visible_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
2408
4325
|
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
2409
|
-
let
|
|
2410
|
-
|
|
2411
|
-
let v = acos(clamp(rayDirection.y, -1.0, 1.0)) / 3.14159265359;
|
|
2412
|
-
return vec2<f32>(u, clamp(v, 0.0, 1.0));
|
|
4326
|
+
let visible = !scene_visibility_blocked(origin, rayDirection, 1000000.0);
|
|
4327
|
+
return select(vec3<f32>(0.0), direct_environment_radiance(origin, rayDirection), visible);
|
|
2413
4328
|
}
|
|
2414
4329
|
|
|
2415
|
-
fn
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
4330
|
+
fn glossy_environment_direction(
|
|
4331
|
+
incidentDirection: vec3<f32>,
|
|
4332
|
+
normal: vec3<f32>,
|
|
4333
|
+
roughness: f32,
|
|
4334
|
+
normalBlendScale: f32
|
|
4335
|
+
) -> vec3<f32> {
|
|
4336
|
+
let reflectionDirection = reflect(incidentDirection, normal);
|
|
4337
|
+
let blend = clamp(roughness * roughness * normalBlendScale, 0.0, 0.92);
|
|
4338
|
+
return safe_normalize(mix(reflectionDirection, normal, blend), normal);
|
|
2419
4339
|
}
|
|
2420
4340
|
|
|
2421
|
-
fn
|
|
2422
|
-
let
|
|
2423
|
-
let
|
|
2424
|
-
let
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
);
|
|
2428
|
-
let
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
config.environmentSunColor.xyz * sunGlow
|
|
2435
|
-
) * max(config.environmentSunDirectionIntensity.w, 0.0001);
|
|
4341
|
+
fn surface_glossiness(hit: HitRecord) -> f32 {
|
|
4342
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4343
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4344
|
+
let sheen = clamp(max_component(hit.materialResponse.xyz), 0.0, 1.0);
|
|
4345
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4346
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4347
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
4348
|
+
let baseGloss =
|
|
4349
|
+
max(
|
|
4350
|
+
clearcoat,
|
|
4351
|
+
max(sheen * 0.72, max(specularWeight * (0.38 + metallic * 0.62), transmission))
|
|
4352
|
+
);
|
|
4353
|
+
return clamp(baseGloss * (1.0 - roughness * 0.72) + metallic * (1.0 - roughness) * 0.35, 0.0, 1.0);
|
|
2436
4354
|
}
|
|
2437
4355
|
|
|
2438
|
-
fn
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
4356
|
+
fn surface_specular_f0(hit: HitRecord, surfaceColor: vec3<f32>) -> vec3<f32> {
|
|
4357
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4358
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4359
|
+
let specularColor = clamp(hit.specularColor.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4360
|
+
let dielectricF0 = vec3<f32>(0.04) * specularWeight * specularColor;
|
|
4361
|
+
return mix(dielectricF0, surfaceColor, metallic);
|
|
2443
4362
|
}
|
|
2444
4363
|
|
|
2445
|
-
fn
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
if (distance > 0.001) {
|
|
2463
|
-
let hitPosition = origin + direction * distance;
|
|
2464
|
-
let local = hitPosition - portal.position.xyz;
|
|
2465
|
-
let tangent = safe_normalize(portal.tangent.xyz, vec3<f32>(1.0, 0.0, 0.0));
|
|
2466
|
-
let bitangent = safe_normalize(portal.bitangent.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2467
|
-
let u = dot(local, tangent);
|
|
2468
|
-
let v = dot(local, bitangent);
|
|
2469
|
-
if (abs(u) <= portal.tangent.w && abs(v) <= portal.bitangent.w) {
|
|
2470
|
-
let areaWeight = clamp(sqrt(max(portal.position.w, 0.0001)), 0.25, 4.0);
|
|
2471
|
-
let angleWeight = max(abs(denominator), 0.08);
|
|
2472
|
-
let portalScale = portal.color.rgb * portal.normal.w * portal.color.a * areaWeight * angleWeight;
|
|
2473
|
-
scale = max(scale, portalScale);
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
return scale;
|
|
4364
|
+
fn surface_bsdf_sampling_weights(hit: HitRecord) -> vec3<f32> {
|
|
4365
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4366
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4367
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4368
|
+
let diffuseWeight = clamp(
|
|
4369
|
+
(1.0 - metallic) * max(1.0 - specularWeight * 0.5 - clearcoat * 0.25, 0.15),
|
|
4370
|
+
0.0,
|
|
4371
|
+
1.0
|
|
4372
|
+
);
|
|
4373
|
+
let specWeight = clamp(max(metallic, specularWeight * 0.75) * (1.0 - clearcoat * 0.5), 0.0, 1.0);
|
|
4374
|
+
let clearcoatWeight = clamp(clearcoat, 0.0, 1.0);
|
|
4375
|
+
let totalWeight = max(diffuseWeight + specWeight + clearcoatWeight, 0.000001);
|
|
4376
|
+
return vec3<f32>(
|
|
4377
|
+
diffuseWeight / totalWeight,
|
|
4378
|
+
specWeight / totalWeight,
|
|
4379
|
+
clearcoatWeight / totalWeight
|
|
4380
|
+
);
|
|
2480
4381
|
}
|
|
2481
4382
|
|
|
2482
|
-
fn
|
|
2483
|
-
let
|
|
2484
|
-
let
|
|
2485
|
-
let
|
|
2486
|
-
|
|
2487
|
-
|
|
4383
|
+
fn evaluate_surface_bsdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> vec3<f32> {
|
|
4384
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4385
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4386
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4387
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4388
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4389
|
+
let clearcoatRoughness = clamp(hit.materialExtension.x, 0.0, 1.0);
|
|
4390
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
4391
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
4392
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4393
|
+
if (nDotV <= 0.0 || nDotL <= 0.0) {
|
|
4394
|
+
return vec3<f32>(0.0);
|
|
4395
|
+
}
|
|
4396
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
4397
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
4398
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
4399
|
+
let fresnel = fresnel_schlick(vDotH, f0);
|
|
4400
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
4401
|
+
let geometry = geometry_smith(normal, viewDirection, lightDirection, roughness);
|
|
4402
|
+
let specular = (distribution * geometry * fresnel) / max(4.0 * nDotV * nDotL, 0.000001);
|
|
4403
|
+
let diffuseWeight = (1.0 - metallic) * (1.0 - clearcoat * 0.24) * (1.0 - clamp(max_component(fresnel), 0.0, 0.98));
|
|
4404
|
+
let diffuse = surfaceColor * diffuseWeight / 3.14159265359;
|
|
4405
|
+
let clearcoatHalf = safe_normalize(viewDirection + lightDirection, normal);
|
|
4406
|
+
let clearcoatDistribution = distribution_ggx(normal, clearcoatHalf, max(clearcoatRoughness, 0.02));
|
|
4407
|
+
let clearcoatGeometry = geometry_smith(normal, viewDirection, lightDirection, max(clearcoatRoughness, 0.02));
|
|
4408
|
+
let clearcoatFresnel = fresnel_schlick(saturate(dot(viewDirection, clearcoatHalf)), vec3<f32>(0.04));
|
|
4409
|
+
let clearcoatTerm =
|
|
4410
|
+
(clearcoatDistribution * clearcoatGeometry * clearcoatFresnel) /
|
|
4411
|
+
max(4.0 * nDotV * nDotL, 0.000001) *
|
|
4412
|
+
clearcoat;
|
|
4413
|
+
return (diffuse + specular + clearcoatTerm) * mix(0.42, 1.0, occlusion);
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4416
|
+
fn diffuse_pdf(normal: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
4417
|
+
return saturate(dot(normal, lightDirection)) / 3.14159265359;
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
fn ggx_pdf(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
4421
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
4422
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
4423
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
4424
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
4425
|
+
return (distribution * nDotH) / max(4.0 * vDotH, 0.000001);
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
fn evaluate_surface_bsdf_pdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
4429
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4430
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4431
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
4432
|
+
let diffuseTerm = diffuse_pdf(normal, lightDirection);
|
|
4433
|
+
let specTerm = ggx_pdf(normal, viewDirection, lightDirection, max(roughness, 0.02));
|
|
4434
|
+
let clearcoatTerm = ggx_pdf(normal, viewDirection, lightDirection, max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02));
|
|
4435
|
+
return weights.x * diffuseTerm + weights.y * specTerm + weights.z * clearcoatTerm;
|
|
2488
4436
|
}
|
|
2489
4437
|
|
|
2490
4438
|
fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
@@ -2499,12 +4447,102 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
2499
4447
|
return environment_radiance(origin, direction);
|
|
2500
4448
|
}
|
|
2501
4449
|
|
|
4450
|
+
fn medium_dimensions() -> vec2<u32> {
|
|
4451
|
+
return textureDimensions(mediumTableTexture);
|
|
4452
|
+
}
|
|
4453
|
+
|
|
4454
|
+
fn medium_valid(mediumRefId: u32) -> bool {
|
|
4455
|
+
let dimensions = medium_dimensions();
|
|
4456
|
+
return mediumRefId > 0u && mediumRefId < dimensions.x;
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
fn medium_absorption(mediumRefId: u32) -> vec3<f32> {
|
|
4460
|
+
if (!medium_valid(mediumRefId)) {
|
|
4461
|
+
return vec3<f32>(0.0);
|
|
4462
|
+
}
|
|
4463
|
+
return max(
|
|
4464
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 0), 0).xyz,
|
|
4465
|
+
vec3<f32>(0.0)
|
|
4466
|
+
);
|
|
4467
|
+
}
|
|
4468
|
+
|
|
4469
|
+
fn medium_scattering(mediumRefId: u32) -> vec3<f32> {
|
|
4470
|
+
if (!medium_valid(mediumRefId)) {
|
|
4471
|
+
return vec3<f32>(0.0);
|
|
4472
|
+
}
|
|
4473
|
+
return max(
|
|
4474
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 1), 0).xyz,
|
|
4475
|
+
vec3<f32>(0.0)
|
|
4476
|
+
);
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
fn medium_transmittance(mediumRefId: u32, distance: f32) -> vec3<f32> {
|
|
4480
|
+
if (!medium_valid(mediumRefId) || distance <= 0.000001) {
|
|
4481
|
+
return vec3<f32>(1.0);
|
|
4482
|
+
}
|
|
4483
|
+
let extinction = medium_absorption(mediumRefId) + medium_scattering(mediumRefId);
|
|
4484
|
+
return vec3<f32>(
|
|
4485
|
+
exp(-extinction.x * distance),
|
|
4486
|
+
exp(-extinction.y * distance),
|
|
4487
|
+
exp(-extinction.z * distance)
|
|
4488
|
+
);
|
|
4489
|
+
}
|
|
4490
|
+
|
|
4491
|
+
fn transmitted_medium_ref_id(ray: RayRecord, hit: HitRecord) -> u32 {
|
|
4492
|
+
if (hit.mediumRefId == 0u) {
|
|
4493
|
+
return ray.mediumRefId;
|
|
4494
|
+
}
|
|
4495
|
+
if (hit.frontFace == 1u) {
|
|
4496
|
+
return hit.mediumRefId;
|
|
4497
|
+
}
|
|
4498
|
+
if (ray.mediumRefId == hit.mediumRefId) {
|
|
4499
|
+
return 0u;
|
|
4500
|
+
}
|
|
4501
|
+
return ray.mediumRefId;
|
|
4502
|
+
}
|
|
4503
|
+
|
|
2502
4504
|
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
2503
4505
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
2504
4506
|
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
4507
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
2505
4508
|
let materialEnergy = select(0.68, 0.92, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2506
4509
|
let transparentEnergy = select(materialEnergy, 0.9, hit.hitType == 3u);
|
|
2507
|
-
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy;
|
|
4510
|
+
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy * mix(0.55, 1.0, occlusion);
|
|
4511
|
+
}
|
|
4512
|
+
|
|
4513
|
+
fn bounded_path_response_luminance(ray: RayRecord, hit: HitRecord) -> f32 {
|
|
4514
|
+
let daylightFloor = max(config.pathResolveSettings.y, 0.0) * 0.08;
|
|
4515
|
+
let hdriFloor = max(config.environmentMapSettings.w, 0.0) * 0.02;
|
|
4516
|
+
let sceneFloor = max(daylightFloor, hdriFloor);
|
|
4517
|
+
if (sceneFloor <= 0.000001) {
|
|
4518
|
+
return 0.0;
|
|
4519
|
+
}
|
|
4520
|
+
let bounceRatio = select(
|
|
4521
|
+
0.0,
|
|
4522
|
+
f32(ray.bounce) / max(f32(config.maxDepth - 1u), 1.0),
|
|
4523
|
+
config.maxDepth > 1u
|
|
4524
|
+
);
|
|
4525
|
+
let bounceScale = 1.0 - bounceRatio * 0.55;
|
|
4526
|
+
let materialScale = select(1.0, 0.34, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
4527
|
+
let transparentScale = select(materialScale, 0.58, hit.hitType == 3u);
|
|
4528
|
+
let opacityScale = mix(0.55, 1.0, clamp(hit.material.z, 0.0, 1.0));
|
|
4529
|
+
return sceneFloor * bounceScale * transparentScale * opacityScale;
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
fn stabilize_surface_path_response(ray: RayRecord, hit: HitRecord, response: vec3<f32>) -> vec3<f32> {
|
|
4533
|
+
let minimumLuminance = bounded_path_response_luminance(ray, hit);
|
|
4534
|
+
let responseLuminance = radiance_luminance(response);
|
|
4535
|
+
if (minimumLuminance <= 0.000001 || responseLuminance >= minimumLuminance) {
|
|
4536
|
+
return response;
|
|
4537
|
+
}
|
|
4538
|
+
let tintBase = max(response, max(hit.color.xyz * 0.65, config.ambientColor.xyz * 0.35));
|
|
4539
|
+
let tint = tintBase / max(max_component(tintBase), 0.0001);
|
|
4540
|
+
let lifted = select(
|
|
4541
|
+
tint * minimumLuminance,
|
|
4542
|
+
response * (minimumLuminance / max(responseLuminance, 0.0001)),
|
|
4543
|
+
responseLuminance > 0.0001
|
|
4544
|
+
);
|
|
4545
|
+
return clamp(lifted, vec3<f32>(0.0), vec3<f32>(0.98));
|
|
2508
4546
|
}
|
|
2509
4547
|
|
|
2510
4548
|
fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
@@ -2523,12 +4561,24 @@ fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
|
2523
4561
|
return clamp_sample_radiance(sunTint * baseline * skyFacing * directionalWeight * 0.04);
|
|
2524
4562
|
}
|
|
2525
4563
|
|
|
2526
|
-
fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
4564
|
+
fn terminal_surface_environment_source(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2527
4565
|
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2528
|
-
let
|
|
2529
|
-
|
|
2530
|
-
|
|
4566
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4567
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4568
|
+
let glossiness = surface_glossiness(hit);
|
|
4569
|
+
let normalEnvironment = gated_environment_radiance(origin, normal);
|
|
4570
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4571
|
+
let reflectionDirection = glossy_environment_direction(
|
|
4572
|
+
ray.direction.xyz,
|
|
4573
|
+
normal,
|
|
4574
|
+
roughness,
|
|
4575
|
+
mix(0.88, 0.38, glossiness)
|
|
2531
4576
|
);
|
|
4577
|
+
let reflectionEnvironment = prefiltered_environment_radiance(reflectionDirection, roughness);
|
|
4578
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4579
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
4580
|
+
let brdfTerm = sample_brdf_lut(saturate(dot(normal, viewDirection)), roughness);
|
|
4581
|
+
let specularEnvironment = reflectionEnvironment * (f0 * brdfTerm.x + vec3<f32>(brdfTerm.y));
|
|
2532
4582
|
let sunlitFloor = sunlit_baseline_radiance(normal);
|
|
2533
4583
|
let ambientFloor = select(
|
|
2534
4584
|
max(config.ambientColor.xyz, sunlitFloor * 0.82),
|
|
@@ -2540,17 +4590,27 @@ fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
|
2540
4590
|
max(config.environmentMapSettings.w, max(0.12, config.pathResolveSettings.y * 0.42)),
|
|
2541
4591
|
environment_map_enabled()
|
|
2542
4592
|
);
|
|
2543
|
-
let
|
|
4593
|
+
let glossyEnvironment = max(
|
|
4594
|
+
normalEnvironment,
|
|
4595
|
+
max(reflectionEnvironment * mix(0.24, 0.92, glossiness), specularEnvironment)
|
|
4596
|
+
);
|
|
4597
|
+
let environmentFloor = max(ambientFloor, max(sunlitFloor, glossyEnvironment * environmentInfluence));
|
|
2544
4598
|
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
2545
4599
|
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
2546
4600
|
}
|
|
2547
4601
|
|
|
2548
|
-
fn terminal_surface_environment_contribution(
|
|
4602
|
+
fn terminal_surface_environment_contribution(
|
|
4603
|
+
ray: RayRecord,
|
|
4604
|
+
throughput: vec3<f32>,
|
|
4605
|
+
hit: HitRecord
|
|
4606
|
+
) -> vec3<f32> {
|
|
2549
4607
|
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
4608
|
+
let occlusion = mix(0.75, 1.0, clamp(hit.occlusion, 0.0, 1.0));
|
|
2550
4609
|
return clamp_sample_radiance(
|
|
2551
|
-
|
|
4610
|
+
throughput *
|
|
2552
4611
|
surfaceColor *
|
|
2553
|
-
terminal_surface_environment_source(hit)
|
|
4612
|
+
terminal_surface_environment_source(ray, hit) *
|
|
4613
|
+
occlusion
|
|
2554
4614
|
);
|
|
2555
4615
|
}
|
|
2556
4616
|
|
|
@@ -2583,6 +4643,10 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2583
4643
|
);
|
|
2584
4644
|
let area = max(portal.position.w, 0.0001);
|
|
2585
4645
|
let distanceFalloff = clamp(area / max(distanceSquared, area * 0.25), 0.0, 2.5);
|
|
4646
|
+
let traceDistance = max(sqrt(distanceSquared) - 0.01, 0.01);
|
|
4647
|
+
if (scene_visibility_blocked(origin, direction, traceDistance)) {
|
|
4648
|
+
continue;
|
|
4649
|
+
}
|
|
2586
4650
|
irradiance = irradiance +
|
|
2587
4651
|
portal.color.rgb *
|
|
2588
4652
|
portal.normal.w *
|
|
@@ -2594,48 +4658,79 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2594
4658
|
return irradiance;
|
|
2595
4659
|
}
|
|
2596
4660
|
|
|
2597
|
-
fn
|
|
2598
|
-
let
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
);
|
|
2612
|
-
let environmentIrradianceScale = select(
|
|
2613
|
-
max(0.16, config.pathResolveSettings.y * 0.45),
|
|
2614
|
-
max(config.environmentMapSettings.w, max(0.16, config.pathResolveSettings.y * 0.45)),
|
|
2615
|
-
environment_map_enabled()
|
|
4661
|
+
fn visibility_test_ray(origin: vec3<f32>, direction: vec3<f32>) -> RayRecord {
|
|
4662
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4663
|
+
return RayRecord(
|
|
4664
|
+
0u,
|
|
4665
|
+
0u,
|
|
4666
|
+
0u,
|
|
4667
|
+
0u,
|
|
4668
|
+
0u,
|
|
4669
|
+
0u,
|
|
4670
|
+
0u,
|
|
4671
|
+
0u,
|
|
4672
|
+
vec4<f32>(origin, 1.0),
|
|
4673
|
+
vec4<f32>(rayDirection, 0.0),
|
|
4674
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2616
4675
|
);
|
|
2617
|
-
|
|
4676
|
+
}
|
|
2618
4677
|
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
);
|
|
2623
|
-
let sunFacing = saturate(dot(normal, sunDirection));
|
|
2624
|
-
let sunRadiance = gated_environment_radiance(origin, sunDirection);
|
|
2625
|
-
let sunIrradiance = sunRadiance * sunFacing * 0.2;
|
|
2626
|
-
let portalIrradiance = direct_environment_portal_irradiance(origin, normal);
|
|
4678
|
+
fn scene_visibility_blocked(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> bool {
|
|
4679
|
+
let testRay = visibility_test_ray(origin, direction);
|
|
4680
|
+
let nearest = max(maxDistance, 0.001);
|
|
2627
4681
|
|
|
2628
|
-
|
|
2629
|
-
|
|
4682
|
+
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
4683
|
+
let object = sceneObjects[objectIndex];
|
|
4684
|
+
var current = no_candidate();
|
|
4685
|
+
if (object.kind == 1u) {
|
|
4686
|
+
current = intersect_sphere(testRay, object);
|
|
4687
|
+
} else if (object.kind == 2u) {
|
|
4688
|
+
current = intersect_box(testRay, object);
|
|
4689
|
+
}
|
|
4690
|
+
if (current.hit == 1u && current.distance < nearest) {
|
|
4691
|
+
return true;
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
2630
4694
|
|
|
2631
|
-
let
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
let specularTint = mix(vec3<f32>(0.04), surfaceColor, metallic);
|
|
2635
|
-
let specular = specularTint * sunRadiance * specularFacing * select(0.16, 0.48, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
4695
|
+
let meshCandidate = intersect_bvh(testRay, nearest);
|
|
4696
|
+
return meshCandidate.hit == 1u && meshCandidate.distance < nearest;
|
|
4697
|
+
}
|
|
2636
4698
|
|
|
2637
|
-
|
|
2638
|
-
|
|
4699
|
+
fn surface_direct_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
4700
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4701
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4702
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4703
|
+
let lightSample = sample_environment_importance(vec2<f32>(
|
|
4704
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 41u)),
|
|
4705
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 43u))
|
|
4706
|
+
));
|
|
4707
|
+
if (lightSample.pdf <= 0.000001) {
|
|
4708
|
+
return vec3<f32>(0.0);
|
|
4709
|
+
}
|
|
4710
|
+
let lightDirection = safe_normalize(lightSample.direction, normal);
|
|
4711
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4712
|
+
if (nDotL <= 0.000001) {
|
|
4713
|
+
return vec3<f32>(0.0);
|
|
4714
|
+
}
|
|
4715
|
+
if (scene_visibility_blocked(origin, lightDirection, 1000000.0)) {
|
|
4716
|
+
return vec3<f32>(0.0);
|
|
4717
|
+
}
|
|
4718
|
+
let incidentRadiance = direct_environment_radiance(origin, lightDirection);
|
|
4719
|
+
if (max_component(incidentRadiance) <= 0.000001) {
|
|
4720
|
+
return vec3<f32>(0.0);
|
|
4721
|
+
}
|
|
4722
|
+
let bsdf = evaluate_surface_bsdf(hit, viewDirection, lightDirection);
|
|
4723
|
+
if (max_component(bsdf) <= 0.000001) {
|
|
4724
|
+
return vec3<f32>(0.0);
|
|
4725
|
+
}
|
|
4726
|
+
let bsdfPdf = evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection);
|
|
4727
|
+
let misWeight = power_heuristic(lightSample.pdf, bsdfPdf);
|
|
4728
|
+
let contribution =
|
|
4729
|
+
ray.throughput.xyz *
|
|
4730
|
+
bsdf *
|
|
4731
|
+
incidentRadiance *
|
|
4732
|
+
(nDotL * misWeight / max(lightSample.pdf, 0.000001));
|
|
4733
|
+
return clamp_sample_radiance(contribution);
|
|
2639
4734
|
}
|
|
2640
4735
|
|
|
2641
4736
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -2654,7 +4749,16 @@ fn default_mesh_range() -> MeshRange {
|
|
|
2654
4749
|
0u,
|
|
2655
4750
|
vec4<f32>(0.72, 0.72, 0.68, 1.0),
|
|
2656
4751
|
vec4<f32>(0.0),
|
|
2657
|
-
vec4<f32>(0.72, 0.0, 1.0, 1.45)
|
|
4752
|
+
vec4<f32>(0.72, 0.0, 1.0, 1.45),
|
|
4753
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4754
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
4755
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4756
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4757
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4758
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4759
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4760
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4761
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
2658
4762
|
);
|
|
2659
4763
|
}
|
|
2660
4764
|
|
|
@@ -2750,7 +4854,7 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2750
4854
|
mesh.flags,
|
|
2751
4855
|
mesh.materialRefId,
|
|
2752
4856
|
mesh.mediumRefId,
|
|
2753
|
-
|
|
4857
|
+
mesh.materialSlot,
|
|
2754
4858
|
0u,
|
|
2755
4859
|
vec4<f32>(vertex0.position.xyz, 0.0),
|
|
2756
4860
|
vec4<f32>(vertex1.position.xyz, 0.0),
|
|
@@ -2762,7 +4866,16 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2762
4866
|
vec4<f32>(uv2, 0.0, 0.0),
|
|
2763
4867
|
mesh.color,
|
|
2764
4868
|
mesh.emission,
|
|
2765
|
-
mesh.material
|
|
4869
|
+
mesh.material,
|
|
4870
|
+
mesh.materialResponse,
|
|
4871
|
+
mesh.materialExtension,
|
|
4872
|
+
mesh.specularColor,
|
|
4873
|
+
mesh.baseColorAtlas,
|
|
4874
|
+
mesh.metallicRoughnessAtlas,
|
|
4875
|
+
mesh.normalAtlas,
|
|
4876
|
+
mesh.occlusionAtlas,
|
|
4877
|
+
mesh.emissiveAtlas,
|
|
4878
|
+
mesh.textureSettings
|
|
2766
4879
|
);
|
|
2767
4880
|
|
|
2768
4881
|
let leafBase = config.triangleCount - 1u;
|
|
@@ -2921,7 +5034,8 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2921
5034
|
0u,
|
|
2922
5035
|
0u,
|
|
2923
5036
|
-1.0,
|
|
2924
|
-
|
|
5037
|
+
1.0,
|
|
5038
|
+
vec2<f32>(0.0),
|
|
2925
5039
|
vec4<f32>(ray.origin.xyz + ray.direction.xyz * 1000.0, 1.0),
|
|
2926
5040
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
2927
5041
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
@@ -2929,7 +5043,10 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2929
5043
|
vec4<f32>(0.0),
|
|
2930
5044
|
vec4<f32>(radiance, 1.0),
|
|
2931
5045
|
vec4<f32>(0.0),
|
|
2932
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
5046
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
5047
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
5048
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
5049
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2933
5050
|
);
|
|
2934
5051
|
}
|
|
2935
5052
|
|
|
@@ -2964,7 +5081,7 @@ fn intersect_sphere(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
2964
5081
|
0xffffffffu,
|
|
2965
5082
|
object.objectId,
|
|
2966
5083
|
object.objectId,
|
|
2967
|
-
|
|
5084
|
+
object.mediumRefId
|
|
2968
5085
|
);
|
|
2969
5086
|
}
|
|
2970
5087
|
|
|
@@ -3016,7 +5133,7 @@ fn intersect_box(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
3016
5133
|
0xffffffffu,
|
|
3017
5134
|
object.objectId,
|
|
3018
5135
|
object.objectId,
|
|
3019
|
-
|
|
5136
|
+
object.mediumRefId
|
|
3020
5137
|
);
|
|
3021
5138
|
}
|
|
3022
5139
|
|
|
@@ -3224,6 +5341,19 @@ fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
|
|
|
3224
5341
|
return value / (vec3<f32>(1.0) + value);
|
|
3225
5342
|
}
|
|
3226
5343
|
|
|
5344
|
+
fn denoise_sample_count() -> f32 {
|
|
5345
|
+
return clamp(1.0 / max(config.projectionAndSampling.z, 0.000001), 1.0, 256.0);
|
|
5346
|
+
}
|
|
5347
|
+
|
|
5348
|
+
fn denoise_strength() -> f32 {
|
|
5349
|
+
let spp = denoise_sample_count();
|
|
5350
|
+
return clamp(0.44 / sqrt(spp), 0.08, 0.44);
|
|
5351
|
+
}
|
|
5352
|
+
|
|
5353
|
+
fn denoise_kernel_radius() -> i32 {
|
|
5354
|
+
return select(1i, 2i, denoise_sample_count() < 2.5);
|
|
5355
|
+
}
|
|
5356
|
+
|
|
3227
5357
|
@compute @workgroup_size(64)
|
|
3228
5358
|
fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
3229
5359
|
let index = globalId.x;
|
|
@@ -3254,6 +5384,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3254
5384
|
let ray = activeQueue[index];
|
|
3255
5385
|
var nearest = 1000000.0;
|
|
3256
5386
|
var hitObject = SceneObject(
|
|
5387
|
+
0u,
|
|
5388
|
+
0u,
|
|
5389
|
+
0u,
|
|
5390
|
+
0u,
|
|
3257
5391
|
0u,
|
|
3258
5392
|
0u,
|
|
3259
5393
|
0u,
|
|
@@ -3262,7 +5396,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3262
5396
|
vec4<f32>(0.0),
|
|
3263
5397
|
vec4<f32>(0.0),
|
|
3264
5398
|
vec4<f32>(0.0),
|
|
3265
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
5399
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
5400
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
5401
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
5402
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
3266
5403
|
);
|
|
3267
5404
|
var candidate = no_candidate();
|
|
3268
5405
|
var hitTriangle = TriangleRecord(
|
|
@@ -3284,7 +5421,16 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3284
5421
|
vec4<f32>(0.0),
|
|
3285
5422
|
vec4<f32>(0.0),
|
|
3286
5423
|
vec4<f32>(0.0),
|
|
3287
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
5424
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
5425
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
5426
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
5427
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
5428
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5429
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5430
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5431
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5432
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5433
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
3288
5434
|
);
|
|
3289
5435
|
|
|
3290
5436
|
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
@@ -3317,16 +5463,28 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3317
5463
|
let position = ray.origin.xyz + ray.direction.xyz * candidate.distance;
|
|
3318
5464
|
let hitMaterialKind = select(hitObject.materialKind, hitTriangle.materialKind, candidate.triangleIndex != 0xffffffffu);
|
|
3319
5465
|
let hitObjectId = select(hitObject.objectId, hitTriangle.meshId, candidate.triangleIndex != 0xffffffffu);
|
|
3320
|
-
let
|
|
3321
|
-
|
|
3322
|
-
|
|
5466
|
+
let meshSurface = sample_surface_material(
|
|
5467
|
+
hitTriangle,
|
|
5468
|
+
candidate.uv,
|
|
5469
|
+
candidate.geometricNormal,
|
|
5470
|
+
candidate.shadingNormal
|
|
5471
|
+
);
|
|
5472
|
+
let hitColor = select(hitObject.color, meshSurface.color, candidate.triangleIndex != 0xffffffffu);
|
|
5473
|
+
let hitEmission = select(hitObject.emission, meshSurface.emission, candidate.triangleIndex != 0xffffffffu);
|
|
5474
|
+
let hitMaterial = select(hitObject.material, meshSurface.material, candidate.triangleIndex != 0xffffffffu);
|
|
5475
|
+
let hitMaterialResponse = select(hitObject.materialResponse, meshSurface.materialResponse, candidate.triangleIndex != 0xffffffffu);
|
|
5476
|
+
let hitMaterialExtension = select(hitObject.materialExtension, meshSurface.materialExtension, candidate.triangleIndex != 0xffffffffu);
|
|
5477
|
+
let hitSpecularColor = select(hitObject.specularColor, meshSurface.specularColor, candidate.triangleIndex != 0xffffffffu);
|
|
5478
|
+
let hitShadingNormal = select(candidate.shadingNormal, meshSurface.shadingNormal, candidate.triangleIndex != 0xffffffffu);
|
|
3323
5479
|
let hitPrimitiveId = select(candidate.primitiveId, hitTriangle.triangleId, candidate.triangleIndex != 0xffffffffu);
|
|
3324
5480
|
let hitMaterialRefId = select(candidate.materialRefId, hitTriangle.materialRefId, candidate.triangleIndex != 0xffffffffu);
|
|
3325
5481
|
let hitMediumRefId = select(candidate.mediumRefId, hitTriangle.mediumRefId, candidate.triangleIndex != 0xffffffffu);
|
|
5482
|
+
let hitMaterialSlot = select(0u, hitTriangle.materialSlot, candidate.triangleIndex != 0xffffffffu);
|
|
5483
|
+
let hitOcclusion = select(1.0, meshSurface.occlusion, candidate.triangleIndex != 0xffffffffu);
|
|
3326
5484
|
var hitType = 0u;
|
|
3327
5485
|
if (hitMaterialKind == 4u || emission_power(hitEmission) > 0.0001) {
|
|
3328
5486
|
hitType = 1u;
|
|
3329
|
-
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999) {
|
|
5487
|
+
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999 || hitMaterialExtension.z > 0.001) {
|
|
3330
5488
|
hitType = 3u;
|
|
3331
5489
|
}
|
|
3332
5490
|
atomicAdd(&counters.hitCount, 1u);
|
|
@@ -3340,19 +5498,23 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3340
5498
|
hitPrimitiveId,
|
|
3341
5499
|
hitMaterialRefId,
|
|
3342
5500
|
hitMediumRefId,
|
|
3343
|
-
|
|
5501
|
+
hitMaterialSlot,
|
|
3344
5502
|
0u,
|
|
3345
5503
|
0u,
|
|
3346
5504
|
candidate.distance,
|
|
3347
|
-
|
|
5505
|
+
hitOcclusion,
|
|
5506
|
+
vec2<f32>(0.0),
|
|
3348
5507
|
vec4<f32>(position, 1.0),
|
|
3349
5508
|
vec4<f32>(candidate.geometricNormal, 0.0),
|
|
3350
|
-
vec4<f32>(
|
|
5509
|
+
vec4<f32>(hitShadingNormal, 0.0),
|
|
3351
5510
|
vec4<f32>(candidate.barycentric, 0.0),
|
|
3352
5511
|
vec4<f32>(candidate.uv, 0.0, 0.0),
|
|
3353
5512
|
hitColor,
|
|
3354
5513
|
hitEmission,
|
|
3355
|
-
hitMaterial
|
|
5514
|
+
hitMaterial,
|
|
5515
|
+
hitMaterialResponse,
|
|
5516
|
+
hitMaterialExtension,
|
|
5517
|
+
hitSpecularColor
|
|
3356
5518
|
);
|
|
3357
5519
|
}
|
|
3358
5520
|
|
|
@@ -3421,60 +5583,106 @@ fn sample_environment_portal_direction(hit: HitRecord, seed: u32, fallback: vec3
|
|
|
3421
5583
|
}
|
|
3422
5584
|
|
|
3423
5585
|
fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult {
|
|
5586
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
5587
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
3424
5588
|
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3425
|
-
|
|
5589
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
5590
|
+
if (hit.materialKind == 1u && roughness <= 0.02) {
|
|
3426
5591
|
return ScatterResult(
|
|
3427
|
-
vec4<f32>(
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
),
|
|
3432
|
-
0.0
|
|
3433
|
-
),
|
|
3434
|
-
0u,
|
|
5592
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5593
|
+
1.0,
|
|
5594
|
+
ray.mediumRefId,
|
|
5595
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
3435
5596
|
0u,
|
|
3436
|
-
0u,
|
|
3437
|
-
0u
|
|
3438
5597
|
);
|
|
3439
5598
|
}
|
|
3440
5599
|
|
|
3441
|
-
if (hit.materialKind == 2u || hit.materialKind == 3u) {
|
|
5600
|
+
if (hit.materialKind == 2u || hit.materialKind == 3u || transmission > 0.001) {
|
|
3442
5601
|
let ior = max(hit.material.w, 1.01);
|
|
3443
5602
|
let etaRatio = select(ior, 1.0 / ior, hit.frontFace == 1u);
|
|
3444
|
-
let cosTheta = min(dot(-ray.direction.xyz,
|
|
5603
|
+
let cosTheta = min(dot(-ray.direction.xyz, normal), 1.0);
|
|
3445
5604
|
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3446
5605
|
let cannotRefract = etaRatio * sinTheta > 1.0;
|
|
3447
5606
|
let reflectChance = schlick(cosTheta, etaRatio);
|
|
3448
|
-
|
|
3449
|
-
|
|
5607
|
+
let transmissionReflectChance = select(
|
|
5608
|
+
reflectChance,
|
|
5609
|
+
max(reflectChance, 1.0 - transmission),
|
|
5610
|
+
transmission > 0.001
|
|
5611
|
+
);
|
|
5612
|
+
if (cannotRefract || random01(seed + 23u) < transmissionReflectChance) {
|
|
5613
|
+
return ScatterResult(
|
|
5614
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5615
|
+
1.0,
|
|
5616
|
+
ray.mediumRefId,
|
|
5617
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
5618
|
+
0u,
|
|
5619
|
+
);
|
|
3450
5620
|
}
|
|
3451
|
-
return ScatterResult(
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
let
|
|
3461
|
-
let
|
|
3462
|
-
|
|
3463
|
-
let
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
)
|
|
5621
|
+
return ScatterResult(
|
|
5622
|
+
vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
|
|
5623
|
+
1.0,
|
|
5624
|
+
transmitted_medium_ref_id(ray, hit),
|
|
5625
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
5626
|
+
0u,
|
|
5627
|
+
);
|
|
5628
|
+
}
|
|
5629
|
+
|
|
5630
|
+
let guidedEmissiveAvailable = config.emissiveTriangleCount > 0u;
|
|
5631
|
+
let guidedPortalAvailable =
|
|
5632
|
+
config.environmentPortalCount > 0u && config.environmentPortalMode != 0u;
|
|
5633
|
+
let guidedSelector = random01(seed + 17u);
|
|
5634
|
+
if (guidedEmissiveAvailable && guidedSelector < 0.18) {
|
|
5635
|
+
let guidedDirection = sample_emissive_triangle_direction(hit, seed + 101u, normal);
|
|
5636
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5637
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5638
|
+
return ScatterResult(
|
|
5639
|
+
vec4<f32>(guidedDirection, 0.0),
|
|
5640
|
+
guidedPdf,
|
|
5641
|
+
ray.mediumRefId,
|
|
5642
|
+
RAY_FLAG_GUIDED_EMISSIVE,
|
|
5643
|
+
0u,
|
|
5644
|
+
);
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
if (guidedPortalAvailable && guidedSelector < 0.32) {
|
|
5648
|
+
let guidedDirection = sample_environment_portal_direction(hit, seed + 131u, normal);
|
|
5649
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5650
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5651
|
+
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf, ray.mediumRefId, 0u, 0u);
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
|
|
5655
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
5656
|
+
let selector = random01(seed + 31u);
|
|
5657
|
+
var lightDirection = normal;
|
|
5658
|
+
if (selector < weights.x) {
|
|
5659
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5660
|
+
vec2<f32>(random01(seed + 37u), random01(seed + 41u)),
|
|
5661
|
+
normal
|
|
5662
|
+
);
|
|
5663
|
+
} else if (selector < weights.x + weights.y) {
|
|
5664
|
+
let halfVector = importance_sample_ggx(
|
|
5665
|
+
vec2<f32>(random01(seed + 47u), random01(seed + 53u)),
|
|
5666
|
+
max(roughness, 0.02),
|
|
5667
|
+
normal
|
|
5668
|
+
);
|
|
5669
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5670
|
+
} else {
|
|
5671
|
+
let halfVector = importance_sample_ggx(
|
|
5672
|
+
vec2<f32>(random01(seed + 59u), random01(seed + 61u)),
|
|
5673
|
+
max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02),
|
|
5674
|
+
normal
|
|
5675
|
+
);
|
|
5676
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5677
|
+
}
|
|
5678
|
+
if (dot(normal, lightDirection) <= 0.000001) {
|
|
5679
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5680
|
+
vec2<f32>(random01(seed + 67u), random01(seed + 71u)),
|
|
5681
|
+
normal
|
|
5682
|
+
);
|
|
5683
|
+
}
|
|
5684
|
+
let pdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection), 0.000001);
|
|
5685
|
+
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf, ray.mediumRefId, 0u, 0u);
|
|
3478
5686
|
}
|
|
3479
5687
|
|
|
3480
5688
|
@compute @workgroup_size(64)
|
|
@@ -3487,15 +5695,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3487
5695
|
|
|
3488
5696
|
let ray = activeQueue[index];
|
|
3489
5697
|
let hit = hits[index];
|
|
5698
|
+
let segmentTransmittance = medium_transmittance(ray.mediumRefId, hit.distance);
|
|
5699
|
+
let arrivingThroughput = ray.throughput.xyz * segmentTransmittance;
|
|
3490
5700
|
var contribution = vec3<f32>(0.0);
|
|
3491
5701
|
|
|
3492
5702
|
if (hit.hitType == 1u) {
|
|
3493
5703
|
let guidedLightWeight = select(1.0, 0.24, (ray.flags & RAY_FLAG_GUIDED_EMISSIVE) != 0u);
|
|
3494
5704
|
let sourceRadiance = max(hit.emission.xyz, hit.color.xyz) * guidedLightWeight;
|
|
3495
5705
|
if (deferred_path_resolve_enabled()) {
|
|
3496
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5706
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
3497
5707
|
} else {
|
|
3498
|
-
contribution = clamp_sample_radiance(
|
|
5708
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
3499
5709
|
accumulation[ray.rayId] =
|
|
3500
5710
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3501
5711
|
}
|
|
@@ -3504,10 +5714,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3504
5714
|
}
|
|
3505
5715
|
|
|
3506
5716
|
if (hit.hitType == 2u) {
|
|
5717
|
+
var sourceRadiance = hit.color.xyz;
|
|
5718
|
+
if ((ray.flags & RAY_FLAG_DELTA_SAMPLE) == 0u) {
|
|
5719
|
+
let bsdfPdf = max(ray.throughput.w, 0.000001);
|
|
5720
|
+
let lightPdf = environment_direction_pdf(ray.direction.xyz);
|
|
5721
|
+
let misWeight = power_heuristic(bsdfPdf, lightPdf);
|
|
5722
|
+
sourceRadiance = sourceRadiance * misWeight;
|
|
5723
|
+
}
|
|
3507
5724
|
if (deferred_path_resolve_enabled()) {
|
|
3508
|
-
record_deferred_terminal_source(ray,
|
|
5725
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
3509
5726
|
} else {
|
|
3510
|
-
contribution = clamp_sample_radiance(
|
|
5727
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
3511
5728
|
accumulation[ray.rayId] =
|
|
3512
5729
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3513
5730
|
}
|
|
@@ -3515,24 +5732,47 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3515
5732
|
return;
|
|
3516
5733
|
}
|
|
3517
5734
|
|
|
3518
|
-
let response =
|
|
5735
|
+
let response = stabilize_surface_path_response(
|
|
5736
|
+
ray,
|
|
5737
|
+
hit,
|
|
5738
|
+
surface_path_response(hit) * segmentTransmittance
|
|
5739
|
+
);
|
|
3519
5740
|
record_deferred_path_response(ray, response);
|
|
3520
5741
|
|
|
3521
5742
|
let shouldEstimateDirectEnvironment =
|
|
3522
|
-
!deferred_path_resolve_enabled() &&
|
|
3523
5743
|
(hit.materialKind == 0u || hit.materialKind == 1u) &&
|
|
3524
|
-
hit.material.z >= 0.95
|
|
5744
|
+
hit.material.z >= 0.95 &&
|
|
5745
|
+
ray.bounce < 2u;
|
|
3525
5746
|
if (shouldEstimateDirectEnvironment) {
|
|
3526
|
-
let directEnvironment = surface_direct_environment_contribution(
|
|
5747
|
+
let directEnvironment = surface_direct_environment_contribution(
|
|
5748
|
+
RayRecord(
|
|
5749
|
+
ray.rayId,
|
|
5750
|
+
ray.parentRayId,
|
|
5751
|
+
ray.sourcePixelId,
|
|
5752
|
+
ray.sampleId,
|
|
5753
|
+
ray.bounce,
|
|
5754
|
+
ray.mediumRefId,
|
|
5755
|
+
ray.flags,
|
|
5756
|
+
0u,
|
|
5757
|
+
ray.origin,
|
|
5758
|
+
ray.direction,
|
|
5759
|
+
vec4<f32>(arrivingThroughput, ray.throughput.w)
|
|
5760
|
+
),
|
|
5761
|
+
hit
|
|
5762
|
+
);
|
|
3527
5763
|
accumulation[ray.rayId] =
|
|
3528
5764
|
accumulation[ray.rayId] + vec4<f32>(directEnvironment * sample_weight(), 0.0);
|
|
3529
5765
|
}
|
|
3530
5766
|
|
|
3531
5767
|
if (ray.bounce + 1u >= config.maxDepth) {
|
|
3532
5768
|
if (deferred_path_resolve_enabled()) {
|
|
3533
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5769
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3534
5770
|
} else {
|
|
3535
|
-
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5771
|
+
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5772
|
+
ray,
|
|
5773
|
+
arrivingThroughput,
|
|
5774
|
+
hit
|
|
5775
|
+
);
|
|
3536
5776
|
accumulation[ray.rayId] =
|
|
3537
5777
|
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
3538
5778
|
}
|
|
@@ -3545,9 +5785,13 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3545
5785
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
3546
5786
|
if (nextIndex >= config.tilePixelCount) {
|
|
3547
5787
|
if (deferred_path_resolve_enabled()) {
|
|
3548
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5788
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3549
5789
|
} else {
|
|
3550
|
-
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5790
|
+
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5791
|
+
ray,
|
|
5792
|
+
arrivingThroughput,
|
|
5793
|
+
hit
|
|
5794
|
+
);
|
|
3551
5795
|
accumulation[ray.rayId] =
|
|
3552
5796
|
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
3553
5797
|
}
|
|
@@ -3561,12 +5805,12 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3561
5805
|
ray.sourcePixelId,
|
|
3562
5806
|
ray.sampleId,
|
|
3563
5807
|
ray.bounce + 1u,
|
|
3564
|
-
|
|
5808
|
+
scatter.mediumRefId,
|
|
3565
5809
|
scatter.flags,
|
|
3566
5810
|
0u,
|
|
3567
5811
|
vec4<f32>(offset_origin(hit.position.xyz, hit.shadingNormal.xyz), 1.0),
|
|
3568
5812
|
scatter.direction,
|
|
3569
|
-
vec4<f32>(throughput,
|
|
5813
|
+
vec4<f32>(throughput, scatter.pdf)
|
|
3570
5814
|
);
|
|
3571
5815
|
}
|
|
3572
5816
|
|
|
@@ -3635,8 +5879,11 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3635
5879
|
|
|
3636
5880
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3637
5881
|
let center = textureLoad(denoiseInputRadiance, pixel, 0).xyz;
|
|
3638
|
-
|
|
3639
|
-
|
|
5882
|
+
let strength = denoise_strength();
|
|
5883
|
+
let kernelRadius = denoise_kernel_radius();
|
|
5884
|
+
let centerWeight = 1.7 - strength * 0.35;
|
|
5885
|
+
var sum = center * centerWeight;
|
|
5886
|
+
var totalWeight = centerWeight;
|
|
3640
5887
|
let centerRange = denoise_range_space(center);
|
|
3641
5888
|
|
|
3642
5889
|
for (var oy = -2i; oy <= 2i; oy = oy + 1i) {
|
|
@@ -3644,13 +5891,16 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3644
5891
|
if (ox == 0i && oy == 0i) {
|
|
3645
5892
|
continue;
|
|
3646
5893
|
}
|
|
5894
|
+
if (abs(ox) > kernelRadius || abs(oy) > kernelRadius) {
|
|
5895
|
+
continue;
|
|
5896
|
+
}
|
|
3647
5897
|
let sx = clamp(i32(x) + ox, 0i, i32(config.canvasWidth) - 1i);
|
|
3648
5898
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3649
5899
|
let sampleColor = textureLoad(denoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3650
5900
|
let colorDistance = length(denoise_range_space(sampleColor) - centerRange);
|
|
3651
|
-
let rangeWeight = 1.0 / (1.0 + colorDistance *
|
|
3652
|
-
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * 0.24);
|
|
3653
|
-
let diagonalWeight = select(1.0, 0.
|
|
5901
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (11.0 + strength * 6.0));
|
|
5902
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.62 + strength * 0.24));
|
|
5903
|
+
let diagonalWeight = select(1.0, 0.92, abs(ox) + abs(oy) > 1i);
|
|
3654
5904
|
let weight = rangeWeight * diagonalWeight * distanceWeight;
|
|
3655
5905
|
sum = sum + sampleColor * weight;
|
|
3656
5906
|
totalWeight = totalWeight + weight;
|
|
@@ -3658,8 +5908,9 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3658
5908
|
}
|
|
3659
5909
|
|
|
3660
5910
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3661
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3662
|
-
let
|
|
5911
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.1);
|
|
5912
|
+
let blend = min(0.3, strength * (0.62 + outlier * 0.12));
|
|
5913
|
+
let color = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3663
5914
|
textureStore(denoisedRadianceImage, pixel, vec4<f32>(color, 1.0));
|
|
3664
5915
|
}
|
|
3665
5916
|
|
|
@@ -3673,8 +5924,10 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3673
5924
|
|
|
3674
5925
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3675
5926
|
let center = textureLoad(finalDenoiseInputRadiance, pixel, 0).xyz;
|
|
3676
|
-
|
|
3677
|
-
|
|
5927
|
+
let strength = denoise_strength();
|
|
5928
|
+
let centerWeight = 1.35 - strength * 0.25;
|
|
5929
|
+
var sum = center * centerWeight;
|
|
5930
|
+
var totalWeight = centerWeight;
|
|
3678
5931
|
let centerRange = denoise_range_space(center);
|
|
3679
5932
|
|
|
3680
5933
|
for (var oy = -1i; oy <= 1i; oy = oy + 1i) {
|
|
@@ -3686,8 +5939,8 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3686
5939
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3687
5940
|
let sampleColor = textureLoad(finalDenoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3688
5941
|
let colorDistance = length(denoise_range_space(sampleColor) - centerRange);
|
|
3689
|
-
let rangeWeight = 1.0 / (1.0 + colorDistance *
|
|
3690
|
-
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * 0.
|
|
5942
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (12.0 + strength * 8.0));
|
|
5943
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.82 + strength * 0.28));
|
|
3691
5944
|
let weight = rangeWeight * distanceWeight;
|
|
3692
5945
|
sum = sum + sampleColor * weight;
|
|
3693
5946
|
totalWeight = totalWeight + weight;
|
|
@@ -3695,8 +5948,9 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3695
5948
|
}
|
|
3696
5949
|
|
|
3697
5950
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3698
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3699
|
-
let
|
|
5951
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.2);
|
|
5952
|
+
let blend = min(0.18, strength * (0.42 + outlier * 0.08));
|
|
5953
|
+
let radiance = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3700
5954
|
textureStore(denoisedOutputImage, pixel, vec4<f32>(tone_map_radiance(radiance), 1.0));
|
|
3701
5955
|
}
|
|
3702
5956
|
`;
|
|
@@ -3801,96 +6055,47 @@ function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
|
3801
6055
|
});
|
|
3802
6056
|
}
|
|
3803
6057
|
|
|
3804
|
-
function createGpuParallelismCounters() {
|
|
3805
|
-
return {
|
|
3806
|
-
directDispatches: 0,
|
|
3807
|
-
directWorkgroups: 0,
|
|
3808
|
-
directShaderInvocations: 0,
|
|
3809
|
-
multiWorkgroupDispatches: 0,
|
|
3810
|
-
largestDirectWorkgroupsPerDispatch: 0,
|
|
3811
|
-
indirectDispatches: 0,
|
|
3812
|
-
estimatedIndirectWorkgroupsUpperBound: 0,
|
|
3813
|
-
estimatedIndirectShaderInvocationsUpperBound: 0,
|
|
3814
|
-
indirectDispatchesWithMultiWorkgroupCapacity: 0,
|
|
3815
|
-
largestEstimatedIndirectWorkgroupsPerDispatch: 0,
|
|
3816
|
-
};
|
|
3817
|
-
}
|
|
3818
|
-
|
|
3819
|
-
function countDispatchWorkgroups(groups) {
|
|
3820
|
-
return groups.reduce((product, value) => {
|
|
3821
|
-
const numeric = Number(value ?? 1);
|
|
3822
|
-
const count = Number.isFinite(numeric) ? Math.max(1, Math.trunc(numeric)) : 1;
|
|
3823
|
-
return product * count;
|
|
3824
|
-
}, 1);
|
|
3825
|
-
}
|
|
3826
|
-
|
|
3827
|
-
function recordDirectDispatch(parallelism, groups, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3828
|
-
const workgroups = countDispatchWorkgroups(groups);
|
|
3829
|
-
parallelism.directDispatches += 1;
|
|
3830
|
-
parallelism.directWorkgroups += workgroups;
|
|
3831
|
-
parallelism.directShaderInvocations += workgroups * invocationsPerWorkgroup;
|
|
3832
|
-
parallelism.largestDirectWorkgroupsPerDispatch = Math.max(
|
|
3833
|
-
parallelism.largestDirectWorkgroupsPerDispatch,
|
|
3834
|
-
workgroups
|
|
3835
|
-
);
|
|
3836
|
-
if (workgroups > 1) {
|
|
3837
|
-
parallelism.multiWorkgroupDispatches += 1;
|
|
3838
|
-
}
|
|
3839
|
-
}
|
|
3840
|
-
|
|
3841
|
-
function recordIndirectDispatch(parallelism, estimatedWorkgroupsUpperBound, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3842
|
-
const workgroups = Math.max(1, Math.trunc(Number(estimatedWorkgroupsUpperBound) || 1));
|
|
3843
|
-
parallelism.indirectDispatches += 1;
|
|
3844
|
-
parallelism.estimatedIndirectWorkgroupsUpperBound += workgroups;
|
|
3845
|
-
parallelism.estimatedIndirectShaderInvocationsUpperBound += workgroups * invocationsPerWorkgroup;
|
|
3846
|
-
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch = Math.max(
|
|
3847
|
-
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3848
|
-
workgroups
|
|
3849
|
-
);
|
|
3850
|
-
if (workgroups > 1) {
|
|
3851
|
-
parallelism.indirectDispatchesWithMultiWorkgroupCapacity += 1;
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
|
|
3855
|
-
function createGpuParallelismDiagnostics(adapterDiagnostics, counters) {
|
|
3856
|
-
const totalEstimatedWorkgroupsUpperBound =
|
|
3857
|
-
counters.directWorkgroups + counters.estimatedIndirectWorkgroupsUpperBound;
|
|
3858
|
-
const totalEstimatedShaderInvocationsUpperBound =
|
|
3859
|
-
counters.directShaderInvocations + counters.estimatedIndirectShaderInvocationsUpperBound;
|
|
3860
|
-
const exposesMultiWorkgroupParallelism =
|
|
3861
|
-
counters.multiWorkgroupDispatches > 0 || counters.indirectDispatchesWithMultiWorkgroupCapacity > 0;
|
|
3862
|
-
return Object.freeze({
|
|
3863
|
-
...adapterDiagnostics,
|
|
3864
|
-
directDispatches: counters.directDispatches,
|
|
3865
|
-
directWorkgroups: counters.directWorkgroups,
|
|
3866
|
-
directShaderInvocations: counters.directShaderInvocations,
|
|
3867
|
-
multiWorkgroupDispatches: counters.multiWorkgroupDispatches,
|
|
3868
|
-
largestDirectWorkgroupsPerDispatch: counters.largestDirectWorkgroupsPerDispatch,
|
|
3869
|
-
indirectDispatches: counters.indirectDispatches,
|
|
3870
|
-
estimatedIndirectWorkgroupsUpperBound: counters.estimatedIndirectWorkgroupsUpperBound,
|
|
3871
|
-
estimatedIndirectShaderInvocationsUpperBound: counters.estimatedIndirectShaderInvocationsUpperBound,
|
|
3872
|
-
indirectDispatchesWithMultiWorkgroupCapacity: counters.indirectDispatchesWithMultiWorkgroupCapacity,
|
|
3873
|
-
largestEstimatedIndirectWorkgroupsPerDispatch: counters.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3874
|
-
totalEstimatedWorkgroupsUpperBound,
|
|
3875
|
-
totalEstimatedShaderInvocationsUpperBound,
|
|
3876
|
-
exposesMultiWorkgroupParallelism,
|
|
3877
|
-
likelyUsesMoreThanOnePhysicalGpuCore: null,
|
|
3878
|
-
coreUtilizationStatus: "not-exposed-by-webgpu",
|
|
3879
|
-
});
|
|
3880
|
-
}
|
|
3881
|
-
|
|
3882
6058
|
function createEnvironmentMapSnapshot(environmentMap) {
|
|
3883
6059
|
return Object.freeze({
|
|
3884
6060
|
enabled: environmentMap.enabled,
|
|
3885
6061
|
width: environmentMap.width,
|
|
3886
6062
|
height: environmentMap.height,
|
|
6063
|
+
mipLevelCount: environmentMap.mipLevelCount ?? 1,
|
|
3887
6064
|
projection: environmentMap.projection,
|
|
3888
6065
|
intensity: environmentMap.intensity,
|
|
3889
6066
|
rotationRadians: environmentMap.rotationRadians,
|
|
3890
6067
|
ambientStrength: environmentMap.ambientStrength,
|
|
6068
|
+
hasImportanceData: environmentMap.hasImportanceData === true,
|
|
3891
6069
|
});
|
|
3892
6070
|
}
|
|
3893
6071
|
|
|
6072
|
+
function nowMs() {
|
|
6073
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
6074
|
+
return performance.now();
|
|
6075
|
+
}
|
|
6076
|
+
return Date.now();
|
|
6077
|
+
}
|
|
6078
|
+
|
|
6079
|
+
function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs = null) {
|
|
6080
|
+
if (Number.isFinite(overrideTimeoutMs)) {
|
|
6081
|
+
return Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
6082
|
+
}
|
|
6083
|
+
const samplesPerPixel = Math.max(
|
|
6084
|
+
1,
|
|
6085
|
+
Number(config?.renderedSamplesPerPixel ?? config?.samplesPerPixel ?? 1)
|
|
6086
|
+
);
|
|
6087
|
+
const maxDepth = Math.max(1, Number(config?.maxDepth ?? 1));
|
|
6088
|
+
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
6089
|
+
const denoisePasses = config?.denoise ? (samplesPerPixel < 4 ? 2 : 1) : 0;
|
|
6090
|
+
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
6091
|
+
const estimatedPasses =
|
|
6092
|
+
tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
6093
|
+
return Math.min(
|
|
6094
|
+
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
6095
|
+
GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5
|
|
6096
|
+
);
|
|
6097
|
+
}
|
|
6098
|
+
|
|
3894
6099
|
export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
3895
6100
|
assertAnalyticDisplayQualityPolicy(options);
|
|
3896
6101
|
const constants = getGpuUsageConstants();
|
|
@@ -4116,6 +6321,65 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4116
6321
|
config.environmentMap,
|
|
4117
6322
|
config.environmentColor
|
|
4118
6323
|
);
|
|
6324
|
+
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
6325
|
+
device,
|
|
6326
|
+
constants,
|
|
6327
|
+
config.environmentMap,
|
|
6328
|
+
config.environmentColor
|
|
6329
|
+
);
|
|
6330
|
+
let mediumTextureResource = createMediumTextureResource(
|
|
6331
|
+
device,
|
|
6332
|
+
constants,
|
|
6333
|
+
config.mediums
|
|
6334
|
+
);
|
|
6335
|
+
config = Object.freeze({
|
|
6336
|
+
...config,
|
|
6337
|
+
environmentMap: Object.freeze({
|
|
6338
|
+
...config.environmentMap,
|
|
6339
|
+
width: environmentMapResource.width,
|
|
6340
|
+
height: environmentMapResource.height,
|
|
6341
|
+
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
6342
|
+
hasImportanceData: environmentSamplingResource.hasImportanceData,
|
|
6343
|
+
}),
|
|
6344
|
+
});
|
|
6345
|
+
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
6346
|
+
const baseColorAtlasResource = createAtlasTextureResource(
|
|
6347
|
+
device,
|
|
6348
|
+
constants,
|
|
6349
|
+
config.gpuMaterialSource.baseColorAtlas,
|
|
6350
|
+
"plasius.wavefront.materialAtlas.baseColor"
|
|
6351
|
+
);
|
|
6352
|
+
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
6353
|
+
device,
|
|
6354
|
+
constants,
|
|
6355
|
+
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
6356
|
+
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
6357
|
+
);
|
|
6358
|
+
const normalAtlasResource = createAtlasTextureResource(
|
|
6359
|
+
device,
|
|
6360
|
+
constants,
|
|
6361
|
+
config.gpuMaterialSource.normalAtlas,
|
|
6362
|
+
"plasius.wavefront.materialAtlas.normal"
|
|
6363
|
+
);
|
|
6364
|
+
const occlusionAtlasResource = createAtlasTextureResource(
|
|
6365
|
+
device,
|
|
6366
|
+
constants,
|
|
6367
|
+
config.gpuMaterialSource.occlusionAtlas,
|
|
6368
|
+
"plasius.wavefront.materialAtlas.occlusion"
|
|
6369
|
+
);
|
|
6370
|
+
const emissiveAtlasResource = createAtlasTextureResource(
|
|
6371
|
+
device,
|
|
6372
|
+
constants,
|
|
6373
|
+
config.gpuMaterialSource.emissiveAtlas,
|
|
6374
|
+
"plasius.wavefront.materialAtlas.emissive"
|
|
6375
|
+
);
|
|
6376
|
+
const materialAtlasSampler = device.createSampler({
|
|
6377
|
+
label: "plasius.wavefront.materialAtlasSampler",
|
|
6378
|
+
addressModeU: "clamp-to-edge",
|
|
6379
|
+
addressModeV: "clamp-to-edge",
|
|
6380
|
+
magFilter: "linear",
|
|
6381
|
+
minFilter: "linear",
|
|
6382
|
+
});
|
|
4119
6383
|
|
|
4120
6384
|
const traceBindGroupLayout = device.createBindGroupLayout({
|
|
4121
6385
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
@@ -4147,6 +6411,16 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4147
6411
|
{ binding: 20, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
4148
6412
|
{ binding: 21, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
4149
6413
|
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } },
|
|
6414
|
+
{ binding: 23, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6415
|
+
{ binding: 24, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6416
|
+
{ binding: 25, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6417
|
+
{ binding: 26, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6418
|
+
{ binding: 27, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6419
|
+
{ binding: 28, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
6420
|
+
{ binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6421
|
+
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
6422
|
+
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6423
|
+
{ binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
4150
6424
|
],
|
|
4151
6425
|
});
|
|
4152
6426
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -4225,6 +6499,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4225
6499
|
label: "plasius.wavefront.computeShader",
|
|
4226
6500
|
code: WAVEFRONT_COMPUTE_WGSL,
|
|
4227
6501
|
});
|
|
6502
|
+
await assertShaderModuleCompiles(computeShader, "plasius.wavefront.computeShader");
|
|
4228
6503
|
|
|
4229
6504
|
const pipelines = {
|
|
4230
6505
|
prepareMeshTrianglesAndLeaves: await createComputePipeline(
|
|
@@ -4326,14 +6601,28 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4326
6601
|
{ binding: 20, resource: environmentMapResource.view },
|
|
4327
6602
|
{ binding: 21, resource: environmentMapResource.sampler },
|
|
4328
6603
|
{ binding: 22, resource: { buffer: pathVertexBuffer } },
|
|
6604
|
+
{ binding: 23, resource: baseColorAtlasResource.view },
|
|
6605
|
+
{ binding: 24, resource: metallicRoughnessAtlasResource.view },
|
|
6606
|
+
{ binding: 25, resource: normalAtlasResource.view },
|
|
6607
|
+
{ binding: 26, resource: occlusionAtlasResource.view },
|
|
6608
|
+
{ binding: 27, resource: emissiveAtlasResource.view },
|
|
6609
|
+
{ binding: 28, resource: materialAtlasSampler },
|
|
6610
|
+
{ binding: 29, resource: brdfLutResource.view },
|
|
6611
|
+
{ binding: 30, resource: brdfLutResource.sampler },
|
|
6612
|
+
{ binding: 31, resource: environmentSamplingResource.view },
|
|
6613
|
+
{ binding: 32, resource: mediumTextureResource.view },
|
|
4329
6614
|
],
|
|
4330
6615
|
});
|
|
4331
6616
|
}
|
|
4332
6617
|
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
6618
|
+
function createTraceBindGroups() {
|
|
6619
|
+
return [
|
|
6620
|
+
createTraceBindGroup(activeQueue, nextQueue, "plasius.wavefront.bind.activeNext"),
|
|
6621
|
+
createTraceBindGroup(nextQueue, activeQueue, "plasius.wavefront.bind.nextActive"),
|
|
6622
|
+
];
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6625
|
+
let bindGroups = createTraceBindGroups();
|
|
4337
6626
|
const bvhBuildBindGroup = device.createBindGroup({
|
|
4338
6627
|
label: "plasius.wavefront.bind.bvhBuild",
|
|
4339
6628
|
layout: accelerationBindGroupLayout,
|
|
@@ -4381,6 +6670,11 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4381
6670
|
outputView,
|
|
4382
6671
|
"plasius.wavefront.bind.denoise.scratchToOutput"
|
|
4383
6672
|
);
|
|
6673
|
+
const denoiseDirectResolveBindGroup = createDenoiseResolveBindGroup(
|
|
6674
|
+
radianceView,
|
|
6675
|
+
outputView,
|
|
6676
|
+
"plasius.wavefront.bind.denoise.radianceToOutput"
|
|
6677
|
+
);
|
|
4384
6678
|
|
|
4385
6679
|
const presentBindGroupLayout = device.createBindGroupLayout({
|
|
4386
6680
|
label: "plasius.wavefront.presentBindGroupLayout",
|
|
@@ -4420,24 +6714,138 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4420
6714
|
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
4421
6715
|
let accelerationBuildCount = 0;
|
|
4422
6716
|
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA;
|
|
6717
|
+
let lastCompletedFrameTimeMs = null;
|
|
6718
|
+
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
4423
6719
|
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
4424
6720
|
gpuAdapterParallelism,
|
|
4425
6721
|
createGpuParallelismCounters()
|
|
4426
6722
|
);
|
|
4427
6723
|
|
|
6724
|
+
function resolveRenderedSamplesPerPixel(renderOptions = {}, awaitGPUCompletion = true) {
|
|
6725
|
+
const targetSamplesPerPixel = clamp(
|
|
6726
|
+
readPositiveInteger(
|
|
6727
|
+
"samplesPerPixel",
|
|
6728
|
+
renderOptions.samplesPerPixel,
|
|
6729
|
+
config.samplesPerPixel
|
|
6730
|
+
),
|
|
6731
|
+
1,
|
|
6732
|
+
config.samplesPerPixel
|
|
6733
|
+
);
|
|
6734
|
+
const frameTimeBudgetMs = Number.isFinite(renderOptions.frameTimeBudgetMs)
|
|
6735
|
+
? Math.max(0, Number(renderOptions.frameTimeBudgetMs))
|
|
6736
|
+
: null;
|
|
6737
|
+
const minimumSamplesPerPixel = clamp(
|
|
6738
|
+
readPositiveInteger(
|
|
6739
|
+
"minimumSamplesPerPixel",
|
|
6740
|
+
renderOptions.minimumSamplesPerPixel,
|
|
6741
|
+
frameTimeBudgetMs !== null && targetSamplesPerPixel > 1 ? 1 : targetSamplesPerPixel
|
|
6742
|
+
),
|
|
6743
|
+
1,
|
|
6744
|
+
targetSamplesPerPixel
|
|
6745
|
+
);
|
|
6746
|
+
if (frameTimeBudgetMs === null || !awaitGPUCompletion || targetSamplesPerPixel <= minimumSamplesPerPixel) {
|
|
6747
|
+
return Object.freeze({
|
|
6748
|
+
renderedSamplesPerPixel: targetSamplesPerPixel,
|
|
6749
|
+
targetSamplesPerPixel,
|
|
6750
|
+
minimumSamplesPerPixel,
|
|
6751
|
+
frameTimeBudgetMs,
|
|
6752
|
+
budgetConstrained: false,
|
|
6753
|
+
});
|
|
6754
|
+
}
|
|
6755
|
+
const estimatedSampleTimeMs =
|
|
6756
|
+
Number.isFinite(lastCompletedFrameTimeMs) && lastCompletedFrameTimeMs > 0
|
|
6757
|
+
? lastCompletedFrameTimeMs / Math.max(1, lastCompletedSamplesPerPixel)
|
|
6758
|
+
: null;
|
|
6759
|
+
if (!Number.isFinite(estimatedSampleTimeMs) || estimatedSampleTimeMs <= 0) {
|
|
6760
|
+
return Object.freeze({
|
|
6761
|
+
renderedSamplesPerPixel: minimumSamplesPerPixel,
|
|
6762
|
+
targetSamplesPerPixel,
|
|
6763
|
+
minimumSamplesPerPixel,
|
|
6764
|
+
frameTimeBudgetMs,
|
|
6765
|
+
budgetConstrained: minimumSamplesPerPixel < targetSamplesPerPixel,
|
|
6766
|
+
});
|
|
6767
|
+
}
|
|
6768
|
+
const budgetLimitedSamples = clamp(
|
|
6769
|
+
Math.floor(frameTimeBudgetMs / estimatedSampleTimeMs),
|
|
6770
|
+
minimumSamplesPerPixel,
|
|
6771
|
+
targetSamplesPerPixel
|
|
6772
|
+
);
|
|
6773
|
+
return Object.freeze({
|
|
6774
|
+
renderedSamplesPerPixel: budgetLimitedSamples,
|
|
6775
|
+
targetSamplesPerPixel,
|
|
6776
|
+
minimumSamplesPerPixel,
|
|
6777
|
+
frameTimeBudgetMs,
|
|
6778
|
+
budgetConstrained: budgetLimitedSamples < targetSamplesPerPixel,
|
|
6779
|
+
});
|
|
6780
|
+
}
|
|
6781
|
+
|
|
6782
|
+
function createFrameStats({
|
|
6783
|
+
frameIndex,
|
|
6784
|
+
accelerationBuildSubmitted,
|
|
6785
|
+
frameSubmissionCount,
|
|
6786
|
+
parallelismCounters,
|
|
6787
|
+
renderedSamplesPerPixel,
|
|
6788
|
+
targetSamplesPerPixel,
|
|
6789
|
+
frameTimeBudgetMs,
|
|
6790
|
+
budgetConstrained,
|
|
6791
|
+
}) {
|
|
6792
|
+
lastGpuParallelism = createGpuParallelismDiagnostics(gpuAdapterParallelism, parallelismCounters);
|
|
6793
|
+
const commandSubmissions = frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0);
|
|
6794
|
+
return Object.freeze({
|
|
6795
|
+
frame,
|
|
6796
|
+
frameIndex,
|
|
6797
|
+
width: config.width,
|
|
6798
|
+
height: config.height,
|
|
6799
|
+
maxDepth: config.maxDepth,
|
|
6800
|
+
tiles: tiles.length,
|
|
6801
|
+
tileSize: config.tileSize,
|
|
6802
|
+
samplesPerPixel: targetSamplesPerPixel,
|
|
6803
|
+
renderedSamplesPerPixel,
|
|
6804
|
+
frameTimeBudgetMs,
|
|
6805
|
+
budgetConstrained,
|
|
6806
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6807
|
+
screenRays: config.width * config.height,
|
|
6808
|
+
primaryRays: config.width * config.height * renderedSamplesPerPixel,
|
|
6809
|
+
sceneObjectCount: config.sceneObjectCount,
|
|
6810
|
+
triangleCount: config.triangleCount,
|
|
6811
|
+
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6812
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
6813
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
6814
|
+
mediumCount: config.mediumCount,
|
|
6815
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6816
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
6817
|
+
bvhNodeCount: config.bvhNodeCount,
|
|
6818
|
+
displayQuality: config.displayQuality,
|
|
6819
|
+
accelerationBuildMode: config.accelerationBuildMode,
|
|
6820
|
+
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
6821
|
+
accelerationBuildSubmitted,
|
|
6822
|
+
accelerationBuilt,
|
|
6823
|
+
accelerationBuildCount,
|
|
6824
|
+
commandSubmissions,
|
|
6825
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
6826
|
+
gpuParallelism: lastGpuParallelism,
|
|
6827
|
+
memory: config.memory,
|
|
6828
|
+
});
|
|
6829
|
+
}
|
|
6830
|
+
|
|
6831
|
+
function writeFrameConfigSlot(slot, tile, frameIndex, buildRange = {}) {
|
|
6832
|
+
if (slot >= frameConfigSlotCount) {
|
|
6833
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
6834
|
+
}
|
|
6835
|
+
const offset = slot * configBufferStride;
|
|
6836
|
+
device.queue.writeBuffer(
|
|
6837
|
+
configBuffer,
|
|
6838
|
+
offset,
|
|
6839
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
6840
|
+
);
|
|
6841
|
+
return offset;
|
|
6842
|
+
}
|
|
6843
|
+
|
|
4428
6844
|
function createFrameConfigWriter(frameIndex) {
|
|
4429
6845
|
let slot = 0;
|
|
4430
6846
|
return (tile, buildRange = {}) => {
|
|
4431
|
-
|
|
4432
|
-
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
4433
|
-
}
|
|
4434
|
-
const offset = slot * configBufferStride;
|
|
6847
|
+
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
4435
6848
|
slot += 1;
|
|
4436
|
-
device.queue.writeBuffer(
|
|
4437
|
-
configBuffer,
|
|
4438
|
-
offset,
|
|
4439
|
-
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
4440
|
-
);
|
|
4441
6849
|
return offset;
|
|
4442
6850
|
};
|
|
4443
6851
|
}
|
|
@@ -4483,7 +6891,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4483
6891
|
passEncoder.setPipeline(pipelines.prepareMeshTrianglesAndLeaves);
|
|
4484
6892
|
const prepareWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4485
6893
|
passEncoder.dispatchWorkgroups(prepareWorkgroups);
|
|
4486
|
-
recordDirectDispatch(parallelism, [prepareWorkgroups]);
|
|
6894
|
+
recordDirectDispatch(parallelism, [prepareWorkgroups], WORKGROUP_SIZE);
|
|
4487
6895
|
passEncoder.setPipeline(pipelines.sortBvhLeafRefs);
|
|
4488
6896
|
for (let stageIndex = 0; stageIndex < config.bvhSortStages.length; stageIndex += 1) {
|
|
4489
6897
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [
|
|
@@ -4491,13 +6899,13 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4491
6899
|
]);
|
|
4492
6900
|
const sortWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4493
6901
|
passEncoder.dispatchWorkgroups(sortWorkgroups);
|
|
4494
|
-
recordDirectDispatch(parallelism, [sortWorkgroups]);
|
|
6902
|
+
recordDirectDispatch(parallelism, [sortWorkgroups], WORKGROUP_SIZE);
|
|
4495
6903
|
}
|
|
4496
6904
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [0]);
|
|
4497
6905
|
passEncoder.setPipeline(pipelines.writeSortedBvhLeaves);
|
|
4498
6906
|
const leafWriteWorkgroups = Math.ceil(config.triangleCount / WORKGROUP_SIZE);
|
|
4499
6907
|
passEncoder.dispatchWorkgroups(leafWriteWorkgroups);
|
|
4500
|
-
recordDirectDispatch(parallelism, [leafWriteWorkgroups]);
|
|
6908
|
+
recordDirectDispatch(parallelism, [leafWriteWorkgroups], WORKGROUP_SIZE);
|
|
4501
6909
|
passEncoder.setPipeline(pipelines.buildBvhInternalLevel);
|
|
4502
6910
|
for (let levelIndex = 0; levelIndex < config.bvhBuildLevels.length; levelIndex += 1) {
|
|
4503
6911
|
const buildLevel = config.bvhBuildLevels[levelIndex];
|
|
@@ -4506,7 +6914,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4506
6914
|
]);
|
|
4507
6915
|
const levelWorkgroups = Math.ceil(buildLevel.count / WORKGROUP_SIZE);
|
|
4508
6916
|
passEncoder.dispatchWorkgroups(levelWorkgroups);
|
|
4509
|
-
recordDirectDispatch(parallelism, [levelWorkgroups]);
|
|
6917
|
+
recordDirectDispatch(parallelism, [levelWorkgroups], WORKGROUP_SIZE);
|
|
4510
6918
|
}
|
|
4511
6919
|
passEncoder.end();
|
|
4512
6920
|
device.queue.submit([encoder.finish()]);
|
|
@@ -4524,7 +6932,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4524
6932
|
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4525
6933
|
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
4526
6934
|
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
4527
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6935
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4528
6936
|
generatePass.end();
|
|
4529
6937
|
|
|
4530
6938
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
@@ -4541,10 +6949,10 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4541
6949
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
4542
6950
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
4543
6951
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4544
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6952
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4545
6953
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
4546
6954
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4547
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6955
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4548
6956
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
4549
6957
|
passEncoder.dispatchWorkgroups(1);
|
|
4550
6958
|
recordDirectDispatch(parallelism, [1], 1);
|
|
@@ -4561,32 +6969,47 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4561
6969
|
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4562
6970
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
4563
6971
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
4564
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6972
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4565
6973
|
passEncoder.end();
|
|
4566
6974
|
}
|
|
4567
6975
|
|
|
4568
|
-
function encodeDenoise(encoder, configOffset, parallelism) {
|
|
6976
|
+
function encodeDenoise(encoder, configOffset, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4569
6977
|
if (!config.denoise) {
|
|
4570
6978
|
return;
|
|
4571
6979
|
}
|
|
4572
6980
|
const denoiseWorkgroupsX = Math.ceil(config.width / 8);
|
|
4573
6981
|
const denoiseWorkgroupsY = Math.ceil(config.height / 8);
|
|
4574
|
-
const
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
6982
|
+
const useTwoPassDenoise = renderedSamplesPerPixel < 4;
|
|
6983
|
+
if (useTwoPassDenoise) {
|
|
6984
|
+
const radiancePass = encoder.beginComputePass({
|
|
6985
|
+
label: "plasius.wavefront.denoiseRadiancePass",
|
|
6986
|
+
});
|
|
6987
|
+
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
6988
|
+
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
6989
|
+
radiancePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
6990
|
+
recordDirectDispatch(
|
|
6991
|
+
parallelism,
|
|
6992
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6993
|
+
WORKGROUP_SIZE
|
|
6994
|
+
);
|
|
6995
|
+
radiancePass.end();
|
|
6996
|
+
}
|
|
4582
6997
|
|
|
4583
6998
|
const resolvePass = encoder.beginComputePass({
|
|
4584
6999
|
label: "plasius.wavefront.denoiseResolvePass",
|
|
4585
7000
|
});
|
|
4586
|
-
resolvePass.setBindGroup(
|
|
7001
|
+
resolvePass.setBindGroup(
|
|
7002
|
+
0,
|
|
7003
|
+
useTwoPassDenoise ? denoiseResolveBindGroup : denoiseDirectResolveBindGroup,
|
|
7004
|
+
[configOffset]
|
|
7005
|
+
);
|
|
4587
7006
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
4588
7007
|
resolvePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
4589
|
-
recordDirectDispatch(
|
|
7008
|
+
recordDirectDispatch(
|
|
7009
|
+
parallelism,
|
|
7010
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
7011
|
+
WORKGROUP_SIZE
|
|
7012
|
+
);
|
|
4590
7013
|
resolvePass.end();
|
|
4591
7014
|
}
|
|
4592
7015
|
|
|
@@ -4609,105 +7032,233 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4609
7032
|
passEncoder.end();
|
|
4610
7033
|
}
|
|
4611
7034
|
|
|
4612
|
-
function dispatchFrame(frameIndex, parallelism) {
|
|
7035
|
+
function dispatchFrame(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4613
7036
|
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
7037
|
+
const batch = createGpuSubmissionBatcher({
|
|
7038
|
+
device,
|
|
7039
|
+
frameIndex,
|
|
7040
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
4618
7041
|
});
|
|
4619
7042
|
|
|
4620
|
-
function submitCurrentEncoder() {
|
|
4621
|
-
if (encodedFramePasses <= 0) {
|
|
4622
|
-
return;
|
|
4623
|
-
}
|
|
4624
|
-
device.queue.submit([encoder.finish()]);
|
|
4625
|
-
submissionCount += 1;
|
|
4626
|
-
encodedFramePasses = 0;
|
|
4627
|
-
encoder = device.createCommandEncoder({
|
|
4628
|
-
label: `plasius.wavefront.frame.${frameIndex}.batched.${submissionCount + 1}`,
|
|
4629
|
-
});
|
|
4630
|
-
}
|
|
4631
|
-
|
|
4632
|
-
function reserveEncoder(passCount = 1) {
|
|
4633
|
-
if (
|
|
4634
|
-
encodedFramePasses > 0 &&
|
|
4635
|
-
encodedFramePasses + passCount > config.maxFramePassesPerSubmission
|
|
4636
|
-
) {
|
|
4637
|
-
submitCurrentEncoder();
|
|
4638
|
-
}
|
|
4639
|
-
encodedFramePasses += passCount;
|
|
4640
|
-
return encoder;
|
|
4641
|
-
}
|
|
4642
|
-
|
|
4643
7043
|
for (const tile of tiles) {
|
|
4644
|
-
for (let sampleIndex = 0; sampleIndex <
|
|
7044
|
+
for (let sampleIndex = 0; sampleIndex < renderedSamplesPerPixel; sampleIndex += 1) {
|
|
4645
7045
|
const configOffset = writeFrameConfig(tile, {
|
|
4646
7046
|
sampleIndex,
|
|
4647
|
-
sampleWeight: 1 /
|
|
7047
|
+
sampleWeight: 1 / renderedSamplesPerPixel,
|
|
4648
7048
|
});
|
|
4649
|
-
encodeTileSample(
|
|
7049
|
+
encodeTileSample(
|
|
7050
|
+
batch.reserve(config.maxDepth + 1),
|
|
7051
|
+
tile,
|
|
7052
|
+
configOffset,
|
|
7053
|
+
parallelism
|
|
7054
|
+
);
|
|
4650
7055
|
if (config.deferredPathResolve) {
|
|
4651
|
-
encodeTileOutput(
|
|
7056
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
4652
7057
|
}
|
|
4653
7058
|
}
|
|
4654
7059
|
if (!config.deferredPathResolve) {
|
|
4655
7060
|
const outputConfigOffset = writeFrameConfig(tile, {
|
|
4656
7061
|
sampleIndex: 0,
|
|
4657
|
-
sampleWeight: 1 /
|
|
7062
|
+
sampleWeight: 1 / renderedSamplesPerPixel,
|
|
4658
7063
|
});
|
|
4659
|
-
encodeTileOutput(
|
|
7064
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
4660
7065
|
}
|
|
4661
7066
|
}
|
|
4662
7067
|
if (config.denoise) {
|
|
4663
7068
|
const denoiseConfigOffset = writeFrameConfig(
|
|
4664
7069
|
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
4665
|
-
{ sampleIndex: 0, sampleWeight: 1 /
|
|
7070
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
7071
|
+
);
|
|
7072
|
+
const denoisePassCount = renderedSamplesPerPixel < 4 ? 2 : 1;
|
|
7073
|
+
encodeDenoise(
|
|
7074
|
+
batch.reserve(denoisePassCount),
|
|
7075
|
+
denoiseConfigOffset,
|
|
7076
|
+
parallelism,
|
|
7077
|
+
renderedSamplesPerPixel
|
|
4666
7078
|
);
|
|
4667
|
-
encodeDenoise(reserveEncoder(), denoiseConfigOffset, parallelism);
|
|
4668
7079
|
}
|
|
4669
|
-
encodePresent(
|
|
4670
|
-
|
|
4671
|
-
return submissionCount;
|
|
7080
|
+
encodePresent(batch.reserve(1));
|
|
7081
|
+
return batch.flush();
|
|
4672
7082
|
}
|
|
4673
7083
|
|
|
4674
|
-
function renderOnce() {
|
|
7084
|
+
function renderOnce(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
7085
|
+
const frameStartTimeMs = nowMs();
|
|
4675
7086
|
frame += 1;
|
|
4676
7087
|
const frameIndex = frame + config.frameIndex;
|
|
7088
|
+
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
4677
7089
|
const parallelismCounters = createGpuParallelismCounters();
|
|
4678
7090
|
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
4679
|
-
const frameSubmissionCount = dispatchFrame(
|
|
4680
|
-
|
|
7091
|
+
const frameSubmissionCount = dispatchFrame(
|
|
7092
|
+
frameIndex,
|
|
7093
|
+
parallelismCounters,
|
|
7094
|
+
samplingPlan.renderedSamplesPerPixel
|
|
7095
|
+
);
|
|
7096
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
4681
7097
|
return Object.freeze({
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
7098
|
+
...createFrameStats({
|
|
7099
|
+
frameIndex,
|
|
7100
|
+
accelerationBuildSubmitted,
|
|
7101
|
+
frameSubmissionCount,
|
|
7102
|
+
parallelismCounters,
|
|
7103
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
7104
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
7105
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
7106
|
+
budgetConstrained: samplingPlan.budgetConstrained,
|
|
7107
|
+
}),
|
|
7108
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
7109
|
+
lastGpuParallelism,
|
|
7110
|
+
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
7111
|
+
frameTimeMs,
|
|
7112
|
+
false
|
|
7113
|
+
),
|
|
7114
|
+
});
|
|
7115
|
+
}
|
|
7116
|
+
|
|
7117
|
+
async function waitForSubmittedGpuWork(options = {}) {
|
|
7118
|
+
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
7119
|
+
return true;
|
|
7120
|
+
}
|
|
7121
|
+
const timeoutMs = Math.max(
|
|
7122
|
+
1,
|
|
7123
|
+
Number.isFinite(options.timeoutMs)
|
|
7124
|
+
? Number(options.timeoutMs)
|
|
7125
|
+
: GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
7126
|
+
);
|
|
7127
|
+
const allowTimeout = options.allowTimeout !== false;
|
|
7128
|
+
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
7129
|
+
() => ({ status: "done" }),
|
|
7130
|
+
(error) => {
|
|
7131
|
+
throw error;
|
|
7132
|
+
}
|
|
7133
|
+
);
|
|
7134
|
+
const lossPromise =
|
|
7135
|
+
typeof device.lost?.then === "function"
|
|
7136
|
+
? device.lost.then((info) => {
|
|
7137
|
+
throw new Error(
|
|
7138
|
+
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
7139
|
+
);
|
|
7140
|
+
})
|
|
7141
|
+
: null;
|
|
7142
|
+
let timeoutHandle = null;
|
|
7143
|
+
let resolveTimeoutPromise = null;
|
|
7144
|
+
let timeoutSettled = false;
|
|
7145
|
+
const settleTimeoutPromise = (value) => {
|
|
7146
|
+
if (timeoutSettled) {
|
|
7147
|
+
return;
|
|
7148
|
+
}
|
|
7149
|
+
timeoutSettled = true;
|
|
7150
|
+
resolveTimeoutPromise?.(value);
|
|
7151
|
+
};
|
|
7152
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
7153
|
+
resolveTimeoutPromise = resolve;
|
|
7154
|
+
timeoutHandle = setTimeout(() => settleTimeoutPromise({ status: "timeout" }), timeoutMs);
|
|
7155
|
+
});
|
|
7156
|
+
let result;
|
|
7157
|
+
try {
|
|
7158
|
+
result = await Promise.race(
|
|
7159
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
7160
|
+
);
|
|
7161
|
+
} finally {
|
|
7162
|
+
if (timeoutHandle !== null) {
|
|
7163
|
+
clearTimeout(timeoutHandle);
|
|
7164
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
7165
|
+
}
|
|
7166
|
+
}
|
|
7167
|
+
if (result?.status === "timeout") {
|
|
7168
|
+
if (!allowTimeout) {
|
|
7169
|
+
throw new Error(`Timed out after ${timeoutMs} ms waiting for submitted GPU work.`);
|
|
7170
|
+
}
|
|
7171
|
+
console.warn(
|
|
7172
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
|
|
7173
|
+
);
|
|
7174
|
+
return false;
|
|
7175
|
+
}
|
|
7176
|
+
return true;
|
|
7177
|
+
}
|
|
7178
|
+
|
|
7179
|
+
function dispatchFrameAwaitingGpu(
|
|
7180
|
+
frameIndex,
|
|
7181
|
+
parallelism,
|
|
7182
|
+
renderedSamplesPerPixel = config.samplesPerPixel
|
|
7183
|
+
) {
|
|
7184
|
+
const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
|
|
7185
|
+
const denoisePassCount = config.denoise ? (renderedSamplesPerPixel < 4 ? 2 : 1) : 0;
|
|
7186
|
+
const tailPassCount = denoisePassCount + 1;
|
|
7187
|
+
const sampleBatchSize = Math.max(
|
|
7188
|
+
1,
|
|
7189
|
+
Math.floor(
|
|
7190
|
+
Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) /
|
|
7191
|
+
Math.max(samplePassesPerSample, 1)
|
|
7192
|
+
)
|
|
7193
|
+
);
|
|
7194
|
+
let submissionCount = 0;
|
|
7195
|
+
|
|
7196
|
+
for (const tile of tiles) {
|
|
7197
|
+
for (
|
|
7198
|
+
let sampleStart = 0;
|
|
7199
|
+
sampleStart < renderedSamplesPerPixel;
|
|
7200
|
+
sampleStart += sampleBatchSize
|
|
7201
|
+
) {
|
|
7202
|
+
const sampleEnd = Math.min(renderedSamplesPerPixel, sampleStart + sampleBatchSize);
|
|
7203
|
+
const batch = createGpuSubmissionBatcher({
|
|
7204
|
+
device,
|
|
7205
|
+
frameIndex,
|
|
7206
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
7207
|
+
startingSubmissionCount: submissionCount,
|
|
7208
|
+
});
|
|
7209
|
+
let slot = 0;
|
|
7210
|
+
for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
|
|
7211
|
+
const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
7212
|
+
sampleIndex,
|
|
7213
|
+
sampleWeight: 1 / renderedSamplesPerPixel,
|
|
7214
|
+
});
|
|
7215
|
+
slot += 1;
|
|
7216
|
+
encodeTileSample(
|
|
7217
|
+
batch.reserve(config.maxDepth + 1),
|
|
7218
|
+
tile,
|
|
7219
|
+
configOffset,
|
|
7220
|
+
parallelism
|
|
7221
|
+
);
|
|
7222
|
+
if (config.deferredPathResolve) {
|
|
7223
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
7224
|
+
}
|
|
7225
|
+
}
|
|
7226
|
+
if (!config.deferredPathResolve && sampleEnd >= renderedSamplesPerPixel) {
|
|
7227
|
+
const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
7228
|
+
sampleIndex: 0,
|
|
7229
|
+
sampleWeight: 1 / renderedSamplesPerPixel,
|
|
7230
|
+
});
|
|
7231
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
7232
|
+
}
|
|
7233
|
+
batch.flush();
|
|
7234
|
+
submissionCount += batch.getSubmissionCount();
|
|
7235
|
+
}
|
|
7236
|
+
}
|
|
7237
|
+
|
|
7238
|
+
const tail = createGpuSubmissionBatcher({
|
|
7239
|
+
device,
|
|
7240
|
+
frameIndex,
|
|
4689
7241
|
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
4690
|
-
|
|
4691
|
-
primaryRays: config.width * config.height * config.samplesPerPixel,
|
|
4692
|
-
sceneObjectCount: config.sceneObjectCount,
|
|
4693
|
-
triangleCount: config.triangleCount,
|
|
4694
|
-
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
4695
|
-
environmentPortalCount: config.environmentPortalCount,
|
|
4696
|
-
environmentPortalMode: config.environmentPortalMode,
|
|
4697
|
-
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4698
|
-
deferredPathResolve: config.deferredPathResolve,
|
|
4699
|
-
bvhNodeCount: config.bvhNodeCount,
|
|
4700
|
-
displayQuality: config.displayQuality,
|
|
4701
|
-
accelerationBuildMode: config.accelerationBuildMode,
|
|
4702
|
-
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
4703
|
-
accelerationBuildSubmitted,
|
|
4704
|
-
accelerationBuilt,
|
|
4705
|
-
accelerationBuildCount,
|
|
4706
|
-
commandSubmissions: frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
4707
|
-
frameConfigSlots: frameConfigSlotCount,
|
|
4708
|
-
gpuParallelism: lastGpuParallelism,
|
|
4709
|
-
memory: config.memory,
|
|
7242
|
+
startingSubmissionCount: submissionCount,
|
|
4710
7243
|
});
|
|
7244
|
+
if (config.denoise) {
|
|
7245
|
+
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
7246
|
+
0,
|
|
7247
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
7248
|
+
frameIndex,
|
|
7249
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
7250
|
+
);
|
|
7251
|
+
encodeDenoise(
|
|
7252
|
+
tail.reserve(denoisePassCount),
|
|
7253
|
+
denoiseConfigOffset,
|
|
7254
|
+
parallelism,
|
|
7255
|
+
renderedSamplesPerPixel
|
|
7256
|
+
);
|
|
7257
|
+
}
|
|
7258
|
+
encodePresent(tail.reserve(1));
|
|
7259
|
+
tail.flush();
|
|
7260
|
+
submissionCount += tail.getSubmissionCount();
|
|
7261
|
+
return submissionCount;
|
|
4711
7262
|
}
|
|
4712
7263
|
|
|
4713
7264
|
async function readOutputProbe(optionsForProbe = {}) {
|
|
@@ -4722,6 +7273,10 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4722
7273
|
size: 256,
|
|
4723
7274
|
usage: constants.buffer.COPY_DST | constants.buffer.MAP_READ,
|
|
4724
7275
|
});
|
|
7276
|
+
await waitForSubmittedGpuWork({
|
|
7277
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
7278
|
+
allowTimeout: false,
|
|
7279
|
+
});
|
|
4725
7280
|
const encoder = device.createCommandEncoder({
|
|
4726
7281
|
label: "plasius.wavefront.outputProbe.copy",
|
|
4727
7282
|
});
|
|
@@ -4731,6 +7286,10 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4731
7286
|
{ width: 1, height: 1, depthOrArrayLayers: 1 }
|
|
4732
7287
|
);
|
|
4733
7288
|
device.queue.submit([encoder.finish()]);
|
|
7289
|
+
await waitForSubmittedGpuWork({
|
|
7290
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
7291
|
+
allowTimeout: false,
|
|
7292
|
+
});
|
|
4734
7293
|
await readback.mapAsync(mapMode.READ);
|
|
4735
7294
|
const bytes = new Uint8Array(readback.getMappedRange()).slice(0, 4);
|
|
4736
7295
|
readback.unmap();
|
|
@@ -4744,7 +7303,60 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4744
7303
|
}
|
|
4745
7304
|
|
|
4746
7305
|
async function renderFrame(renderOptions = {}) {
|
|
4747
|
-
const
|
|
7306
|
+
const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
|
|
7307
|
+
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
7308
|
+
const useThrottledHighSamplePath =
|
|
7309
|
+
awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
7310
|
+
const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
|
|
7311
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
7312
|
+
tiles.length,
|
|
7313
|
+
renderOptions.submittedWorkTimeoutMs
|
|
7314
|
+
);
|
|
7315
|
+
const frameStartTimeMs = nowMs();
|
|
7316
|
+
const submissionWaitOptions = awaitGPUCompletion
|
|
7317
|
+
? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false }
|
|
7318
|
+
: { timeoutMs: submittedWorkTimeoutMs };
|
|
7319
|
+
let frameStats;
|
|
7320
|
+
if (useThrottledHighSamplePath) {
|
|
7321
|
+
frame += 1;
|
|
7322
|
+
const frameIndex = frame + config.frameIndex;
|
|
7323
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
7324
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
7325
|
+
const frameSubmissionCount = dispatchFrameAwaitingGpu(
|
|
7326
|
+
frameIndex,
|
|
7327
|
+
parallelismCounters,
|
|
7328
|
+
samplingPlan.renderedSamplesPerPixel
|
|
7329
|
+
);
|
|
7330
|
+
frameStats = createFrameStats({
|
|
7331
|
+
frameIndex,
|
|
7332
|
+
accelerationBuildSubmitted,
|
|
7333
|
+
frameSubmissionCount,
|
|
7334
|
+
parallelismCounters,
|
|
7335
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
7336
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
7337
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
7338
|
+
budgetConstrained: samplingPlan.budgetConstrained,
|
|
7339
|
+
});
|
|
7340
|
+
} else {
|
|
7341
|
+
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
7342
|
+
}
|
|
7343
|
+
if (awaitGPUCompletion) {
|
|
7344
|
+
await waitForSubmittedGpuWork(submissionWaitOptions);
|
|
7345
|
+
}
|
|
7346
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
7347
|
+
if (awaitGPUCompletion) {
|
|
7348
|
+
lastCompletedFrameTimeMs = frameTimeMs;
|
|
7349
|
+
lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
|
|
7350
|
+
}
|
|
7351
|
+
frameStats = Object.freeze({
|
|
7352
|
+
...frameStats,
|
|
7353
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
7354
|
+
frameStats.gpuParallelism,
|
|
7355
|
+
frameStats.commandSubmissions,
|
|
7356
|
+
frameTimeMs,
|
|
7357
|
+
awaitGPUCompletion
|
|
7358
|
+
),
|
|
7359
|
+
});
|
|
4748
7360
|
const probe =
|
|
4749
7361
|
renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
4750
7362
|
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
@@ -4769,10 +7381,8 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4769
7381
|
});
|
|
4770
7382
|
}
|
|
4771
7383
|
|
|
4772
|
-
function
|
|
4773
|
-
|
|
4774
|
-
packedScene = nextPackedScene;
|
|
4775
|
-
config = createWavefrontPathTracingComputeConfig({
|
|
7384
|
+
function rebuildLiveConfig(overrides = {}) {
|
|
7385
|
+
return createWavefrontPathTracingComputeConfig({
|
|
4776
7386
|
...options,
|
|
4777
7387
|
canvas,
|
|
4778
7388
|
width: config.width,
|
|
@@ -4783,27 +7393,38 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4783
7393
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4784
7394
|
sceneObjects: packedScene.objects,
|
|
4785
7395
|
camera: activeCameraOptions,
|
|
7396
|
+
environmentMap: {
|
|
7397
|
+
...config.environmentMap,
|
|
7398
|
+
},
|
|
4786
7399
|
frameIndex: config.frameIndex,
|
|
7400
|
+
...overrides,
|
|
4787
7401
|
});
|
|
7402
|
+
}
|
|
7403
|
+
|
|
7404
|
+
function rebuildMediumResources(nextConfig) {
|
|
7405
|
+
const previousMediumTextureResource = mediumTextureResource;
|
|
7406
|
+
mediumTextureResource = createMediumTextureResource(device, constants, nextConfig.mediums);
|
|
7407
|
+
bindGroups = createTraceBindGroups();
|
|
7408
|
+
if (previousMediumTextureResource?.ownsTexture) {
|
|
7409
|
+
previousMediumTextureResource.texture?.destroy?.();
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7412
|
+
|
|
7413
|
+
function updateSceneObjects(sceneObjects) {
|
|
7414
|
+
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
7415
|
+
packedScene = nextPackedScene;
|
|
7416
|
+
const nextConfig = rebuildLiveConfig();
|
|
7417
|
+
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
7418
|
+
rebuildMediumResources(nextConfig);
|
|
7419
|
+
}
|
|
7420
|
+
config = nextConfig;
|
|
4788
7421
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
4789
7422
|
return config;
|
|
4790
7423
|
}
|
|
4791
7424
|
|
|
4792
7425
|
function updateCamera(cameraOptions = {}) {
|
|
4793
7426
|
activeCameraOptions = cameraOptions;
|
|
4794
|
-
config =
|
|
4795
|
-
...options,
|
|
4796
|
-
canvas,
|
|
4797
|
-
width: config.width,
|
|
4798
|
-
height: config.height,
|
|
4799
|
-
maxDepth: config.maxDepth,
|
|
4800
|
-
tileSize: config.tileSize,
|
|
4801
|
-
samplesPerPixel: config.samplesPerPixel,
|
|
4802
|
-
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4803
|
-
sceneObjects: packedScene.objects,
|
|
4804
|
-
camera: activeCameraOptions,
|
|
4805
|
-
frameIndex: config.frameIndex,
|
|
4806
|
-
});
|
|
7427
|
+
config = rebuildLiveConfig();
|
|
4807
7428
|
return config;
|
|
4808
7429
|
}
|
|
4809
7430
|
|
|
@@ -4822,6 +7443,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4822
7443
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
4823
7444
|
environmentPortalCount: config.environmentPortalCount,
|
|
4824
7445
|
environmentPortalMode: config.environmentPortalMode,
|
|
7446
|
+
mediumCount: config.mediumCount,
|
|
4825
7447
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4826
7448
|
deferredPathResolve: config.deferredPathResolve,
|
|
4827
7449
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -4860,6 +7482,28 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4860
7482
|
if (environmentMapResource.ownsTexture) {
|
|
4861
7483
|
environmentMapResource.texture?.destroy?.();
|
|
4862
7484
|
}
|
|
7485
|
+
if (environmentSamplingResource.ownsTexture) {
|
|
7486
|
+
environmentSamplingResource.texture?.destroy?.();
|
|
7487
|
+
}
|
|
7488
|
+
if (mediumTextureResource.ownsTexture) {
|
|
7489
|
+
mediumTextureResource.texture?.destroy?.();
|
|
7490
|
+
}
|
|
7491
|
+
brdfLutResource.texture?.destroy?.();
|
|
7492
|
+
if (baseColorAtlasResource.ownsTexture) {
|
|
7493
|
+
baseColorAtlasResource.texture?.destroy?.();
|
|
7494
|
+
}
|
|
7495
|
+
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
7496
|
+
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
7497
|
+
}
|
|
7498
|
+
if (normalAtlasResource.ownsTexture) {
|
|
7499
|
+
normalAtlasResource.texture?.destroy?.();
|
|
7500
|
+
}
|
|
7501
|
+
if (occlusionAtlasResource.ownsTexture) {
|
|
7502
|
+
occlusionAtlasResource.texture?.destroy?.();
|
|
7503
|
+
}
|
|
7504
|
+
if (emissiveAtlasResource.ownsTexture) {
|
|
7505
|
+
emissiveAtlasResource.texture?.destroy?.();
|
|
7506
|
+
}
|
|
4863
7507
|
context.unconfigure?.();
|
|
4864
7508
|
}
|
|
4865
7509
|
|