@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.cjs
CHANGED
|
@@ -212,17 +212,19 @@ var DEFAULT_MAX_DEPTH = 6;
|
|
|
212
212
|
var DEFAULT_TILE_SIZE = 128;
|
|
213
213
|
var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
214
214
|
var MAX_SAMPLES_PER_PIXEL = 256;
|
|
215
|
-
var DEFAULT_BRDF_LUT_SIZE =
|
|
215
|
+
var DEFAULT_BRDF_LUT_SIZE = 128;
|
|
216
|
+
var DEFAULT_BRDF_LUT_SAMPLE_COUNT = 256;
|
|
216
217
|
var DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
217
218
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
218
219
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
220
|
+
var DEFAULT_MEDIUM_PHASE_MODEL = 0;
|
|
219
221
|
var WORKGROUP_SIZE = 64;
|
|
220
222
|
var rendererWavefrontComputeMode = "webgpu-compute";
|
|
221
223
|
var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
222
224
|
var rendererWavefrontComputeStatsStride = 8;
|
|
223
225
|
var RAY_RECORD_BYTES = 80;
|
|
224
226
|
var HIT_RECORD_BYTES = 256;
|
|
225
|
-
var SCENE_OBJECT_RECORD_BYTES =
|
|
227
|
+
var SCENE_OBJECT_RECORD_BYTES = 160;
|
|
226
228
|
var MESH_VERTEX_RECORD_BYTES = 48;
|
|
227
229
|
var MESH_RANGE_RECORD_BYTES = 240;
|
|
228
230
|
var TRIANGLE_RECORD_BYTES = 352;
|
|
@@ -231,11 +233,13 @@ var BVH_NODE_RECORD_BYTES = 48;
|
|
|
231
233
|
var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
232
234
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
233
235
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
236
|
+
var MEDIUM_TABLE_ROWS = 2;
|
|
234
237
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
235
238
|
var PATH_VERTEX_RECORD_BYTES = 16;
|
|
236
239
|
var GPU_SUBMITTED_WORK_TIMEOUT_MS = 5e3;
|
|
237
240
|
var GPU_READBACK_COMPLETION_TIMEOUT_MS = 6e4;
|
|
238
241
|
var GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 6e4;
|
|
242
|
+
var GPU_MAX_SUBMITTED_WORK_DEADLINE_MS = 18e4;
|
|
239
243
|
var CONFIG_BUFFER_BYTES = 320;
|
|
240
244
|
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
241
245
|
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
@@ -577,6 +581,156 @@ function deriveBounds(input) {
|
|
|
577
581
|
}
|
|
578
582
|
return null;
|
|
579
583
|
}
|
|
584
|
+
function deriveBeerLambertAbsorptionFromAttenuationColor(attenuationColor, attenuationDistance, density = 1) {
|
|
585
|
+
const distance = Number(attenuationDistance);
|
|
586
|
+
const densityScale = Math.max(0, Number(density) || 0);
|
|
587
|
+
if (!Number.isFinite(distance) || distance <= 0 || densityScale <= 0) {
|
|
588
|
+
return [0, 0, 0];
|
|
589
|
+
}
|
|
590
|
+
return attenuationColor.slice(0, 3).map((channel) => {
|
|
591
|
+
const clamped = clamp(Number(channel) || 0, 1e-4, 1);
|
|
592
|
+
return Math.max(0, -Math.log(clamped) / distance * densityScale);
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
function readMediumPhaseModel(value) {
|
|
596
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
597
|
+
return Math.max(0, Math.trunc(value));
|
|
598
|
+
}
|
|
599
|
+
switch (String(value ?? "").trim().toLowerCase()) {
|
|
600
|
+
case "isotropic":
|
|
601
|
+
default:
|
|
602
|
+
return DEFAULT_MEDIUM_PHASE_MODEL;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function resolveWavefrontVolumeInput(input) {
|
|
606
|
+
return input?.volume ?? input?.material?.volume ?? null;
|
|
607
|
+
}
|
|
608
|
+
function normalizeWavefrontThickness(input, label) {
|
|
609
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
610
|
+
return Math.max(
|
|
611
|
+
0,
|
|
612
|
+
readFiniteNumber(
|
|
613
|
+
label,
|
|
614
|
+
input?.thickness ?? volume?.thickness ?? input?.material?.thickness,
|
|
615
|
+
0
|
|
616
|
+
)
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
function resolveWavefrontMediumId(input, fallbackId = 1) {
|
|
620
|
+
return input?.mediumRefId ?? input?.mediumId ?? input?.material?.mediumId ?? input?.materialRefId ?? input?.material?.id ?? input?.materialId ?? input?.id ?? fallbackId;
|
|
621
|
+
}
|
|
622
|
+
function deriveWavefrontTransportMedium(input, fallbackId = 1) {
|
|
623
|
+
const resolvedId = resolveWavefrontMediumId(input, fallbackId);
|
|
624
|
+
if (input?.medium) {
|
|
625
|
+
return normalizeWavefrontMedium(
|
|
626
|
+
{
|
|
627
|
+
...input.medium,
|
|
628
|
+
id: input.medium.id ?? input.medium.mediumId ?? resolvedId
|
|
629
|
+
},
|
|
630
|
+
fallbackId
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
634
|
+
if (!volume) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
return normalizeWavefrontMedium(
|
|
638
|
+
{
|
|
639
|
+
id: resolvedId,
|
|
640
|
+
phaseModel: volume.phaseModel,
|
|
641
|
+
density: volume.density,
|
|
642
|
+
attenuationColor: volume.attenuationColor,
|
|
643
|
+
attenuationDistance: volume.attenuationDistance,
|
|
644
|
+
absorption: volume.absorption,
|
|
645
|
+
scattering: volume.scattering
|
|
646
|
+
},
|
|
647
|
+
fallbackId
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
function normalizeWavefrontMedium(input = {}, index = 0) {
|
|
651
|
+
const id = readNonNegativeInteger("medium id", input.id ?? input.mediumId, index);
|
|
652
|
+
const density = Math.max(0, readFiniteNumber("medium density", input.density, 1));
|
|
653
|
+
const attenuationColor = asColor(
|
|
654
|
+
input.attenuationColor ?? input.color ?? input.medium?.attenuationColor,
|
|
655
|
+
[1, 1, 1, 1]
|
|
656
|
+
);
|
|
657
|
+
const attenuationDistance = readFiniteNumber(
|
|
658
|
+
"medium attenuationDistance",
|
|
659
|
+
input.attenuationDistance ?? input.distance ?? input.medium?.attenuationDistance,
|
|
660
|
+
0
|
|
661
|
+
);
|
|
662
|
+
const absorption = Array.isArray(input.absorption) || Array.isArray(input.medium?.absorption) ? asVec3(input.absorption ?? input.medium?.absorption, [0, 0, 0]).map(
|
|
663
|
+
(value) => Math.max(0, Number(value) || 0)
|
|
664
|
+
) : deriveBeerLambertAbsorptionFromAttenuationColor(
|
|
665
|
+
attenuationColor,
|
|
666
|
+
attenuationDistance,
|
|
667
|
+
density
|
|
668
|
+
);
|
|
669
|
+
const scattering = asVec3(
|
|
670
|
+
input.scattering ?? input.medium?.scattering,
|
|
671
|
+
[0, 0, 0]
|
|
672
|
+
).map((value) => Math.max(0, Number(value) || 0));
|
|
673
|
+
return Object.freeze({
|
|
674
|
+
id,
|
|
675
|
+
phaseModel: readMediumPhaseModel(input.phaseModel ?? input.medium?.phaseModel),
|
|
676
|
+
density,
|
|
677
|
+
attenuationColor: Object.freeze(attenuationColor),
|
|
678
|
+
attenuationDistance,
|
|
679
|
+
absorption: Object.freeze(absorption),
|
|
680
|
+
scattering: Object.freeze(scattering)
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
function collectWavefrontMediums(options, meshes, sceneObjects = []) {
|
|
684
|
+
const mediumsById = /* @__PURE__ */ new Map();
|
|
685
|
+
mediumsById.set(
|
|
686
|
+
0,
|
|
687
|
+
Object.freeze({
|
|
688
|
+
id: 0,
|
|
689
|
+
phaseModel: DEFAULT_MEDIUM_PHASE_MODEL,
|
|
690
|
+
density: 0,
|
|
691
|
+
attenuationColor: Object.freeze([1, 1, 1, 1]),
|
|
692
|
+
attenuationDistance: 0,
|
|
693
|
+
absorption: Object.freeze([0, 0, 0]),
|
|
694
|
+
scattering: Object.freeze([0, 0, 0])
|
|
695
|
+
})
|
|
696
|
+
);
|
|
697
|
+
const register = (input, fallbackId = mediumsById.size) => {
|
|
698
|
+
if (!input) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const normalized = normalizeWavefrontMedium(
|
|
702
|
+
typeof input === "object" ? { id: fallbackId, ...input } : { id: fallbackId },
|
|
703
|
+
fallbackId
|
|
704
|
+
);
|
|
705
|
+
const existing = mediumsById.get(normalized.id);
|
|
706
|
+
if (existing && JSON.stringify(existing) !== JSON.stringify(normalized)) {
|
|
707
|
+
throw new Error(`Medium id ${normalized.id} is defined more than once with different values.`);
|
|
708
|
+
}
|
|
709
|
+
mediumsById.set(normalized.id, normalized);
|
|
710
|
+
};
|
|
711
|
+
for (const medium of options.mediums ?? []) {
|
|
712
|
+
register(medium);
|
|
713
|
+
}
|
|
714
|
+
for (const mesh of meshes) {
|
|
715
|
+
register(mesh.medium, mesh.mediumRefId ?? mesh.medium?.id ?? 0);
|
|
716
|
+
}
|
|
717
|
+
for (const mesh of meshes) {
|
|
718
|
+
if ((mesh.mediumRefId ?? 0) > 0 && !mediumsById.has(mesh.mediumRefId)) {
|
|
719
|
+
register({ id: mesh.mediumRefId });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
for (const object of sceneObjects) {
|
|
723
|
+
register(object.medium, object.mediumRefId ?? object.medium?.id ?? 0);
|
|
724
|
+
}
|
|
725
|
+
for (const object of sceneObjects) {
|
|
726
|
+
if ((object.mediumRefId ?? 0) > 0 && !mediumsById.has(object.mediumRefId)) {
|
|
727
|
+
register({ id: object.mediumRefId });
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return Object.freeze(
|
|
731
|
+
Array.from(mediumsById.values()).sort((left, right) => left.id - right.id)
|
|
732
|
+
);
|
|
733
|
+
}
|
|
580
734
|
function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
581
735
|
const bounds = deriveBounds(input);
|
|
582
736
|
const kind = readObjectKind(input.kind ?? input.type ?? (bounds ? "box" : "sphere"));
|
|
@@ -607,12 +761,19 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
607
761
|
input.specularColor ?? input.material?.specularColor,
|
|
608
762
|
[1, 1, 1, 1]
|
|
609
763
|
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
764
|
+
const medium = deriveWavefrontTransportMedium(input, index + 1);
|
|
610
765
|
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;
|
|
611
766
|
return Object.freeze({
|
|
612
767
|
id: readNonNegativeInteger("id", input.id, index + 1),
|
|
613
768
|
kind,
|
|
614
769
|
materialKind: resolvedMaterialKind,
|
|
615
770
|
flags: readNonNegativeInteger("flags", input.flags, 0),
|
|
771
|
+
mediumRefId: readNonNegativeInteger(
|
|
772
|
+
"mediumRefId",
|
|
773
|
+
input.mediumRefId ?? medium?.id ?? input.medium?.id ?? input.mediumId,
|
|
774
|
+
0
|
|
775
|
+
),
|
|
776
|
+
medium,
|
|
616
777
|
center: Object.freeze(center),
|
|
617
778
|
halfExtent: Object.freeze(halfExtent),
|
|
618
779
|
color: Object.freeze(color),
|
|
@@ -636,6 +797,7 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
636
797
|
),
|
|
637
798
|
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
638
799
|
specularColor: Object.freeze(specularColor),
|
|
800
|
+
thickness: normalizeWavefrontThickness(input, "thickness"),
|
|
639
801
|
transmission
|
|
640
802
|
});
|
|
641
803
|
}
|
|
@@ -729,6 +891,7 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
729
891
|
input.specularColor ?? input.material?.specularColor,
|
|
730
892
|
[1, 1, 1, 1]
|
|
731
893
|
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
894
|
+
const medium = deriveWavefrontTransportMedium(input, meshIndex + 1);
|
|
732
895
|
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;
|
|
733
896
|
return Object.freeze({
|
|
734
897
|
id: readNonNegativeInteger("mesh id", input.id, meshIndex + 1),
|
|
@@ -745,9 +908,10 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
745
908
|
),
|
|
746
909
|
mediumRefId: readNonNegativeInteger(
|
|
747
910
|
"mesh mediumRefId",
|
|
748
|
-
input.mediumRefId ?? input.medium?.id ?? input.mediumId,
|
|
911
|
+
input.mediumRefId ?? medium?.id ?? input.medium?.id ?? input.mediumId ?? input.material?.mediumId,
|
|
749
912
|
0
|
|
750
913
|
),
|
|
914
|
+
medium,
|
|
751
915
|
color: Object.freeze(color),
|
|
752
916
|
emission: Object.freeze(emission),
|
|
753
917
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
@@ -769,6 +933,7 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
769
933
|
),
|
|
770
934
|
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
771
935
|
specularColor: Object.freeze(specularColor),
|
|
936
|
+
thickness: normalizeWavefrontThickness(input, "mesh thickness"),
|
|
772
937
|
transmission,
|
|
773
938
|
baseColorTexture: input.baseColorTexture ?? input.material?.baseColorTexture ?? null,
|
|
774
939
|
metallicRoughnessTexture: input.metallicRoughnessTexture ?? input.material?.metallicRoughnessTexture ?? null,
|
|
@@ -780,141 +945,9 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
780
945
|
function clampUnit(value) {
|
|
781
946
|
return clamp(Number(value) || 0, 0, 1);
|
|
782
947
|
}
|
|
783
|
-
function
|
|
784
|
-
const channel = clampUnit(value);
|
|
785
|
-
if (channel <= 0.04045) {
|
|
786
|
-
return channel / 12.92;
|
|
787
|
-
}
|
|
788
|
-
return ((channel + 0.055) / 1.055) ** 2.4;
|
|
789
|
-
}
|
|
790
|
-
function sampleTextureRgba(texture, uv = [0, 0], colorSpace = "linear") {
|
|
791
|
-
if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || !texture.data || texture.width <= 0 || texture.height <= 0) {
|
|
792
|
-
return [1, 1, 1, 1];
|
|
793
|
-
}
|
|
794
|
-
const u = (uv[0] % 1 + 1) % 1;
|
|
795
|
-
const v = (uv[1] % 1 + 1) % 1;
|
|
796
|
-
const x = Math.min(texture.width - 1, Math.max(0, Math.round(u * (texture.width - 1))));
|
|
797
|
-
const y = Math.min(texture.height - 1, Math.max(0, Math.round((1 - v) * (texture.height - 1))));
|
|
798
|
-
const offset = (y * texture.width + x) * 4;
|
|
799
|
-
const data = texture.data;
|
|
800
|
-
const scale2 = resolveTextureSampleScale(data);
|
|
801
|
-
const defaultChannel = scale2 === 1 ? 1 : Math.round(1 / scale2);
|
|
802
|
-
const color = [
|
|
803
|
-
(data[offset] ?? defaultChannel) * scale2,
|
|
804
|
-
(data[offset + 1] ?? defaultChannel) * scale2,
|
|
805
|
-
(data[offset + 2] ?? defaultChannel) * scale2,
|
|
806
|
-
(data[offset + 3] ?? defaultChannel) * scale2
|
|
807
|
-
];
|
|
808
|
-
if (colorSpace === "srgb") {
|
|
809
|
-
return [srgbToLinear(color[0]), srgbToLinear(color[1]), srgbToLinear(color[2]), color[3]];
|
|
810
|
-
}
|
|
811
|
-
return color;
|
|
812
|
-
}
|
|
813
|
-
function resolveTextureSampleScale(data) {
|
|
814
|
-
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
|
|
815
|
-
return 1 / 255;
|
|
816
|
-
}
|
|
817
|
-
if (data instanceof Uint16Array) {
|
|
818
|
-
return 1 / 65535;
|
|
819
|
-
}
|
|
820
|
-
if (Array.isArray(data) && data.some((value) => Number(value) > 1)) {
|
|
821
|
-
return 1 / 255;
|
|
822
|
-
}
|
|
823
|
-
return 1;
|
|
824
|
-
}
|
|
825
|
-
function normalizeVectorOrFallback(vector, fallback) {
|
|
826
|
-
return normalize(Array.isArray(vector) ? vector : fallback, fallback);
|
|
827
|
-
}
|
|
828
|
-
function buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, fallbackNormal) {
|
|
829
|
-
const edge1 = subtract(v1, v0);
|
|
830
|
-
const edge2 = subtract(v2, v0);
|
|
831
|
-
const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
|
|
832
|
-
const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
|
|
833
|
-
const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
|
|
834
|
-
if (Math.abs(determinant) < 1e-6) {
|
|
835
|
-
const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
836
|
-
const tangent2 = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
|
|
837
|
-
const bitangent2 = normalize(cross(fallbackNormal, tangent2), [0, 0, 1]);
|
|
838
|
-
return { tangent: tangent2, bitangent: bitangent2 };
|
|
839
|
-
}
|
|
840
|
-
const inverse = 1 / determinant;
|
|
841
|
-
const tangent = normalize(
|
|
842
|
-
[
|
|
843
|
-
inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
|
|
844
|
-
inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
|
|
845
|
-
inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1])
|
|
846
|
-
],
|
|
847
|
-
[1, 0, 0]
|
|
848
|
-
);
|
|
849
|
-
const bitangent = normalize(
|
|
850
|
-
[
|
|
851
|
-
inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
|
|
852
|
-
inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
|
|
853
|
-
inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0])
|
|
854
|
-
],
|
|
855
|
-
[0, 0, 1]
|
|
856
|
-
);
|
|
857
|
-
return { tangent, bitangent };
|
|
858
|
-
}
|
|
859
|
-
function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
|
|
860
|
-
if (!normalTexture) {
|
|
861
|
-
return normalizeVectorOrFallback(normal, [0, 1, 0]);
|
|
862
|
-
}
|
|
863
|
-
const sample = sampleTextureRgba(normalTexture, uv, "linear");
|
|
864
|
-
const strength = clampUnit(normalTexture.scale ?? 1);
|
|
865
|
-
const tangentNormal = normalize(
|
|
866
|
-
[
|
|
867
|
-
(sample[0] * 2 - 1) * strength,
|
|
868
|
-
(sample[1] * 2 - 1) * strength,
|
|
869
|
-
1 + (sample[2] * 2 - 1 - 1) * strength
|
|
870
|
-
],
|
|
871
|
-
[0, 0, 1]
|
|
872
|
-
);
|
|
873
|
-
return normalize(
|
|
874
|
-
[
|
|
875
|
-
tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
|
|
876
|
-
tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
|
|
877
|
-
tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2]
|
|
878
|
-
],
|
|
879
|
-
normal
|
|
880
|
-
);
|
|
881
|
-
}
|
|
882
|
-
function sampleBaseColor(mesh, uv) {
|
|
883
|
-
const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
|
|
884
|
-
return [
|
|
885
|
-
clampUnit(mesh.color[0] * sample[0]),
|
|
886
|
-
clampUnit(mesh.color[1] * sample[1]),
|
|
887
|
-
clampUnit(mesh.color[2] * sample[2]),
|
|
888
|
-
clampUnit((mesh.color[3] ?? 1) * sample[3])
|
|
889
|
-
];
|
|
890
|
-
}
|
|
891
|
-
function sampleSurfaceMaterial(mesh, uv) {
|
|
892
|
-
const textureSample = mesh.metallicRoughnessTexture ? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear") : [1, 1, 1, 1];
|
|
893
|
-
return {
|
|
894
|
-
roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
|
|
895
|
-
metallic: clamp(mesh.metallic * textureSample[2], 0, 1)
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
function averageColors(colors) {
|
|
899
|
-
const count = Math.max(colors.length, 1);
|
|
900
|
-
return colors.reduce(
|
|
901
|
-
(accumulator, color) => [
|
|
902
|
-
accumulator[0] + color[0] / count,
|
|
903
|
-
accumulator[1] + color[1] / count,
|
|
904
|
-
accumulator[2] + color[2] / count,
|
|
905
|
-
accumulator[3] + color[3] / count
|
|
906
|
-
],
|
|
907
|
-
[0, 0, 0, 0]
|
|
908
|
-
);
|
|
909
|
-
}
|
|
910
|
-
function averageNumbers(values, fallback = 0) {
|
|
911
|
-
if (!Array.isArray(values) || values.length === 0) {
|
|
912
|
-
return fallback;
|
|
913
|
-
}
|
|
914
|
-
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
915
|
-
}
|
|
916
|
-
function createMeshTriangleRecords(meshes) {
|
|
948
|
+
function createMeshTriangleRecords(meshes, gpuMaterialSource = null) {
|
|
917
949
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
950
|
+
const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
|
|
918
951
|
let nextTriangleId = 0;
|
|
919
952
|
return source.flatMap((meshInput, meshIndex) => {
|
|
920
953
|
const mesh = normalizeWavefrontMesh(meshInput, meshIndex);
|
|
@@ -933,16 +966,6 @@ function createMeshTriangleRecords(meshes) {
|
|
|
933
966
|
const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
|
|
934
967
|
const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
|
|
935
968
|
const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
|
|
936
|
-
const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
|
|
937
|
-
const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
|
|
938
|
-
const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
|
|
939
|
-
const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
|
|
940
|
-
const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
|
|
941
|
-
const sampledMaterials = [
|
|
942
|
-
sampleSurfaceMaterial(mesh, uv0),
|
|
943
|
-
sampleSurfaceMaterial(mesh, uv1),
|
|
944
|
-
sampleSurfaceMaterial(mesh, uv2)
|
|
945
|
-
];
|
|
946
969
|
const bounds = triangleBounds(v0, v1, v2);
|
|
947
970
|
triangles.push(
|
|
948
971
|
Object.freeze({
|
|
@@ -956,17 +979,17 @@ function createMeshTriangleRecords(meshes) {
|
|
|
956
979
|
v0: Object.freeze(v0),
|
|
957
980
|
v1: Object.freeze(v1),
|
|
958
981
|
v2: Object.freeze(v2),
|
|
959
|
-
n0: Object.freeze(
|
|
960
|
-
n1: Object.freeze(
|
|
961
|
-
n2: Object.freeze(
|
|
982
|
+
n0: Object.freeze(n0),
|
|
983
|
+
n1: Object.freeze(n1),
|
|
984
|
+
n2: Object.freeze(n2),
|
|
962
985
|
uv0: Object.freeze(uv0),
|
|
963
986
|
uv1: Object.freeze(uv1),
|
|
964
987
|
uv2: Object.freeze(uv2),
|
|
965
|
-
color:
|
|
988
|
+
color: mesh.color,
|
|
966
989
|
emission: mesh.emission,
|
|
967
990
|
material: Object.freeze([
|
|
968
|
-
|
|
969
|
-
|
|
991
|
+
mesh.roughness,
|
|
992
|
+
mesh.metallic,
|
|
970
993
|
mesh.opacity,
|
|
971
994
|
mesh.ior
|
|
972
995
|
]),
|
|
@@ -980,7 +1003,7 @@ function createMeshTriangleRecords(meshes) {
|
|
|
980
1003
|
mesh.clearcoatRoughness,
|
|
981
1004
|
mesh.specular,
|
|
982
1005
|
mesh.transmission,
|
|
983
|
-
|
|
1006
|
+
mesh.thickness
|
|
984
1007
|
]),
|
|
985
1008
|
specularColor: Object.freeze([
|
|
986
1009
|
mesh.specularColor[0] ?? 1,
|
|
@@ -988,6 +1011,27 @@ function createMeshTriangleRecords(meshes) {
|
|
|
988
1011
|
mesh.specularColor[2] ?? 1,
|
|
989
1012
|
1
|
|
990
1013
|
]),
|
|
1014
|
+
baseColorAtlas: Object.freeze(
|
|
1015
|
+
resolvedMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
|
|
1016
|
+
),
|
|
1017
|
+
metallicRoughnessAtlas: Object.freeze(
|
|
1018
|
+
resolvedMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1019
|
+
),
|
|
1020
|
+
normalAtlas: Object.freeze(
|
|
1021
|
+
resolvedMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
|
|
1022
|
+
),
|
|
1023
|
+
occlusionAtlas: Object.freeze(
|
|
1024
|
+
resolvedMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
|
|
1025
|
+
),
|
|
1026
|
+
emissiveAtlas: Object.freeze(
|
|
1027
|
+
resolvedMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
|
|
1028
|
+
),
|
|
1029
|
+
textureSettings: Object.freeze([
|
|
1030
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1031
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1032
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1033
|
+
0
|
|
1034
|
+
]),
|
|
991
1035
|
bounds: Object.freeze({
|
|
992
1036
|
min: Object.freeze(bounds.min),
|
|
993
1037
|
max: Object.freeze(bounds.max)
|
|
@@ -1062,9 +1106,10 @@ function buildBvh(triangles, maxLeafTriangles = 4) {
|
|
|
1062
1106
|
triangles: Object.freeze(orderedTriangles)
|
|
1063
1107
|
});
|
|
1064
1108
|
}
|
|
1065
|
-
function createWavefrontMeshAcceleration(meshes = []) {
|
|
1109
|
+
function createWavefrontMeshAcceleration(meshes = [], gpuMaterialSource = null) {
|
|
1066
1110
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
1067
|
-
const
|
|
1111
|
+
const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
|
|
1112
|
+
const triangles = createMeshTriangleRecords(source, resolvedMaterialSource);
|
|
1068
1113
|
return buildBvh(triangles);
|
|
1069
1114
|
}
|
|
1070
1115
|
function estimateMeshSourceShape(meshes) {
|
|
@@ -1274,7 +1319,7 @@ function createWavefrontGpuMaterialSource(meshes = []) {
|
|
|
1274
1319
|
mesh.clearcoatRoughness,
|
|
1275
1320
|
mesh.specular,
|
|
1276
1321
|
mesh.transmission,
|
|
1277
|
-
|
|
1322
|
+
mesh.thickness
|
|
1278
1323
|
]);
|
|
1279
1324
|
writeVec4(floatView, byteOffset + 80, [
|
|
1280
1325
|
mesh.specularColor[0] ?? 1,
|
|
@@ -1358,12 +1403,12 @@ function createWavefrontBvhBuildLevels(triangleCountInput) {
|
|
|
1358
1403
|
return Object.freeze(levels);
|
|
1359
1404
|
}
|
|
1360
1405
|
function resolveAccelerationBuildMode(options = {}) {
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1406
|
+
const requestedMode = options.accelerationBuildMode ?? (options.displayQuality === true ? "cpu-upload" : "cpu-debug");
|
|
1407
|
+
const mode = requestedMode === "cpu-debug" ? "cpu-upload" : requestedMode;
|
|
1408
|
+
if (mode !== "gpu" && mode !== "cpu-upload") {
|
|
1409
|
+
throw new Error(
|
|
1410
|
+
'accelerationBuildMode must be either "gpu", "cpu-upload", or the legacy alias "cpu-debug".'
|
|
1411
|
+
);
|
|
1367
1412
|
}
|
|
1368
1413
|
return mode;
|
|
1369
1414
|
}
|
|
@@ -1442,7 +1487,7 @@ function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput = null
|
|
|
1442
1487
|
mesh.clearcoatRoughness,
|
|
1443
1488
|
mesh.specular,
|
|
1444
1489
|
mesh.transmission,
|
|
1445
|
-
|
|
1490
|
+
mesh.thickness
|
|
1446
1491
|
]);
|
|
1447
1492
|
writeVec4(meshFloats, floatOffset * 4 + 128, [
|
|
1448
1493
|
mesh.specularColor[0] ?? 1,
|
|
@@ -1543,12 +1588,16 @@ function normalizeSceneObjects(sceneObjects, useDefaultScene = true) {
|
|
|
1543
1588
|
const source = Array.isArray(sceneObjects) && sceneObjects.length > 0 ? sceneObjects : useDefaultScene ? createDefaultWavefrontSceneObjects() : [];
|
|
1544
1589
|
return source.map((object, index) => normalizeWavefrontSceneObject(object, index));
|
|
1545
1590
|
}
|
|
1591
|
+
function normalizeWavefrontMeshes(meshes) {
|
|
1592
|
+
const source = Array.isArray(meshes) ? meshes : [];
|
|
1593
|
+
return source.map((mesh, index) => normalizeWavefrontMesh(mesh, index));
|
|
1594
|
+
}
|
|
1546
1595
|
function normalizeMeshes(options = {}) {
|
|
1547
1596
|
if (Array.isArray(options.meshes)) {
|
|
1548
|
-
return options.meshes;
|
|
1597
|
+
return normalizeWavefrontMeshes(options.meshes);
|
|
1549
1598
|
}
|
|
1550
1599
|
if (options.mesh) {
|
|
1551
|
-
return [options.mesh];
|
|
1600
|
+
return normalizeWavefrontMeshes([options.mesh]);
|
|
1552
1601
|
}
|
|
1553
1602
|
return [];
|
|
1554
1603
|
}
|
|
@@ -1845,7 +1894,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1845
1894
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
1846
1895
|
const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
|
|
1847
1896
|
const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
|
|
1848
|
-
const meshAcceleration = accelerationBuildMode === "cpu-
|
|
1897
|
+
const meshAcceleration = accelerationBuildMode === "cpu-upload" ? createWavefrontMeshAcceleration(meshes, gpuMaterialSource) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
1849
1898
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
1850
1899
|
meshes,
|
|
1851
1900
|
options.emissiveTriangleCapacity
|
|
@@ -1855,6 +1904,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1855
1904
|
const sceneObjects = Object.freeze(
|
|
1856
1905
|
normalizeSceneObjects(options.sceneObjects, meshes.length === 0)
|
|
1857
1906
|
);
|
|
1907
|
+
const mediums = collectWavefrontMediums(options, meshes, sceneObjects);
|
|
1858
1908
|
const sceneObjectCapacity = Math.max(
|
|
1859
1909
|
sceneObjects.length,
|
|
1860
1910
|
readPositiveInteger("sceneObjectCapacity", options.sceneObjectCapacity, DEFAULT_SCENE_OBJECT_CAPACITY)
|
|
@@ -1913,6 +1963,8 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1913
1963
|
sceneObjects,
|
|
1914
1964
|
sceneObjectCount: sceneObjects.length,
|
|
1915
1965
|
sceneObjectCapacity,
|
|
1966
|
+
mediums,
|
|
1967
|
+
mediumCount: mediums.length,
|
|
1916
1968
|
accelerationBuildMode,
|
|
1917
1969
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
1918
1970
|
gpuMeshSource,
|
|
@@ -2014,29 +2066,30 @@ function packWavefrontSceneObjects(sceneObjects, capacity = sceneObjects.length)
|
|
|
2014
2066
|
uintView[u32 + 1] = object.id;
|
|
2015
2067
|
uintView[u32 + 2] = object.materialKind;
|
|
2016
2068
|
uintView[u32 + 3] = object.flags;
|
|
2017
|
-
|
|
2018
|
-
writeVec4(floatView, byteOffset + 32, [...object.
|
|
2019
|
-
writeVec4(floatView, byteOffset + 48, object.
|
|
2020
|
-
writeVec4(floatView, byteOffset + 64, object.
|
|
2021
|
-
writeVec4(floatView, byteOffset + 80,
|
|
2069
|
+
uintView[u32 + 4] = object.mediumRefId;
|
|
2070
|
+
writeVec4(floatView, byteOffset + 32, [...object.center, 0]);
|
|
2071
|
+
writeVec4(floatView, byteOffset + 48, [...object.halfExtent, 0]);
|
|
2072
|
+
writeVec4(floatView, byteOffset + 64, object.color);
|
|
2073
|
+
writeVec4(floatView, byteOffset + 80, object.emission);
|
|
2074
|
+
writeVec4(floatView, byteOffset + 96, [
|
|
2022
2075
|
object.roughness,
|
|
2023
2076
|
object.metallic,
|
|
2024
2077
|
object.opacity,
|
|
2025
2078
|
object.ior
|
|
2026
2079
|
]);
|
|
2027
|
-
writeVec4(floatView, byteOffset +
|
|
2080
|
+
writeVec4(floatView, byteOffset + 112, [
|
|
2028
2081
|
object.sheenColor[0] ?? 0,
|
|
2029
2082
|
object.sheenColor[1] ?? 0,
|
|
2030
2083
|
object.sheenColor[2] ?? 0,
|
|
2031
2084
|
object.clearcoat
|
|
2032
2085
|
]);
|
|
2033
|
-
writeVec4(floatView, byteOffset +
|
|
2086
|
+
writeVec4(floatView, byteOffset + 128, [
|
|
2034
2087
|
object.clearcoatRoughness,
|
|
2035
2088
|
object.specular,
|
|
2036
2089
|
object.transmission,
|
|
2037
|
-
|
|
2090
|
+
object.thickness
|
|
2038
2091
|
]);
|
|
2039
|
-
writeVec4(floatView, byteOffset +
|
|
2092
|
+
writeVec4(floatView, byteOffset + 144, [
|
|
2040
2093
|
object.specularColor[0] ?? 1,
|
|
2041
2094
|
object.specularColor[1] ?? 1,
|
|
2042
2095
|
object.specularColor[2] ?? 1,
|
|
@@ -2572,7 +2625,7 @@ function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
|
2572
2625
|
}
|
|
2573
2626
|
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
2574
2627
|
}
|
|
2575
|
-
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount =
|
|
2628
|
+
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount = DEFAULT_BRDF_LUT_SAMPLE_COUNT) {
|
|
2576
2629
|
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
2577
2630
|
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
2578
2631
|
if (cached) {
|
|
@@ -2944,6 +2997,80 @@ function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE)
|
|
|
2944
2997
|
height: upload.height
|
|
2945
2998
|
});
|
|
2946
2999
|
}
|
|
3000
|
+
function createMediumTextureResource(device, constants, mediums) {
|
|
3001
|
+
const normalized = Array.isArray(mediums) && mediums.length > 0 ? mediums : [{ id: 0 }];
|
|
3002
|
+
const width = Math.max(
|
|
3003
|
+
1,
|
|
3004
|
+
normalized.reduce((maximum, medium) => Math.max(maximum, medium.id ?? 0), 0) + 1
|
|
3005
|
+
);
|
|
3006
|
+
const level = {
|
|
3007
|
+
width,
|
|
3008
|
+
height: MEDIUM_TABLE_ROWS,
|
|
3009
|
+
data: new Float32Array(width * MEDIUM_TABLE_ROWS * 4)
|
|
3010
|
+
};
|
|
3011
|
+
for (const medium of normalized) {
|
|
3012
|
+
const mediumId = Math.max(0, Math.trunc(Number(medium.id) || 0));
|
|
3013
|
+
const absorptionOffset = mediumId * 4;
|
|
3014
|
+
level.data[absorptionOffset] = Math.max(0, medium.absorption?.[0] ?? 0);
|
|
3015
|
+
level.data[absorptionOffset + 1] = Math.max(0, medium.absorption?.[1] ?? 0);
|
|
3016
|
+
level.data[absorptionOffset + 2] = Math.max(0, medium.absorption?.[2] ?? 0);
|
|
3017
|
+
level.data[absorptionOffset + 3] = Math.max(0, medium.phaseModel ?? 0);
|
|
3018
|
+
const scatteringOffset = (width + mediumId) * 4;
|
|
3019
|
+
level.data[scatteringOffset] = Math.max(0, medium.scattering?.[0] ?? 0);
|
|
3020
|
+
level.data[scatteringOffset + 1] = Math.max(0, medium.scattering?.[1] ?? 0);
|
|
3021
|
+
level.data[scatteringOffset + 2] = Math.max(0, medium.scattering?.[2] ?? 0);
|
|
3022
|
+
level.data[scatteringOffset + 3] = Math.max(0, medium.density ?? 0);
|
|
3023
|
+
}
|
|
3024
|
+
const upload = createFloat16RgbaUploadFromLevels([level])[0];
|
|
3025
|
+
const texture = device.createTexture({
|
|
3026
|
+
label: "plasius.wavefront.mediumTable",
|
|
3027
|
+
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
3028
|
+
format: "rgba16float",
|
|
3029
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
3030
|
+
});
|
|
3031
|
+
device.queue.writeTexture(
|
|
3032
|
+
{ texture },
|
|
3033
|
+
upload.bytes,
|
|
3034
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3035
|
+
{ width, height: MEDIUM_TABLE_ROWS, depthOrArrayLayers: 1 }
|
|
3036
|
+
);
|
|
3037
|
+
return Object.freeze({
|
|
3038
|
+
texture,
|
|
3039
|
+
view: texture.createView(),
|
|
3040
|
+
ownsTexture: true,
|
|
3041
|
+
count: normalized.length,
|
|
3042
|
+
width
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
function mediumTablesEqual(left, right) {
|
|
3046
|
+
const leftMediums = Array.isArray(left) ? left : [];
|
|
3047
|
+
const rightMediums = Array.isArray(right) ? right : [];
|
|
3048
|
+
if (leftMediums.length !== rightMediums.length) {
|
|
3049
|
+
return false;
|
|
3050
|
+
}
|
|
3051
|
+
for (let index = 0; index < leftMediums.length; index += 1) {
|
|
3052
|
+
const leftMedium = leftMediums[index];
|
|
3053
|
+
const rightMedium = rightMediums[index];
|
|
3054
|
+
if ((leftMedium?.id ?? 0) !== (rightMedium?.id ?? 0)) {
|
|
3055
|
+
return false;
|
|
3056
|
+
}
|
|
3057
|
+
if ((leftMedium?.phaseModel ?? 0) !== (rightMedium?.phaseModel ?? 0)) {
|
|
3058
|
+
return false;
|
|
3059
|
+
}
|
|
3060
|
+
if ((leftMedium?.density ?? 0) !== (rightMedium?.density ?? 0)) {
|
|
3061
|
+
return false;
|
|
3062
|
+
}
|
|
3063
|
+
for (let component = 0; component < 3; component += 1) {
|
|
3064
|
+
if ((leftMedium?.absorption?.[component] ?? 0) !== (rightMedium?.absorption?.[component] ?? 0)) {
|
|
3065
|
+
return false;
|
|
3066
|
+
}
|
|
3067
|
+
if ((leftMedium?.scattering?.[component] ?? 0) !== (rightMedium?.scattering?.[component] ?? 0)) {
|
|
3068
|
+
return false;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
return true;
|
|
3073
|
+
}
|
|
2947
3074
|
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
2948
3075
|
const upload = createRgba8TextureUpload(atlas);
|
|
2949
3076
|
const texture = device.createTexture({
|
|
@@ -3082,6 +3209,10 @@ struct SceneObject {
|
|
|
3082
3209
|
objectId: u32,
|
|
3083
3210
|
materialKind: u32,
|
|
3084
3211
|
flags: u32,
|
|
3212
|
+
mediumRefId: u32,
|
|
3213
|
+
pad0: u32,
|
|
3214
|
+
pad1: u32,
|
|
3215
|
+
pad2: u32,
|
|
3085
3216
|
center: vec4<f32>,
|
|
3086
3217
|
halfExtent: vec4<f32>,
|
|
3087
3218
|
color: vec4<f32>,
|
|
@@ -3142,9 +3273,9 @@ struct BvhLeafRef {
|
|
|
3142
3273
|
struct ScatterResult {
|
|
3143
3274
|
direction: vec4<f32>,
|
|
3144
3275
|
pdf: f32,
|
|
3276
|
+
mediumRefId: u32,
|
|
3145
3277
|
flags: u32,
|
|
3146
3278
|
pad0: u32,
|
|
3147
|
-
pad1: u32,
|
|
3148
3279
|
};
|
|
3149
3280
|
|
|
3150
3281
|
struct MeshVertex {
|
|
@@ -3290,6 +3421,7 @@ struct EnvironmentPortal {
|
|
|
3290
3421
|
@group(0) @binding(29) var brdfLutTexture: texture_2d<f32>;
|
|
3291
3422
|
@group(0) @binding(30) var brdfLutSampler: sampler;
|
|
3292
3423
|
@group(0) @binding(31) var environmentSamplingTexture: texture_2d<f32>;
|
|
3424
|
+
@group(0) @binding(32) var mediumTableTexture: texture_2d<f32>;
|
|
3293
3425
|
|
|
3294
3426
|
fn hash_u32(value: u32) -> u32 {
|
|
3295
3427
|
var x = value;
|
|
@@ -3955,6 +4087,60 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
3955
4087
|
return environment_radiance(origin, direction);
|
|
3956
4088
|
}
|
|
3957
4089
|
|
|
4090
|
+
fn medium_dimensions() -> vec2<u32> {
|
|
4091
|
+
return textureDimensions(mediumTableTexture);
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
fn medium_valid(mediumRefId: u32) -> bool {
|
|
4095
|
+
let dimensions = medium_dimensions();
|
|
4096
|
+
return mediumRefId > 0u && mediumRefId < dimensions.x;
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
fn medium_absorption(mediumRefId: u32) -> vec3<f32> {
|
|
4100
|
+
if (!medium_valid(mediumRefId)) {
|
|
4101
|
+
return vec3<f32>(0.0);
|
|
4102
|
+
}
|
|
4103
|
+
return max(
|
|
4104
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 0), 0).xyz,
|
|
4105
|
+
vec3<f32>(0.0)
|
|
4106
|
+
);
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4109
|
+
fn medium_scattering(mediumRefId: u32) -> vec3<f32> {
|
|
4110
|
+
if (!medium_valid(mediumRefId)) {
|
|
4111
|
+
return vec3<f32>(0.0);
|
|
4112
|
+
}
|
|
4113
|
+
return max(
|
|
4114
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 1), 0).xyz,
|
|
4115
|
+
vec3<f32>(0.0)
|
|
4116
|
+
);
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
fn medium_transmittance(mediumRefId: u32, distance: f32) -> vec3<f32> {
|
|
4120
|
+
if (!medium_valid(mediumRefId) || distance <= 0.000001) {
|
|
4121
|
+
return vec3<f32>(1.0);
|
|
4122
|
+
}
|
|
4123
|
+
let extinction = medium_absorption(mediumRefId) + medium_scattering(mediumRefId);
|
|
4124
|
+
return vec3<f32>(
|
|
4125
|
+
exp(-extinction.x * distance),
|
|
4126
|
+
exp(-extinction.y * distance),
|
|
4127
|
+
exp(-extinction.z * distance)
|
|
4128
|
+
);
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
fn transmitted_medium_ref_id(ray: RayRecord, hit: HitRecord) -> u32 {
|
|
4132
|
+
if (hit.mediumRefId == 0u) {
|
|
4133
|
+
return ray.mediumRefId;
|
|
4134
|
+
}
|
|
4135
|
+
if (hit.frontFace == 1u) {
|
|
4136
|
+
return hit.mediumRefId;
|
|
4137
|
+
}
|
|
4138
|
+
if (ray.mediumRefId == hit.mediumRefId) {
|
|
4139
|
+
return 0u;
|
|
4140
|
+
}
|
|
4141
|
+
return ray.mediumRefId;
|
|
4142
|
+
}
|
|
4143
|
+
|
|
3958
4144
|
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
3959
4145
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
3960
4146
|
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
@@ -4053,11 +4239,15 @@ fn terminal_surface_environment_source(ray: RayRecord, hit: HitRecord) -> vec3<f
|
|
|
4053
4239
|
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
4054
4240
|
}
|
|
4055
4241
|
|
|
4056
|
-
fn terminal_surface_environment_contribution(
|
|
4242
|
+
fn terminal_surface_environment_contribution(
|
|
4243
|
+
ray: RayRecord,
|
|
4244
|
+
throughput: vec3<f32>,
|
|
4245
|
+
hit: HitRecord
|
|
4246
|
+
) -> vec3<f32> {
|
|
4057
4247
|
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
4058
4248
|
let occlusion = mix(0.75, 1.0, clamp(hit.occlusion, 0.0, 1.0));
|
|
4059
4249
|
return clamp_sample_radiance(
|
|
4060
|
-
|
|
4250
|
+
throughput *
|
|
4061
4251
|
surfaceColor *
|
|
4062
4252
|
terminal_surface_environment_source(ray, hit) *
|
|
4063
4253
|
occlusion
|
|
@@ -4531,7 +4721,7 @@ fn intersect_sphere(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
4531
4721
|
0xffffffffu,
|
|
4532
4722
|
object.objectId,
|
|
4533
4723
|
object.objectId,
|
|
4534
|
-
|
|
4724
|
+
object.mediumRefId
|
|
4535
4725
|
);
|
|
4536
4726
|
}
|
|
4537
4727
|
|
|
@@ -4583,7 +4773,7 @@ fn intersect_box(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
4583
4773
|
0xffffffffu,
|
|
4584
4774
|
object.objectId,
|
|
4585
4775
|
object.objectId,
|
|
4586
|
-
|
|
4776
|
+
object.mediumRefId
|
|
4587
4777
|
);
|
|
4588
4778
|
}
|
|
4589
4779
|
|
|
@@ -4834,6 +5024,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
4834
5024
|
let ray = activeQueue[index];
|
|
4835
5025
|
var nearest = 1000000.0;
|
|
4836
5026
|
var hitObject = SceneObject(
|
|
5027
|
+
0u,
|
|
5028
|
+
0u,
|
|
5029
|
+
0u,
|
|
5030
|
+
0u,
|
|
4837
5031
|
0u,
|
|
4838
5032
|
0u,
|
|
4839
5033
|
0u,
|
|
@@ -5037,9 +5231,9 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5037
5231
|
return ScatterResult(
|
|
5038
5232
|
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5039
5233
|
1.0,
|
|
5234
|
+
ray.mediumRefId,
|
|
5040
5235
|
RAY_FLAG_DELTA_SAMPLE,
|
|
5041
5236
|
0u,
|
|
5042
|
-
0u
|
|
5043
5237
|
);
|
|
5044
5238
|
}
|
|
5045
5239
|
|
|
@@ -5059,17 +5253,17 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5059
5253
|
return ScatterResult(
|
|
5060
5254
|
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5061
5255
|
1.0,
|
|
5256
|
+
ray.mediumRefId,
|
|
5062
5257
|
RAY_FLAG_DELTA_SAMPLE,
|
|
5063
5258
|
0u,
|
|
5064
|
-
0u
|
|
5065
5259
|
);
|
|
5066
5260
|
}
|
|
5067
5261
|
return ScatterResult(
|
|
5068
5262
|
vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
|
|
5069
5263
|
1.0,
|
|
5264
|
+
transmitted_medium_ref_id(ray, hit),
|
|
5070
5265
|
RAY_FLAG_DELTA_SAMPLE,
|
|
5071
5266
|
0u,
|
|
5072
|
-
0u
|
|
5073
5267
|
);
|
|
5074
5268
|
}
|
|
5075
5269
|
|
|
@@ -5084,9 +5278,9 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5084
5278
|
return ScatterResult(
|
|
5085
5279
|
vec4<f32>(guidedDirection, 0.0),
|
|
5086
5280
|
guidedPdf,
|
|
5281
|
+
ray.mediumRefId,
|
|
5087
5282
|
RAY_FLAG_GUIDED_EMISSIVE,
|
|
5088
5283
|
0u,
|
|
5089
|
-
0u
|
|
5090
5284
|
);
|
|
5091
5285
|
}
|
|
5092
5286
|
}
|
|
@@ -5094,7 +5288,7 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5094
5288
|
let guidedDirection = sample_environment_portal_direction(hit, seed + 131u, normal);
|
|
5095
5289
|
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5096
5290
|
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5097
|
-
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf,
|
|
5291
|
+
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf, ray.mediumRefId, 0u, 0u);
|
|
5098
5292
|
}
|
|
5099
5293
|
}
|
|
5100
5294
|
|
|
@@ -5128,7 +5322,7 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5128
5322
|
);
|
|
5129
5323
|
}
|
|
5130
5324
|
let pdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection), 0.000001);
|
|
5131
|
-
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf,
|
|
5325
|
+
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf, ray.mediumRefId, 0u, 0u);
|
|
5132
5326
|
}
|
|
5133
5327
|
|
|
5134
5328
|
@compute @workgroup_size(64)
|
|
@@ -5141,15 +5335,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5141
5335
|
|
|
5142
5336
|
let ray = activeQueue[index];
|
|
5143
5337
|
let hit = hits[index];
|
|
5338
|
+
let segmentTransmittance = medium_transmittance(ray.mediumRefId, hit.distance);
|
|
5339
|
+
let arrivingThroughput = ray.throughput.xyz * segmentTransmittance;
|
|
5144
5340
|
var contribution = vec3<f32>(0.0);
|
|
5145
5341
|
|
|
5146
5342
|
if (hit.hitType == 1u) {
|
|
5147
5343
|
let guidedLightWeight = select(1.0, 0.24, (ray.flags & RAY_FLAG_GUIDED_EMISSIVE) != 0u);
|
|
5148
5344
|
let sourceRadiance = max(hit.emission.xyz, hit.color.xyz) * guidedLightWeight;
|
|
5149
5345
|
if (deferred_path_resolve_enabled()) {
|
|
5150
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5346
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
5151
5347
|
} else {
|
|
5152
|
-
contribution = clamp_sample_radiance(
|
|
5348
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
5153
5349
|
accumulation[ray.rayId] =
|
|
5154
5350
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
5155
5351
|
}
|
|
@@ -5166,9 +5362,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5166
5362
|
sourceRadiance = sourceRadiance * misWeight;
|
|
5167
5363
|
}
|
|
5168
5364
|
if (deferred_path_resolve_enabled()) {
|
|
5169
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5365
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
5170
5366
|
} else {
|
|
5171
|
-
contribution = clamp_sample_radiance(
|
|
5367
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
5172
5368
|
accumulation[ray.rayId] =
|
|
5173
5369
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
5174
5370
|
}
|
|
@@ -5176,7 +5372,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5176
5372
|
return;
|
|
5177
5373
|
}
|
|
5178
5374
|
|
|
5179
|
-
let response = stabilize_surface_path_response(
|
|
5375
|
+
let response = stabilize_surface_path_response(
|
|
5376
|
+
ray,
|
|
5377
|
+
hit,
|
|
5378
|
+
surface_path_response(hit) * segmentTransmittance
|
|
5379
|
+
);
|
|
5180
5380
|
record_deferred_path_response(ray, response);
|
|
5181
5381
|
|
|
5182
5382
|
let shouldEstimateDirectEnvironment =
|
|
@@ -5184,7 +5384,22 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5184
5384
|
hit.material.z >= 0.95 &&
|
|
5185
5385
|
ray.bounce < 2u;
|
|
5186
5386
|
if (shouldEstimateDirectEnvironment) {
|
|
5187
|
-
let directEnvironment = surface_direct_environment_contribution(
|
|
5387
|
+
let directEnvironment = surface_direct_environment_contribution(
|
|
5388
|
+
RayRecord(
|
|
5389
|
+
ray.rayId,
|
|
5390
|
+
ray.parentRayId,
|
|
5391
|
+
ray.sourcePixelId,
|
|
5392
|
+
ray.sampleId,
|
|
5393
|
+
ray.bounce,
|
|
5394
|
+
ray.mediumRefId,
|
|
5395
|
+
ray.flags,
|
|
5396
|
+
0u,
|
|
5397
|
+
ray.origin,
|
|
5398
|
+
ray.direction,
|
|
5399
|
+
vec4<f32>(arrivingThroughput, ray.throughput.w)
|
|
5400
|
+
),
|
|
5401
|
+
hit
|
|
5402
|
+
);
|
|
5188
5403
|
accumulation[ray.rayId] =
|
|
5189
5404
|
accumulation[ray.rayId] + vec4<f32>(directEnvironment * sample_weight(), 0.0);
|
|
5190
5405
|
}
|
|
@@ -5193,7 +5408,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5193
5408
|
if (deferred_path_resolve_enabled()) {
|
|
5194
5409
|
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
5195
5410
|
} else {
|
|
5196
|
-
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5411
|
+
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5412
|
+
ray,
|
|
5413
|
+
arrivingThroughput,
|
|
5414
|
+
hit
|
|
5415
|
+
);
|
|
5197
5416
|
accumulation[ray.rayId] =
|
|
5198
5417
|
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
5199
5418
|
}
|
|
@@ -5208,7 +5427,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5208
5427
|
if (deferred_path_resolve_enabled()) {
|
|
5209
5428
|
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
5210
5429
|
} else {
|
|
5211
|
-
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5430
|
+
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5431
|
+
ray,
|
|
5432
|
+
arrivingThroughput,
|
|
5433
|
+
hit
|
|
5434
|
+
);
|
|
5212
5435
|
accumulation[ray.rayId] =
|
|
5213
5436
|
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
5214
5437
|
}
|
|
@@ -5222,7 +5445,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5222
5445
|
ray.sourcePixelId,
|
|
5223
5446
|
ray.sampleId,
|
|
5224
5447
|
ray.bounce + 1u,
|
|
5225
|
-
|
|
5448
|
+
scatter.mediumRefId,
|
|
5226
5449
|
scatter.flags,
|
|
5227
5450
|
0u,
|
|
5228
5451
|
vec4<f32>(offset_origin(hit.position.xyz, hit.shadingNormal.xyz), 1.0),
|
|
@@ -5483,9 +5706,22 @@ function nowMs() {
|
|
|
5483
5706
|
}
|
|
5484
5707
|
return Date.now();
|
|
5485
5708
|
}
|
|
5486
|
-
function
|
|
5709
|
+
function estimateAccelerationBuildWaitFactor(config) {
|
|
5710
|
+
if (config?.gpuAccelerationBuildRequired !== true) {
|
|
5711
|
+
return 1;
|
|
5712
|
+
}
|
|
5713
|
+
const bvhSortStageCount = Array.isArray(config?.bvhSortStages) ? config.bvhSortStages.length : 0;
|
|
5714
|
+
const bvhBuildLevelCount = Array.isArray(config?.bvhBuildLevels) ? config.bvhBuildLevels.length : 0;
|
|
5715
|
+
const accelerationStageCount = 2 + bvhSortStageCount + bvhBuildLevelCount;
|
|
5716
|
+
return Math.max(1, 1 + accelerationStageCount / 96);
|
|
5717
|
+
}
|
|
5718
|
+
function estimateSubmittedGpuWorkTiming(config, tileCount, overrideTimeoutMs = null, options = {}) {
|
|
5487
5719
|
if (Number.isFinite(overrideTimeoutMs)) {
|
|
5488
|
-
|
|
5720
|
+
const overrideMs = Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
5721
|
+
return Object.freeze({
|
|
5722
|
+
timeoutMs: overrideMs,
|
|
5723
|
+
maxWaitMs: overrideMs
|
|
5724
|
+
});
|
|
5489
5725
|
}
|
|
5490
5726
|
const samplesPerPixel = Math.max(
|
|
5491
5727
|
1,
|
|
@@ -5496,10 +5732,26 @@ function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs
|
|
|
5496
5732
|
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
5497
5733
|
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
5498
5734
|
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
5499
|
-
|
|
5735
|
+
const triangleCount = Math.max(0, Number(config?.triangleCount ?? 0));
|
|
5736
|
+
const geometryFactor = Math.max(1, triangleCount / 131072);
|
|
5737
|
+
const includeAccelerationBuild = options.includeAccelerationBuild === true;
|
|
5738
|
+
const accelerationFactor = includeAccelerationBuild ? estimateAccelerationBuildWaitFactor(config) : 1;
|
|
5739
|
+
const estimatedWindowMs = Math.round(
|
|
5740
|
+
(GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5) * geometryFactor * accelerationFactor
|
|
5741
|
+
);
|
|
5742
|
+
const timeoutMs = Math.min(
|
|
5500
5743
|
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
5501
|
-
GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
5744
|
+
Math.max(GPU_SUBMITTED_WORK_TIMEOUT_MS, estimatedWindowMs)
|
|
5745
|
+
);
|
|
5746
|
+
const maxWaitMultiplier = includeAccelerationBuild ? 3 : 2;
|
|
5747
|
+
const maxWaitMs = Math.min(
|
|
5748
|
+
GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
|
|
5749
|
+
Math.max(timeoutMs, estimatedWindowMs * maxWaitMultiplier)
|
|
5502
5750
|
);
|
|
5751
|
+
return Object.freeze({
|
|
5752
|
+
timeoutMs,
|
|
5753
|
+
maxWaitMs
|
|
5754
|
+
});
|
|
5503
5755
|
}
|
|
5504
5756
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
5505
5757
|
assertAnalyticDisplayQualityPolicy(options);
|
|
@@ -5711,6 +5963,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
5711
5963
|
config.environmentMap,
|
|
5712
5964
|
config.environmentColor
|
|
5713
5965
|
);
|
|
5966
|
+
let mediumTextureResource = createMediumTextureResource(
|
|
5967
|
+
device,
|
|
5968
|
+
constants,
|
|
5969
|
+
config.mediums
|
|
5970
|
+
);
|
|
5714
5971
|
config = Object.freeze({
|
|
5715
5972
|
...config,
|
|
5716
5973
|
environmentMap: Object.freeze({
|
|
@@ -5797,7 +6054,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
5797
6054
|
{ binding: 28, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
5798
6055
|
{ binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5799
6056
|
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
5800
|
-
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
|
|
6057
|
+
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6058
|
+
{ binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
|
|
5801
6059
|
]
|
|
5802
6060
|
});
|
|
5803
6061
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -5984,14 +6242,18 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
5984
6242
|
{ binding: 28, resource: materialAtlasSampler },
|
|
5985
6243
|
{ binding: 29, resource: brdfLutResource.view },
|
|
5986
6244
|
{ binding: 30, resource: brdfLutResource.sampler },
|
|
5987
|
-
{ binding: 31, resource: environmentSamplingResource.view }
|
|
6245
|
+
{ binding: 31, resource: environmentSamplingResource.view },
|
|
6246
|
+
{ binding: 32, resource: mediumTextureResource.view }
|
|
5988
6247
|
]
|
|
5989
6248
|
});
|
|
5990
6249
|
}
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
6250
|
+
function createTraceBindGroups() {
|
|
6251
|
+
return [
|
|
6252
|
+
createTraceBindGroup(activeQueue, nextQueue, "plasius.wavefront.bind.activeNext"),
|
|
6253
|
+
createTraceBindGroup(nextQueue, activeQueue, "plasius.wavefront.bind.nextActive")
|
|
6254
|
+
];
|
|
6255
|
+
}
|
|
6256
|
+
let bindGroups = createTraceBindGroups();
|
|
5995
6257
|
const bvhBuildBindGroup = device.createBindGroup({
|
|
5996
6258
|
label: "plasius.wavefront.bind.bvhBuild",
|
|
5997
6259
|
layout: accelerationBindGroupLayout,
|
|
@@ -6169,6 +6431,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6169
6431
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6170
6432
|
environmentPortalCount: config.environmentPortalCount,
|
|
6171
6433
|
environmentPortalMode: config.environmentPortalMode,
|
|
6434
|
+
mediumCount: config.mediumCount,
|
|
6172
6435
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6173
6436
|
deferredPathResolve: config.deferredPathResolve,
|
|
6174
6437
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -6464,6 +6727,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6464
6727
|
1,
|
|
6465
6728
|
Number.isFinite(options2.timeoutMs) ? Number(options2.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
6466
6729
|
);
|
|
6730
|
+
const maxWaitMs = Math.max(
|
|
6731
|
+
timeoutMs,
|
|
6732
|
+
Number.isFinite(options2.maxWaitMs) ? Number(options2.maxWaitMs) : timeoutMs
|
|
6733
|
+
);
|
|
6467
6734
|
const allowTimeout = options2.allowTimeout !== false;
|
|
6468
6735
|
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
6469
6736
|
() => ({ status: "done" }),
|
|
@@ -6476,43 +6743,57 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6476
6743
|
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
6477
6744
|
);
|
|
6478
6745
|
}) : null;
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
if (
|
|
6484
|
-
|
|
6746
|
+
const startedAtMs = nowMs();
|
|
6747
|
+
while (true) {
|
|
6748
|
+
const elapsedMs = Math.max(0, nowMs() - startedAtMs);
|
|
6749
|
+
const remainingMs = Math.max(0, maxWaitMs - elapsedMs);
|
|
6750
|
+
if (remainingMs <= 0) {
|
|
6751
|
+
if (!allowTimeout) {
|
|
6752
|
+
throw new Error(`Timed out after ${Math.round(maxWaitMs)} ms waiting for submitted GPU work.`);
|
|
6753
|
+
}
|
|
6754
|
+
console.warn(
|
|
6755
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${Math.round(maxWaitMs)} ms; continuing.`
|
|
6756
|
+
);
|
|
6757
|
+
return false;
|
|
6485
6758
|
}
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
)
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6759
|
+
const waitWindowMs = Math.max(1, Math.min(timeoutMs, remainingMs));
|
|
6760
|
+
let timeoutHandle = null;
|
|
6761
|
+
let resolveTimeoutPromise = null;
|
|
6762
|
+
let timeoutSettled = false;
|
|
6763
|
+
const settleTimeoutPromise = (value) => {
|
|
6764
|
+
if (timeoutSettled) {
|
|
6765
|
+
return;
|
|
6766
|
+
}
|
|
6767
|
+
timeoutSettled = true;
|
|
6768
|
+
resolveTimeoutPromise?.(value);
|
|
6769
|
+
};
|
|
6770
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
6771
|
+
resolveTimeoutPromise = resolve;
|
|
6772
|
+
timeoutHandle = setTimeout(
|
|
6773
|
+
() => settleTimeoutPromise({ status: "timeout" }),
|
|
6774
|
+
waitWindowMs
|
|
6775
|
+
);
|
|
6776
|
+
});
|
|
6777
|
+
let result;
|
|
6778
|
+
try {
|
|
6779
|
+
result = await Promise.race(
|
|
6780
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
6781
|
+
);
|
|
6782
|
+
} finally {
|
|
6783
|
+
if (timeoutHandle !== null) {
|
|
6784
|
+
clearTimeout(timeoutHandle);
|
|
6785
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
6786
|
+
}
|
|
6502
6787
|
}
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6788
|
+
if (result?.status === "done") {
|
|
6789
|
+
return true;
|
|
6790
|
+
}
|
|
6791
|
+
if (result?.status !== "timeout") {
|
|
6792
|
+
return true;
|
|
6507
6793
|
}
|
|
6508
|
-
console.warn(
|
|
6509
|
-
`[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
|
|
6510
|
-
);
|
|
6511
|
-
return false;
|
|
6512
6794
|
}
|
|
6513
|
-
return true;
|
|
6514
6795
|
}
|
|
6515
|
-
function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
6796
|
+
function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel, optionsForFrame = {}) {
|
|
6516
6797
|
const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
|
|
6517
6798
|
const denoisePassCount = config.denoise ? renderedSamplesPerPixel < 4 ? 2 : 1 : 0;
|
|
6518
6799
|
const tailPassCount = denoisePassCount + 1;
|
|
@@ -6522,17 +6803,42 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6522
6803
|
Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) / Math.max(samplePassesPerSample, 1)
|
|
6523
6804
|
)
|
|
6524
6805
|
);
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6806
|
+
const sampleRangeStart = clamp(
|
|
6807
|
+
readNonNegativeInteger("sampleRangeStart", optionsForFrame.sampleRangeStart, 0),
|
|
6808
|
+
0,
|
|
6809
|
+
renderedSamplesPerPixel
|
|
6810
|
+
);
|
|
6811
|
+
const sampleRangeEnd = clamp(
|
|
6812
|
+
readPositiveInteger("sampleRangeEnd", optionsForFrame.sampleRangeEnd, renderedSamplesPerPixel),
|
|
6813
|
+
sampleRangeStart,
|
|
6814
|
+
renderedSamplesPerPixel
|
|
6815
|
+
);
|
|
6816
|
+
const includeDenoise = optionsForFrame.includeDenoise === true;
|
|
6817
|
+
const includePresent = optionsForFrame.includePresent === true;
|
|
6818
|
+
const tileStartIndex = clamp(
|
|
6819
|
+
readNonNegativeInteger("tileStartIndex", optionsForFrame.tileStartIndex, 0),
|
|
6820
|
+
0,
|
|
6821
|
+
tiles.length
|
|
6822
|
+
);
|
|
6823
|
+
const tileEndIndex = clamp(
|
|
6824
|
+
readPositiveInteger("tileEndIndex", optionsForFrame.tileEndIndex, tiles.length),
|
|
6825
|
+
tileStartIndex,
|
|
6826
|
+
tiles.length
|
|
6827
|
+
);
|
|
6828
|
+
let submissionCount = Math.max(
|
|
6829
|
+
0,
|
|
6830
|
+
readNonNegativeInteger("startingSubmissionCount", optionsForFrame.startingSubmissionCount, 0)
|
|
6831
|
+
);
|
|
6832
|
+
let slot = Math.max(0, readNonNegativeInteger("startingSlot", optionsForFrame.startingSlot, 0));
|
|
6833
|
+
for (const tile of tiles.slice(tileStartIndex, tileEndIndex)) {
|
|
6834
|
+
for (let sampleStart = sampleRangeStart; sampleStart < sampleRangeEnd; sampleStart += sampleBatchSize) {
|
|
6835
|
+
const sampleEnd = Math.min(sampleRangeEnd, sampleStart + sampleBatchSize);
|
|
6529
6836
|
const batch = createGpuSubmissionBatcher({
|
|
6530
6837
|
device,
|
|
6531
6838
|
frameIndex,
|
|
6532
6839
|
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6533
6840
|
startingSubmissionCount: submissionCount
|
|
6534
6841
|
});
|
|
6535
|
-
let slot = 0;
|
|
6536
6842
|
for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
|
|
6537
6843
|
const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6538
6844
|
sampleIndex,
|
|
@@ -6549,41 +6855,50 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6549
6855
|
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
6550
6856
|
}
|
|
6551
6857
|
}
|
|
6552
|
-
if (!config.deferredPathResolve &&
|
|
6858
|
+
if (!config.deferredPathResolve && sampleRangeEnd >= renderedSamplesPerPixel) {
|
|
6553
6859
|
const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6554
6860
|
sampleIndex: 0,
|
|
6555
6861
|
sampleWeight: 1 / renderedSamplesPerPixel
|
|
6556
6862
|
});
|
|
6863
|
+
slot += 1;
|
|
6557
6864
|
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
6558
6865
|
}
|
|
6559
6866
|
batch.flush();
|
|
6560
6867
|
submissionCount += batch.getSubmissionCount();
|
|
6561
6868
|
}
|
|
6562
6869
|
}
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6567
|
-
startingSubmissionCount: submissionCount
|
|
6568
|
-
});
|
|
6569
|
-
if (config.denoise) {
|
|
6570
|
-
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
6571
|
-
0,
|
|
6572
|
-
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
6870
|
+
if (includeDenoise || includePresent) {
|
|
6871
|
+
const tail = createGpuSubmissionBatcher({
|
|
6872
|
+
device,
|
|
6573
6873
|
frameIndex,
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
denoiseConfigOffset
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6874
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6875
|
+
startingSubmissionCount: submissionCount
|
|
6876
|
+
});
|
|
6877
|
+
if (includeDenoise && config.denoise) {
|
|
6878
|
+
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
6879
|
+
slot,
|
|
6880
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
6881
|
+
frameIndex,
|
|
6882
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6883
|
+
);
|
|
6884
|
+
slot += 1;
|
|
6885
|
+
encodeDenoise(
|
|
6886
|
+
tail.reserve(denoisePassCount),
|
|
6887
|
+
denoiseConfigOffset,
|
|
6888
|
+
parallelism,
|
|
6889
|
+
renderedSamplesPerPixel
|
|
6890
|
+
);
|
|
6891
|
+
}
|
|
6892
|
+
if (includePresent) {
|
|
6893
|
+
encodePresent(tail.reserve(1));
|
|
6894
|
+
}
|
|
6895
|
+
tail.flush();
|
|
6896
|
+
submissionCount += tail.getSubmissionCount();
|
|
6582
6897
|
}
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6898
|
+
return Object.freeze({
|
|
6899
|
+
submissionCount,
|
|
6900
|
+
slot
|
|
6901
|
+
});
|
|
6587
6902
|
}
|
|
6588
6903
|
async function readOutputProbe(optionsForProbe = {}) {
|
|
6589
6904
|
const mapMode = constants.map;
|
|
@@ -6629,24 +6944,59 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6629
6944
|
const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
|
|
6630
6945
|
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
6631
6946
|
const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
6632
|
-
const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
|
|
6633
|
-
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6634
|
-
tiles.length,
|
|
6635
|
-
renderOptions.submittedWorkTimeoutMs
|
|
6636
|
-
);
|
|
6637
6947
|
const frameStartTimeMs = nowMs();
|
|
6638
|
-
const submissionWaitOptions = awaitGPUCompletion ? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false } : { timeoutMs: submittedWorkTimeoutMs };
|
|
6639
6948
|
let frameStats;
|
|
6640
6949
|
if (useThrottledHighSamplePath) {
|
|
6641
6950
|
frame += 1;
|
|
6642
6951
|
const frameIndex = frame + config.frameIndex;
|
|
6643
6952
|
const parallelismCounters = createGpuParallelismCounters();
|
|
6644
6953
|
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6954
|
+
let frameSubmissionCount = 0;
|
|
6955
|
+
let frameConfigSlot = 0;
|
|
6956
|
+
if (accelerationBuildSubmitted) {
|
|
6957
|
+
const accelerationWaitOptions = {
|
|
6958
|
+
...estimateSubmittedGpuWorkTiming(
|
|
6959
|
+
{ ...config, renderedSamplesPerPixel: 1 },
|
|
6960
|
+
1,
|
|
6961
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
6962
|
+
{ includeAccelerationBuild: true }
|
|
6963
|
+
),
|
|
6964
|
+
allowTimeout: false
|
|
6965
|
+
};
|
|
6966
|
+
await waitForSubmittedGpuWork(accelerationWaitOptions);
|
|
6967
|
+
}
|
|
6968
|
+
for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
|
|
6969
|
+
const tileRangeDispatch = dispatchFrameAwaitingGpu(
|
|
6970
|
+
frameIndex,
|
|
6971
|
+
parallelismCounters,
|
|
6972
|
+
samplingPlan.renderedSamplesPerPixel,
|
|
6973
|
+
{
|
|
6974
|
+
sampleRangeStart: 0,
|
|
6975
|
+
sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
|
|
6976
|
+
tileStartIndex: tileIndex,
|
|
6977
|
+
tileEndIndex: tileIndex + 1,
|
|
6978
|
+
startingSubmissionCount: frameSubmissionCount,
|
|
6979
|
+
startingSlot: frameConfigSlot,
|
|
6980
|
+
includeDenoise: tileIndex + 1 >= tiles.length,
|
|
6981
|
+
includePresent: tileIndex + 1 >= tiles.length
|
|
6982
|
+
}
|
|
6983
|
+
);
|
|
6984
|
+
frameSubmissionCount = tileRangeDispatch.submissionCount;
|
|
6985
|
+
frameConfigSlot = tileRangeDispatch.slot;
|
|
6986
|
+
const tileWaitOptions = {
|
|
6987
|
+
...estimateSubmittedGpuWorkTiming(
|
|
6988
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6989
|
+
1,
|
|
6990
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
6991
|
+
{
|
|
6992
|
+
includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
|
|
6993
|
+
includePresent: tileIndex + 1 >= tiles.length
|
|
6994
|
+
}
|
|
6995
|
+
),
|
|
6996
|
+
allowTimeout: false
|
|
6997
|
+
};
|
|
6998
|
+
await waitForSubmittedGpuWork(tileWaitOptions);
|
|
6999
|
+
}
|
|
6650
7000
|
frameStats = createFrameStats({
|
|
6651
7001
|
frameIndex,
|
|
6652
7002
|
accelerationBuildSubmitted,
|
|
@@ -6658,10 +7008,24 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6658
7008
|
budgetConstrained: samplingPlan.budgetConstrained
|
|
6659
7009
|
});
|
|
6660
7010
|
} else {
|
|
7011
|
+
const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
|
|
7012
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
7013
|
+
tiles.length,
|
|
7014
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
7015
|
+
{ includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
|
|
7016
|
+
);
|
|
7017
|
+
const submissionWaitOptions = awaitGPUCompletion ? {
|
|
7018
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
7019
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs,
|
|
7020
|
+
allowTimeout: false
|
|
7021
|
+
} : {
|
|
7022
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
7023
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs
|
|
7024
|
+
};
|
|
6661
7025
|
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
7026
|
+
if (awaitGPUCompletion) {
|
|
7027
|
+
await waitForSubmittedGpuWork(submissionWaitOptions);
|
|
7028
|
+
}
|
|
6665
7029
|
}
|
|
6666
7030
|
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
6667
7031
|
if (awaitGPUCompletion) {
|
|
@@ -6716,10 +7080,22 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6716
7080
|
...overrides
|
|
6717
7081
|
});
|
|
6718
7082
|
}
|
|
7083
|
+
function rebuildMediumResources(nextConfig) {
|
|
7084
|
+
const previousMediumTextureResource = mediumTextureResource;
|
|
7085
|
+
mediumTextureResource = createMediumTextureResource(device, constants, nextConfig.mediums);
|
|
7086
|
+
bindGroups = createTraceBindGroups();
|
|
7087
|
+
if (previousMediumTextureResource?.ownsTexture) {
|
|
7088
|
+
previousMediumTextureResource.texture?.destroy?.();
|
|
7089
|
+
}
|
|
7090
|
+
}
|
|
6719
7091
|
function updateSceneObjects(sceneObjects) {
|
|
6720
7092
|
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
6721
7093
|
packedScene = nextPackedScene;
|
|
6722
|
-
|
|
7094
|
+
const nextConfig = rebuildLiveConfig();
|
|
7095
|
+
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
7096
|
+
rebuildMediumResources(nextConfig);
|
|
7097
|
+
}
|
|
7098
|
+
config = nextConfig;
|
|
6723
7099
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
6724
7100
|
return config;
|
|
6725
7101
|
}
|
|
@@ -6743,6 +7119,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6743
7119
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6744
7120
|
environmentPortalCount: config.environmentPortalCount,
|
|
6745
7121
|
environmentPortalMode: config.environmentPortalMode,
|
|
7122
|
+
mediumCount: config.mediumCount,
|
|
6746
7123
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6747
7124
|
deferredPathResolve: config.deferredPathResolve,
|
|
6748
7125
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -6783,6 +7160,9 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6783
7160
|
if (environmentSamplingResource.ownsTexture) {
|
|
6784
7161
|
environmentSamplingResource.texture?.destroy?.();
|
|
6785
7162
|
}
|
|
7163
|
+
if (mediumTextureResource.ownsTexture) {
|
|
7164
|
+
mediumTextureResource.texture?.destroy?.();
|
|
7165
|
+
}
|
|
6786
7166
|
brdfLutResource.texture?.destroy?.();
|
|
6787
7167
|
if (baseColorAtlasResource.ownsTexture) {
|
|
6788
7168
|
baseColorAtlasResource.texture?.destroy?.();
|