@plasius/gpu-renderer 0.1.14 → 0.2.0
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 +28 -0
- package/README.md +21 -0
- package/dist/index.cjs +643 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +640 -76
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.d.ts +144 -1
- package/src/index.js +3 -0
- package/src/wavefront-compute.js +684 -73
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var DEFAULT_MAX_DEPTH = 6;
|
|
|
5
5
|
var DEFAULT_TILE_SIZE = 128;
|
|
6
6
|
var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
7
7
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
8
|
+
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
8
9
|
var WORKGROUP_SIZE = 64;
|
|
9
10
|
var RAY_RECORD_BYTES = 80;
|
|
10
11
|
var HIT_RECORD_BYTES = 208;
|
|
@@ -15,9 +16,11 @@ var TRIANGLE_RECORD_BYTES = 208;
|
|
|
15
16
|
var BVH_NODE_RECORD_BYTES = 48;
|
|
16
17
|
var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
17
18
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
19
|
+
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
18
20
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
19
|
-
var CONFIG_BUFFER_BYTES =
|
|
21
|
+
var CONFIG_BUFFER_BYTES = 272;
|
|
20
22
|
var COUNTER_BUFFER_BYTES = 16;
|
|
23
|
+
var TRACE_STORAGE_BUFFER_BINDINGS = 9;
|
|
21
24
|
var MATERIAL_DIFFUSE = 0;
|
|
22
25
|
var MATERIAL_METAL = 1;
|
|
23
26
|
var MATERIAL_DIELECTRIC = 2;
|
|
@@ -44,6 +47,7 @@ var DEFAULT_ENVIRONMENT_LIGHTING = Object.freeze({
|
|
|
44
47
|
});
|
|
45
48
|
var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
46
49
|
workgroupSize: WORKGROUP_SIZE,
|
|
50
|
+
traceStorageBufferBindings: TRACE_STORAGE_BUFFER_BINDINGS,
|
|
47
51
|
rayRecordBytes: RAY_RECORD_BYTES,
|
|
48
52
|
hitRecordBytes: HIT_RECORD_BYTES,
|
|
49
53
|
sceneObjectRecordBytes: SCENE_OBJECT_RECORD_BYTES,
|
|
@@ -54,6 +58,7 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
54
58
|
bvhLeafReferenceRecordBytes: BVH_LEAF_REF_RECORD_BYTES,
|
|
55
59
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
56
60
|
emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
61
|
+
environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
57
62
|
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES
|
|
58
63
|
});
|
|
59
64
|
var wavefrontSceneObjectKinds = Object.freeze({
|
|
@@ -145,6 +150,9 @@ function subtract(a, b) {
|
|
|
145
150
|
function scale(a, scalar) {
|
|
146
151
|
return [a[0] * scalar, a[1] * scalar, a[2] * scalar];
|
|
147
152
|
}
|
|
153
|
+
function dot(a, b) {
|
|
154
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
155
|
+
}
|
|
148
156
|
function cross(a, b) {
|
|
149
157
|
return [
|
|
150
158
|
a[1] * b[2] - a[2] * b[1],
|
|
@@ -159,6 +167,25 @@ function normalize(value, fallback = [0, 0, 1]) {
|
|
|
159
167
|
}
|
|
160
168
|
return [value[0] / length, value[1] / length, value[2] / length];
|
|
161
169
|
}
|
|
170
|
+
function hashUint32(value) {
|
|
171
|
+
let x = value >>> 0;
|
|
172
|
+
x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
|
|
173
|
+
x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
|
|
174
|
+
return (x >>> 16 ^ x) >>> 0;
|
|
175
|
+
}
|
|
176
|
+
function mixSeed(pixelId, sampleId, bounce, frameIndex, dimension) {
|
|
177
|
+
let x = (pixelId >>> 0) * 747796405 ^ (sampleId >>> 0) * 2891336453 ^ (bounce >>> 0) * 277803737 ^ (frameIndex >>> 0) * 1442695041 ^ (dimension >>> 0) * 1597334677;
|
|
178
|
+
x >>>= 0;
|
|
179
|
+
x ^= x >>> 16;
|
|
180
|
+
x = x * 2146121005 >>> 0;
|
|
181
|
+
x ^= x >>> 15;
|
|
182
|
+
x = x * 2221713035 >>> 0;
|
|
183
|
+
x ^= x >>> 16;
|
|
184
|
+
return x >>> 0;
|
|
185
|
+
}
|
|
186
|
+
function random01FromSeed(seed) {
|
|
187
|
+
return (hashUint32(seed) & 16777215) / 16777215;
|
|
188
|
+
}
|
|
162
189
|
function getArrayLikeLength(value) {
|
|
163
190
|
return Array.isArray(value) || ArrayBuffer.isView(value) ? value.length : 0;
|
|
164
191
|
}
|
|
@@ -757,6 +784,150 @@ function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
|
|
|
757
784
|
exposure: Math.max(1e-4, readFiniteNumber("environmentLighting.exposure", source.exposure, DEFAULT_ENVIRONMENT_LIGHTING.exposure))
|
|
758
785
|
});
|
|
759
786
|
}
|
|
787
|
+
function evaluateReferenceEnvironmentRadiance(config, origin, direction) {
|
|
788
|
+
void origin;
|
|
789
|
+
const rayDirection = normalize(direction, [0, 1, 0]);
|
|
790
|
+
const upFactor = clamp(rayDirection[1] * 0.5 + 0.5, 0, 1);
|
|
791
|
+
const sunDirection = normalize(
|
|
792
|
+
config.environmentLighting?.sunDirection ?? DEFAULT_ENVIRONMENT_LIGHTING.sunDirection,
|
|
793
|
+
DEFAULT_ENVIRONMENT_LIGHTING.sunDirection
|
|
794
|
+
);
|
|
795
|
+
const sunGlow = Math.pow(clamp(dot(rayDirection, sunDirection), 0, 1), 192);
|
|
796
|
+
const horizonColor = config.environmentLighting?.horizonColor ?? DEFAULT_ENVIRONMENT_LIGHTING.horizonColor;
|
|
797
|
+
const zenithColor = config.environmentLighting?.zenithColor ?? DEFAULT_ENVIRONMENT_LIGHTING.zenithColor;
|
|
798
|
+
const sunColor = config.environmentLighting?.sunColor ?? DEFAULT_ENVIRONMENT_LIGHTING.sunColor;
|
|
799
|
+
const intensity = Math.max(
|
|
800
|
+
1e-4,
|
|
801
|
+
Number(config.environmentLighting?.intensity ?? DEFAULT_ENVIRONMENT_LIGHTING.intensity)
|
|
802
|
+
);
|
|
803
|
+
return Object.freeze([
|
|
804
|
+
(horizonColor[0] * (1 - upFactor) + zenithColor[0] * upFactor + sunColor[0] * sunGlow) * intensity,
|
|
805
|
+
(horizonColor[1] * (1 - upFactor) + zenithColor[1] * upFactor + sunColor[1] * sunGlow) * intensity,
|
|
806
|
+
(horizonColor[2] * (1 - upFactor) + zenithColor[2] * upFactor + sunColor[2] * sunGlow) * intensity,
|
|
807
|
+
1
|
|
808
|
+
]);
|
|
809
|
+
}
|
|
810
|
+
function resolveEnvironmentPortalMode(value, hasPortals) {
|
|
811
|
+
if (value === void 0 || value === null) {
|
|
812
|
+
return hasPortals ? 2 : 0;
|
|
813
|
+
}
|
|
814
|
+
if (Number.isInteger(value) && value >= 0 && value <= 2) {
|
|
815
|
+
return value;
|
|
816
|
+
}
|
|
817
|
+
if (value === "disabled") {
|
|
818
|
+
return 0;
|
|
819
|
+
}
|
|
820
|
+
if (value === "guide") {
|
|
821
|
+
return 1;
|
|
822
|
+
}
|
|
823
|
+
if (value === "guide-and-gate" || value === "gate") {
|
|
824
|
+
return 2;
|
|
825
|
+
}
|
|
826
|
+
throw new Error(
|
|
827
|
+
"environmentPortalMode must be disabled, guide, guide-and-gate, or an integer between 0 and 2."
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
function orthogonalPortalTangent(normal) {
|
|
831
|
+
if (Math.abs(normal[1]) < 0.92) {
|
|
832
|
+
return normalize(cross([0, 1, 0], normal), [1, 0, 0]);
|
|
833
|
+
}
|
|
834
|
+
return normalize(cross([1, 0, 0], normal), [0, 0, 1]);
|
|
835
|
+
}
|
|
836
|
+
function resolvePortalTangent(value, normal) {
|
|
837
|
+
const fallback = orthogonalPortalTangent(normal);
|
|
838
|
+
const tangent = asUnitVec3(value, fallback);
|
|
839
|
+
const projected = subtract(tangent, scale(normal, dot(tangent, normal)));
|
|
840
|
+
return normalize(projected, fallback);
|
|
841
|
+
}
|
|
842
|
+
function readPositiveFiniteNumber(name, value, fallback) {
|
|
843
|
+
const numeric = readFiniteNumber(name, value, fallback);
|
|
844
|
+
if (numeric <= 0) {
|
|
845
|
+
throw new Error(`${name} must be a positive finite number.`);
|
|
846
|
+
}
|
|
847
|
+
return numeric;
|
|
848
|
+
}
|
|
849
|
+
function readPortalExtent(name, value, halfName, halfValue) {
|
|
850
|
+
if (value !== void 0 && value !== null) {
|
|
851
|
+
return readPositiveFiniteNumber(name, value, 1);
|
|
852
|
+
}
|
|
853
|
+
return readPositiveFiniteNumber(halfName, halfValue, 0.5) * 2;
|
|
854
|
+
}
|
|
855
|
+
function normalizeEnvironmentPortal(portal, index) {
|
|
856
|
+
if (!portal || typeof portal !== "object") {
|
|
857
|
+
throw new Error(`environmentPortals[${index}] must be an object.`);
|
|
858
|
+
}
|
|
859
|
+
const shape = portal.shape ?? portal.kind ?? "rectangle";
|
|
860
|
+
if (shape !== "rectangle") {
|
|
861
|
+
throw new Error(`environmentPortals[${index}].shape must be "rectangle".`);
|
|
862
|
+
}
|
|
863
|
+
const position = asVec3(portal.position ?? portal.center, [0, 0, 0]);
|
|
864
|
+
const normal = asUnitVec3(portal.normal, [0, 0, 1]);
|
|
865
|
+
const tangent = resolvePortalTangent(portal.tangent, normal);
|
|
866
|
+
const bitangent = normalize(cross(normal, tangent), [0, 1, 0]);
|
|
867
|
+
const width = readPortalExtent(
|
|
868
|
+
`environmentPortals[${index}].width`,
|
|
869
|
+
portal.width,
|
|
870
|
+
`environmentPortals[${index}].halfWidth`,
|
|
871
|
+
portal.halfWidth
|
|
872
|
+
);
|
|
873
|
+
const height = readPortalExtent(
|
|
874
|
+
`environmentPortals[${index}].height`,
|
|
875
|
+
portal.height,
|
|
876
|
+
`environmentPortals[${index}].halfHeight`,
|
|
877
|
+
portal.halfHeight
|
|
878
|
+
);
|
|
879
|
+
const radianceScale = Math.max(
|
|
880
|
+
0,
|
|
881
|
+
readFiniteNumber(
|
|
882
|
+
`environmentPortals[${index}].radianceScale`,
|
|
883
|
+
portal.radianceScale ?? portal.intensity,
|
|
884
|
+
1
|
|
885
|
+
)
|
|
886
|
+
);
|
|
887
|
+
return Object.freeze({
|
|
888
|
+
kind: 1,
|
|
889
|
+
flags: portal.twoSided === false ? 0 : 1,
|
|
890
|
+
position: Object.freeze([position[0], position[1], position[2], width * height]),
|
|
891
|
+
normal: Object.freeze([normal[0], normal[1], normal[2], radianceScale]),
|
|
892
|
+
tangent: Object.freeze([tangent[0], tangent[1], tangent[2], width * 0.5]),
|
|
893
|
+
bitangent: Object.freeze([bitangent[0], bitangent[1], bitangent[2], height * 0.5]),
|
|
894
|
+
color: Object.freeze(asColor(portal.color, [1, 1, 1, 1]))
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
function normalizeEnvironmentPortals(value) {
|
|
898
|
+
if (value === void 0 || value === null) {
|
|
899
|
+
return Object.freeze([]);
|
|
900
|
+
}
|
|
901
|
+
if (!Array.isArray(value)) {
|
|
902
|
+
throw new Error("environmentPortals must be an array when provided.");
|
|
903
|
+
}
|
|
904
|
+
return Object.freeze(value.map(normalizeEnvironmentPortal));
|
|
905
|
+
}
|
|
906
|
+
function packEnvironmentPortals(portals, capacity) {
|
|
907
|
+
const bytes = new ArrayBuffer(capacity * ENVIRONMENT_PORTAL_RECORD_BYTES);
|
|
908
|
+
const data = new DataView(bytes);
|
|
909
|
+
const floatView = new Float32Array(bytes);
|
|
910
|
+
portals.forEach((portal, index) => {
|
|
911
|
+
const byteOffset = index * ENVIRONMENT_PORTAL_RECORD_BYTES;
|
|
912
|
+
const floatOffset = byteOffset / Float32Array.BYTES_PER_ELEMENT;
|
|
913
|
+
data.setUint32(byteOffset, portal.kind, true);
|
|
914
|
+
data.setUint32(byteOffset + 4, portal.flags, true);
|
|
915
|
+
data.setUint32(byteOffset + 8, 0, true);
|
|
916
|
+
data.setUint32(byteOffset + 12, 0, true);
|
|
917
|
+
writeVec4(floatView, floatOffset + 4, portal.position);
|
|
918
|
+
writeVec4(floatView, floatOffset + 8, portal.normal);
|
|
919
|
+
writeVec4(floatView, floatOffset + 12, portal.tangent);
|
|
920
|
+
writeVec4(floatView, floatOffset + 16, portal.bitangent);
|
|
921
|
+
writeVec4(floatView, floatOffset + 20, portal.color);
|
|
922
|
+
});
|
|
923
|
+
return Object.freeze({
|
|
924
|
+
buffer: bytes,
|
|
925
|
+
portals,
|
|
926
|
+
count: portals.length,
|
|
927
|
+
capacity,
|
|
928
|
+
recordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES
|
|
929
|
+
});
|
|
930
|
+
}
|
|
760
931
|
function getCanvasDimension(canvas, key, fallback) {
|
|
761
932
|
const value = Number(canvas?.[key]);
|
|
762
933
|
if (Number.isFinite(value) && value > 0) {
|
|
@@ -812,6 +983,11 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
812
983
|
options.emissiveTriangleCapacity,
|
|
813
984
|
0
|
|
814
985
|
);
|
|
986
|
+
const environmentPortalCapacity = readNonNegativeInteger(
|
|
987
|
+
"environmentPortalCapacity",
|
|
988
|
+
options.environmentPortalCapacity,
|
|
989
|
+
0
|
|
990
|
+
);
|
|
815
991
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
816
992
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
817
993
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
@@ -820,6 +996,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
820
996
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
821
997
|
const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
|
|
822
998
|
const emissiveTriangleMetadataBytes = emissiveTriangleCapacity * BVH_NODE_RECORD_BYTES;
|
|
999
|
+
const environmentPortalBytes = environmentPortalCapacity * ENVIRONMENT_PORTAL_RECORD_BYTES;
|
|
823
1000
|
return Object.freeze({
|
|
824
1001
|
queueBytes,
|
|
825
1002
|
queuePairBytes: queueBytes * 2,
|
|
@@ -830,9 +1007,10 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
830
1007
|
bvhNodeBytes,
|
|
831
1008
|
bvhLeafReferenceBytes,
|
|
832
1009
|
emissiveTriangleMetadataBytes,
|
|
1010
|
+
environmentPortalBytes,
|
|
833
1011
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
834
1012
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
835
|
-
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES
|
|
1013
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES
|
|
836
1014
|
});
|
|
837
1015
|
}
|
|
838
1016
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -893,6 +1071,21 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
893
1071
|
environmentColor,
|
|
894
1072
|
ambientColor
|
|
895
1073
|
);
|
|
1074
|
+
const environmentPortals = normalizeEnvironmentPortals(
|
|
1075
|
+
options.environmentPortals ?? options.environmentLightPortals ?? options.environmentLighting?.environmentPortals
|
|
1076
|
+
);
|
|
1077
|
+
const environmentPortalCapacity = Math.max(
|
|
1078
|
+
environmentPortals.length,
|
|
1079
|
+
readNonNegativeInteger(
|
|
1080
|
+
"environmentPortalCapacity",
|
|
1081
|
+
options.environmentPortalCapacity,
|
|
1082
|
+
DEFAULT_ENVIRONMENT_PORTAL_CAPACITY
|
|
1083
|
+
)
|
|
1084
|
+
);
|
|
1085
|
+
const environmentPortalMode = resolveEnvironmentPortalMode(
|
|
1086
|
+
options.environmentPortalMode ?? options.portalMode ?? options.environmentLighting?.environmentPortalMode,
|
|
1087
|
+
environmentPortals.length > 0
|
|
1088
|
+
);
|
|
896
1089
|
return Object.freeze({
|
|
897
1090
|
width,
|
|
898
1091
|
height,
|
|
@@ -921,6 +1114,10 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
921
1114
|
environmentColor: environmentLighting.environmentColor,
|
|
922
1115
|
ambientColor: environmentLighting.ambientColor,
|
|
923
1116
|
environmentLighting,
|
|
1117
|
+
environmentPortals,
|
|
1118
|
+
environmentPortalCount: environmentPortals.length,
|
|
1119
|
+
environmentPortalCapacity,
|
|
1120
|
+
environmentPortalMode,
|
|
924
1121
|
displayQuality: options.displayQuality === true,
|
|
925
1122
|
requiresMeshBvhForDisplayQuality: true,
|
|
926
1123
|
denoise: options.denoise !== false,
|
|
@@ -931,7 +1128,8 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
931
1128
|
triangleCapacity,
|
|
932
1129
|
bvhNodeCapacity,
|
|
933
1130
|
bvhLeafSortCapacity,
|
|
934
|
-
emissiveTriangleCapacity: emissiveTriangleIndices.capacity
|
|
1131
|
+
emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
|
|
1132
|
+
environmentPortalCapacity
|
|
935
1133
|
})
|
|
936
1134
|
});
|
|
937
1135
|
}
|
|
@@ -1115,6 +1313,10 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1115
1313
|
data.setUint32(244, buildRange.count ?? 0, true);
|
|
1116
1314
|
data.setUint32(248, buildRange.sortItemCount ?? 0, true);
|
|
1117
1315
|
data.setUint32(252, config.emissiveTriangleCount ?? 0, true);
|
|
1316
|
+
data.setUint32(256, config.environmentPortalCount ?? 0, true);
|
|
1317
|
+
data.setUint32(260, config.environmentPortalMode ?? 0, true);
|
|
1318
|
+
data.setUint32(264, 0, true);
|
|
1319
|
+
data.setUint32(268, 0, true);
|
|
1118
1320
|
return bytes;
|
|
1119
1321
|
}
|
|
1120
1322
|
function createTiles(width, height, tileSize) {
|
|
@@ -1133,6 +1335,209 @@ function createTiles(width, height, tileSize) {
|
|
|
1133
1335
|
}
|
|
1134
1336
|
return Object.freeze(tiles);
|
|
1135
1337
|
}
|
|
1338
|
+
function normalizeReferenceTile(config, tileInput = {}) {
|
|
1339
|
+
const tileX = clamp(
|
|
1340
|
+
readNonNegativeInteger("tile.x", tileInput.x, 0),
|
|
1341
|
+
0,
|
|
1342
|
+
Math.max(0, config.width - 1)
|
|
1343
|
+
);
|
|
1344
|
+
const tileY = clamp(
|
|
1345
|
+
readNonNegativeInteger("tile.y", tileInput.y, 0),
|
|
1346
|
+
0,
|
|
1347
|
+
Math.max(0, config.height - 1)
|
|
1348
|
+
);
|
|
1349
|
+
const tileWidth = clamp(
|
|
1350
|
+
readPositiveInteger("tile.width", tileInput.width, config.width - tileX),
|
|
1351
|
+
1,
|
|
1352
|
+
config.width - tileX
|
|
1353
|
+
);
|
|
1354
|
+
const tileHeight = clamp(
|
|
1355
|
+
readPositiveInteger("tile.height", tileInput.height, config.height - tileY),
|
|
1356
|
+
1,
|
|
1357
|
+
config.height - tileY
|
|
1358
|
+
);
|
|
1359
|
+
return Object.freeze({
|
|
1360
|
+
x: tileX,
|
|
1361
|
+
y: tileY,
|
|
1362
|
+
width: tileWidth,
|
|
1363
|
+
height: tileHeight
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
function repairReferenceShadingNormal(geometricNormal, shadingNormal) {
|
|
1367
|
+
const normal = normalize(shadingNormal, geometricNormal);
|
|
1368
|
+
return dot(normal, geometricNormal) < 0 ? scale(normal, -1) : normal;
|
|
1369
|
+
}
|
|
1370
|
+
function readOptionalMaxDistance(value) {
|
|
1371
|
+
if (value === void 0 || value === null) {
|
|
1372
|
+
return Number.POSITIVE_INFINITY;
|
|
1373
|
+
}
|
|
1374
|
+
const numeric = Number(value);
|
|
1375
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
1376
|
+
throw new Error("maxDistance must be a positive finite number when provided.");
|
|
1377
|
+
}
|
|
1378
|
+
return numeric;
|
|
1379
|
+
}
|
|
1380
|
+
function createWavefrontReferenceRay(config, options = {}) {
|
|
1381
|
+
if (!config || typeof config !== "object") {
|
|
1382
|
+
throw new Error("config must be a wavefront path tracing config.");
|
|
1383
|
+
}
|
|
1384
|
+
const tile = normalizeReferenceTile(config, options.tile);
|
|
1385
|
+
const tilePixelCount = tile.width * tile.height;
|
|
1386
|
+
const pixelIndex = readNonNegativeInteger("pixelIndex", options.pixelIndex, 0);
|
|
1387
|
+
if (pixelIndex >= tilePixelCount) {
|
|
1388
|
+
throw new Error(`pixelIndex ${pixelIndex} exceeds tile capacity ${tilePixelCount}.`);
|
|
1389
|
+
}
|
|
1390
|
+
const sampleIndex = readNonNegativeInteger("sampleIndex", options.sampleIndex, 0);
|
|
1391
|
+
const frameIndex = readNonNegativeInteger("frameIndex", options.frameIndex, config.frameIndex ?? 0);
|
|
1392
|
+
const jitterScale = clamp(readFiniteNumber("jitterScale", options.jitterScale, 0.35), 0, 1);
|
|
1393
|
+
const localX = pixelIndex % tile.width;
|
|
1394
|
+
const localY = Math.floor(pixelIndex / tile.width);
|
|
1395
|
+
const pixelX = tile.x + localX;
|
|
1396
|
+
const pixelY = tile.y + localY;
|
|
1397
|
+
const sourcePixelId = pixelY * config.width + pixelX;
|
|
1398
|
+
const jitterX = random01FromSeed(mixSeed(sourcePixelId, sampleIndex, 0, frameIndex, 1)) - 0.5;
|
|
1399
|
+
const jitterY = random01FromSeed(mixSeed(sourcePixelId, sampleIndex, 0, frameIndex, 2)) - 0.5;
|
|
1400
|
+
const ndcX = (pixelX + 0.5 + jitterX * jitterScale) / config.width * 2 - 1;
|
|
1401
|
+
const ndcY = 1 - (pixelY + 0.5 + jitterY * jitterScale) / config.height * 2;
|
|
1402
|
+
const viewX = ndcX * config.camera.tanHalfFovY * config.camera.aspect;
|
|
1403
|
+
const viewY = ndcY * config.camera.tanHalfFovY;
|
|
1404
|
+
const direction = normalize(
|
|
1405
|
+
add(
|
|
1406
|
+
add(config.camera.forward, scale(config.camera.right, viewX)),
|
|
1407
|
+
scale(config.camera.up, viewY)
|
|
1408
|
+
),
|
|
1409
|
+
config.camera.forward
|
|
1410
|
+
);
|
|
1411
|
+
return Object.freeze({
|
|
1412
|
+
rayId: pixelIndex,
|
|
1413
|
+
parentRayId: 4294967295,
|
|
1414
|
+
sourcePixelId,
|
|
1415
|
+
sampleId: sampleIndex,
|
|
1416
|
+
bounce: 0,
|
|
1417
|
+
mediumRefId: 0,
|
|
1418
|
+
flags: 0,
|
|
1419
|
+
origin: Object.freeze([...config.camera.position]),
|
|
1420
|
+
direction: Object.freeze(direction),
|
|
1421
|
+
throughput: Object.freeze([1, 1, 1, 1]),
|
|
1422
|
+
pixelX,
|
|
1423
|
+
pixelY
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
function intersectWavefrontReferenceTriangle(ray, triangle, options = {}) {
|
|
1427
|
+
if (!ray || typeof ray !== "object") {
|
|
1428
|
+
throw new Error("ray must be a wavefront reference ray.");
|
|
1429
|
+
}
|
|
1430
|
+
if (!triangle || typeof triangle !== "object") {
|
|
1431
|
+
throw new Error("triangle must be a wavefront triangle record.");
|
|
1432
|
+
}
|
|
1433
|
+
const maxDistance = readOptionalMaxDistance(options.maxDistance);
|
|
1434
|
+
const triangleIndex = readNonNegativeInteger("triangleIndex", options.triangleIndex, 0);
|
|
1435
|
+
const edge1 = subtract(triangle.v1, triangle.v0);
|
|
1436
|
+
const edge2 = subtract(triangle.v2, triangle.v0);
|
|
1437
|
+
const pvec = cross(ray.direction, edge2);
|
|
1438
|
+
const determinant = dot(edge1, pvec);
|
|
1439
|
+
if (Math.abs(determinant) < 1e-7) {
|
|
1440
|
+
return null;
|
|
1441
|
+
}
|
|
1442
|
+
const invDet = 1 / determinant;
|
|
1443
|
+
const tvec = subtract(ray.origin, triangle.v0);
|
|
1444
|
+
const u = dot(tvec, pvec) * invDet;
|
|
1445
|
+
if (u < 0 || u > 1) {
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
const qvec = cross(tvec, edge1);
|
|
1449
|
+
const v = dot(ray.direction, qvec) * invDet;
|
|
1450
|
+
if (v < 0 || u + v > 1) {
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
const distance = dot(edge2, qvec) * invDet;
|
|
1454
|
+
if (distance <= 1e-3 || distance > maxDistance) {
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
const geometric = normalize(cross(edge1, edge2), [0, 1, 0]);
|
|
1458
|
+
const frontFace = dot(ray.direction, geometric) < 0;
|
|
1459
|
+
const orientedGeometric = frontFace ? geometric : scale(geometric, -1);
|
|
1460
|
+
const w = 1 - u - v;
|
|
1461
|
+
const interpolated = [
|
|
1462
|
+
triangle.n0[0] * w + triangle.n1[0] * u + triangle.n2[0] * v,
|
|
1463
|
+
triangle.n0[1] * w + triangle.n1[1] * u + triangle.n2[1] * v,
|
|
1464
|
+
triangle.n0[2] * w + triangle.n1[2] * u + triangle.n2[2] * v
|
|
1465
|
+
];
|
|
1466
|
+
const shadingNormal = repairReferenceShadingNormal(orientedGeometric, interpolated);
|
|
1467
|
+
const uv = [
|
|
1468
|
+
triangle.uv0[0] * w + triangle.uv1[0] * u + triangle.uv2[0] * v,
|
|
1469
|
+
triangle.uv0[1] * w + triangle.uv1[1] * u + triangle.uv2[1] * v
|
|
1470
|
+
];
|
|
1471
|
+
const position = add(ray.origin, scale(ray.direction, distance));
|
|
1472
|
+
return Object.freeze({
|
|
1473
|
+
hitType: "surface",
|
|
1474
|
+
rayId: ray.rayId,
|
|
1475
|
+
sourcePixelId: ray.sourcePixelId,
|
|
1476
|
+
distance,
|
|
1477
|
+
entityId: triangle.meshId,
|
|
1478
|
+
instanceId: 0,
|
|
1479
|
+
primitiveId: triangle.triangleId,
|
|
1480
|
+
materialId: triangle.materialKind,
|
|
1481
|
+
materialRefId: triangle.materialRefId,
|
|
1482
|
+
mediumRefId: triangle.mediumRefId,
|
|
1483
|
+
barycentrics: Object.freeze([w, u, v]),
|
|
1484
|
+
uv: Object.freeze(uv),
|
|
1485
|
+
geometricNormal: Object.freeze(orientedGeometric),
|
|
1486
|
+
shadingNormal: Object.freeze(shadingNormal),
|
|
1487
|
+
frontFace,
|
|
1488
|
+
triangleIndex,
|
|
1489
|
+
triangleId: triangle.triangleId,
|
|
1490
|
+
position: Object.freeze(position),
|
|
1491
|
+
color: triangle.color,
|
|
1492
|
+
emission: triangle.emission,
|
|
1493
|
+
material: triangle.material
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
1497
|
+
const radiance = evaluateReferenceEnvironmentRadiance(config, ray.origin, ray.direction);
|
|
1498
|
+
return Object.freeze({
|
|
1499
|
+
hitType: "environment",
|
|
1500
|
+
rayId: ray.rayId,
|
|
1501
|
+
sourcePixelId: ray.sourcePixelId,
|
|
1502
|
+
distance: -1,
|
|
1503
|
+
entityId: 0,
|
|
1504
|
+
instanceId: 0,
|
|
1505
|
+
primitiveId: 0,
|
|
1506
|
+
materialId: 0,
|
|
1507
|
+
materialRefId: 0,
|
|
1508
|
+
mediumRefId: 0,
|
|
1509
|
+
barycentrics: Object.freeze([0, 0, 0]),
|
|
1510
|
+
uv: Object.freeze([0, 0]),
|
|
1511
|
+
geometricNormal: Object.freeze(scale(ray.direction, -1)),
|
|
1512
|
+
shadingNormal: Object.freeze(scale(ray.direction, -1)),
|
|
1513
|
+
frontFace: true,
|
|
1514
|
+
triangleIndex: -1,
|
|
1515
|
+
triangleId: -1,
|
|
1516
|
+
position: Object.freeze(add(ray.origin, scale(ray.direction, 1e3))),
|
|
1517
|
+
color: Object.freeze([0, 0, 0, 0]),
|
|
1518
|
+
emission: radiance,
|
|
1519
|
+
material: Object.freeze([1, 0, 1, 1])
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
function traceWavefrontReferenceTriangles(config, ray, triangles, options = {}) {
|
|
1523
|
+
if (!config || typeof config !== "object") {
|
|
1524
|
+
throw new Error("config must be a wavefront path tracing config.");
|
|
1525
|
+
}
|
|
1526
|
+
const source = Array.isArray(triangles) ? triangles : [];
|
|
1527
|
+
let nearestHit = null;
|
|
1528
|
+
let nearestDistance = readOptionalMaxDistance(options.maxDistance);
|
|
1529
|
+
source.forEach((triangle, index) => {
|
|
1530
|
+
const hit = intersectWavefrontReferenceTriangle(ray, triangle, {
|
|
1531
|
+
maxDistance: Number.isFinite(nearestDistance) ? nearestDistance : void 0,
|
|
1532
|
+
triangleIndex: index
|
|
1533
|
+
});
|
|
1534
|
+
if (hit && hit.distance < nearestDistance) {
|
|
1535
|
+
nearestDistance = hit.distance;
|
|
1536
|
+
nearestHit = hit;
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
return nearestHit ?? createWavefrontReferenceEnvironmentHit(config, ray);
|
|
1540
|
+
}
|
|
1136
1541
|
function clampTileSizeForDevice(config, device) {
|
|
1137
1542
|
const limit = Number(device?.limits?.maxStorageBufferBindingSize);
|
|
1138
1543
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
@@ -1361,6 +1766,10 @@ struct FrameConfig {
|
|
|
1361
1766
|
bvhBuildNodeCount: u32,
|
|
1362
1767
|
bvhSortItemCount: u32,
|
|
1363
1768
|
emissiveTriangleCount: u32,
|
|
1769
|
+
environmentPortalCount: u32,
|
|
1770
|
+
environmentPortalMode: u32,
|
|
1771
|
+
_portalPad0: u32,
|
|
1772
|
+
_portalPad1: u32,
|
|
1364
1773
|
};
|
|
1365
1774
|
|
|
1366
1775
|
struct Counters {
|
|
@@ -1384,6 +1793,18 @@ struct Candidate {
|
|
|
1384
1793
|
mediumRefId: u32,
|
|
1385
1794
|
};
|
|
1386
1795
|
|
|
1796
|
+
struct EnvironmentPortal {
|
|
1797
|
+
kind: u32,
|
|
1798
|
+
flags: u32,
|
|
1799
|
+
_pad0: u32,
|
|
1800
|
+
_pad1: u32,
|
|
1801
|
+
position: vec4<f32>,
|
|
1802
|
+
normal: vec4<f32>,
|
|
1803
|
+
tangent: vec4<f32>,
|
|
1804
|
+
bitangent: vec4<f32>,
|
|
1805
|
+
color: vec4<f32>,
|
|
1806
|
+
};
|
|
1807
|
+
|
|
1387
1808
|
@group(0) @binding(0) var<storage, read_write> activeQueue: array<RayRecord>;
|
|
1388
1809
|
@group(0) @binding(1) var<storage, read_write> nextQueue: array<RayRecord>;
|
|
1389
1810
|
@group(0) @binding(2) var<storage, read_write> hits: array<HitRecord>;
|
|
@@ -1403,6 +1824,7 @@ struct Candidate {
|
|
|
1403
1824
|
@group(0) @binding(16) var radianceImage: texture_storage_2d<rgba16float, write>;
|
|
1404
1825
|
@group(0) @binding(17) var finalDenoiseInputRadiance: texture_2d<f32>;
|
|
1405
1826
|
@group(0) @binding(18) var denoisedOutputImage: texture_storage_2d<rgba8unorm, write>;
|
|
1827
|
+
@group(0) @binding(19) var<storage, read> environmentPortals: array<EnvironmentPortal>;
|
|
1406
1828
|
|
|
1407
1829
|
fn hash_u32(value: u32) -> u32 {
|
|
1408
1830
|
var x = value;
|
|
@@ -1443,7 +1865,48 @@ fn saturate(value: f32) -> f32 {
|
|
|
1443
1865
|
return clamp(value, 0.0, 1.0);
|
|
1444
1866
|
}
|
|
1445
1867
|
|
|
1446
|
-
fn
|
|
1868
|
+
fn max_component(value: vec3<f32>) -> f32 {
|
|
1869
|
+
return max(max(value.x, value.y), value.z);
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1873
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
1874
|
+
return vec3<f32>(1.0);
|
|
1875
|
+
}
|
|
1876
|
+
var scale = vec3<f32>(0.0);
|
|
1877
|
+
for (var portalIndex = 0u; portalIndex < config.environmentPortalCount; portalIndex = portalIndex + 1u) {
|
|
1878
|
+
let portal = environmentPortals[portalIndex];
|
|
1879
|
+
if (portal.kind == 1u) {
|
|
1880
|
+
let portalNormal = safe_normalize(portal.normal.xyz, vec3<f32>(0.0, 0.0, 1.0));
|
|
1881
|
+
let denominator = dot(direction, portalNormal);
|
|
1882
|
+
let twoSided = (portal.flags & 1u) != 0u;
|
|
1883
|
+
var facing = abs(denominator) > 0.0001;
|
|
1884
|
+
if (!twoSided && denominator <= 0.0001) {
|
|
1885
|
+
facing = false;
|
|
1886
|
+
}
|
|
1887
|
+
if (facing) {
|
|
1888
|
+
let distance = dot(portal.position.xyz - origin, portalNormal) / denominator;
|
|
1889
|
+
if (distance > 0.001) {
|
|
1890
|
+
let hitPosition = origin + direction * distance;
|
|
1891
|
+
let local = hitPosition - portal.position.xyz;
|
|
1892
|
+
let tangent = safe_normalize(portal.tangent.xyz, vec3<f32>(1.0, 0.0, 0.0));
|
|
1893
|
+
let bitangent = safe_normalize(portal.bitangent.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
1894
|
+
let u = dot(local, tangent);
|
|
1895
|
+
let v = dot(local, bitangent);
|
|
1896
|
+
if (abs(u) <= portal.tangent.w && abs(v) <= portal.bitangent.w) {
|
|
1897
|
+
let areaWeight = clamp(sqrt(max(portal.position.w, 0.0001)), 0.25, 4.0);
|
|
1898
|
+
let angleWeight = max(abs(denominator), 0.08);
|
|
1899
|
+
let portalScale = portal.color.rgb * portal.normal.w * portal.color.a * areaWeight * angleWeight;
|
|
1900
|
+
scale = max(scale, portalScale);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
return scale;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1447
1910
|
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
1448
1911
|
let upFactor = saturate(rayDirection.y * 0.5 + 0.5);
|
|
1449
1912
|
let sunDirection = safe_normalize(
|
|
@@ -1454,10 +1917,26 @@ fn environment_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
|
1454
1917
|
let gradient =
|
|
1455
1918
|
config.environmentHorizonColor.xyz * (1.0 - upFactor) +
|
|
1456
1919
|
config.environmentZenithColor.xyz * upFactor;
|
|
1920
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
1921
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
1457
1922
|
return (
|
|
1458
1923
|
gradient +
|
|
1459
1924
|
config.environmentSunColor.xyz * sunGlow
|
|
1460
|
-
) *
|
|
1925
|
+
) *
|
|
1926
|
+
max(config.environmentSunDirectionIntensity.w, 0.0001) *
|
|
1927
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1931
|
+
let portalScale = environment_portal_radiance_scale(origin, safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0)));
|
|
1932
|
+
if (
|
|
1933
|
+
config.environmentPortalCount > 0u &&
|
|
1934
|
+
config.environmentPortalMode == 2u &&
|
|
1935
|
+
max_component(portalScale) <= 0.0001
|
|
1936
|
+
) {
|
|
1937
|
+
return config.ambientColor.xyz * 0.65;
|
|
1938
|
+
}
|
|
1939
|
+
return environment_radiance(origin, direction);
|
|
1461
1940
|
}
|
|
1462
1941
|
|
|
1463
1942
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -1728,7 +2207,7 @@ fn make_ray(pixelIndex: u32) -> RayRecord {
|
|
|
1728
2207
|
}
|
|
1729
2208
|
|
|
1730
2209
|
fn make_miss(ray: RayRecord) -> HitRecord {
|
|
1731
|
-
let radiance =
|
|
2210
|
+
let radiance = gated_environment_radiance(ray.origin.xyz, ray.direction.xyz);
|
|
1732
2211
|
return HitRecord(
|
|
1733
2212
|
ray.rayId,
|
|
1734
2213
|
ray.sourcePixelId,
|
|
@@ -2214,6 +2693,21 @@ fn sample_emissive_triangle_direction(hit: HitRecord, seed: u32, fallback: vec3<
|
|
|
2214
2693
|
return safe_normalize(lightPoint - hit.position.xyz, fallback);
|
|
2215
2694
|
}
|
|
2216
2695
|
|
|
2696
|
+
fn sample_environment_portal_direction(hit: HitRecord, seed: u32, fallback: vec3<f32>) -> vec3<f32> {
|
|
2697
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
2698
|
+
return fallback;
|
|
2699
|
+
}
|
|
2700
|
+
let portalSlot = min(
|
|
2701
|
+
u32(random01(seed + 211u) * f32(config.environmentPortalCount)),
|
|
2702
|
+
config.environmentPortalCount - 1u
|
|
2703
|
+
);
|
|
2704
|
+
let portal = environmentPortals[portalSlot];
|
|
2705
|
+
let u = (random01(seed + 223u) * 2.0 - 1.0) * portal.tangent.w;
|
|
2706
|
+
let v = (random01(seed + 227u) * 2.0 - 1.0) * portal.bitangent.w;
|
|
2707
|
+
let portalTarget = portal.position.xyz + portal.tangent.xyz * u + portal.bitangent.xyz * v;
|
|
2708
|
+
return safe_normalize(portalTarget - hit.position.xyz, fallback);
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2217
2711
|
fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult {
|
|
2218
2712
|
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
2219
2713
|
if (hit.materialKind == 1u) {
|
|
@@ -2253,8 +2747,17 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
2253
2747
|
let canSampleLight = dot(hit.shadingNormal.xyz, guidedLight) > -0.04;
|
|
2254
2748
|
let guideProbability = select(0.38, 0.72, ray.bounce == 0u);
|
|
2255
2749
|
let useGuidedLight = canSampleLight && random01(seed + 37u) < guideProbability;
|
|
2750
|
+
let guidedPortal = sample_environment_portal_direction(hit, seed, randomDiffuse);
|
|
2751
|
+
let canSamplePortal = dot(hit.shadingNormal.xyz, guidedPortal) > -0.04;
|
|
2752
|
+
let useGuidedPortal =
|
|
2753
|
+
!useGuidedLight &&
|
|
2754
|
+
canSamplePortal &&
|
|
2755
|
+
config.environmentPortalCount > 0u &&
|
|
2756
|
+
config.environmentPortalMode > 0u &&
|
|
2757
|
+
random01(seed + 89u) < 0.58;
|
|
2758
|
+
let guidedDirection = select(randomDiffuse, guidedPortal, useGuidedPortal);
|
|
2256
2759
|
return ScatterResult(
|
|
2257
|
-
vec4<f32>(select(
|
|
2760
|
+
vec4<f32>(select(guidedDirection, guidedLight, useGuidedLight), 0.0),
|
|
2258
2761
|
select(0u, RAY_FLAG_GUIDED_EMISSIVE, useGuidedLight),
|
|
2259
2762
|
0u,
|
|
2260
2763
|
0u,
|
|
@@ -2461,6 +2964,29 @@ fn fragmentMain(in: VertexOut) -> @location(0) vec4<f32> {
|
|
|
2461
2964
|
return textureSample(renderTexture, renderSampler, in.uv);
|
|
2462
2965
|
}
|
|
2463
2966
|
`;
|
|
2967
|
+
function createWavefrontDeviceDescriptor(adapter, options = {}) {
|
|
2968
|
+
const requiredLimits = { ...options.requiredLimits ?? {} };
|
|
2969
|
+
const exposedStorageBufferLimit = Number(adapter?.limits?.maxStorageBuffersPerShaderStage);
|
|
2970
|
+
if (Number.isFinite(exposedStorageBufferLimit)) {
|
|
2971
|
+
if (exposedStorageBufferLimit < TRACE_STORAGE_BUFFER_BINDINGS) {
|
|
2972
|
+
throw new Error(
|
|
2973
|
+
`Wavefront mesh tracing requires maxStorageBuffersPerShaderStage>=${TRACE_STORAGE_BUFFER_BINDINGS}, but this adapter exposes ${exposedStorageBufferLimit}.`
|
|
2974
|
+
);
|
|
2975
|
+
}
|
|
2976
|
+
requiredLimits.maxStorageBuffersPerShaderStage = Math.max(
|
|
2977
|
+
Number(requiredLimits.maxStorageBuffersPerShaderStage ?? 0),
|
|
2978
|
+
TRACE_STORAGE_BUFFER_BINDINGS
|
|
2979
|
+
);
|
|
2980
|
+
}
|
|
2981
|
+
const descriptor = { ...options.deviceDescriptor ?? {} };
|
|
2982
|
+
if (Object.keys(requiredLimits).length > 0) {
|
|
2983
|
+
descriptor.requiredLimits = {
|
|
2984
|
+
...descriptor.requiredLimits ?? {},
|
|
2985
|
+
...requiredLimits
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
return Object.keys(descriptor).length > 0 ? descriptor : void 0;
|
|
2989
|
+
}
|
|
2464
2990
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
2465
2991
|
assertAnalyticDisplayQualityPolicy(options);
|
|
2466
2992
|
const constants = getGpuUsageConstants();
|
|
@@ -2479,7 +3005,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2479
3005
|
if (!adapter) {
|
|
2480
3006
|
throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
|
|
2481
3007
|
}
|
|
2482
|
-
const device = await adapter.requestDevice();
|
|
3008
|
+
const device = await adapter.requestDevice(createWavefrontDeviceDescriptor(adapter, options));
|
|
2483
3009
|
const context = canvas.getContext("webgpu");
|
|
2484
3010
|
if (!context || typeof context.configure !== "function") {
|
|
2485
3011
|
throw new Error("Canvas WebGPU context does not support configure().");
|
|
@@ -2552,23 +3078,34 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2552
3078
|
Math.max(1, config.gpuMeshSource.meshes.count) * MESH_RANGE_RECORD_BYTES,
|
|
2553
3079
|
"plasius.wavefront.meshRanges"
|
|
2554
3080
|
);
|
|
3081
|
+
const environmentPortalBuffer = createBuffer(
|
|
3082
|
+
device,
|
|
3083
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
3084
|
+
Math.max(1, config.environmentPortalCapacity) * ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
3085
|
+
"plasius.wavefront.environmentPortals"
|
|
3086
|
+
);
|
|
2555
3087
|
const bvhLeafRefBuffer = createBuffer(
|
|
2556
3088
|
device,
|
|
2557
3089
|
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
2558
3090
|
Math.max(1, config.bvhLeafSortCapacity) * BVH_LEAF_REF_RECORD_BYTES,
|
|
2559
3091
|
"plasius.wavefront.bvhLeafRefs"
|
|
2560
3092
|
);
|
|
2561
|
-
const
|
|
2562
|
-
device,
|
|
2563
|
-
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
2564
|
-
CONFIG_BUFFER_BYTES,
|
|
2565
|
-
"plasius.wavefront.frameConfig"
|
|
2566
|
-
);
|
|
3093
|
+
const tiles = createTiles(config.width, config.height, config.tileSize);
|
|
2567
3094
|
const uniformOffsetAlignment = Number(device?.limits?.minUniformBufferOffsetAlignment);
|
|
2568
3095
|
const configBufferStride = alignTo(
|
|
2569
3096
|
CONFIG_BUFFER_BYTES,
|
|
2570
3097
|
Number.isFinite(uniformOffsetAlignment) && uniformOffsetAlignment > 0 ? uniformOffsetAlignment : CONFIG_BUFFER_BYTES
|
|
2571
3098
|
);
|
|
3099
|
+
const frameConfigSlotCount = Math.max(
|
|
3100
|
+
1,
|
|
3101
|
+
tiles.length * config.samplesPerPixel + tiles.length + (config.denoise ? 1 : 0)
|
|
3102
|
+
);
|
|
3103
|
+
const configBuffer = createBuffer(
|
|
3104
|
+
device,
|
|
3105
|
+
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
3106
|
+
frameConfigSlotCount * configBufferStride,
|
|
3107
|
+
"plasius.wavefront.frameConfig"
|
|
3108
|
+
);
|
|
2572
3109
|
const bvhBuildConfigSlots = 1 + config.bvhSortStages.length + config.bvhBuildLevels.length;
|
|
2573
3110
|
const bvhBuildConfigBuffer = createBuffer(
|
|
2574
3111
|
device,
|
|
@@ -2602,6 +3139,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2602
3139
|
device.queue.writeBuffer(meshVertexBuffer, 0, config.gpuMeshSource.vertices.buffer);
|
|
2603
3140
|
device.queue.writeBuffer(meshIndexBuffer, 0, config.gpuMeshSource.indices.buffer);
|
|
2604
3141
|
device.queue.writeBuffer(meshRangeBuffer, 0, config.gpuMeshSource.meshes.buffer);
|
|
3142
|
+
const packedEnvironmentPortals = packEnvironmentPortals(
|
|
3143
|
+
config.environmentPortals,
|
|
3144
|
+
Math.max(1, config.environmentPortalCapacity)
|
|
3145
|
+
);
|
|
3146
|
+
device.queue.writeBuffer(environmentPortalBuffer, 0, packedEnvironmentPortals.buffer);
|
|
2605
3147
|
const radianceTexture = device.createTexture({
|
|
2606
3148
|
label: "plasius.wavefront.radiance",
|
|
2607
3149
|
size: { width: config.width, height: config.height },
|
|
@@ -2653,7 +3195,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2653
3195
|
binding: 16,
|
|
2654
3196
|
visibility: constants.shader.COMPUTE,
|
|
2655
3197
|
storageTexture: { access: "write-only", format: "rgba16float" }
|
|
2656
|
-
}
|
|
3198
|
+
},
|
|
3199
|
+
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
2657
3200
|
]
|
|
2658
3201
|
});
|
|
2659
3202
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -2826,7 +3369,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2826
3369
|
{ binding: 7, resource: outputView },
|
|
2827
3370
|
{ binding: 8, resource: { buffer: triangleBuffer } },
|
|
2828
3371
|
{ binding: 9, resource: { buffer: bvhNodeBuffer } },
|
|
2829
|
-
{ binding: 16, resource: radianceView }
|
|
3372
|
+
{ binding: 16, resource: radianceView },
|
|
3373
|
+
{ binding: 19, resource: { buffer: environmentPortalBuffer } }
|
|
2830
3374
|
]
|
|
2831
3375
|
});
|
|
2832
3376
|
}
|
|
@@ -2913,10 +3457,27 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2913
3457
|
]
|
|
2914
3458
|
});
|
|
2915
3459
|
let frame = 0;
|
|
2916
|
-
|
|
3460
|
+
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
3461
|
+
let accelerationBuildCount = 0;
|
|
3462
|
+
function createFrameConfigWriter(frameIndex) {
|
|
3463
|
+
let slot = 0;
|
|
3464
|
+
return (tile, buildRange = {}) => {
|
|
3465
|
+
if (slot >= frameConfigSlotCount) {
|
|
3466
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
3467
|
+
}
|
|
3468
|
+
const offset = slot * configBufferStride;
|
|
3469
|
+
slot += 1;
|
|
3470
|
+
device.queue.writeBuffer(
|
|
3471
|
+
configBuffer,
|
|
3472
|
+
offset,
|
|
3473
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
3474
|
+
);
|
|
3475
|
+
return offset;
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
2917
3478
|
function dispatchGpuAccelerationBuild(frameIndex) {
|
|
2918
|
-
if (!config.gpuAccelerationBuildRequired) {
|
|
2919
|
-
return;
|
|
3479
|
+
if (!config.gpuAccelerationBuildRequired || accelerationBuilt) {
|
|
3480
|
+
return false;
|
|
2920
3481
|
}
|
|
2921
3482
|
const buildTile = tiles[0] ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
2922
3483
|
const encoder = device.createCommandEncoder({
|
|
@@ -2974,27 +3535,21 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2974
3535
|
}
|
|
2975
3536
|
passEncoder.end();
|
|
2976
3537
|
device.queue.submit([encoder.finish()]);
|
|
3538
|
+
accelerationBuilt = true;
|
|
3539
|
+
accelerationBuildCount += 1;
|
|
3540
|
+
return true;
|
|
2977
3541
|
}
|
|
2978
|
-
function
|
|
2979
|
-
const sampleWeight = 1 / config.samplesPerPixel;
|
|
2980
|
-
const configPayload = createConfigPayload(config, tile, frameIndex, {
|
|
2981
|
-
sampleIndex,
|
|
2982
|
-
sampleWeight
|
|
2983
|
-
});
|
|
2984
|
-
device.queue.writeBuffer(configBuffer, 0, configPayload);
|
|
2985
|
-
const encoder = device.createCommandEncoder({
|
|
2986
|
-
label: `plasius.wavefront.frame.${frameIndex}.tile.${tile.x}.${tile.y}.sample.${sampleIndex}`
|
|
2987
|
-
});
|
|
3542
|
+
function encodeTileSample(encoder, tile, configOffset) {
|
|
2988
3543
|
const passEncoder = encoder.beginComputePass({
|
|
2989
3544
|
label: "plasius.wavefront.computePass"
|
|
2990
3545
|
});
|
|
2991
3546
|
const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
|
|
2992
3547
|
const capacityWorkgroups = Math.ceil(config.tilePixelCapacity / WORKGROUP_SIZE);
|
|
2993
|
-
passEncoder.setBindGroup(0, bindGroups[0], [
|
|
3548
|
+
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
2994
3549
|
passEncoder.setPipeline(pipelines.generatePrimaryRays);
|
|
2995
3550
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
2996
3551
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
2997
|
-
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [
|
|
3552
|
+
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
2998
3553
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
2999
3554
|
passEncoder.dispatchWorkgroups(capacityWorkgroups);
|
|
3000
3555
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
@@ -3003,71 +3558,38 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3003
3558
|
passEncoder.dispatchWorkgroups(1);
|
|
3004
3559
|
}
|
|
3005
3560
|
passEncoder.end();
|
|
3006
|
-
device.queue.submit([encoder.finish()]);
|
|
3007
3561
|
}
|
|
3008
|
-
function
|
|
3009
|
-
const configPayload = createConfigPayload(config, tile, frameIndex, {
|
|
3010
|
-
sampleIndex: 0,
|
|
3011
|
-
sampleWeight: 1 / config.samplesPerPixel
|
|
3012
|
-
});
|
|
3013
|
-
device.queue.writeBuffer(configBuffer, 0, configPayload);
|
|
3014
|
-
const encoder = device.createCommandEncoder({
|
|
3015
|
-
label: `plasius.wavefront.frame.${frameIndex}.tile.${tile.x}.${tile.y}.output`
|
|
3016
|
-
});
|
|
3562
|
+
function encodeTileOutput(encoder, tile, configOffset) {
|
|
3017
3563
|
const passEncoder = encoder.beginComputePass({
|
|
3018
3564
|
label: "plasius.wavefront.outputPass"
|
|
3019
3565
|
});
|
|
3020
3566
|
const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
|
|
3021
|
-
passEncoder.setBindGroup(0, bindGroups[0], [
|
|
3567
|
+
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3022
3568
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
3023
3569
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
3024
3570
|
passEncoder.end();
|
|
3025
|
-
device.queue.submit([encoder.finish()]);
|
|
3026
|
-
}
|
|
3027
|
-
function dispatchTile(tile, frameIndex) {
|
|
3028
|
-
for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
|
|
3029
|
-
dispatchTileSample(tile, frameIndex, sampleIndex);
|
|
3030
|
-
}
|
|
3031
|
-
dispatchTileOutput(tile, frameIndex);
|
|
3032
3571
|
}
|
|
3033
|
-
function
|
|
3572
|
+
function encodeDenoise(encoder, configOffset) {
|
|
3034
3573
|
if (!config.denoise) {
|
|
3035
3574
|
return;
|
|
3036
3575
|
}
|
|
3037
|
-
device.queue.writeBuffer(
|
|
3038
|
-
configBuffer,
|
|
3039
|
-
0,
|
|
3040
|
-
createConfigPayload(
|
|
3041
|
-
config,
|
|
3042
|
-
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
3043
|
-
frameIndex,
|
|
3044
|
-
{ sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
|
|
3045
|
-
)
|
|
3046
|
-
);
|
|
3047
|
-
const encoder = device.createCommandEncoder({
|
|
3048
|
-
label: `plasius.wavefront.frame.${frameIndex}.denoise`
|
|
3049
|
-
});
|
|
3050
3576
|
const radiancePass = encoder.beginComputePass({
|
|
3051
3577
|
label: "plasius.wavefront.denoiseRadiancePass"
|
|
3052
3578
|
});
|
|
3053
|
-
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [
|
|
3579
|
+
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
3054
3580
|
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
3055
3581
|
radiancePass.dispatchWorkgroups(Math.ceil(config.width / 8), Math.ceil(config.height / 8));
|
|
3056
3582
|
radiancePass.end();
|
|
3057
3583
|
const resolvePass = encoder.beginComputePass({
|
|
3058
3584
|
label: "plasius.wavefront.denoiseResolvePass"
|
|
3059
3585
|
});
|
|
3060
|
-
resolvePass.setBindGroup(0, denoiseResolveBindGroup, [
|
|
3586
|
+
resolvePass.setBindGroup(0, denoiseResolveBindGroup, [configOffset]);
|
|
3061
3587
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
3062
3588
|
resolvePass.dispatchWorkgroups(Math.ceil(config.width / 8), Math.ceil(config.height / 8));
|
|
3063
3589
|
resolvePass.end();
|
|
3064
|
-
device.queue.submit([encoder.finish()]);
|
|
3065
3590
|
}
|
|
3066
|
-
function
|
|
3591
|
+
function encodePresent(encoder) {
|
|
3067
3592
|
const texture = context.getCurrentTexture();
|
|
3068
|
-
const encoder = device.createCommandEncoder({
|
|
3069
|
-
label: `plasius.wavefront.present.${frame}`
|
|
3070
|
-
});
|
|
3071
3593
|
const passEncoder = encoder.beginRenderPass({
|
|
3072
3594
|
label: "plasius.wavefront.presentPass",
|
|
3073
3595
|
colorAttachments: [
|
|
@@ -3083,16 +3605,42 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3083
3605
|
passEncoder.setBindGroup(0, presentBindGroup);
|
|
3084
3606
|
passEncoder.draw(3);
|
|
3085
3607
|
passEncoder.end();
|
|
3608
|
+
}
|
|
3609
|
+
function dispatchFrame(frameIndex) {
|
|
3610
|
+
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
3611
|
+
const encoder = device.createCommandEncoder({
|
|
3612
|
+
label: `plasius.wavefront.frame.${frameIndex}.batched`
|
|
3613
|
+
});
|
|
3614
|
+
for (const tile of tiles) {
|
|
3615
|
+
for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
|
|
3616
|
+
const configOffset = writeFrameConfig(tile, {
|
|
3617
|
+
sampleIndex,
|
|
3618
|
+
sampleWeight: 1 / config.samplesPerPixel
|
|
3619
|
+
});
|
|
3620
|
+
encodeTileSample(encoder, tile, configOffset);
|
|
3621
|
+
}
|
|
3622
|
+
const outputConfigOffset = writeFrameConfig(tile, {
|
|
3623
|
+
sampleIndex: 0,
|
|
3624
|
+
sampleWeight: 1 / config.samplesPerPixel
|
|
3625
|
+
});
|
|
3626
|
+
encodeTileOutput(encoder, tile, outputConfigOffset);
|
|
3627
|
+
}
|
|
3628
|
+
if (config.denoise) {
|
|
3629
|
+
const denoiseConfigOffset = writeFrameConfig(
|
|
3630
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
3631
|
+
{ sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
|
|
3632
|
+
);
|
|
3633
|
+
encodeDenoise(encoder, denoiseConfigOffset);
|
|
3634
|
+
}
|
|
3635
|
+
encodePresent(encoder);
|
|
3086
3636
|
device.queue.submit([encoder.finish()]);
|
|
3637
|
+
return 1;
|
|
3087
3638
|
}
|
|
3088
3639
|
function renderOnce() {
|
|
3089
3640
|
frame += 1;
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
}
|
|
3094
|
-
dispatchDenoise(frame + config.frameIndex);
|
|
3095
|
-
present();
|
|
3641
|
+
const frameIndex = frame + config.frameIndex;
|
|
3642
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex);
|
|
3643
|
+
const frameSubmissionCount = dispatchFrame(frameIndex);
|
|
3096
3644
|
return Object.freeze({
|
|
3097
3645
|
frame,
|
|
3098
3646
|
width: config.width,
|
|
@@ -3106,10 +3654,17 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3106
3654
|
sceneObjectCount: config.sceneObjectCount,
|
|
3107
3655
|
triangleCount: config.triangleCount,
|
|
3108
3656
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3657
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
3658
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
3109
3659
|
bvhNodeCount: config.bvhNodeCount,
|
|
3110
3660
|
displayQuality: config.displayQuality,
|
|
3111
3661
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
3112
3662
|
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
3663
|
+
accelerationBuildSubmitted,
|
|
3664
|
+
accelerationBuilt,
|
|
3665
|
+
accelerationBuildCount,
|
|
3666
|
+
commandSubmissions: frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
3667
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
3113
3668
|
memory: config.memory
|
|
3114
3669
|
});
|
|
3115
3670
|
}
|
|
@@ -3175,10 +3730,15 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3175
3730
|
sceneObjectCount: config.sceneObjectCount,
|
|
3176
3731
|
triangleCount: config.triangleCount,
|
|
3177
3732
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3733
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
3734
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
3178
3735
|
bvhNodeCount: config.bvhNodeCount,
|
|
3179
3736
|
displayQuality: config.displayQuality,
|
|
3180
3737
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
3181
3738
|
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
3739
|
+
accelerationBuilt,
|
|
3740
|
+
accelerationBuildCount,
|
|
3741
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
3182
3742
|
memory: config.memory
|
|
3183
3743
|
});
|
|
3184
3744
|
}
|
|
@@ -3193,6 +3753,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3193
3753
|
meshVertexBuffer.destroy?.();
|
|
3194
3754
|
meshIndexBuffer.destroy?.();
|
|
3195
3755
|
meshRangeBuffer.destroy?.();
|
|
3756
|
+
environmentPortalBuffer.destroy?.();
|
|
3196
3757
|
bvhLeafRefBuffer.destroy?.();
|
|
3197
3758
|
configBuffer.destroy?.();
|
|
3198
3759
|
bvhBuildConfigBuffer.destroy?.();
|
|
@@ -4515,11 +5076,13 @@ export {
|
|
|
4515
5076
|
createWavefrontPathTracingComputeConfig,
|
|
4516
5077
|
createWavefrontPathTracingComputeRenderer,
|
|
4517
5078
|
createWavefrontPathTracingPlan,
|
|
5079
|
+
createWavefrontReferenceRay,
|
|
4518
5080
|
defaultRendererClearColor,
|
|
4519
5081
|
defaultRendererWorkerProfile,
|
|
4520
5082
|
estimateWavefrontPathTracingMemory,
|
|
4521
5083
|
getRendererWorkerManifest,
|
|
4522
5084
|
getRendererWorkerProfile,
|
|
5085
|
+
intersectWavefrontReferenceTriangle,
|
|
4523
5086
|
normalizeWavefrontMesh,
|
|
4524
5087
|
normalizeWavefrontSceneObject,
|
|
4525
5088
|
packWavefrontBvhNodes,
|
|
@@ -4539,6 +5102,7 @@ export {
|
|
|
4539
5102
|
rendererWorkerQueueClass,
|
|
4540
5103
|
supportsWavefrontPathTracingCompute,
|
|
4541
5104
|
supportsWebGpu,
|
|
5105
|
+
traceWavefrontReferenceTriangles,
|
|
4542
5106
|
wavefrontMaterialKinds,
|
|
4543
5107
|
wavefrontPathTracingComputeLimits,
|
|
4544
5108
|
wavefrontSceneObjectKinds
|