@plasius/gpu-renderer 0.2.6 → 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 +6 -2
- package/README.md +9 -0
- package/dist/index.cjs +414 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +414 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.d.ts +56 -4
- package/src/wavefront-compute.js +465 -54
package/src/wavefront-compute.js
CHANGED
|
@@ -13,17 +13,19 @@ const DEFAULT_MAX_DEPTH = 6;
|
|
|
13
13
|
const DEFAULT_TILE_SIZE = 128;
|
|
14
14
|
const DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
15
15
|
const MAX_SAMPLES_PER_PIXEL = 256;
|
|
16
|
-
const DEFAULT_BRDF_LUT_SIZE =
|
|
16
|
+
const DEFAULT_BRDF_LUT_SIZE = 128;
|
|
17
|
+
const DEFAULT_BRDF_LUT_SAMPLE_COUNT = 256;
|
|
17
18
|
const DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
18
19
|
const DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
19
20
|
const DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
21
|
+
const DEFAULT_MEDIUM_PHASE_MODEL = 0;
|
|
20
22
|
const WORKGROUP_SIZE = 64;
|
|
21
23
|
export const rendererWavefrontComputeMode = "webgpu-compute";
|
|
22
24
|
export const rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
23
25
|
export const rendererWavefrontComputeStatsStride = 8;
|
|
24
26
|
const RAY_RECORD_BYTES = 80;
|
|
25
27
|
const HIT_RECORD_BYTES = 256;
|
|
26
|
-
const SCENE_OBJECT_RECORD_BYTES =
|
|
28
|
+
const SCENE_OBJECT_RECORD_BYTES = 160;
|
|
27
29
|
const MESH_VERTEX_RECORD_BYTES = 48;
|
|
28
30
|
const MESH_RANGE_RECORD_BYTES = 240;
|
|
29
31
|
const TRIANGLE_RECORD_BYTES = 352;
|
|
@@ -32,6 +34,7 @@ const BVH_NODE_RECORD_BYTES = 48;
|
|
|
32
34
|
const BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
33
35
|
const EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
34
36
|
const ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
37
|
+
const MEDIUM_TABLE_ROWS = 2;
|
|
35
38
|
const ACCUMULATION_RECORD_BYTES = 16;
|
|
36
39
|
const PATH_VERTEX_RECORD_BYTES = 16;
|
|
37
40
|
const GPU_SUBMITTED_WORK_TIMEOUT_MS = 5_000;
|
|
@@ -437,6 +440,183 @@ function deriveBounds(input) {
|
|
|
437
440
|
return null;
|
|
438
441
|
}
|
|
439
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
|
+
|
|
440
620
|
export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
441
621
|
const bounds = deriveBounds(input);
|
|
442
622
|
const kind = readObjectKind(input.kind ?? input.type ?? (bounds ? "box" : "sphere"));
|
|
@@ -474,6 +654,7 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
474
654
|
input.specularColor ?? input.material?.specularColor,
|
|
475
655
|
[1, 1, 1, 1]
|
|
476
656
|
).map((value, componentIndex) => (componentIndex < 3 ? clamp(value, 0, 1) : 1));
|
|
657
|
+
const medium = deriveWavefrontTransportMedium(input, index + 1);
|
|
477
658
|
const resolvedMaterialKind =
|
|
478
659
|
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
479
660
|
? MATERIAL_EMISSIVE
|
|
@@ -488,6 +669,12 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
488
669
|
kind,
|
|
489
670
|
materialKind: resolvedMaterialKind,
|
|
490
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,
|
|
491
678
|
center: Object.freeze(center),
|
|
492
679
|
halfExtent: Object.freeze(halfExtent),
|
|
493
680
|
color: Object.freeze(color),
|
|
@@ -511,6 +698,7 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
511
698
|
),
|
|
512
699
|
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
513
700
|
specularColor: Object.freeze(specularColor),
|
|
701
|
+
thickness: normalizeWavefrontThickness(input, "thickness"),
|
|
514
702
|
transmission,
|
|
515
703
|
});
|
|
516
704
|
}
|
|
@@ -620,6 +808,7 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
620
808
|
input.specularColor ?? input.material?.specularColor,
|
|
621
809
|
[1, 1, 1, 1]
|
|
622
810
|
).map((value, componentIndex) => (componentIndex < 3 ? clamp(value, 0, 1) : 1));
|
|
811
|
+
const medium = deriveWavefrontTransportMedium(input, meshIndex + 1);
|
|
623
812
|
const resolvedMaterialKind =
|
|
624
813
|
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
625
814
|
? MATERIAL_EMISSIVE
|
|
@@ -644,9 +833,14 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
644
833
|
),
|
|
645
834
|
mediumRefId: readNonNegativeInteger(
|
|
646
835
|
"mesh mediumRefId",
|
|
647
|
-
input.mediumRefId ??
|
|
836
|
+
input.mediumRefId ??
|
|
837
|
+
medium?.id ??
|
|
838
|
+
input.medium?.id ??
|
|
839
|
+
input.mediumId ??
|
|
840
|
+
input.material?.mediumId,
|
|
648
841
|
0
|
|
649
842
|
),
|
|
843
|
+
medium,
|
|
650
844
|
color: Object.freeze(color),
|
|
651
845
|
emission: Object.freeze(emission),
|
|
652
846
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
@@ -668,6 +862,7 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
668
862
|
),
|
|
669
863
|
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
670
864
|
specularColor: Object.freeze(specularColor),
|
|
865
|
+
thickness: normalizeWavefrontThickness(input, "mesh thickness"),
|
|
671
866
|
transmission,
|
|
672
867
|
baseColorTexture: input.baseColorTexture ?? input.material?.baseColorTexture ?? null,
|
|
673
868
|
metallicRoughnessTexture:
|
|
@@ -902,7 +1097,7 @@ function createMeshTriangleRecords(meshes) {
|
|
|
902
1097
|
mesh.clearcoatRoughness,
|
|
903
1098
|
mesh.specular,
|
|
904
1099
|
mesh.transmission,
|
|
905
|
-
|
|
1100
|
+
mesh.thickness,
|
|
906
1101
|
]),
|
|
907
1102
|
specularColor: Object.freeze([
|
|
908
1103
|
mesh.specularColor[0] ?? 1,
|
|
@@ -1231,7 +1426,7 @@ export function createWavefrontGpuMaterialSource(meshes = []) {
|
|
|
1231
1426
|
mesh.clearcoatRoughness,
|
|
1232
1427
|
mesh.specular,
|
|
1233
1428
|
mesh.transmission,
|
|
1234
|
-
|
|
1429
|
+
mesh.thickness,
|
|
1235
1430
|
]);
|
|
1236
1431
|
writeVec4(floatView, byteOffset + 80, [
|
|
1237
1432
|
mesh.specularColor[0] ?? 1,
|
|
@@ -1419,7 +1614,7 @@ export function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput
|
|
|
1419
1614
|
mesh.clearcoatRoughness,
|
|
1420
1615
|
mesh.specular,
|
|
1421
1616
|
mesh.transmission,
|
|
1422
|
-
|
|
1617
|
+
mesh.thickness,
|
|
1423
1618
|
]);
|
|
1424
1619
|
writeVec4(meshFloats, floatOffset * 4 + 128, [
|
|
1425
1620
|
mesh.specularColor[0] ?? 1,
|
|
@@ -1534,12 +1729,17 @@ function normalizeSceneObjects(sceneObjects, useDefaultScene = true) {
|
|
|
1534
1729
|
return source.map((object, index) => normalizeWavefrontSceneObject(object, index));
|
|
1535
1730
|
}
|
|
1536
1731
|
|
|
1732
|
+
function normalizeWavefrontMeshes(meshes) {
|
|
1733
|
+
const source = Array.isArray(meshes) ? meshes : [];
|
|
1734
|
+
return source.map((mesh, index) => normalizeWavefrontMesh(mesh, index));
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1537
1737
|
function normalizeMeshes(options = {}) {
|
|
1538
1738
|
if (Array.isArray(options.meshes)) {
|
|
1539
|
-
return options.meshes;
|
|
1739
|
+
return normalizeWavefrontMeshes(options.meshes);
|
|
1540
1740
|
}
|
|
1541
1741
|
if (options.mesh) {
|
|
1542
|
-
return [options.mesh];
|
|
1742
|
+
return normalizeWavefrontMeshes([options.mesh]);
|
|
1543
1743
|
}
|
|
1544
1744
|
return [];
|
|
1545
1745
|
}
|
|
@@ -1899,6 +2099,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1899
2099
|
const sceneObjects = Object.freeze(
|
|
1900
2100
|
normalizeSceneObjects(options.sceneObjects, meshes.length === 0)
|
|
1901
2101
|
);
|
|
2102
|
+
const mediums = collectWavefrontMediums(options, meshes, sceneObjects);
|
|
1902
2103
|
const sceneObjectCapacity = Math.max(
|
|
1903
2104
|
sceneObjects.length,
|
|
1904
2105
|
readPositiveInteger("sceneObjectCapacity", options.sceneObjectCapacity, DEFAULT_SCENE_OBJECT_CAPACITY)
|
|
@@ -1971,6 +2172,8 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1971
2172
|
sceneObjects,
|
|
1972
2173
|
sceneObjectCount: sceneObjects.length,
|
|
1973
2174
|
sceneObjectCapacity,
|
|
2175
|
+
mediums,
|
|
2176
|
+
mediumCount: mediums.length,
|
|
1974
2177
|
accelerationBuildMode,
|
|
1975
2178
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
1976
2179
|
gpuMeshSource,
|
|
@@ -2089,29 +2292,30 @@ export function packWavefrontSceneObjects(sceneObjects, capacity = sceneObjects.
|
|
|
2089
2292
|
uintView[u32 + 1] = object.id;
|
|
2090
2293
|
uintView[u32 + 2] = object.materialKind;
|
|
2091
2294
|
uintView[u32 + 3] = object.flags;
|
|
2092
|
-
|
|
2093
|
-
writeVec4(floatView, byteOffset + 32, [...object.
|
|
2094
|
-
writeVec4(floatView, byteOffset + 48, object.
|
|
2095
|
-
writeVec4(floatView, byteOffset + 64, object.
|
|
2096
|
-
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, [
|
|
2097
2301
|
object.roughness,
|
|
2098
2302
|
object.metallic,
|
|
2099
2303
|
object.opacity,
|
|
2100
2304
|
object.ior,
|
|
2101
2305
|
]);
|
|
2102
|
-
writeVec4(floatView, byteOffset +
|
|
2306
|
+
writeVec4(floatView, byteOffset + 112, [
|
|
2103
2307
|
object.sheenColor[0] ?? 0,
|
|
2104
2308
|
object.sheenColor[1] ?? 0,
|
|
2105
2309
|
object.sheenColor[2] ?? 0,
|
|
2106
2310
|
object.clearcoat,
|
|
2107
2311
|
]);
|
|
2108
|
-
writeVec4(floatView, byteOffset +
|
|
2312
|
+
writeVec4(floatView, byteOffset + 128, [
|
|
2109
2313
|
object.clearcoatRoughness,
|
|
2110
2314
|
object.specular,
|
|
2111
2315
|
object.transmission,
|
|
2112
|
-
|
|
2316
|
+
object.thickness,
|
|
2113
2317
|
]);
|
|
2114
|
-
writeVec4(floatView, byteOffset +
|
|
2318
|
+
writeVec4(floatView, byteOffset + 144, [
|
|
2115
2319
|
object.specularColor[0] ?? 1,
|
|
2116
2320
|
object.specularColor[1] ?? 1,
|
|
2117
2321
|
object.specularColor[2] ?? 1,
|
|
@@ -2710,7 +2914,10 @@ function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
|
2710
2914
|
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
2711
2915
|
}
|
|
2712
2916
|
|
|
2713
|
-
function createBrdfLutUploadBytes(
|
|
2917
|
+
function createBrdfLutUploadBytes(
|
|
2918
|
+
size = DEFAULT_BRDF_LUT_SIZE,
|
|
2919
|
+
sampleCount = DEFAULT_BRDF_LUT_SAMPLE_COUNT
|
|
2920
|
+
) {
|
|
2714
2921
|
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
2715
2922
|
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
2716
2923
|
if (cached) {
|
|
@@ -3137,6 +3344,85 @@ function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE)
|
|
|
3137
3344
|
});
|
|
3138
3345
|
}
|
|
3139
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
|
+
|
|
3140
3426
|
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
3141
3427
|
const upload = createRgba8TextureUpload(atlas);
|
|
3142
3428
|
const texture = device.createTexture({
|
|
@@ -3283,6 +3569,10 @@ struct SceneObject {
|
|
|
3283
3569
|
objectId: u32,
|
|
3284
3570
|
materialKind: u32,
|
|
3285
3571
|
flags: u32,
|
|
3572
|
+
mediumRefId: u32,
|
|
3573
|
+
pad0: u32,
|
|
3574
|
+
pad1: u32,
|
|
3575
|
+
pad2: u32,
|
|
3286
3576
|
center: vec4<f32>,
|
|
3287
3577
|
halfExtent: vec4<f32>,
|
|
3288
3578
|
color: vec4<f32>,
|
|
@@ -3343,9 +3633,9 @@ struct BvhLeafRef {
|
|
|
3343
3633
|
struct ScatterResult {
|
|
3344
3634
|
direction: vec4<f32>,
|
|
3345
3635
|
pdf: f32,
|
|
3636
|
+
mediumRefId: u32,
|
|
3346
3637
|
flags: u32,
|
|
3347
3638
|
pad0: u32,
|
|
3348
|
-
pad1: u32,
|
|
3349
3639
|
};
|
|
3350
3640
|
|
|
3351
3641
|
struct MeshVertex {
|
|
@@ -3491,6 +3781,7 @@ struct EnvironmentPortal {
|
|
|
3491
3781
|
@group(0) @binding(29) var brdfLutTexture: texture_2d<f32>;
|
|
3492
3782
|
@group(0) @binding(30) var brdfLutSampler: sampler;
|
|
3493
3783
|
@group(0) @binding(31) var environmentSamplingTexture: texture_2d<f32>;
|
|
3784
|
+
@group(0) @binding(32) var mediumTableTexture: texture_2d<f32>;
|
|
3494
3785
|
|
|
3495
3786
|
fn hash_u32(value: u32) -> u32 {
|
|
3496
3787
|
var x = value;
|
|
@@ -4156,6 +4447,60 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
4156
4447
|
return environment_radiance(origin, direction);
|
|
4157
4448
|
}
|
|
4158
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
|
+
|
|
4159
4504
|
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
4160
4505
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4161
4506
|
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
@@ -4254,11 +4599,15 @@ fn terminal_surface_environment_source(ray: RayRecord, hit: HitRecord) -> vec3<f
|
|
|
4254
4599
|
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
4255
4600
|
}
|
|
4256
4601
|
|
|
4257
|
-
fn terminal_surface_environment_contribution(
|
|
4602
|
+
fn terminal_surface_environment_contribution(
|
|
4603
|
+
ray: RayRecord,
|
|
4604
|
+
throughput: vec3<f32>,
|
|
4605
|
+
hit: HitRecord
|
|
4606
|
+
) -> vec3<f32> {
|
|
4258
4607
|
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
4259
4608
|
let occlusion = mix(0.75, 1.0, clamp(hit.occlusion, 0.0, 1.0));
|
|
4260
4609
|
return clamp_sample_radiance(
|
|
4261
|
-
|
|
4610
|
+
throughput *
|
|
4262
4611
|
surfaceColor *
|
|
4263
4612
|
terminal_surface_environment_source(ray, hit) *
|
|
4264
4613
|
occlusion
|
|
@@ -4732,7 +5081,7 @@ fn intersect_sphere(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
4732
5081
|
0xffffffffu,
|
|
4733
5082
|
object.objectId,
|
|
4734
5083
|
object.objectId,
|
|
4735
|
-
|
|
5084
|
+
object.mediumRefId
|
|
4736
5085
|
);
|
|
4737
5086
|
}
|
|
4738
5087
|
|
|
@@ -4784,7 +5133,7 @@ fn intersect_box(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
4784
5133
|
0xffffffffu,
|
|
4785
5134
|
object.objectId,
|
|
4786
5135
|
object.objectId,
|
|
4787
|
-
|
|
5136
|
+
object.mediumRefId
|
|
4788
5137
|
);
|
|
4789
5138
|
}
|
|
4790
5139
|
|
|
@@ -5035,6 +5384,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5035
5384
|
let ray = activeQueue[index];
|
|
5036
5385
|
var nearest = 1000000.0;
|
|
5037
5386
|
var hitObject = SceneObject(
|
|
5387
|
+
0u,
|
|
5388
|
+
0u,
|
|
5389
|
+
0u,
|
|
5390
|
+
0u,
|
|
5038
5391
|
0u,
|
|
5039
5392
|
0u,
|
|
5040
5393
|
0u,
|
|
@@ -5238,9 +5591,9 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5238
5591
|
return ScatterResult(
|
|
5239
5592
|
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5240
5593
|
1.0,
|
|
5594
|
+
ray.mediumRefId,
|
|
5241
5595
|
RAY_FLAG_DELTA_SAMPLE,
|
|
5242
5596
|
0u,
|
|
5243
|
-
0u
|
|
5244
5597
|
);
|
|
5245
5598
|
}
|
|
5246
5599
|
|
|
@@ -5260,17 +5613,17 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5260
5613
|
return ScatterResult(
|
|
5261
5614
|
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5262
5615
|
1.0,
|
|
5616
|
+
ray.mediumRefId,
|
|
5263
5617
|
RAY_FLAG_DELTA_SAMPLE,
|
|
5264
5618
|
0u,
|
|
5265
|
-
0u
|
|
5266
5619
|
);
|
|
5267
5620
|
}
|
|
5268
5621
|
return ScatterResult(
|
|
5269
5622
|
vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
|
|
5270
5623
|
1.0,
|
|
5624
|
+
transmitted_medium_ref_id(ray, hit),
|
|
5271
5625
|
RAY_FLAG_DELTA_SAMPLE,
|
|
5272
5626
|
0u,
|
|
5273
|
-
0u
|
|
5274
5627
|
);
|
|
5275
5628
|
}
|
|
5276
5629
|
|
|
@@ -5285,9 +5638,9 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5285
5638
|
return ScatterResult(
|
|
5286
5639
|
vec4<f32>(guidedDirection, 0.0),
|
|
5287
5640
|
guidedPdf,
|
|
5641
|
+
ray.mediumRefId,
|
|
5288
5642
|
RAY_FLAG_GUIDED_EMISSIVE,
|
|
5289
5643
|
0u,
|
|
5290
|
-
0u
|
|
5291
5644
|
);
|
|
5292
5645
|
}
|
|
5293
5646
|
}
|
|
@@ -5295,7 +5648,7 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5295
5648
|
let guidedDirection = sample_environment_portal_direction(hit, seed + 131u, normal);
|
|
5296
5649
|
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5297
5650
|
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5298
|
-
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf,
|
|
5651
|
+
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf, ray.mediumRefId, 0u, 0u);
|
|
5299
5652
|
}
|
|
5300
5653
|
}
|
|
5301
5654
|
|
|
@@ -5329,7 +5682,7 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5329
5682
|
);
|
|
5330
5683
|
}
|
|
5331
5684
|
let pdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection), 0.000001);
|
|
5332
|
-
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf,
|
|
5685
|
+
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf, ray.mediumRefId, 0u, 0u);
|
|
5333
5686
|
}
|
|
5334
5687
|
|
|
5335
5688
|
@compute @workgroup_size(64)
|
|
@@ -5342,15 +5695,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5342
5695
|
|
|
5343
5696
|
let ray = activeQueue[index];
|
|
5344
5697
|
let hit = hits[index];
|
|
5698
|
+
let segmentTransmittance = medium_transmittance(ray.mediumRefId, hit.distance);
|
|
5699
|
+
let arrivingThroughput = ray.throughput.xyz * segmentTransmittance;
|
|
5345
5700
|
var contribution = vec3<f32>(0.0);
|
|
5346
5701
|
|
|
5347
5702
|
if (hit.hitType == 1u) {
|
|
5348
5703
|
let guidedLightWeight = select(1.0, 0.24, (ray.flags & RAY_FLAG_GUIDED_EMISSIVE) != 0u);
|
|
5349
5704
|
let sourceRadiance = max(hit.emission.xyz, hit.color.xyz) * guidedLightWeight;
|
|
5350
5705
|
if (deferred_path_resolve_enabled()) {
|
|
5351
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5706
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
5352
5707
|
} else {
|
|
5353
|
-
contribution = clamp_sample_radiance(
|
|
5708
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
5354
5709
|
accumulation[ray.rayId] =
|
|
5355
5710
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
5356
5711
|
}
|
|
@@ -5367,9 +5722,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5367
5722
|
sourceRadiance = sourceRadiance * misWeight;
|
|
5368
5723
|
}
|
|
5369
5724
|
if (deferred_path_resolve_enabled()) {
|
|
5370
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5725
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
5371
5726
|
} else {
|
|
5372
|
-
contribution = clamp_sample_radiance(
|
|
5727
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
5373
5728
|
accumulation[ray.rayId] =
|
|
5374
5729
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
5375
5730
|
}
|
|
@@ -5377,7 +5732,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5377
5732
|
return;
|
|
5378
5733
|
}
|
|
5379
5734
|
|
|
5380
|
-
let response = stabilize_surface_path_response(
|
|
5735
|
+
let response = stabilize_surface_path_response(
|
|
5736
|
+
ray,
|
|
5737
|
+
hit,
|
|
5738
|
+
surface_path_response(hit) * segmentTransmittance
|
|
5739
|
+
);
|
|
5381
5740
|
record_deferred_path_response(ray, response);
|
|
5382
5741
|
|
|
5383
5742
|
let shouldEstimateDirectEnvironment =
|
|
@@ -5385,7 +5744,22 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5385
5744
|
hit.material.z >= 0.95 &&
|
|
5386
5745
|
ray.bounce < 2u;
|
|
5387
5746
|
if (shouldEstimateDirectEnvironment) {
|
|
5388
|
-
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
|
+
);
|
|
5389
5763
|
accumulation[ray.rayId] =
|
|
5390
5764
|
accumulation[ray.rayId] + vec4<f32>(directEnvironment * sample_weight(), 0.0);
|
|
5391
5765
|
}
|
|
@@ -5394,7 +5768,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5394
5768
|
if (deferred_path_resolve_enabled()) {
|
|
5395
5769
|
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
5396
5770
|
} else {
|
|
5397
|
-
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5771
|
+
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5772
|
+
ray,
|
|
5773
|
+
arrivingThroughput,
|
|
5774
|
+
hit
|
|
5775
|
+
);
|
|
5398
5776
|
accumulation[ray.rayId] =
|
|
5399
5777
|
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
5400
5778
|
}
|
|
@@ -5409,7 +5787,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5409
5787
|
if (deferred_path_resolve_enabled()) {
|
|
5410
5788
|
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
5411
5789
|
} else {
|
|
5412
|
-
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5790
|
+
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5791
|
+
ray,
|
|
5792
|
+
arrivingThroughput,
|
|
5793
|
+
hit
|
|
5794
|
+
);
|
|
5413
5795
|
accumulation[ray.rayId] =
|
|
5414
5796
|
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
5415
5797
|
}
|
|
@@ -5423,7 +5805,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5423
5805
|
ray.sourcePixelId,
|
|
5424
5806
|
ray.sampleId,
|
|
5425
5807
|
ray.bounce + 1u,
|
|
5426
|
-
|
|
5808
|
+
scatter.mediumRefId,
|
|
5427
5809
|
scatter.flags,
|
|
5428
5810
|
0u,
|
|
5429
5811
|
vec4<f32>(offset_origin(hit.position.xyz, hit.shadingNormal.xyz), 1.0),
|
|
@@ -5945,6 +6327,11 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
5945
6327
|
config.environmentMap,
|
|
5946
6328
|
config.environmentColor
|
|
5947
6329
|
);
|
|
6330
|
+
let mediumTextureResource = createMediumTextureResource(
|
|
6331
|
+
device,
|
|
6332
|
+
constants,
|
|
6333
|
+
config.mediums
|
|
6334
|
+
);
|
|
5948
6335
|
config = Object.freeze({
|
|
5949
6336
|
...config,
|
|
5950
6337
|
environmentMap: Object.freeze({
|
|
@@ -6033,6 +6420,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6033
6420
|
{ binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6034
6421
|
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
6035
6422
|
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6423
|
+
{ binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6036
6424
|
],
|
|
6037
6425
|
});
|
|
6038
6426
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -6222,14 +6610,19 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6222
6610
|
{ binding: 29, resource: brdfLutResource.view },
|
|
6223
6611
|
{ binding: 30, resource: brdfLutResource.sampler },
|
|
6224
6612
|
{ binding: 31, resource: environmentSamplingResource.view },
|
|
6613
|
+
{ binding: 32, resource: mediumTextureResource.view },
|
|
6225
6614
|
],
|
|
6226
6615
|
});
|
|
6227
6616
|
}
|
|
6228
6617
|
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
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();
|
|
6233
6626
|
const bvhBuildBindGroup = device.createBindGroup({
|
|
6234
6627
|
label: "plasius.wavefront.bind.bvhBuild",
|
|
6235
6628
|
layout: accelerationBindGroupLayout,
|
|
@@ -6418,6 +6811,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6418
6811
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6419
6812
|
environmentPortalCount: config.environmentPortalCount,
|
|
6420
6813
|
environmentPortalMode: config.environmentPortalMode,
|
|
6814
|
+
mediumCount: config.mediumCount,
|
|
6421
6815
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6422
6816
|
deferredPathResolve: config.deferredPathResolve,
|
|
6423
6817
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -7007,10 +7401,23 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
7007
7401
|
});
|
|
7008
7402
|
}
|
|
7009
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
|
+
|
|
7010
7413
|
function updateSceneObjects(sceneObjects) {
|
|
7011
7414
|
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
7012
7415
|
packedScene = nextPackedScene;
|
|
7013
|
-
|
|
7416
|
+
const nextConfig = rebuildLiveConfig();
|
|
7417
|
+
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
7418
|
+
rebuildMediumResources(nextConfig);
|
|
7419
|
+
}
|
|
7420
|
+
config = nextConfig;
|
|
7014
7421
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
7015
7422
|
return config;
|
|
7016
7423
|
}
|
|
@@ -7036,6 +7443,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
7036
7443
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
7037
7444
|
environmentPortalCount: config.environmentPortalCount,
|
|
7038
7445
|
environmentPortalMode: config.environmentPortalMode,
|
|
7446
|
+
mediumCount: config.mediumCount,
|
|
7039
7447
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
7040
7448
|
deferredPathResolve: config.deferredPathResolve,
|
|
7041
7449
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -7070,17 +7478,20 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
7070
7478
|
activeDispatchBuffer.destroy?.();
|
|
7071
7479
|
radianceTexture.destroy?.();
|
|
7072
7480
|
denoiseScratchTexture.destroy?.();
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7481
|
+
outputTexture.destroy?.();
|
|
7482
|
+
if (environmentMapResource.ownsTexture) {
|
|
7483
|
+
environmentMapResource.texture?.destroy?.();
|
|
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
|
+
}
|
|
7084
7495
|
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
7085
7496
|
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
7086
7497
|
}
|