@plasius/gpu-renderer 0.2.6 → 0.2.8
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 +27 -2
- package/README.md +26 -10
- package/dist/index.cjs +662 -282
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +662 -282
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.d.ts +56 -4
- package/src/wavefront-compute.js +728 -259
package/dist/index.js
CHANGED
|
@@ -138,17 +138,19 @@ var DEFAULT_MAX_DEPTH = 6;
|
|
|
138
138
|
var DEFAULT_TILE_SIZE = 128;
|
|
139
139
|
var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
140
140
|
var MAX_SAMPLES_PER_PIXEL = 256;
|
|
141
|
-
var DEFAULT_BRDF_LUT_SIZE =
|
|
141
|
+
var DEFAULT_BRDF_LUT_SIZE = 128;
|
|
142
|
+
var DEFAULT_BRDF_LUT_SAMPLE_COUNT = 256;
|
|
142
143
|
var DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
143
144
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
144
145
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
146
|
+
var DEFAULT_MEDIUM_PHASE_MODEL = 0;
|
|
145
147
|
var WORKGROUP_SIZE = 64;
|
|
146
148
|
var rendererWavefrontComputeMode = "webgpu-compute";
|
|
147
149
|
var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
148
150
|
var rendererWavefrontComputeStatsStride = 8;
|
|
149
151
|
var RAY_RECORD_BYTES = 80;
|
|
150
152
|
var HIT_RECORD_BYTES = 256;
|
|
151
|
-
var SCENE_OBJECT_RECORD_BYTES =
|
|
153
|
+
var SCENE_OBJECT_RECORD_BYTES = 160;
|
|
152
154
|
var MESH_VERTEX_RECORD_BYTES = 48;
|
|
153
155
|
var MESH_RANGE_RECORD_BYTES = 240;
|
|
154
156
|
var TRIANGLE_RECORD_BYTES = 352;
|
|
@@ -157,11 +159,13 @@ var BVH_NODE_RECORD_BYTES = 48;
|
|
|
157
159
|
var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
158
160
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
159
161
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
162
|
+
var MEDIUM_TABLE_ROWS = 2;
|
|
160
163
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
161
164
|
var PATH_VERTEX_RECORD_BYTES = 16;
|
|
162
165
|
var GPU_SUBMITTED_WORK_TIMEOUT_MS = 5e3;
|
|
163
166
|
var GPU_READBACK_COMPLETION_TIMEOUT_MS = 6e4;
|
|
164
167
|
var GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 6e4;
|
|
168
|
+
var GPU_MAX_SUBMITTED_WORK_DEADLINE_MS = 18e4;
|
|
165
169
|
var CONFIG_BUFFER_BYTES = 320;
|
|
166
170
|
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
167
171
|
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
@@ -503,6 +507,156 @@ function deriveBounds(input) {
|
|
|
503
507
|
}
|
|
504
508
|
return null;
|
|
505
509
|
}
|
|
510
|
+
function deriveBeerLambertAbsorptionFromAttenuationColor(attenuationColor, attenuationDistance, density = 1) {
|
|
511
|
+
const distance = Number(attenuationDistance);
|
|
512
|
+
const densityScale = Math.max(0, Number(density) || 0);
|
|
513
|
+
if (!Number.isFinite(distance) || distance <= 0 || densityScale <= 0) {
|
|
514
|
+
return [0, 0, 0];
|
|
515
|
+
}
|
|
516
|
+
return attenuationColor.slice(0, 3).map((channel) => {
|
|
517
|
+
const clamped = clamp(Number(channel) || 0, 1e-4, 1);
|
|
518
|
+
return Math.max(0, -Math.log(clamped) / distance * densityScale);
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
function readMediumPhaseModel(value) {
|
|
522
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
523
|
+
return Math.max(0, Math.trunc(value));
|
|
524
|
+
}
|
|
525
|
+
switch (String(value ?? "").trim().toLowerCase()) {
|
|
526
|
+
case "isotropic":
|
|
527
|
+
default:
|
|
528
|
+
return DEFAULT_MEDIUM_PHASE_MODEL;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function resolveWavefrontVolumeInput(input) {
|
|
532
|
+
return input?.volume ?? input?.material?.volume ?? null;
|
|
533
|
+
}
|
|
534
|
+
function normalizeWavefrontThickness(input, label) {
|
|
535
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
536
|
+
return Math.max(
|
|
537
|
+
0,
|
|
538
|
+
readFiniteNumber(
|
|
539
|
+
label,
|
|
540
|
+
input?.thickness ?? volume?.thickness ?? input?.material?.thickness,
|
|
541
|
+
0
|
|
542
|
+
)
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
function resolveWavefrontMediumId(input, fallbackId = 1) {
|
|
546
|
+
return input?.mediumRefId ?? input?.mediumId ?? input?.material?.mediumId ?? input?.materialRefId ?? input?.material?.id ?? input?.materialId ?? input?.id ?? fallbackId;
|
|
547
|
+
}
|
|
548
|
+
function deriveWavefrontTransportMedium(input, fallbackId = 1) {
|
|
549
|
+
const resolvedId = resolveWavefrontMediumId(input, fallbackId);
|
|
550
|
+
if (input?.medium) {
|
|
551
|
+
return normalizeWavefrontMedium(
|
|
552
|
+
{
|
|
553
|
+
...input.medium,
|
|
554
|
+
id: input.medium.id ?? input.medium.mediumId ?? resolvedId
|
|
555
|
+
},
|
|
556
|
+
fallbackId
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
560
|
+
if (!volume) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
return normalizeWavefrontMedium(
|
|
564
|
+
{
|
|
565
|
+
id: resolvedId,
|
|
566
|
+
phaseModel: volume.phaseModel,
|
|
567
|
+
density: volume.density,
|
|
568
|
+
attenuationColor: volume.attenuationColor,
|
|
569
|
+
attenuationDistance: volume.attenuationDistance,
|
|
570
|
+
absorption: volume.absorption,
|
|
571
|
+
scattering: volume.scattering
|
|
572
|
+
},
|
|
573
|
+
fallbackId
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
function normalizeWavefrontMedium(input = {}, index = 0) {
|
|
577
|
+
const id = readNonNegativeInteger("medium id", input.id ?? input.mediumId, index);
|
|
578
|
+
const density = Math.max(0, readFiniteNumber("medium density", input.density, 1));
|
|
579
|
+
const attenuationColor = asColor(
|
|
580
|
+
input.attenuationColor ?? input.color ?? input.medium?.attenuationColor,
|
|
581
|
+
[1, 1, 1, 1]
|
|
582
|
+
);
|
|
583
|
+
const attenuationDistance = readFiniteNumber(
|
|
584
|
+
"medium attenuationDistance",
|
|
585
|
+
input.attenuationDistance ?? input.distance ?? input.medium?.attenuationDistance,
|
|
586
|
+
0
|
|
587
|
+
);
|
|
588
|
+
const absorption = Array.isArray(input.absorption) || Array.isArray(input.medium?.absorption) ? asVec3(input.absorption ?? input.medium?.absorption, [0, 0, 0]).map(
|
|
589
|
+
(value) => Math.max(0, Number(value) || 0)
|
|
590
|
+
) : deriveBeerLambertAbsorptionFromAttenuationColor(
|
|
591
|
+
attenuationColor,
|
|
592
|
+
attenuationDistance,
|
|
593
|
+
density
|
|
594
|
+
);
|
|
595
|
+
const scattering = asVec3(
|
|
596
|
+
input.scattering ?? input.medium?.scattering,
|
|
597
|
+
[0, 0, 0]
|
|
598
|
+
).map((value) => Math.max(0, Number(value) || 0));
|
|
599
|
+
return Object.freeze({
|
|
600
|
+
id,
|
|
601
|
+
phaseModel: readMediumPhaseModel(input.phaseModel ?? input.medium?.phaseModel),
|
|
602
|
+
density,
|
|
603
|
+
attenuationColor: Object.freeze(attenuationColor),
|
|
604
|
+
attenuationDistance,
|
|
605
|
+
absorption: Object.freeze(absorption),
|
|
606
|
+
scattering: Object.freeze(scattering)
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
function collectWavefrontMediums(options, meshes, sceneObjects = []) {
|
|
610
|
+
const mediumsById = /* @__PURE__ */ new Map();
|
|
611
|
+
mediumsById.set(
|
|
612
|
+
0,
|
|
613
|
+
Object.freeze({
|
|
614
|
+
id: 0,
|
|
615
|
+
phaseModel: DEFAULT_MEDIUM_PHASE_MODEL,
|
|
616
|
+
density: 0,
|
|
617
|
+
attenuationColor: Object.freeze([1, 1, 1, 1]),
|
|
618
|
+
attenuationDistance: 0,
|
|
619
|
+
absorption: Object.freeze([0, 0, 0]),
|
|
620
|
+
scattering: Object.freeze([0, 0, 0])
|
|
621
|
+
})
|
|
622
|
+
);
|
|
623
|
+
const register = (input, fallbackId = mediumsById.size) => {
|
|
624
|
+
if (!input) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const normalized = normalizeWavefrontMedium(
|
|
628
|
+
typeof input === "object" ? { id: fallbackId, ...input } : { id: fallbackId },
|
|
629
|
+
fallbackId
|
|
630
|
+
);
|
|
631
|
+
const existing = mediumsById.get(normalized.id);
|
|
632
|
+
if (existing && JSON.stringify(existing) !== JSON.stringify(normalized)) {
|
|
633
|
+
throw new Error(`Medium id ${normalized.id} is defined more than once with different values.`);
|
|
634
|
+
}
|
|
635
|
+
mediumsById.set(normalized.id, normalized);
|
|
636
|
+
};
|
|
637
|
+
for (const medium of options.mediums ?? []) {
|
|
638
|
+
register(medium);
|
|
639
|
+
}
|
|
640
|
+
for (const mesh of meshes) {
|
|
641
|
+
register(mesh.medium, mesh.mediumRefId ?? mesh.medium?.id ?? 0);
|
|
642
|
+
}
|
|
643
|
+
for (const mesh of meshes) {
|
|
644
|
+
if ((mesh.mediumRefId ?? 0) > 0 && !mediumsById.has(mesh.mediumRefId)) {
|
|
645
|
+
register({ id: mesh.mediumRefId });
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
for (const object of sceneObjects) {
|
|
649
|
+
register(object.medium, object.mediumRefId ?? object.medium?.id ?? 0);
|
|
650
|
+
}
|
|
651
|
+
for (const object of sceneObjects) {
|
|
652
|
+
if ((object.mediumRefId ?? 0) > 0 && !mediumsById.has(object.mediumRefId)) {
|
|
653
|
+
register({ id: object.mediumRefId });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return Object.freeze(
|
|
657
|
+
Array.from(mediumsById.values()).sort((left, right) => left.id - right.id)
|
|
658
|
+
);
|
|
659
|
+
}
|
|
506
660
|
function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
507
661
|
const bounds = deriveBounds(input);
|
|
508
662
|
const kind = readObjectKind(input.kind ?? input.type ?? (bounds ? "box" : "sphere"));
|
|
@@ -533,12 +687,19 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
533
687
|
input.specularColor ?? input.material?.specularColor,
|
|
534
688
|
[1, 1, 1, 1]
|
|
535
689
|
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
690
|
+
const medium = deriveWavefrontTransportMedium(input, index + 1);
|
|
536
691
|
const resolvedMaterialKind = emission[0] > 0 || emission[1] > 0 || emission[2] > 0 ? MATERIAL_EMISSIVE : materialKindInput === void 0 || materialKindInput === null ? transmission > 1e-3 || opacity < 0.999 ? MATERIAL_TRANSPARENT : materialKind : materialKind;
|
|
537
692
|
return Object.freeze({
|
|
538
693
|
id: readNonNegativeInteger("id", input.id, index + 1),
|
|
539
694
|
kind,
|
|
540
695
|
materialKind: resolvedMaterialKind,
|
|
541
696
|
flags: readNonNegativeInteger("flags", input.flags, 0),
|
|
697
|
+
mediumRefId: readNonNegativeInteger(
|
|
698
|
+
"mediumRefId",
|
|
699
|
+
input.mediumRefId ?? medium?.id ?? input.medium?.id ?? input.mediumId,
|
|
700
|
+
0
|
|
701
|
+
),
|
|
702
|
+
medium,
|
|
542
703
|
center: Object.freeze(center),
|
|
543
704
|
halfExtent: Object.freeze(halfExtent),
|
|
544
705
|
color: Object.freeze(color),
|
|
@@ -562,6 +723,7 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
562
723
|
),
|
|
563
724
|
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
564
725
|
specularColor: Object.freeze(specularColor),
|
|
726
|
+
thickness: normalizeWavefrontThickness(input, "thickness"),
|
|
565
727
|
transmission
|
|
566
728
|
});
|
|
567
729
|
}
|
|
@@ -655,6 +817,7 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
655
817
|
input.specularColor ?? input.material?.specularColor,
|
|
656
818
|
[1, 1, 1, 1]
|
|
657
819
|
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
820
|
+
const medium = deriveWavefrontTransportMedium(input, meshIndex + 1);
|
|
658
821
|
const resolvedMaterialKind = emission[0] > 0 || emission[1] > 0 || emission[2] > 0 ? MATERIAL_EMISSIVE : materialKindInput === void 0 || materialKindInput === null ? transmission > 1e-3 || opacity < 0.999 ? MATERIAL_TRANSPARENT : materialKind : materialKind;
|
|
659
822
|
return Object.freeze({
|
|
660
823
|
id: readNonNegativeInteger("mesh id", input.id, meshIndex + 1),
|
|
@@ -671,9 +834,10 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
671
834
|
),
|
|
672
835
|
mediumRefId: readNonNegativeInteger(
|
|
673
836
|
"mesh mediumRefId",
|
|
674
|
-
input.mediumRefId ?? input.medium?.id ?? input.mediumId,
|
|
837
|
+
input.mediumRefId ?? medium?.id ?? input.medium?.id ?? input.mediumId ?? input.material?.mediumId,
|
|
675
838
|
0
|
|
676
839
|
),
|
|
840
|
+
medium,
|
|
677
841
|
color: Object.freeze(color),
|
|
678
842
|
emission: Object.freeze(emission),
|
|
679
843
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
@@ -695,6 +859,7 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
695
859
|
),
|
|
696
860
|
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
697
861
|
specularColor: Object.freeze(specularColor),
|
|
862
|
+
thickness: normalizeWavefrontThickness(input, "mesh thickness"),
|
|
698
863
|
transmission,
|
|
699
864
|
baseColorTexture: input.baseColorTexture ?? input.material?.baseColorTexture ?? null,
|
|
700
865
|
metallicRoughnessTexture: input.metallicRoughnessTexture ?? input.material?.metallicRoughnessTexture ?? null,
|
|
@@ -706,141 +871,9 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
706
871
|
function clampUnit(value) {
|
|
707
872
|
return clamp(Number(value) || 0, 0, 1);
|
|
708
873
|
}
|
|
709
|
-
function
|
|
710
|
-
const channel = clampUnit(value);
|
|
711
|
-
if (channel <= 0.04045) {
|
|
712
|
-
return channel / 12.92;
|
|
713
|
-
}
|
|
714
|
-
return ((channel + 0.055) / 1.055) ** 2.4;
|
|
715
|
-
}
|
|
716
|
-
function sampleTextureRgba(texture, uv = [0, 0], colorSpace = "linear") {
|
|
717
|
-
if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || !texture.data || texture.width <= 0 || texture.height <= 0) {
|
|
718
|
-
return [1, 1, 1, 1];
|
|
719
|
-
}
|
|
720
|
-
const u = (uv[0] % 1 + 1) % 1;
|
|
721
|
-
const v = (uv[1] % 1 + 1) % 1;
|
|
722
|
-
const x = Math.min(texture.width - 1, Math.max(0, Math.round(u * (texture.width - 1))));
|
|
723
|
-
const y = Math.min(texture.height - 1, Math.max(0, Math.round((1 - v) * (texture.height - 1))));
|
|
724
|
-
const offset = (y * texture.width + x) * 4;
|
|
725
|
-
const data = texture.data;
|
|
726
|
-
const scale2 = resolveTextureSampleScale(data);
|
|
727
|
-
const defaultChannel = scale2 === 1 ? 1 : Math.round(1 / scale2);
|
|
728
|
-
const color = [
|
|
729
|
-
(data[offset] ?? defaultChannel) * scale2,
|
|
730
|
-
(data[offset + 1] ?? defaultChannel) * scale2,
|
|
731
|
-
(data[offset + 2] ?? defaultChannel) * scale2,
|
|
732
|
-
(data[offset + 3] ?? defaultChannel) * scale2
|
|
733
|
-
];
|
|
734
|
-
if (colorSpace === "srgb") {
|
|
735
|
-
return [srgbToLinear(color[0]), srgbToLinear(color[1]), srgbToLinear(color[2]), color[3]];
|
|
736
|
-
}
|
|
737
|
-
return color;
|
|
738
|
-
}
|
|
739
|
-
function resolveTextureSampleScale(data) {
|
|
740
|
-
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
|
|
741
|
-
return 1 / 255;
|
|
742
|
-
}
|
|
743
|
-
if (data instanceof Uint16Array) {
|
|
744
|
-
return 1 / 65535;
|
|
745
|
-
}
|
|
746
|
-
if (Array.isArray(data) && data.some((value) => Number(value) > 1)) {
|
|
747
|
-
return 1 / 255;
|
|
748
|
-
}
|
|
749
|
-
return 1;
|
|
750
|
-
}
|
|
751
|
-
function normalizeVectorOrFallback(vector, fallback) {
|
|
752
|
-
return normalize(Array.isArray(vector) ? vector : fallback, fallback);
|
|
753
|
-
}
|
|
754
|
-
function buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, fallbackNormal) {
|
|
755
|
-
const edge1 = subtract(v1, v0);
|
|
756
|
-
const edge2 = subtract(v2, v0);
|
|
757
|
-
const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
|
|
758
|
-
const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
|
|
759
|
-
const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
|
|
760
|
-
if (Math.abs(determinant) < 1e-6) {
|
|
761
|
-
const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
762
|
-
const tangent2 = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
|
|
763
|
-
const bitangent2 = normalize(cross(fallbackNormal, tangent2), [0, 0, 1]);
|
|
764
|
-
return { tangent: tangent2, bitangent: bitangent2 };
|
|
765
|
-
}
|
|
766
|
-
const inverse = 1 / determinant;
|
|
767
|
-
const tangent = normalize(
|
|
768
|
-
[
|
|
769
|
-
inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
|
|
770
|
-
inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
|
|
771
|
-
inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1])
|
|
772
|
-
],
|
|
773
|
-
[1, 0, 0]
|
|
774
|
-
);
|
|
775
|
-
const bitangent = normalize(
|
|
776
|
-
[
|
|
777
|
-
inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
|
|
778
|
-
inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
|
|
779
|
-
inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0])
|
|
780
|
-
],
|
|
781
|
-
[0, 0, 1]
|
|
782
|
-
);
|
|
783
|
-
return { tangent, bitangent };
|
|
784
|
-
}
|
|
785
|
-
function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
|
|
786
|
-
if (!normalTexture) {
|
|
787
|
-
return normalizeVectorOrFallback(normal, [0, 1, 0]);
|
|
788
|
-
}
|
|
789
|
-
const sample = sampleTextureRgba(normalTexture, uv, "linear");
|
|
790
|
-
const strength = clampUnit(normalTexture.scale ?? 1);
|
|
791
|
-
const tangentNormal = normalize(
|
|
792
|
-
[
|
|
793
|
-
(sample[0] * 2 - 1) * strength,
|
|
794
|
-
(sample[1] * 2 - 1) * strength,
|
|
795
|
-
1 + (sample[2] * 2 - 1 - 1) * strength
|
|
796
|
-
],
|
|
797
|
-
[0, 0, 1]
|
|
798
|
-
);
|
|
799
|
-
return normalize(
|
|
800
|
-
[
|
|
801
|
-
tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
|
|
802
|
-
tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
|
|
803
|
-
tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2]
|
|
804
|
-
],
|
|
805
|
-
normal
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
function sampleBaseColor(mesh, uv) {
|
|
809
|
-
const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
|
|
810
|
-
return [
|
|
811
|
-
clampUnit(mesh.color[0] * sample[0]),
|
|
812
|
-
clampUnit(mesh.color[1] * sample[1]),
|
|
813
|
-
clampUnit(mesh.color[2] * sample[2]),
|
|
814
|
-
clampUnit((mesh.color[3] ?? 1) * sample[3])
|
|
815
|
-
];
|
|
816
|
-
}
|
|
817
|
-
function sampleSurfaceMaterial(mesh, uv) {
|
|
818
|
-
const textureSample = mesh.metallicRoughnessTexture ? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear") : [1, 1, 1, 1];
|
|
819
|
-
return {
|
|
820
|
-
roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
|
|
821
|
-
metallic: clamp(mesh.metallic * textureSample[2], 0, 1)
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
function averageColors(colors) {
|
|
825
|
-
const count = Math.max(colors.length, 1);
|
|
826
|
-
return colors.reduce(
|
|
827
|
-
(accumulator, color) => [
|
|
828
|
-
accumulator[0] + color[0] / count,
|
|
829
|
-
accumulator[1] + color[1] / count,
|
|
830
|
-
accumulator[2] + color[2] / count,
|
|
831
|
-
accumulator[3] + color[3] / count
|
|
832
|
-
],
|
|
833
|
-
[0, 0, 0, 0]
|
|
834
|
-
);
|
|
835
|
-
}
|
|
836
|
-
function averageNumbers(values, fallback = 0) {
|
|
837
|
-
if (!Array.isArray(values) || values.length === 0) {
|
|
838
|
-
return fallback;
|
|
839
|
-
}
|
|
840
|
-
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
841
|
-
}
|
|
842
|
-
function createMeshTriangleRecords(meshes) {
|
|
874
|
+
function createMeshTriangleRecords(meshes, gpuMaterialSource = null) {
|
|
843
875
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
876
|
+
const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
|
|
844
877
|
let nextTriangleId = 0;
|
|
845
878
|
return source.flatMap((meshInput, meshIndex) => {
|
|
846
879
|
const mesh = normalizeWavefrontMesh(meshInput, meshIndex);
|
|
@@ -859,16 +892,6 @@ function createMeshTriangleRecords(meshes) {
|
|
|
859
892
|
const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
|
|
860
893
|
const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
|
|
861
894
|
const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
|
|
862
|
-
const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
|
|
863
|
-
const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
|
|
864
|
-
const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
|
|
865
|
-
const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
|
|
866
|
-
const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
|
|
867
|
-
const sampledMaterials = [
|
|
868
|
-
sampleSurfaceMaterial(mesh, uv0),
|
|
869
|
-
sampleSurfaceMaterial(mesh, uv1),
|
|
870
|
-
sampleSurfaceMaterial(mesh, uv2)
|
|
871
|
-
];
|
|
872
895
|
const bounds = triangleBounds(v0, v1, v2);
|
|
873
896
|
triangles.push(
|
|
874
897
|
Object.freeze({
|
|
@@ -882,17 +905,17 @@ function createMeshTriangleRecords(meshes) {
|
|
|
882
905
|
v0: Object.freeze(v0),
|
|
883
906
|
v1: Object.freeze(v1),
|
|
884
907
|
v2: Object.freeze(v2),
|
|
885
|
-
n0: Object.freeze(
|
|
886
|
-
n1: Object.freeze(
|
|
887
|
-
n2: Object.freeze(
|
|
908
|
+
n0: Object.freeze(n0),
|
|
909
|
+
n1: Object.freeze(n1),
|
|
910
|
+
n2: Object.freeze(n2),
|
|
888
911
|
uv0: Object.freeze(uv0),
|
|
889
912
|
uv1: Object.freeze(uv1),
|
|
890
913
|
uv2: Object.freeze(uv2),
|
|
891
|
-
color:
|
|
914
|
+
color: mesh.color,
|
|
892
915
|
emission: mesh.emission,
|
|
893
916
|
material: Object.freeze([
|
|
894
|
-
|
|
895
|
-
|
|
917
|
+
mesh.roughness,
|
|
918
|
+
mesh.metallic,
|
|
896
919
|
mesh.opacity,
|
|
897
920
|
mesh.ior
|
|
898
921
|
]),
|
|
@@ -906,7 +929,7 @@ function createMeshTriangleRecords(meshes) {
|
|
|
906
929
|
mesh.clearcoatRoughness,
|
|
907
930
|
mesh.specular,
|
|
908
931
|
mesh.transmission,
|
|
909
|
-
|
|
932
|
+
mesh.thickness
|
|
910
933
|
]),
|
|
911
934
|
specularColor: Object.freeze([
|
|
912
935
|
mesh.specularColor[0] ?? 1,
|
|
@@ -914,6 +937,27 @@ function createMeshTriangleRecords(meshes) {
|
|
|
914
937
|
mesh.specularColor[2] ?? 1,
|
|
915
938
|
1
|
|
916
939
|
]),
|
|
940
|
+
baseColorAtlas: Object.freeze(
|
|
941
|
+
resolvedMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
|
|
942
|
+
),
|
|
943
|
+
metallicRoughnessAtlas: Object.freeze(
|
|
944
|
+
resolvedMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
945
|
+
),
|
|
946
|
+
normalAtlas: Object.freeze(
|
|
947
|
+
resolvedMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
|
|
948
|
+
),
|
|
949
|
+
occlusionAtlas: Object.freeze(
|
|
950
|
+
resolvedMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
|
|
951
|
+
),
|
|
952
|
+
emissiveAtlas: Object.freeze(
|
|
953
|
+
resolvedMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
|
|
954
|
+
),
|
|
955
|
+
textureSettings: Object.freeze([
|
|
956
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
957
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
958
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
959
|
+
0
|
|
960
|
+
]),
|
|
917
961
|
bounds: Object.freeze({
|
|
918
962
|
min: Object.freeze(bounds.min),
|
|
919
963
|
max: Object.freeze(bounds.max)
|
|
@@ -988,9 +1032,10 @@ function buildBvh(triangles, maxLeafTriangles = 4) {
|
|
|
988
1032
|
triangles: Object.freeze(orderedTriangles)
|
|
989
1033
|
});
|
|
990
1034
|
}
|
|
991
|
-
function createWavefrontMeshAcceleration(meshes = []) {
|
|
1035
|
+
function createWavefrontMeshAcceleration(meshes = [], gpuMaterialSource = null) {
|
|
992
1036
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
993
|
-
const
|
|
1037
|
+
const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
|
|
1038
|
+
const triangles = createMeshTriangleRecords(source, resolvedMaterialSource);
|
|
994
1039
|
return buildBvh(triangles);
|
|
995
1040
|
}
|
|
996
1041
|
function estimateMeshSourceShape(meshes) {
|
|
@@ -1200,7 +1245,7 @@ function createWavefrontGpuMaterialSource(meshes = []) {
|
|
|
1200
1245
|
mesh.clearcoatRoughness,
|
|
1201
1246
|
mesh.specular,
|
|
1202
1247
|
mesh.transmission,
|
|
1203
|
-
|
|
1248
|
+
mesh.thickness
|
|
1204
1249
|
]);
|
|
1205
1250
|
writeVec4(floatView, byteOffset + 80, [
|
|
1206
1251
|
mesh.specularColor[0] ?? 1,
|
|
@@ -1284,12 +1329,12 @@ function createWavefrontBvhBuildLevels(triangleCountInput) {
|
|
|
1284
1329
|
return Object.freeze(levels);
|
|
1285
1330
|
}
|
|
1286
1331
|
function resolveAccelerationBuildMode(options = {}) {
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1332
|
+
const requestedMode = options.accelerationBuildMode ?? (options.displayQuality === true ? "cpu-upload" : "cpu-debug");
|
|
1333
|
+
const mode = requestedMode === "cpu-debug" ? "cpu-upload" : requestedMode;
|
|
1334
|
+
if (mode !== "gpu" && mode !== "cpu-upload") {
|
|
1335
|
+
throw new Error(
|
|
1336
|
+
'accelerationBuildMode must be either "gpu", "cpu-upload", or the legacy alias "cpu-debug".'
|
|
1337
|
+
);
|
|
1293
1338
|
}
|
|
1294
1339
|
return mode;
|
|
1295
1340
|
}
|
|
@@ -1368,7 +1413,7 @@ function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput = null
|
|
|
1368
1413
|
mesh.clearcoatRoughness,
|
|
1369
1414
|
mesh.specular,
|
|
1370
1415
|
mesh.transmission,
|
|
1371
|
-
|
|
1416
|
+
mesh.thickness
|
|
1372
1417
|
]);
|
|
1373
1418
|
writeVec4(meshFloats, floatOffset * 4 + 128, [
|
|
1374
1419
|
mesh.specularColor[0] ?? 1,
|
|
@@ -1469,12 +1514,16 @@ function normalizeSceneObjects(sceneObjects, useDefaultScene = true) {
|
|
|
1469
1514
|
const source = Array.isArray(sceneObjects) && sceneObjects.length > 0 ? sceneObjects : useDefaultScene ? createDefaultWavefrontSceneObjects() : [];
|
|
1470
1515
|
return source.map((object, index) => normalizeWavefrontSceneObject(object, index));
|
|
1471
1516
|
}
|
|
1517
|
+
function normalizeWavefrontMeshes(meshes) {
|
|
1518
|
+
const source = Array.isArray(meshes) ? meshes : [];
|
|
1519
|
+
return source.map((mesh, index) => normalizeWavefrontMesh(mesh, index));
|
|
1520
|
+
}
|
|
1472
1521
|
function normalizeMeshes(options = {}) {
|
|
1473
1522
|
if (Array.isArray(options.meshes)) {
|
|
1474
|
-
return options.meshes;
|
|
1523
|
+
return normalizeWavefrontMeshes(options.meshes);
|
|
1475
1524
|
}
|
|
1476
1525
|
if (options.mesh) {
|
|
1477
|
-
return [options.mesh];
|
|
1526
|
+
return normalizeWavefrontMeshes([options.mesh]);
|
|
1478
1527
|
}
|
|
1479
1528
|
return [];
|
|
1480
1529
|
}
|
|
@@ -1771,7 +1820,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1771
1820
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
1772
1821
|
const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
|
|
1773
1822
|
const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
|
|
1774
|
-
const meshAcceleration = accelerationBuildMode === "cpu-
|
|
1823
|
+
const meshAcceleration = accelerationBuildMode === "cpu-upload" ? createWavefrontMeshAcceleration(meshes, gpuMaterialSource) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
1775
1824
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
1776
1825
|
meshes,
|
|
1777
1826
|
options.emissiveTriangleCapacity
|
|
@@ -1781,6 +1830,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1781
1830
|
const sceneObjects = Object.freeze(
|
|
1782
1831
|
normalizeSceneObjects(options.sceneObjects, meshes.length === 0)
|
|
1783
1832
|
);
|
|
1833
|
+
const mediums = collectWavefrontMediums(options, meshes, sceneObjects);
|
|
1784
1834
|
const sceneObjectCapacity = Math.max(
|
|
1785
1835
|
sceneObjects.length,
|
|
1786
1836
|
readPositiveInteger("sceneObjectCapacity", options.sceneObjectCapacity, DEFAULT_SCENE_OBJECT_CAPACITY)
|
|
@@ -1839,6 +1889,8 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1839
1889
|
sceneObjects,
|
|
1840
1890
|
sceneObjectCount: sceneObjects.length,
|
|
1841
1891
|
sceneObjectCapacity,
|
|
1892
|
+
mediums,
|
|
1893
|
+
mediumCount: mediums.length,
|
|
1842
1894
|
accelerationBuildMode,
|
|
1843
1895
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
1844
1896
|
gpuMeshSource,
|
|
@@ -1940,29 +1992,30 @@ function packWavefrontSceneObjects(sceneObjects, capacity = sceneObjects.length)
|
|
|
1940
1992
|
uintView[u32 + 1] = object.id;
|
|
1941
1993
|
uintView[u32 + 2] = object.materialKind;
|
|
1942
1994
|
uintView[u32 + 3] = object.flags;
|
|
1943
|
-
|
|
1944
|
-
writeVec4(floatView, byteOffset + 32, [...object.
|
|
1945
|
-
writeVec4(floatView, byteOffset + 48, object.
|
|
1946
|
-
writeVec4(floatView, byteOffset + 64, object.
|
|
1947
|
-
writeVec4(floatView, byteOffset + 80,
|
|
1995
|
+
uintView[u32 + 4] = object.mediumRefId;
|
|
1996
|
+
writeVec4(floatView, byteOffset + 32, [...object.center, 0]);
|
|
1997
|
+
writeVec4(floatView, byteOffset + 48, [...object.halfExtent, 0]);
|
|
1998
|
+
writeVec4(floatView, byteOffset + 64, object.color);
|
|
1999
|
+
writeVec4(floatView, byteOffset + 80, object.emission);
|
|
2000
|
+
writeVec4(floatView, byteOffset + 96, [
|
|
1948
2001
|
object.roughness,
|
|
1949
2002
|
object.metallic,
|
|
1950
2003
|
object.opacity,
|
|
1951
2004
|
object.ior
|
|
1952
2005
|
]);
|
|
1953
|
-
writeVec4(floatView, byteOffset +
|
|
2006
|
+
writeVec4(floatView, byteOffset + 112, [
|
|
1954
2007
|
object.sheenColor[0] ?? 0,
|
|
1955
2008
|
object.sheenColor[1] ?? 0,
|
|
1956
2009
|
object.sheenColor[2] ?? 0,
|
|
1957
2010
|
object.clearcoat
|
|
1958
2011
|
]);
|
|
1959
|
-
writeVec4(floatView, byteOffset +
|
|
2012
|
+
writeVec4(floatView, byteOffset + 128, [
|
|
1960
2013
|
object.clearcoatRoughness,
|
|
1961
2014
|
object.specular,
|
|
1962
2015
|
object.transmission,
|
|
1963
|
-
|
|
2016
|
+
object.thickness
|
|
1964
2017
|
]);
|
|
1965
|
-
writeVec4(floatView, byteOffset +
|
|
2018
|
+
writeVec4(floatView, byteOffset + 144, [
|
|
1966
2019
|
object.specularColor[0] ?? 1,
|
|
1967
2020
|
object.specularColor[1] ?? 1,
|
|
1968
2021
|
object.specularColor[2] ?? 1,
|
|
@@ -2498,7 +2551,7 @@ function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
|
2498
2551
|
}
|
|
2499
2552
|
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
2500
2553
|
}
|
|
2501
|
-
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount =
|
|
2554
|
+
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount = DEFAULT_BRDF_LUT_SAMPLE_COUNT) {
|
|
2502
2555
|
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
2503
2556
|
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
2504
2557
|
if (cached) {
|
|
@@ -2870,6 +2923,80 @@ function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE)
|
|
|
2870
2923
|
height: upload.height
|
|
2871
2924
|
});
|
|
2872
2925
|
}
|
|
2926
|
+
function createMediumTextureResource(device, constants, mediums) {
|
|
2927
|
+
const normalized = Array.isArray(mediums) && mediums.length > 0 ? mediums : [{ id: 0 }];
|
|
2928
|
+
const width = Math.max(
|
|
2929
|
+
1,
|
|
2930
|
+
normalized.reduce((maximum, medium) => Math.max(maximum, medium.id ?? 0), 0) + 1
|
|
2931
|
+
);
|
|
2932
|
+
const level = {
|
|
2933
|
+
width,
|
|
2934
|
+
height: MEDIUM_TABLE_ROWS,
|
|
2935
|
+
data: new Float32Array(width * MEDIUM_TABLE_ROWS * 4)
|
|
2936
|
+
};
|
|
2937
|
+
for (const medium of normalized) {
|
|
2938
|
+
const mediumId = Math.max(0, Math.trunc(Number(medium.id) || 0));
|
|
2939
|
+
const absorptionOffset = mediumId * 4;
|
|
2940
|
+
level.data[absorptionOffset] = Math.max(0, medium.absorption?.[0] ?? 0);
|
|
2941
|
+
level.data[absorptionOffset + 1] = Math.max(0, medium.absorption?.[1] ?? 0);
|
|
2942
|
+
level.data[absorptionOffset + 2] = Math.max(0, medium.absorption?.[2] ?? 0);
|
|
2943
|
+
level.data[absorptionOffset + 3] = Math.max(0, medium.phaseModel ?? 0);
|
|
2944
|
+
const scatteringOffset = (width + mediumId) * 4;
|
|
2945
|
+
level.data[scatteringOffset] = Math.max(0, medium.scattering?.[0] ?? 0);
|
|
2946
|
+
level.data[scatteringOffset + 1] = Math.max(0, medium.scattering?.[1] ?? 0);
|
|
2947
|
+
level.data[scatteringOffset + 2] = Math.max(0, medium.scattering?.[2] ?? 0);
|
|
2948
|
+
level.data[scatteringOffset + 3] = Math.max(0, medium.density ?? 0);
|
|
2949
|
+
}
|
|
2950
|
+
const upload = createFloat16RgbaUploadFromLevels([level])[0];
|
|
2951
|
+
const texture = device.createTexture({
|
|
2952
|
+
label: "plasius.wavefront.mediumTable",
|
|
2953
|
+
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
2954
|
+
format: "rgba16float",
|
|
2955
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
2956
|
+
});
|
|
2957
|
+
device.queue.writeTexture(
|
|
2958
|
+
{ texture },
|
|
2959
|
+
upload.bytes,
|
|
2960
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
2961
|
+
{ width, height: MEDIUM_TABLE_ROWS, depthOrArrayLayers: 1 }
|
|
2962
|
+
);
|
|
2963
|
+
return Object.freeze({
|
|
2964
|
+
texture,
|
|
2965
|
+
view: texture.createView(),
|
|
2966
|
+
ownsTexture: true,
|
|
2967
|
+
count: normalized.length,
|
|
2968
|
+
width
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
function mediumTablesEqual(left, right) {
|
|
2972
|
+
const leftMediums = Array.isArray(left) ? left : [];
|
|
2973
|
+
const rightMediums = Array.isArray(right) ? right : [];
|
|
2974
|
+
if (leftMediums.length !== rightMediums.length) {
|
|
2975
|
+
return false;
|
|
2976
|
+
}
|
|
2977
|
+
for (let index = 0; index < leftMediums.length; index += 1) {
|
|
2978
|
+
const leftMedium = leftMediums[index];
|
|
2979
|
+
const rightMedium = rightMediums[index];
|
|
2980
|
+
if ((leftMedium?.id ?? 0) !== (rightMedium?.id ?? 0)) {
|
|
2981
|
+
return false;
|
|
2982
|
+
}
|
|
2983
|
+
if ((leftMedium?.phaseModel ?? 0) !== (rightMedium?.phaseModel ?? 0)) {
|
|
2984
|
+
return false;
|
|
2985
|
+
}
|
|
2986
|
+
if ((leftMedium?.density ?? 0) !== (rightMedium?.density ?? 0)) {
|
|
2987
|
+
return false;
|
|
2988
|
+
}
|
|
2989
|
+
for (let component = 0; component < 3; component += 1) {
|
|
2990
|
+
if ((leftMedium?.absorption?.[component] ?? 0) !== (rightMedium?.absorption?.[component] ?? 0)) {
|
|
2991
|
+
return false;
|
|
2992
|
+
}
|
|
2993
|
+
if ((leftMedium?.scattering?.[component] ?? 0) !== (rightMedium?.scattering?.[component] ?? 0)) {
|
|
2994
|
+
return false;
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
return true;
|
|
2999
|
+
}
|
|
2873
3000
|
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
2874
3001
|
const upload = createRgba8TextureUpload(atlas);
|
|
2875
3002
|
const texture = device.createTexture({
|
|
@@ -3008,6 +3135,10 @@ struct SceneObject {
|
|
|
3008
3135
|
objectId: u32,
|
|
3009
3136
|
materialKind: u32,
|
|
3010
3137
|
flags: u32,
|
|
3138
|
+
mediumRefId: u32,
|
|
3139
|
+
pad0: u32,
|
|
3140
|
+
pad1: u32,
|
|
3141
|
+
pad2: u32,
|
|
3011
3142
|
center: vec4<f32>,
|
|
3012
3143
|
halfExtent: vec4<f32>,
|
|
3013
3144
|
color: vec4<f32>,
|
|
@@ -3068,9 +3199,9 @@ struct BvhLeafRef {
|
|
|
3068
3199
|
struct ScatterResult {
|
|
3069
3200
|
direction: vec4<f32>,
|
|
3070
3201
|
pdf: f32,
|
|
3202
|
+
mediumRefId: u32,
|
|
3071
3203
|
flags: u32,
|
|
3072
3204
|
pad0: u32,
|
|
3073
|
-
pad1: u32,
|
|
3074
3205
|
};
|
|
3075
3206
|
|
|
3076
3207
|
struct MeshVertex {
|
|
@@ -3216,6 +3347,7 @@ struct EnvironmentPortal {
|
|
|
3216
3347
|
@group(0) @binding(29) var brdfLutTexture: texture_2d<f32>;
|
|
3217
3348
|
@group(0) @binding(30) var brdfLutSampler: sampler;
|
|
3218
3349
|
@group(0) @binding(31) var environmentSamplingTexture: texture_2d<f32>;
|
|
3350
|
+
@group(0) @binding(32) var mediumTableTexture: texture_2d<f32>;
|
|
3219
3351
|
|
|
3220
3352
|
fn hash_u32(value: u32) -> u32 {
|
|
3221
3353
|
var x = value;
|
|
@@ -3881,6 +4013,60 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
3881
4013
|
return environment_radiance(origin, direction);
|
|
3882
4014
|
}
|
|
3883
4015
|
|
|
4016
|
+
fn medium_dimensions() -> vec2<u32> {
|
|
4017
|
+
return textureDimensions(mediumTableTexture);
|
|
4018
|
+
}
|
|
4019
|
+
|
|
4020
|
+
fn medium_valid(mediumRefId: u32) -> bool {
|
|
4021
|
+
let dimensions = medium_dimensions();
|
|
4022
|
+
return mediumRefId > 0u && mediumRefId < dimensions.x;
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
fn medium_absorption(mediumRefId: u32) -> vec3<f32> {
|
|
4026
|
+
if (!medium_valid(mediumRefId)) {
|
|
4027
|
+
return vec3<f32>(0.0);
|
|
4028
|
+
}
|
|
4029
|
+
return max(
|
|
4030
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 0), 0).xyz,
|
|
4031
|
+
vec3<f32>(0.0)
|
|
4032
|
+
);
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
fn medium_scattering(mediumRefId: u32) -> vec3<f32> {
|
|
4036
|
+
if (!medium_valid(mediumRefId)) {
|
|
4037
|
+
return vec3<f32>(0.0);
|
|
4038
|
+
}
|
|
4039
|
+
return max(
|
|
4040
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 1), 0).xyz,
|
|
4041
|
+
vec3<f32>(0.0)
|
|
4042
|
+
);
|
|
4043
|
+
}
|
|
4044
|
+
|
|
4045
|
+
fn medium_transmittance(mediumRefId: u32, distance: f32) -> vec3<f32> {
|
|
4046
|
+
if (!medium_valid(mediumRefId) || distance <= 0.000001) {
|
|
4047
|
+
return vec3<f32>(1.0);
|
|
4048
|
+
}
|
|
4049
|
+
let extinction = medium_absorption(mediumRefId) + medium_scattering(mediumRefId);
|
|
4050
|
+
return vec3<f32>(
|
|
4051
|
+
exp(-extinction.x * distance),
|
|
4052
|
+
exp(-extinction.y * distance),
|
|
4053
|
+
exp(-extinction.z * distance)
|
|
4054
|
+
);
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
fn transmitted_medium_ref_id(ray: RayRecord, hit: HitRecord) -> u32 {
|
|
4058
|
+
if (hit.mediumRefId == 0u) {
|
|
4059
|
+
return ray.mediumRefId;
|
|
4060
|
+
}
|
|
4061
|
+
if (hit.frontFace == 1u) {
|
|
4062
|
+
return hit.mediumRefId;
|
|
4063
|
+
}
|
|
4064
|
+
if (ray.mediumRefId == hit.mediumRefId) {
|
|
4065
|
+
return 0u;
|
|
4066
|
+
}
|
|
4067
|
+
return ray.mediumRefId;
|
|
4068
|
+
}
|
|
4069
|
+
|
|
3884
4070
|
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
3885
4071
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
3886
4072
|
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
@@ -3979,11 +4165,15 @@ fn terminal_surface_environment_source(ray: RayRecord, hit: HitRecord) -> vec3<f
|
|
|
3979
4165
|
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
3980
4166
|
}
|
|
3981
4167
|
|
|
3982
|
-
fn terminal_surface_environment_contribution(
|
|
4168
|
+
fn terminal_surface_environment_contribution(
|
|
4169
|
+
ray: RayRecord,
|
|
4170
|
+
throughput: vec3<f32>,
|
|
4171
|
+
hit: HitRecord
|
|
4172
|
+
) -> vec3<f32> {
|
|
3983
4173
|
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
3984
4174
|
let occlusion = mix(0.75, 1.0, clamp(hit.occlusion, 0.0, 1.0));
|
|
3985
4175
|
return clamp_sample_radiance(
|
|
3986
|
-
|
|
4176
|
+
throughput *
|
|
3987
4177
|
surfaceColor *
|
|
3988
4178
|
terminal_surface_environment_source(ray, hit) *
|
|
3989
4179
|
occlusion
|
|
@@ -4457,7 +4647,7 @@ fn intersect_sphere(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
4457
4647
|
0xffffffffu,
|
|
4458
4648
|
object.objectId,
|
|
4459
4649
|
object.objectId,
|
|
4460
|
-
|
|
4650
|
+
object.mediumRefId
|
|
4461
4651
|
);
|
|
4462
4652
|
}
|
|
4463
4653
|
|
|
@@ -4509,7 +4699,7 @@ fn intersect_box(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
4509
4699
|
0xffffffffu,
|
|
4510
4700
|
object.objectId,
|
|
4511
4701
|
object.objectId,
|
|
4512
|
-
|
|
4702
|
+
object.mediumRefId
|
|
4513
4703
|
);
|
|
4514
4704
|
}
|
|
4515
4705
|
|
|
@@ -4760,6 +4950,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
4760
4950
|
let ray = activeQueue[index];
|
|
4761
4951
|
var nearest = 1000000.0;
|
|
4762
4952
|
var hitObject = SceneObject(
|
|
4953
|
+
0u,
|
|
4954
|
+
0u,
|
|
4955
|
+
0u,
|
|
4956
|
+
0u,
|
|
4763
4957
|
0u,
|
|
4764
4958
|
0u,
|
|
4765
4959
|
0u,
|
|
@@ -4963,9 +5157,9 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
4963
5157
|
return ScatterResult(
|
|
4964
5158
|
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
4965
5159
|
1.0,
|
|
5160
|
+
ray.mediumRefId,
|
|
4966
5161
|
RAY_FLAG_DELTA_SAMPLE,
|
|
4967
5162
|
0u,
|
|
4968
|
-
0u
|
|
4969
5163
|
);
|
|
4970
5164
|
}
|
|
4971
5165
|
|
|
@@ -4985,17 +5179,17 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
4985
5179
|
return ScatterResult(
|
|
4986
5180
|
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
4987
5181
|
1.0,
|
|
5182
|
+
ray.mediumRefId,
|
|
4988
5183
|
RAY_FLAG_DELTA_SAMPLE,
|
|
4989
5184
|
0u,
|
|
4990
|
-
0u
|
|
4991
5185
|
);
|
|
4992
5186
|
}
|
|
4993
5187
|
return ScatterResult(
|
|
4994
5188
|
vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
|
|
4995
5189
|
1.0,
|
|
5190
|
+
transmitted_medium_ref_id(ray, hit),
|
|
4996
5191
|
RAY_FLAG_DELTA_SAMPLE,
|
|
4997
5192
|
0u,
|
|
4998
|
-
0u
|
|
4999
5193
|
);
|
|
5000
5194
|
}
|
|
5001
5195
|
|
|
@@ -5010,9 +5204,9 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5010
5204
|
return ScatterResult(
|
|
5011
5205
|
vec4<f32>(guidedDirection, 0.0),
|
|
5012
5206
|
guidedPdf,
|
|
5207
|
+
ray.mediumRefId,
|
|
5013
5208
|
RAY_FLAG_GUIDED_EMISSIVE,
|
|
5014
5209
|
0u,
|
|
5015
|
-
0u
|
|
5016
5210
|
);
|
|
5017
5211
|
}
|
|
5018
5212
|
}
|
|
@@ -5020,7 +5214,7 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5020
5214
|
let guidedDirection = sample_environment_portal_direction(hit, seed + 131u, normal);
|
|
5021
5215
|
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5022
5216
|
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5023
|
-
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf,
|
|
5217
|
+
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf, ray.mediumRefId, 0u, 0u);
|
|
5024
5218
|
}
|
|
5025
5219
|
}
|
|
5026
5220
|
|
|
@@ -5054,7 +5248,7 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5054
5248
|
);
|
|
5055
5249
|
}
|
|
5056
5250
|
let pdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection), 0.000001);
|
|
5057
|
-
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf,
|
|
5251
|
+
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf, ray.mediumRefId, 0u, 0u);
|
|
5058
5252
|
}
|
|
5059
5253
|
|
|
5060
5254
|
@compute @workgroup_size(64)
|
|
@@ -5067,15 +5261,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5067
5261
|
|
|
5068
5262
|
let ray = activeQueue[index];
|
|
5069
5263
|
let hit = hits[index];
|
|
5264
|
+
let segmentTransmittance = medium_transmittance(ray.mediumRefId, hit.distance);
|
|
5265
|
+
let arrivingThroughput = ray.throughput.xyz * segmentTransmittance;
|
|
5070
5266
|
var contribution = vec3<f32>(0.0);
|
|
5071
5267
|
|
|
5072
5268
|
if (hit.hitType == 1u) {
|
|
5073
5269
|
let guidedLightWeight = select(1.0, 0.24, (ray.flags & RAY_FLAG_GUIDED_EMISSIVE) != 0u);
|
|
5074
5270
|
let sourceRadiance = max(hit.emission.xyz, hit.color.xyz) * guidedLightWeight;
|
|
5075
5271
|
if (deferred_path_resolve_enabled()) {
|
|
5076
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5272
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
5077
5273
|
} else {
|
|
5078
|
-
contribution = clamp_sample_radiance(
|
|
5274
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
5079
5275
|
accumulation[ray.rayId] =
|
|
5080
5276
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
5081
5277
|
}
|
|
@@ -5092,9 +5288,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5092
5288
|
sourceRadiance = sourceRadiance * misWeight;
|
|
5093
5289
|
}
|
|
5094
5290
|
if (deferred_path_resolve_enabled()) {
|
|
5095
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5291
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
5096
5292
|
} else {
|
|
5097
|
-
contribution = clamp_sample_radiance(
|
|
5293
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
5098
5294
|
accumulation[ray.rayId] =
|
|
5099
5295
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
5100
5296
|
}
|
|
@@ -5102,7 +5298,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5102
5298
|
return;
|
|
5103
5299
|
}
|
|
5104
5300
|
|
|
5105
|
-
let response = stabilize_surface_path_response(
|
|
5301
|
+
let response = stabilize_surface_path_response(
|
|
5302
|
+
ray,
|
|
5303
|
+
hit,
|
|
5304
|
+
surface_path_response(hit) * segmentTransmittance
|
|
5305
|
+
);
|
|
5106
5306
|
record_deferred_path_response(ray, response);
|
|
5107
5307
|
|
|
5108
5308
|
let shouldEstimateDirectEnvironment =
|
|
@@ -5110,7 +5310,22 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5110
5310
|
hit.material.z >= 0.95 &&
|
|
5111
5311
|
ray.bounce < 2u;
|
|
5112
5312
|
if (shouldEstimateDirectEnvironment) {
|
|
5113
|
-
let directEnvironment = surface_direct_environment_contribution(
|
|
5313
|
+
let directEnvironment = surface_direct_environment_contribution(
|
|
5314
|
+
RayRecord(
|
|
5315
|
+
ray.rayId,
|
|
5316
|
+
ray.parentRayId,
|
|
5317
|
+
ray.sourcePixelId,
|
|
5318
|
+
ray.sampleId,
|
|
5319
|
+
ray.bounce,
|
|
5320
|
+
ray.mediumRefId,
|
|
5321
|
+
ray.flags,
|
|
5322
|
+
0u,
|
|
5323
|
+
ray.origin,
|
|
5324
|
+
ray.direction,
|
|
5325
|
+
vec4<f32>(arrivingThroughput, ray.throughput.w)
|
|
5326
|
+
),
|
|
5327
|
+
hit
|
|
5328
|
+
);
|
|
5114
5329
|
accumulation[ray.rayId] =
|
|
5115
5330
|
accumulation[ray.rayId] + vec4<f32>(directEnvironment * sample_weight(), 0.0);
|
|
5116
5331
|
}
|
|
@@ -5119,7 +5334,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5119
5334
|
if (deferred_path_resolve_enabled()) {
|
|
5120
5335
|
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
5121
5336
|
} else {
|
|
5122
|
-
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5337
|
+
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5338
|
+
ray,
|
|
5339
|
+
arrivingThroughput,
|
|
5340
|
+
hit
|
|
5341
|
+
);
|
|
5123
5342
|
accumulation[ray.rayId] =
|
|
5124
5343
|
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
5125
5344
|
}
|
|
@@ -5134,7 +5353,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5134
5353
|
if (deferred_path_resolve_enabled()) {
|
|
5135
5354
|
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
5136
5355
|
} else {
|
|
5137
|
-
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5356
|
+
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5357
|
+
ray,
|
|
5358
|
+
arrivingThroughput,
|
|
5359
|
+
hit
|
|
5360
|
+
);
|
|
5138
5361
|
accumulation[ray.rayId] =
|
|
5139
5362
|
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
5140
5363
|
}
|
|
@@ -5148,7 +5371,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5148
5371
|
ray.sourcePixelId,
|
|
5149
5372
|
ray.sampleId,
|
|
5150
5373
|
ray.bounce + 1u,
|
|
5151
|
-
|
|
5374
|
+
scatter.mediumRefId,
|
|
5152
5375
|
scatter.flags,
|
|
5153
5376
|
0u,
|
|
5154
5377
|
vec4<f32>(offset_origin(hit.position.xyz, hit.shadingNormal.xyz), 1.0),
|
|
@@ -5409,9 +5632,22 @@ function nowMs() {
|
|
|
5409
5632
|
}
|
|
5410
5633
|
return Date.now();
|
|
5411
5634
|
}
|
|
5412
|
-
function
|
|
5635
|
+
function estimateAccelerationBuildWaitFactor(config) {
|
|
5636
|
+
if (config?.gpuAccelerationBuildRequired !== true) {
|
|
5637
|
+
return 1;
|
|
5638
|
+
}
|
|
5639
|
+
const bvhSortStageCount = Array.isArray(config?.bvhSortStages) ? config.bvhSortStages.length : 0;
|
|
5640
|
+
const bvhBuildLevelCount = Array.isArray(config?.bvhBuildLevels) ? config.bvhBuildLevels.length : 0;
|
|
5641
|
+
const accelerationStageCount = 2 + bvhSortStageCount + bvhBuildLevelCount;
|
|
5642
|
+
return Math.max(1, 1 + accelerationStageCount / 96);
|
|
5643
|
+
}
|
|
5644
|
+
function estimateSubmittedGpuWorkTiming(config, tileCount, overrideTimeoutMs = null, options = {}) {
|
|
5413
5645
|
if (Number.isFinite(overrideTimeoutMs)) {
|
|
5414
|
-
|
|
5646
|
+
const overrideMs = Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
5647
|
+
return Object.freeze({
|
|
5648
|
+
timeoutMs: overrideMs,
|
|
5649
|
+
maxWaitMs: overrideMs
|
|
5650
|
+
});
|
|
5415
5651
|
}
|
|
5416
5652
|
const samplesPerPixel = Math.max(
|
|
5417
5653
|
1,
|
|
@@ -5422,10 +5658,26 @@ function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs
|
|
|
5422
5658
|
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
5423
5659
|
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
5424
5660
|
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
5425
|
-
|
|
5661
|
+
const triangleCount = Math.max(0, Number(config?.triangleCount ?? 0));
|
|
5662
|
+
const geometryFactor = Math.max(1, triangleCount / 131072);
|
|
5663
|
+
const includeAccelerationBuild = options.includeAccelerationBuild === true;
|
|
5664
|
+
const accelerationFactor = includeAccelerationBuild ? estimateAccelerationBuildWaitFactor(config) : 1;
|
|
5665
|
+
const estimatedWindowMs = Math.round(
|
|
5666
|
+
(GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5) * geometryFactor * accelerationFactor
|
|
5667
|
+
);
|
|
5668
|
+
const timeoutMs = Math.min(
|
|
5426
5669
|
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
5427
|
-
GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
5670
|
+
Math.max(GPU_SUBMITTED_WORK_TIMEOUT_MS, estimatedWindowMs)
|
|
5671
|
+
);
|
|
5672
|
+
const maxWaitMultiplier = includeAccelerationBuild ? 3 : 2;
|
|
5673
|
+
const maxWaitMs = Math.min(
|
|
5674
|
+
GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
|
|
5675
|
+
Math.max(timeoutMs, estimatedWindowMs * maxWaitMultiplier)
|
|
5428
5676
|
);
|
|
5677
|
+
return Object.freeze({
|
|
5678
|
+
timeoutMs,
|
|
5679
|
+
maxWaitMs
|
|
5680
|
+
});
|
|
5429
5681
|
}
|
|
5430
5682
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
5431
5683
|
assertAnalyticDisplayQualityPolicy(options);
|
|
@@ -5637,6 +5889,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
5637
5889
|
config.environmentMap,
|
|
5638
5890
|
config.environmentColor
|
|
5639
5891
|
);
|
|
5892
|
+
let mediumTextureResource = createMediumTextureResource(
|
|
5893
|
+
device,
|
|
5894
|
+
constants,
|
|
5895
|
+
config.mediums
|
|
5896
|
+
);
|
|
5640
5897
|
config = Object.freeze({
|
|
5641
5898
|
...config,
|
|
5642
5899
|
environmentMap: Object.freeze({
|
|
@@ -5723,7 +5980,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
5723
5980
|
{ binding: 28, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
5724
5981
|
{ binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5725
5982
|
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
5726
|
-
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
|
|
5983
|
+
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5984
|
+
{ binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
|
|
5727
5985
|
]
|
|
5728
5986
|
});
|
|
5729
5987
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -5910,14 +6168,18 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
5910
6168
|
{ binding: 28, resource: materialAtlasSampler },
|
|
5911
6169
|
{ binding: 29, resource: brdfLutResource.view },
|
|
5912
6170
|
{ binding: 30, resource: brdfLutResource.sampler },
|
|
5913
|
-
{ binding: 31, resource: environmentSamplingResource.view }
|
|
6171
|
+
{ binding: 31, resource: environmentSamplingResource.view },
|
|
6172
|
+
{ binding: 32, resource: mediumTextureResource.view }
|
|
5914
6173
|
]
|
|
5915
6174
|
});
|
|
5916
6175
|
}
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
6176
|
+
function createTraceBindGroups() {
|
|
6177
|
+
return [
|
|
6178
|
+
createTraceBindGroup(activeQueue, nextQueue, "plasius.wavefront.bind.activeNext"),
|
|
6179
|
+
createTraceBindGroup(nextQueue, activeQueue, "plasius.wavefront.bind.nextActive")
|
|
6180
|
+
];
|
|
6181
|
+
}
|
|
6182
|
+
let bindGroups = createTraceBindGroups();
|
|
5921
6183
|
const bvhBuildBindGroup = device.createBindGroup({
|
|
5922
6184
|
label: "plasius.wavefront.bind.bvhBuild",
|
|
5923
6185
|
layout: accelerationBindGroupLayout,
|
|
@@ -6095,6 +6357,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6095
6357
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6096
6358
|
environmentPortalCount: config.environmentPortalCount,
|
|
6097
6359
|
environmentPortalMode: config.environmentPortalMode,
|
|
6360
|
+
mediumCount: config.mediumCount,
|
|
6098
6361
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6099
6362
|
deferredPathResolve: config.deferredPathResolve,
|
|
6100
6363
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -6390,6 +6653,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6390
6653
|
1,
|
|
6391
6654
|
Number.isFinite(options2.timeoutMs) ? Number(options2.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
6392
6655
|
);
|
|
6656
|
+
const maxWaitMs = Math.max(
|
|
6657
|
+
timeoutMs,
|
|
6658
|
+
Number.isFinite(options2.maxWaitMs) ? Number(options2.maxWaitMs) : timeoutMs
|
|
6659
|
+
);
|
|
6393
6660
|
const allowTimeout = options2.allowTimeout !== false;
|
|
6394
6661
|
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
6395
6662
|
() => ({ status: "done" }),
|
|
@@ -6402,43 +6669,57 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6402
6669
|
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
6403
6670
|
);
|
|
6404
6671
|
}) : null;
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
if (
|
|
6410
|
-
|
|
6672
|
+
const startedAtMs = nowMs();
|
|
6673
|
+
while (true) {
|
|
6674
|
+
const elapsedMs = Math.max(0, nowMs() - startedAtMs);
|
|
6675
|
+
const remainingMs = Math.max(0, maxWaitMs - elapsedMs);
|
|
6676
|
+
if (remainingMs <= 0) {
|
|
6677
|
+
if (!allowTimeout) {
|
|
6678
|
+
throw new Error(`Timed out after ${Math.round(maxWaitMs)} ms waiting for submitted GPU work.`);
|
|
6679
|
+
}
|
|
6680
|
+
console.warn(
|
|
6681
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${Math.round(maxWaitMs)} ms; continuing.`
|
|
6682
|
+
);
|
|
6683
|
+
return false;
|
|
6411
6684
|
}
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
)
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6685
|
+
const waitWindowMs = Math.max(1, Math.min(timeoutMs, remainingMs));
|
|
6686
|
+
let timeoutHandle = null;
|
|
6687
|
+
let resolveTimeoutPromise = null;
|
|
6688
|
+
let timeoutSettled = false;
|
|
6689
|
+
const settleTimeoutPromise = (value) => {
|
|
6690
|
+
if (timeoutSettled) {
|
|
6691
|
+
return;
|
|
6692
|
+
}
|
|
6693
|
+
timeoutSettled = true;
|
|
6694
|
+
resolveTimeoutPromise?.(value);
|
|
6695
|
+
};
|
|
6696
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
6697
|
+
resolveTimeoutPromise = resolve;
|
|
6698
|
+
timeoutHandle = setTimeout(
|
|
6699
|
+
() => settleTimeoutPromise({ status: "timeout" }),
|
|
6700
|
+
waitWindowMs
|
|
6701
|
+
);
|
|
6702
|
+
});
|
|
6703
|
+
let result;
|
|
6704
|
+
try {
|
|
6705
|
+
result = await Promise.race(
|
|
6706
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
6707
|
+
);
|
|
6708
|
+
} finally {
|
|
6709
|
+
if (timeoutHandle !== null) {
|
|
6710
|
+
clearTimeout(timeoutHandle);
|
|
6711
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
6712
|
+
}
|
|
6428
6713
|
}
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6714
|
+
if (result?.status === "done") {
|
|
6715
|
+
return true;
|
|
6716
|
+
}
|
|
6717
|
+
if (result?.status !== "timeout") {
|
|
6718
|
+
return true;
|
|
6433
6719
|
}
|
|
6434
|
-
console.warn(
|
|
6435
|
-
`[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
|
|
6436
|
-
);
|
|
6437
|
-
return false;
|
|
6438
6720
|
}
|
|
6439
|
-
return true;
|
|
6440
6721
|
}
|
|
6441
|
-
function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
6722
|
+
function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel, optionsForFrame = {}) {
|
|
6442
6723
|
const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
|
|
6443
6724
|
const denoisePassCount = config.denoise ? renderedSamplesPerPixel < 4 ? 2 : 1 : 0;
|
|
6444
6725
|
const tailPassCount = denoisePassCount + 1;
|
|
@@ -6448,17 +6729,42 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6448
6729
|
Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) / Math.max(samplePassesPerSample, 1)
|
|
6449
6730
|
)
|
|
6450
6731
|
);
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6732
|
+
const sampleRangeStart = clamp(
|
|
6733
|
+
readNonNegativeInteger("sampleRangeStart", optionsForFrame.sampleRangeStart, 0),
|
|
6734
|
+
0,
|
|
6735
|
+
renderedSamplesPerPixel
|
|
6736
|
+
);
|
|
6737
|
+
const sampleRangeEnd = clamp(
|
|
6738
|
+
readPositiveInteger("sampleRangeEnd", optionsForFrame.sampleRangeEnd, renderedSamplesPerPixel),
|
|
6739
|
+
sampleRangeStart,
|
|
6740
|
+
renderedSamplesPerPixel
|
|
6741
|
+
);
|
|
6742
|
+
const includeDenoise = optionsForFrame.includeDenoise === true;
|
|
6743
|
+
const includePresent = optionsForFrame.includePresent === true;
|
|
6744
|
+
const tileStartIndex = clamp(
|
|
6745
|
+
readNonNegativeInteger("tileStartIndex", optionsForFrame.tileStartIndex, 0),
|
|
6746
|
+
0,
|
|
6747
|
+
tiles.length
|
|
6748
|
+
);
|
|
6749
|
+
const tileEndIndex = clamp(
|
|
6750
|
+
readPositiveInteger("tileEndIndex", optionsForFrame.tileEndIndex, tiles.length),
|
|
6751
|
+
tileStartIndex,
|
|
6752
|
+
tiles.length
|
|
6753
|
+
);
|
|
6754
|
+
let submissionCount = Math.max(
|
|
6755
|
+
0,
|
|
6756
|
+
readNonNegativeInteger("startingSubmissionCount", optionsForFrame.startingSubmissionCount, 0)
|
|
6757
|
+
);
|
|
6758
|
+
let slot = Math.max(0, readNonNegativeInteger("startingSlot", optionsForFrame.startingSlot, 0));
|
|
6759
|
+
for (const tile of tiles.slice(tileStartIndex, tileEndIndex)) {
|
|
6760
|
+
for (let sampleStart = sampleRangeStart; sampleStart < sampleRangeEnd; sampleStart += sampleBatchSize) {
|
|
6761
|
+
const sampleEnd = Math.min(sampleRangeEnd, sampleStart + sampleBatchSize);
|
|
6455
6762
|
const batch = createGpuSubmissionBatcher({
|
|
6456
6763
|
device,
|
|
6457
6764
|
frameIndex,
|
|
6458
6765
|
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6459
6766
|
startingSubmissionCount: submissionCount
|
|
6460
6767
|
});
|
|
6461
|
-
let slot = 0;
|
|
6462
6768
|
for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
|
|
6463
6769
|
const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6464
6770
|
sampleIndex,
|
|
@@ -6475,41 +6781,50 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6475
6781
|
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
6476
6782
|
}
|
|
6477
6783
|
}
|
|
6478
|
-
if (!config.deferredPathResolve &&
|
|
6784
|
+
if (!config.deferredPathResolve && sampleRangeEnd >= renderedSamplesPerPixel) {
|
|
6479
6785
|
const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6480
6786
|
sampleIndex: 0,
|
|
6481
6787
|
sampleWeight: 1 / renderedSamplesPerPixel
|
|
6482
6788
|
});
|
|
6789
|
+
slot += 1;
|
|
6483
6790
|
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
6484
6791
|
}
|
|
6485
6792
|
batch.flush();
|
|
6486
6793
|
submissionCount += batch.getSubmissionCount();
|
|
6487
6794
|
}
|
|
6488
6795
|
}
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6493
|
-
startingSubmissionCount: submissionCount
|
|
6494
|
-
});
|
|
6495
|
-
if (config.denoise) {
|
|
6496
|
-
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
6497
|
-
0,
|
|
6498
|
-
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
6796
|
+
if (includeDenoise || includePresent) {
|
|
6797
|
+
const tail = createGpuSubmissionBatcher({
|
|
6798
|
+
device,
|
|
6499
6799
|
frameIndex,
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
denoiseConfigOffset
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6800
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6801
|
+
startingSubmissionCount: submissionCount
|
|
6802
|
+
});
|
|
6803
|
+
if (includeDenoise && config.denoise) {
|
|
6804
|
+
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
6805
|
+
slot,
|
|
6806
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
6807
|
+
frameIndex,
|
|
6808
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6809
|
+
);
|
|
6810
|
+
slot += 1;
|
|
6811
|
+
encodeDenoise(
|
|
6812
|
+
tail.reserve(denoisePassCount),
|
|
6813
|
+
denoiseConfigOffset,
|
|
6814
|
+
parallelism,
|
|
6815
|
+
renderedSamplesPerPixel
|
|
6816
|
+
);
|
|
6817
|
+
}
|
|
6818
|
+
if (includePresent) {
|
|
6819
|
+
encodePresent(tail.reserve(1));
|
|
6820
|
+
}
|
|
6821
|
+
tail.flush();
|
|
6822
|
+
submissionCount += tail.getSubmissionCount();
|
|
6508
6823
|
}
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6824
|
+
return Object.freeze({
|
|
6825
|
+
submissionCount,
|
|
6826
|
+
slot
|
|
6827
|
+
});
|
|
6513
6828
|
}
|
|
6514
6829
|
async function readOutputProbe(optionsForProbe = {}) {
|
|
6515
6830
|
const mapMode = constants.map;
|
|
@@ -6555,24 +6870,59 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6555
6870
|
const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
|
|
6556
6871
|
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
6557
6872
|
const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
6558
|
-
const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
|
|
6559
|
-
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6560
|
-
tiles.length,
|
|
6561
|
-
renderOptions.submittedWorkTimeoutMs
|
|
6562
|
-
);
|
|
6563
6873
|
const frameStartTimeMs = nowMs();
|
|
6564
|
-
const submissionWaitOptions = awaitGPUCompletion ? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false } : { timeoutMs: submittedWorkTimeoutMs };
|
|
6565
6874
|
let frameStats;
|
|
6566
6875
|
if (useThrottledHighSamplePath) {
|
|
6567
6876
|
frame += 1;
|
|
6568
6877
|
const frameIndex = frame + config.frameIndex;
|
|
6569
6878
|
const parallelismCounters = createGpuParallelismCounters();
|
|
6570
6879
|
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6880
|
+
let frameSubmissionCount = 0;
|
|
6881
|
+
let frameConfigSlot = 0;
|
|
6882
|
+
if (accelerationBuildSubmitted) {
|
|
6883
|
+
const accelerationWaitOptions = {
|
|
6884
|
+
...estimateSubmittedGpuWorkTiming(
|
|
6885
|
+
{ ...config, renderedSamplesPerPixel: 1 },
|
|
6886
|
+
1,
|
|
6887
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
6888
|
+
{ includeAccelerationBuild: true }
|
|
6889
|
+
),
|
|
6890
|
+
allowTimeout: false
|
|
6891
|
+
};
|
|
6892
|
+
await waitForSubmittedGpuWork(accelerationWaitOptions);
|
|
6893
|
+
}
|
|
6894
|
+
for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
|
|
6895
|
+
const tileRangeDispatch = dispatchFrameAwaitingGpu(
|
|
6896
|
+
frameIndex,
|
|
6897
|
+
parallelismCounters,
|
|
6898
|
+
samplingPlan.renderedSamplesPerPixel,
|
|
6899
|
+
{
|
|
6900
|
+
sampleRangeStart: 0,
|
|
6901
|
+
sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
|
|
6902
|
+
tileStartIndex: tileIndex,
|
|
6903
|
+
tileEndIndex: tileIndex + 1,
|
|
6904
|
+
startingSubmissionCount: frameSubmissionCount,
|
|
6905
|
+
startingSlot: frameConfigSlot,
|
|
6906
|
+
includeDenoise: tileIndex + 1 >= tiles.length,
|
|
6907
|
+
includePresent: tileIndex + 1 >= tiles.length
|
|
6908
|
+
}
|
|
6909
|
+
);
|
|
6910
|
+
frameSubmissionCount = tileRangeDispatch.submissionCount;
|
|
6911
|
+
frameConfigSlot = tileRangeDispatch.slot;
|
|
6912
|
+
const tileWaitOptions = {
|
|
6913
|
+
...estimateSubmittedGpuWorkTiming(
|
|
6914
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6915
|
+
1,
|
|
6916
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
6917
|
+
{
|
|
6918
|
+
includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
|
|
6919
|
+
includePresent: tileIndex + 1 >= tiles.length
|
|
6920
|
+
}
|
|
6921
|
+
),
|
|
6922
|
+
allowTimeout: false
|
|
6923
|
+
};
|
|
6924
|
+
await waitForSubmittedGpuWork(tileWaitOptions);
|
|
6925
|
+
}
|
|
6576
6926
|
frameStats = createFrameStats({
|
|
6577
6927
|
frameIndex,
|
|
6578
6928
|
accelerationBuildSubmitted,
|
|
@@ -6584,10 +6934,24 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6584
6934
|
budgetConstrained: samplingPlan.budgetConstrained
|
|
6585
6935
|
});
|
|
6586
6936
|
} else {
|
|
6937
|
+
const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
|
|
6938
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6939
|
+
tiles.length,
|
|
6940
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
6941
|
+
{ includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
|
|
6942
|
+
);
|
|
6943
|
+
const submissionWaitOptions = awaitGPUCompletion ? {
|
|
6944
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
6945
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs,
|
|
6946
|
+
allowTimeout: false
|
|
6947
|
+
} : {
|
|
6948
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
6949
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs
|
|
6950
|
+
};
|
|
6587
6951
|
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6952
|
+
if (awaitGPUCompletion) {
|
|
6953
|
+
await waitForSubmittedGpuWork(submissionWaitOptions);
|
|
6954
|
+
}
|
|
6591
6955
|
}
|
|
6592
6956
|
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
6593
6957
|
if (awaitGPUCompletion) {
|
|
@@ -6642,10 +7006,22 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6642
7006
|
...overrides
|
|
6643
7007
|
});
|
|
6644
7008
|
}
|
|
7009
|
+
function rebuildMediumResources(nextConfig) {
|
|
7010
|
+
const previousMediumTextureResource = mediumTextureResource;
|
|
7011
|
+
mediumTextureResource = createMediumTextureResource(device, constants, nextConfig.mediums);
|
|
7012
|
+
bindGroups = createTraceBindGroups();
|
|
7013
|
+
if (previousMediumTextureResource?.ownsTexture) {
|
|
7014
|
+
previousMediumTextureResource.texture?.destroy?.();
|
|
7015
|
+
}
|
|
7016
|
+
}
|
|
6645
7017
|
function updateSceneObjects(sceneObjects) {
|
|
6646
7018
|
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
6647
7019
|
packedScene = nextPackedScene;
|
|
6648
|
-
|
|
7020
|
+
const nextConfig = rebuildLiveConfig();
|
|
7021
|
+
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
7022
|
+
rebuildMediumResources(nextConfig);
|
|
7023
|
+
}
|
|
7024
|
+
config = nextConfig;
|
|
6649
7025
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
6650
7026
|
return config;
|
|
6651
7027
|
}
|
|
@@ -6669,6 +7045,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6669
7045
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6670
7046
|
environmentPortalCount: config.environmentPortalCount,
|
|
6671
7047
|
environmentPortalMode: config.environmentPortalMode,
|
|
7048
|
+
mediumCount: config.mediumCount,
|
|
6672
7049
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6673
7050
|
deferredPathResolve: config.deferredPathResolve,
|
|
6674
7051
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -6709,6 +7086,9 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6709
7086
|
if (environmentSamplingResource.ownsTexture) {
|
|
6710
7087
|
environmentSamplingResource.texture?.destroy?.();
|
|
6711
7088
|
}
|
|
7089
|
+
if (mediumTextureResource.ownsTexture) {
|
|
7090
|
+
mediumTextureResource.texture?.destroy?.();
|
|
7091
|
+
}
|
|
6712
7092
|
brdfLutResource.texture?.destroy?.();
|
|
6713
7093
|
if (baseColorAtlasResource.ownsTexture) {
|
|
6714
7094
|
baseColorAtlasResource.texture?.destroy?.();
|