@plasius/gpu-renderer 0.1.15 → 0.2.1
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 +29 -5
- package/README.md +25 -1
- package/dist/index.cjs +382 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +374 -16
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/index.d.ts +146 -26
- package/src/index.js +8 -0
- package/src/wavefront-compute.js +407 -16
package/dist/index.cjs
CHANGED
|
@@ -31,22 +31,29 @@ __export(index_exports, {
|
|
|
31
31
|
createWavefrontMeshAcceleration: () => createWavefrontMeshAcceleration,
|
|
32
32
|
createWavefrontPathTracingComputeConfig: () => createWavefrontPathTracingComputeConfig,
|
|
33
33
|
createWavefrontPathTracingComputeRenderer: () => createWavefrontPathTracingComputeRenderer,
|
|
34
|
+
createWavefrontPathTracingComputeShaderSource: () => createWavefrontPathTracingComputeShaderSource,
|
|
34
35
|
createWavefrontPathTracingPlan: () => createWavefrontPathTracingPlan,
|
|
36
|
+
createWavefrontReferenceRay: () => createWavefrontReferenceRay,
|
|
35
37
|
defaultRendererClearColor: () => defaultRendererClearColor,
|
|
36
38
|
defaultRendererWorkerProfile: () => defaultRendererWorkerProfile,
|
|
37
39
|
estimateWavefrontPathTracingMemory: () => estimateWavefrontPathTracingMemory,
|
|
38
40
|
getRendererWorkerManifest: () => getRendererWorkerManifest,
|
|
39
41
|
getRendererWorkerProfile: () => getRendererWorkerProfile,
|
|
42
|
+
intersectWavefrontReferenceTriangle: () => intersectWavefrontReferenceTriangle,
|
|
40
43
|
normalizeWavefrontMesh: () => normalizeWavefrontMesh,
|
|
41
44
|
normalizeWavefrontSceneObject: () => normalizeWavefrontSceneObject,
|
|
42
45
|
packWavefrontBvhNodes: () => packWavefrontBvhNodes,
|
|
43
46
|
packWavefrontSceneObjects: () => packWavefrontSceneObjects,
|
|
44
47
|
packWavefrontTriangles: () => packWavefrontTriangles,
|
|
48
|
+
renderWavefrontPathTracingComputeFrame: () => renderWavefrontPathTracingComputeFrame,
|
|
45
49
|
rendererAccelerationStructureUpdateClasses: () => rendererAccelerationStructureUpdateClasses,
|
|
46
50
|
rendererDebugOwner: () => rendererDebugOwner,
|
|
47
51
|
rendererRayTracingStageOrder: () => rendererRayTracingStageOrder,
|
|
48
52
|
rendererRepresentationBands: () => rendererRepresentationBands,
|
|
49
53
|
rendererWavefrontBufferSchemaVersion: () => rendererWavefrontBufferSchemaVersion,
|
|
54
|
+
rendererWavefrontComputeMode: () => rendererWavefrontComputeMode,
|
|
55
|
+
rendererWavefrontComputeStatsStride: () => rendererWavefrontComputeStatsStride,
|
|
56
|
+
rendererWavefrontComputeWorkgroupSize: () => rendererWavefrontComputeWorkgroupSize,
|
|
50
57
|
rendererWavefrontHitTypes: () => rendererWavefrontHitTypes,
|
|
51
58
|
rendererWavefrontPassOrder: () => rendererWavefrontPassOrder,
|
|
52
59
|
rendererWavefrontQueuePairStrategy: () => rendererWavefrontQueuePairStrategy,
|
|
@@ -56,6 +63,7 @@ __export(index_exports, {
|
|
|
56
63
|
rendererWorkerQueueClass: () => rendererWorkerQueueClass,
|
|
57
64
|
supportsWavefrontPathTracingCompute: () => supportsWavefrontPathTracingCompute,
|
|
58
65
|
supportsWebGpu: () => supportsWebGpu,
|
|
66
|
+
traceWavefrontReferenceTriangles: () => traceWavefrontReferenceTriangles,
|
|
59
67
|
wavefrontMaterialKinds: () => wavefrontMaterialKinds,
|
|
60
68
|
wavefrontPathTracingComputeLimits: () => wavefrontPathTracingComputeLimits,
|
|
61
69
|
wavefrontSceneObjectKinds: () => wavefrontSceneObjectKinds
|
|
@@ -71,6 +79,9 @@ var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
|
71
79
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
72
80
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
73
81
|
var WORKGROUP_SIZE = 64;
|
|
82
|
+
var rendererWavefrontComputeMode = "webgpu-compute";
|
|
83
|
+
var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
84
|
+
var rendererWavefrontComputeStatsStride = 8;
|
|
74
85
|
var RAY_RECORD_BYTES = 80;
|
|
75
86
|
var HIT_RECORD_BYTES = 208;
|
|
76
87
|
var SCENE_OBJECT_RECORD_BYTES = 96;
|
|
@@ -83,7 +94,9 @@ var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
|
83
94
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
84
95
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
85
96
|
var CONFIG_BUFFER_BYTES = 272;
|
|
86
|
-
var
|
|
97
|
+
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
98
|
+
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
99
|
+
var COUNTER_BUFFER_BYTES = 32;
|
|
87
100
|
var TRACE_STORAGE_BUFFER_BINDINGS = 9;
|
|
88
101
|
var MATERIAL_DIFFUSE = 0;
|
|
89
102
|
var MATERIAL_METAL = 1;
|
|
@@ -123,7 +136,9 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
123
136
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
124
137
|
emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
125
138
|
environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
126
|
-
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES
|
|
139
|
+
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES,
|
|
140
|
+
counterRecordBytes: COUNTER_BUFFER_BYTES,
|
|
141
|
+
indirectDispatchRecordBytes: INDIRECT_DISPATCH_ARGS_BYTES
|
|
127
142
|
});
|
|
128
143
|
var wavefrontSceneObjectKinds = Object.freeze({
|
|
129
144
|
sphere: OBJECT_KIND_SPHERE,
|
|
@@ -231,6 +246,25 @@ function normalize(value, fallback = [0, 0, 1]) {
|
|
|
231
246
|
}
|
|
232
247
|
return [value[0] / length, value[1] / length, value[2] / length];
|
|
233
248
|
}
|
|
249
|
+
function hashUint32(value) {
|
|
250
|
+
let x = value >>> 0;
|
|
251
|
+
x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
|
|
252
|
+
x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
|
|
253
|
+
return (x >>> 16 ^ x) >>> 0;
|
|
254
|
+
}
|
|
255
|
+
function mixSeed(pixelId, sampleId, bounce, frameIndex, dimension) {
|
|
256
|
+
let x = (pixelId >>> 0) * 747796405 ^ (sampleId >>> 0) * 2891336453 ^ (bounce >>> 0) * 277803737 ^ (frameIndex >>> 0) * 1442695041 ^ (dimension >>> 0) * 1597334677;
|
|
257
|
+
x >>>= 0;
|
|
258
|
+
x ^= x >>> 16;
|
|
259
|
+
x = x * 2146121005 >>> 0;
|
|
260
|
+
x ^= x >>> 15;
|
|
261
|
+
x = x * 2221713035 >>> 0;
|
|
262
|
+
x ^= x >>> 16;
|
|
263
|
+
return x >>> 0;
|
|
264
|
+
}
|
|
265
|
+
function random01FromSeed(seed) {
|
|
266
|
+
return (hashUint32(seed) & 16777215) / 16777215;
|
|
267
|
+
}
|
|
234
268
|
function getArrayLikeLength(value) {
|
|
235
269
|
return Array.isArray(value) || ArrayBuffer.isView(value) ? value.length : 0;
|
|
236
270
|
}
|
|
@@ -829,6 +863,29 @@ function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
|
|
|
829
863
|
exposure: Math.max(1e-4, readFiniteNumber("environmentLighting.exposure", source.exposure, DEFAULT_ENVIRONMENT_LIGHTING.exposure))
|
|
830
864
|
});
|
|
831
865
|
}
|
|
866
|
+
function evaluateReferenceEnvironmentRadiance(config, origin, direction) {
|
|
867
|
+
void origin;
|
|
868
|
+
const rayDirection = normalize(direction, [0, 1, 0]);
|
|
869
|
+
const upFactor = clamp(rayDirection[1] * 0.5 + 0.5, 0, 1);
|
|
870
|
+
const sunDirection = normalize(
|
|
871
|
+
config.environmentLighting?.sunDirection ?? DEFAULT_ENVIRONMENT_LIGHTING.sunDirection,
|
|
872
|
+
DEFAULT_ENVIRONMENT_LIGHTING.sunDirection
|
|
873
|
+
);
|
|
874
|
+
const sunGlow = Math.pow(clamp(dot(rayDirection, sunDirection), 0, 1), 192);
|
|
875
|
+
const horizonColor = config.environmentLighting?.horizonColor ?? DEFAULT_ENVIRONMENT_LIGHTING.horizonColor;
|
|
876
|
+
const zenithColor = config.environmentLighting?.zenithColor ?? DEFAULT_ENVIRONMENT_LIGHTING.zenithColor;
|
|
877
|
+
const sunColor = config.environmentLighting?.sunColor ?? DEFAULT_ENVIRONMENT_LIGHTING.sunColor;
|
|
878
|
+
const intensity = Math.max(
|
|
879
|
+
1e-4,
|
|
880
|
+
Number(config.environmentLighting?.intensity ?? DEFAULT_ENVIRONMENT_LIGHTING.intensity)
|
|
881
|
+
);
|
|
882
|
+
return Object.freeze([
|
|
883
|
+
(horizonColor[0] * (1 - upFactor) + zenithColor[0] * upFactor + sunColor[0] * sunGlow) * intensity,
|
|
884
|
+
(horizonColor[1] * (1 - upFactor) + zenithColor[1] * upFactor + sunColor[1] * sunGlow) * intensity,
|
|
885
|
+
(horizonColor[2] * (1 - upFactor) + zenithColor[2] * upFactor + sunColor[2] * sunGlow) * intensity,
|
|
886
|
+
1
|
|
887
|
+
]);
|
|
888
|
+
}
|
|
832
889
|
function resolveEnvironmentPortalMode(value, hasPortals) {
|
|
833
890
|
if (value === void 0 || value === null) {
|
|
834
891
|
return hasPortals ? 2 : 0;
|
|
@@ -1032,7 +1089,8 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1032
1089
|
environmentPortalBytes,
|
|
1033
1090
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
1034
1091
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
1035
|
-
|
|
1092
|
+
indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
|
|
1093
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1036
1094
|
});
|
|
1037
1095
|
}
|
|
1038
1096
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -1109,6 +1167,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1109
1167
|
environmentPortals.length > 0
|
|
1110
1168
|
);
|
|
1111
1169
|
return Object.freeze({
|
|
1170
|
+
mode: rendererWavefrontComputeMode,
|
|
1112
1171
|
width,
|
|
1113
1172
|
height,
|
|
1114
1173
|
maxDepth,
|
|
@@ -1163,6 +1222,9 @@ function getGpuUsageConstants() {
|
|
|
1163
1222
|
if (typeof GPUBufferUsage === "undefined" || typeof GPUTextureUsage === "undefined" || typeof GPUShaderStage === "undefined") {
|
|
1164
1223
|
throw new Error("WebGPU runtime unavailable. Required GPU constants are missing.");
|
|
1165
1224
|
}
|
|
1225
|
+
if (typeof GPUBufferUsage.INDIRECT !== "number") {
|
|
1226
|
+
throw new Error("WebGPU runtime unavailable. GPUBufferUsage.INDIRECT is missing.");
|
|
1227
|
+
}
|
|
1166
1228
|
return {
|
|
1167
1229
|
buffer: GPUBufferUsage,
|
|
1168
1230
|
texture: GPUTextureUsage,
|
|
@@ -1357,6 +1419,209 @@ function createTiles(width, height, tileSize) {
|
|
|
1357
1419
|
}
|
|
1358
1420
|
return Object.freeze(tiles);
|
|
1359
1421
|
}
|
|
1422
|
+
function normalizeReferenceTile(config, tileInput = {}) {
|
|
1423
|
+
const tileX = clamp(
|
|
1424
|
+
readNonNegativeInteger("tile.x", tileInput.x, 0),
|
|
1425
|
+
0,
|
|
1426
|
+
Math.max(0, config.width - 1)
|
|
1427
|
+
);
|
|
1428
|
+
const tileY = clamp(
|
|
1429
|
+
readNonNegativeInteger("tile.y", tileInput.y, 0),
|
|
1430
|
+
0,
|
|
1431
|
+
Math.max(0, config.height - 1)
|
|
1432
|
+
);
|
|
1433
|
+
const tileWidth = clamp(
|
|
1434
|
+
readPositiveInteger("tile.width", tileInput.width, config.width - tileX),
|
|
1435
|
+
1,
|
|
1436
|
+
config.width - tileX
|
|
1437
|
+
);
|
|
1438
|
+
const tileHeight = clamp(
|
|
1439
|
+
readPositiveInteger("tile.height", tileInput.height, config.height - tileY),
|
|
1440
|
+
1,
|
|
1441
|
+
config.height - tileY
|
|
1442
|
+
);
|
|
1443
|
+
return Object.freeze({
|
|
1444
|
+
x: tileX,
|
|
1445
|
+
y: tileY,
|
|
1446
|
+
width: tileWidth,
|
|
1447
|
+
height: tileHeight
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
function repairReferenceShadingNormal(geometricNormal, shadingNormal) {
|
|
1451
|
+
const normal = normalize(shadingNormal, geometricNormal);
|
|
1452
|
+
return dot(normal, geometricNormal) < 0 ? scale(normal, -1) : normal;
|
|
1453
|
+
}
|
|
1454
|
+
function readOptionalMaxDistance(value) {
|
|
1455
|
+
if (value === void 0 || value === null) {
|
|
1456
|
+
return Number.POSITIVE_INFINITY;
|
|
1457
|
+
}
|
|
1458
|
+
const numeric = Number(value);
|
|
1459
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
1460
|
+
throw new Error("maxDistance must be a positive finite number when provided.");
|
|
1461
|
+
}
|
|
1462
|
+
return numeric;
|
|
1463
|
+
}
|
|
1464
|
+
function createWavefrontReferenceRay(config, options = {}) {
|
|
1465
|
+
if (!config || typeof config !== "object") {
|
|
1466
|
+
throw new Error("config must be a wavefront path tracing config.");
|
|
1467
|
+
}
|
|
1468
|
+
const tile = normalizeReferenceTile(config, options.tile);
|
|
1469
|
+
const tilePixelCount = tile.width * tile.height;
|
|
1470
|
+
const pixelIndex = readNonNegativeInteger("pixelIndex", options.pixelIndex, 0);
|
|
1471
|
+
if (pixelIndex >= tilePixelCount) {
|
|
1472
|
+
throw new Error(`pixelIndex ${pixelIndex} exceeds tile capacity ${tilePixelCount}.`);
|
|
1473
|
+
}
|
|
1474
|
+
const sampleIndex = readNonNegativeInteger("sampleIndex", options.sampleIndex, 0);
|
|
1475
|
+
const frameIndex = readNonNegativeInteger("frameIndex", options.frameIndex, config.frameIndex ?? 0);
|
|
1476
|
+
const jitterScale = clamp(readFiniteNumber("jitterScale", options.jitterScale, 0.35), 0, 1);
|
|
1477
|
+
const localX = pixelIndex % tile.width;
|
|
1478
|
+
const localY = Math.floor(pixelIndex / tile.width);
|
|
1479
|
+
const pixelX = tile.x + localX;
|
|
1480
|
+
const pixelY = tile.y + localY;
|
|
1481
|
+
const sourcePixelId = pixelY * config.width + pixelX;
|
|
1482
|
+
const jitterX = random01FromSeed(mixSeed(sourcePixelId, sampleIndex, 0, frameIndex, 1)) - 0.5;
|
|
1483
|
+
const jitterY = random01FromSeed(mixSeed(sourcePixelId, sampleIndex, 0, frameIndex, 2)) - 0.5;
|
|
1484
|
+
const ndcX = (pixelX + 0.5 + jitterX * jitterScale) / config.width * 2 - 1;
|
|
1485
|
+
const ndcY = 1 - (pixelY + 0.5 + jitterY * jitterScale) / config.height * 2;
|
|
1486
|
+
const viewX = ndcX * config.camera.tanHalfFovY * config.camera.aspect;
|
|
1487
|
+
const viewY = ndcY * config.camera.tanHalfFovY;
|
|
1488
|
+
const direction = normalize(
|
|
1489
|
+
add(
|
|
1490
|
+
add(config.camera.forward, scale(config.camera.right, viewX)),
|
|
1491
|
+
scale(config.camera.up, viewY)
|
|
1492
|
+
),
|
|
1493
|
+
config.camera.forward
|
|
1494
|
+
);
|
|
1495
|
+
return Object.freeze({
|
|
1496
|
+
rayId: pixelIndex,
|
|
1497
|
+
parentRayId: 4294967295,
|
|
1498
|
+
sourcePixelId,
|
|
1499
|
+
sampleId: sampleIndex,
|
|
1500
|
+
bounce: 0,
|
|
1501
|
+
mediumRefId: 0,
|
|
1502
|
+
flags: 0,
|
|
1503
|
+
origin: Object.freeze([...config.camera.position]),
|
|
1504
|
+
direction: Object.freeze(direction),
|
|
1505
|
+
throughput: Object.freeze([1, 1, 1, 1]),
|
|
1506
|
+
pixelX,
|
|
1507
|
+
pixelY
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
function intersectWavefrontReferenceTriangle(ray, triangle, options = {}) {
|
|
1511
|
+
if (!ray || typeof ray !== "object") {
|
|
1512
|
+
throw new Error("ray must be a wavefront reference ray.");
|
|
1513
|
+
}
|
|
1514
|
+
if (!triangle || typeof triangle !== "object") {
|
|
1515
|
+
throw new Error("triangle must be a wavefront triangle record.");
|
|
1516
|
+
}
|
|
1517
|
+
const maxDistance = readOptionalMaxDistance(options.maxDistance);
|
|
1518
|
+
const triangleIndex = readNonNegativeInteger("triangleIndex", options.triangleIndex, 0);
|
|
1519
|
+
const edge1 = subtract(triangle.v1, triangle.v0);
|
|
1520
|
+
const edge2 = subtract(triangle.v2, triangle.v0);
|
|
1521
|
+
const pvec = cross(ray.direction, edge2);
|
|
1522
|
+
const determinant = dot(edge1, pvec);
|
|
1523
|
+
if (Math.abs(determinant) < 1e-7) {
|
|
1524
|
+
return null;
|
|
1525
|
+
}
|
|
1526
|
+
const invDet = 1 / determinant;
|
|
1527
|
+
const tvec = subtract(ray.origin, triangle.v0);
|
|
1528
|
+
const u = dot(tvec, pvec) * invDet;
|
|
1529
|
+
if (u < 0 || u > 1) {
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
const qvec = cross(tvec, edge1);
|
|
1533
|
+
const v = dot(ray.direction, qvec) * invDet;
|
|
1534
|
+
if (v < 0 || u + v > 1) {
|
|
1535
|
+
return null;
|
|
1536
|
+
}
|
|
1537
|
+
const distance = dot(edge2, qvec) * invDet;
|
|
1538
|
+
if (distance <= 1e-3 || distance > maxDistance) {
|
|
1539
|
+
return null;
|
|
1540
|
+
}
|
|
1541
|
+
const geometric = normalize(cross(edge1, edge2), [0, 1, 0]);
|
|
1542
|
+
const frontFace = dot(ray.direction, geometric) < 0;
|
|
1543
|
+
const orientedGeometric = frontFace ? geometric : scale(geometric, -1);
|
|
1544
|
+
const w = 1 - u - v;
|
|
1545
|
+
const interpolated = [
|
|
1546
|
+
triangle.n0[0] * w + triangle.n1[0] * u + triangle.n2[0] * v,
|
|
1547
|
+
triangle.n0[1] * w + triangle.n1[1] * u + triangle.n2[1] * v,
|
|
1548
|
+
triangle.n0[2] * w + triangle.n1[2] * u + triangle.n2[2] * v
|
|
1549
|
+
];
|
|
1550
|
+
const shadingNormal = repairReferenceShadingNormal(orientedGeometric, interpolated);
|
|
1551
|
+
const uv = [
|
|
1552
|
+
triangle.uv0[0] * w + triangle.uv1[0] * u + triangle.uv2[0] * v,
|
|
1553
|
+
triangle.uv0[1] * w + triangle.uv1[1] * u + triangle.uv2[1] * v
|
|
1554
|
+
];
|
|
1555
|
+
const position = add(ray.origin, scale(ray.direction, distance));
|
|
1556
|
+
return Object.freeze({
|
|
1557
|
+
hitType: "surface",
|
|
1558
|
+
rayId: ray.rayId,
|
|
1559
|
+
sourcePixelId: ray.sourcePixelId,
|
|
1560
|
+
distance,
|
|
1561
|
+
entityId: triangle.meshId,
|
|
1562
|
+
instanceId: 0,
|
|
1563
|
+
primitiveId: triangle.triangleId,
|
|
1564
|
+
materialId: triangle.materialKind,
|
|
1565
|
+
materialRefId: triangle.materialRefId,
|
|
1566
|
+
mediumRefId: triangle.mediumRefId,
|
|
1567
|
+
barycentrics: Object.freeze([w, u, v]),
|
|
1568
|
+
uv: Object.freeze(uv),
|
|
1569
|
+
geometricNormal: Object.freeze(orientedGeometric),
|
|
1570
|
+
shadingNormal: Object.freeze(shadingNormal),
|
|
1571
|
+
frontFace,
|
|
1572
|
+
triangleIndex,
|
|
1573
|
+
triangleId: triangle.triangleId,
|
|
1574
|
+
position: Object.freeze(position),
|
|
1575
|
+
color: triangle.color,
|
|
1576
|
+
emission: triangle.emission,
|
|
1577
|
+
material: triangle.material
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
1581
|
+
const radiance = evaluateReferenceEnvironmentRadiance(config, ray.origin, ray.direction);
|
|
1582
|
+
return Object.freeze({
|
|
1583
|
+
hitType: "environment",
|
|
1584
|
+
rayId: ray.rayId,
|
|
1585
|
+
sourcePixelId: ray.sourcePixelId,
|
|
1586
|
+
distance: -1,
|
|
1587
|
+
entityId: 0,
|
|
1588
|
+
instanceId: 0,
|
|
1589
|
+
primitiveId: 0,
|
|
1590
|
+
materialId: 0,
|
|
1591
|
+
materialRefId: 0,
|
|
1592
|
+
mediumRefId: 0,
|
|
1593
|
+
barycentrics: Object.freeze([0, 0, 0]),
|
|
1594
|
+
uv: Object.freeze([0, 0]),
|
|
1595
|
+
geometricNormal: Object.freeze(scale(ray.direction, -1)),
|
|
1596
|
+
shadingNormal: Object.freeze(scale(ray.direction, -1)),
|
|
1597
|
+
frontFace: true,
|
|
1598
|
+
triangleIndex: -1,
|
|
1599
|
+
triangleId: -1,
|
|
1600
|
+
position: Object.freeze(add(ray.origin, scale(ray.direction, 1e3))),
|
|
1601
|
+
color: Object.freeze([0, 0, 0, 0]),
|
|
1602
|
+
emission: radiance,
|
|
1603
|
+
material: Object.freeze([1, 0, 1, 1])
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
function traceWavefrontReferenceTriangles(config, ray, triangles, options = {}) {
|
|
1607
|
+
if (!config || typeof config !== "object") {
|
|
1608
|
+
throw new Error("config must be a wavefront path tracing config.");
|
|
1609
|
+
}
|
|
1610
|
+
const source = Array.isArray(triangles) ? triangles : [];
|
|
1611
|
+
let nearestHit = null;
|
|
1612
|
+
let nearestDistance = readOptionalMaxDistance(options.maxDistance);
|
|
1613
|
+
source.forEach((triangle, index) => {
|
|
1614
|
+
const hit = intersectWavefrontReferenceTriangle(ray, triangle, {
|
|
1615
|
+
maxDistance: Number.isFinite(nearestDistance) ? nearestDistance : void 0,
|
|
1616
|
+
triangleIndex: index
|
|
1617
|
+
});
|
|
1618
|
+
if (hit && hit.distance < nearestDistance) {
|
|
1619
|
+
nearestDistance = hit.distance;
|
|
1620
|
+
nearestHit = hit;
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
return nearestHit ?? createWavefrontReferenceEnvironmentHit(config, ray);
|
|
1624
|
+
}
|
|
1360
1625
|
function clampTileSizeForDevice(config, device) {
|
|
1361
1626
|
const limit = Number(device?.limits?.maxStorageBufferBindingSize);
|
|
1362
1627
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
@@ -1596,6 +1861,10 @@ struct Counters {
|
|
|
1596
1861
|
nextCount: atomic<u32>,
|
|
1597
1862
|
terminatedCount: atomic<u32>,
|
|
1598
1863
|
hitCount: atomic<u32>,
|
|
1864
|
+
dispatchX: u32,
|
|
1865
|
+
dispatchY: u32,
|
|
1866
|
+
dispatchZ: u32,
|
|
1867
|
+
dispatchPad: u32,
|
|
1599
1868
|
};
|
|
1600
1869
|
|
|
1601
1870
|
struct Candidate {
|
|
@@ -1758,6 +2027,18 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
1758
2027
|
return environment_radiance(origin, direction);
|
|
1759
2028
|
}
|
|
1760
2029
|
|
|
2030
|
+
fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2031
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2032
|
+
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
2033
|
+
let normalEnvironment = gated_environment_radiance(
|
|
2034
|
+
hit.position.xyz + normal * 0.003,
|
|
2035
|
+
normal
|
|
2036
|
+
);
|
|
2037
|
+
let environmentFloor = max(config.ambientColor.xyz, normalEnvironment * 0.12);
|
|
2038
|
+
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
2039
|
+
return clamp_sample_radiance(ray.throughput.xyz * surfaceColor * environmentFloor * materialFloor);
|
|
2040
|
+
}
|
|
2041
|
+
|
|
1761
2042
|
fn default_mesh_range() -> MeshRange {
|
|
1762
2043
|
return MeshRange(
|
|
1763
2044
|
0u,
|
|
@@ -2329,6 +2610,17 @@ fn tone_map_radiance(value: vec3<f32>) -> vec3<f32> {
|
|
|
2329
2610
|
return pow(clamp(mapped, vec3<f32>(0.0), vec3<f32>(1.0)), vec3<f32>(1.0 / 2.2));
|
|
2330
2611
|
}
|
|
2331
2612
|
|
|
2613
|
+
fn ray_workgroups_for_count(rayCount: u32) -> u32 {
|
|
2614
|
+
return max(1u, (rayCount + 63u) / 64u);
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
fn write_active_dispatch_args(activeCount: u32) {
|
|
2618
|
+
counters.dispatchX = ray_workgroups_for_count(activeCount);
|
|
2619
|
+
counters.dispatchY = 1u;
|
|
2620
|
+
counters.dispatchZ = 1u;
|
|
2621
|
+
counters.dispatchPad = 0u;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2332
2624
|
fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
|
|
2333
2625
|
return value / (vec3<f32>(1.0) + value);
|
|
2334
2626
|
}
|
|
@@ -2341,6 +2633,7 @@ fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2341
2633
|
atomicStore(&counters.nextCount, 0u);
|
|
2342
2634
|
atomicStore(&counters.terminatedCount, 0u);
|
|
2343
2635
|
atomicStore(&counters.hitCount, 0u);
|
|
2636
|
+
write_active_dispatch_args(config.tilePixelCount);
|
|
2344
2637
|
}
|
|
2345
2638
|
if (index >= config.tilePixelCount) {
|
|
2346
2639
|
return;
|
|
@@ -2616,9 +2909,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2616
2909
|
}
|
|
2617
2910
|
|
|
2618
2911
|
if (ray.bounce + 1u >= config.maxDepth) {
|
|
2912
|
+
let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
2619
2913
|
accumulation[ray.rayId] =
|
|
2620
|
-
accumulation[ray.rayId] +
|
|
2621
|
-
vec4<f32>(ray.throughput.xyz * config.ambientColor.xyz * sample_weight(), 1.0);
|
|
2914
|
+
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
2622
2915
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2623
2916
|
return;
|
|
2624
2917
|
}
|
|
@@ -2627,6 +2920,10 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2627
2920
|
let scatter = scatter_direction(ray, hit, seed);
|
|
2628
2921
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
2629
2922
|
if (nextIndex >= config.tilePixelCount) {
|
|
2923
|
+
let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
2924
|
+
accumulation[ray.rayId] =
|
|
2925
|
+
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
2926
|
+
atomicAdd(&counters.terminatedCount, 1u);
|
|
2630
2927
|
return;
|
|
2631
2928
|
}
|
|
2632
2929
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
@@ -2655,8 +2952,10 @@ fn compactAndSwapQueues(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2655
2952
|
return;
|
|
2656
2953
|
}
|
|
2657
2954
|
let nextCount = atomicLoad(&counters.nextCount);
|
|
2658
|
-
|
|
2955
|
+
let activeCount = min(nextCount, config.tilePixelCount);
|
|
2956
|
+
atomicStore(&counters.activeCount, activeCount);
|
|
2659
2957
|
atomicStore(&counters.nextCount, 0u);
|
|
2958
|
+
write_active_dispatch_args(activeCount);
|
|
2660
2959
|
}
|
|
2661
2960
|
|
|
2662
2961
|
@compute @workgroup_size(64)
|
|
@@ -2934,10 +3233,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2934
3233
|
);
|
|
2935
3234
|
const counterBuffer = createBuffer(
|
|
2936
3235
|
device,
|
|
2937
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
3236
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
|
|
2938
3237
|
COUNTER_BUFFER_BYTES,
|
|
2939
3238
|
"plasius.wavefront.counters"
|
|
2940
3239
|
);
|
|
3240
|
+
const activeDispatchBuffer = createBuffer(
|
|
3241
|
+
device,
|
|
3242
|
+
constants.buffer.INDIRECT | constants.buffer.COPY_DST,
|
|
3243
|
+
INDIRECT_DISPATCH_ARGS_BYTES,
|
|
3244
|
+
"plasius.wavefront.activeDispatchArgs"
|
|
3245
|
+
);
|
|
2941
3246
|
let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
|
|
2942
3247
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
2943
3248
|
const packedTriangles = packWavefrontTriangles(
|
|
@@ -3359,24 +3664,34 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3359
3664
|
return true;
|
|
3360
3665
|
}
|
|
3361
3666
|
function encodeTileSample(encoder, tile, configOffset) {
|
|
3362
|
-
const
|
|
3363
|
-
label: "plasius.wavefront.
|
|
3667
|
+
const generatePass = encoder.beginComputePass({
|
|
3668
|
+
label: "plasius.wavefront.generatePrimaryRaysPass"
|
|
3364
3669
|
});
|
|
3365
3670
|
const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3671
|
+
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3672
|
+
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
3673
|
+
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
3674
|
+
generatePass.end();
|
|
3370
3675
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
3676
|
+
encoder.copyBufferToBuffer(
|
|
3677
|
+
counterBuffer,
|
|
3678
|
+
COUNTER_DISPATCH_ARGS_OFFSET,
|
|
3679
|
+
activeDispatchBuffer,
|
|
3680
|
+
0,
|
|
3681
|
+
INDIRECT_DISPATCH_ARGS_BYTES
|
|
3682
|
+
);
|
|
3683
|
+
const passEncoder = encoder.beginComputePass({
|
|
3684
|
+
label: `plasius.wavefront.bounce.${bounceIndex}`
|
|
3685
|
+
});
|
|
3371
3686
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
3372
3687
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
3373
|
-
passEncoder.
|
|
3688
|
+
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
3374
3689
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
3375
|
-
passEncoder.
|
|
3690
|
+
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
3376
3691
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
3377
3692
|
passEncoder.dispatchWorkgroups(1);
|
|
3693
|
+
passEncoder.end();
|
|
3378
3694
|
}
|
|
3379
|
-
passEncoder.end();
|
|
3380
3695
|
}
|
|
3381
3696
|
function encodeTileOutput(encoder, tile, configOffset) {
|
|
3382
3697
|
const passEncoder = encoder.beginComputePass({
|
|
@@ -3519,6 +3834,28 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3519
3834
|
luminance: (0.2126 * bytes[0] + 0.7152 * bytes[1] + 0.0722 * bytes[2]) / 255
|
|
3520
3835
|
});
|
|
3521
3836
|
}
|
|
3837
|
+
async function renderFrame(renderOptions = {}) {
|
|
3838
|
+
const frameStats = renderOnce();
|
|
3839
|
+
const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
3840
|
+
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
3841
|
+
return Object.freeze({
|
|
3842
|
+
...frameStats,
|
|
3843
|
+
outputProbe: probe ? Object.freeze({
|
|
3844
|
+
...probe,
|
|
3845
|
+
sampledPixels: 1,
|
|
3846
|
+
nonZeroSamples: maxChannel > 0 ? 1 : 0,
|
|
3847
|
+
maxChannel
|
|
3848
|
+
}) : null,
|
|
3849
|
+
bounces: [],
|
|
3850
|
+
termination: Object.freeze({
|
|
3851
|
+
emissive: 0,
|
|
3852
|
+
environment: 0,
|
|
3853
|
+
ambientFallback: 0,
|
|
3854
|
+
maxDepth: 0
|
|
3855
|
+
}),
|
|
3856
|
+
queueOverflow: 0
|
|
3857
|
+
});
|
|
3858
|
+
}
|
|
3522
3859
|
function updateSceneObjects(sceneObjects) {
|
|
3523
3860
|
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
3524
3861
|
packedScene = nextPackedScene;
|
|
@@ -3577,6 +3914,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3577
3914
|
configBuffer.destroy?.();
|
|
3578
3915
|
bvhBuildConfigBuffer.destroy?.();
|
|
3579
3916
|
counterBuffer.destroy?.();
|
|
3917
|
+
activeDispatchBuffer.destroy?.();
|
|
3580
3918
|
radianceTexture.destroy?.();
|
|
3581
3919
|
denoiseScratchTexture.destroy?.();
|
|
3582
3920
|
outputTexture.destroy?.();
|
|
@@ -3589,12 +3927,32 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3589
3927
|
format,
|
|
3590
3928
|
config,
|
|
3591
3929
|
renderOnce,
|
|
3930
|
+
renderFrame,
|
|
3592
3931
|
readOutputProbe,
|
|
3593
3932
|
updateSceneObjects,
|
|
3594
3933
|
getSnapshot,
|
|
3595
3934
|
destroy
|
|
3596
3935
|
});
|
|
3597
3936
|
}
|
|
3937
|
+
async function renderWavefrontPathTracingComputeFrame(options = {}) {
|
|
3938
|
+
const renderer = await createWavefrontPathTracingComputeRenderer(options);
|
|
3939
|
+
try {
|
|
3940
|
+
return await renderer.renderFrame(options);
|
|
3941
|
+
} finally {
|
|
3942
|
+
renderer.destroy();
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
function createWavefrontPathTracingComputeShaderSource(options = {}) {
|
|
3946
|
+
const workgroupSize = readPositiveInteger(
|
|
3947
|
+
"workgroupSize",
|
|
3948
|
+
options.workgroupSize ?? rendererWavefrontComputeWorkgroupSize,
|
|
3949
|
+
rendererWavefrontComputeWorkgroupSize
|
|
3950
|
+
);
|
|
3951
|
+
if (workgroupSize !== rendererWavefrontComputeWorkgroupSize) {
|
|
3952
|
+
throw new Error(`wavefront mesh compute currently requires workgroupSize=${rendererWavefrontComputeWorkgroupSize}.`);
|
|
3953
|
+
}
|
|
3954
|
+
return WAVEFRONT_COMPUTE_WGSL;
|
|
3955
|
+
}
|
|
3598
3956
|
|
|
3599
3957
|
// src/index.js
|
|
3600
3958
|
var DEFAULT_CLEAR_COLOR = Object.freeze([0.07, 0.11, 0.18, 1]);
|
|
@@ -4895,22 +5253,29 @@ var defaultRendererClearColor = DEFAULT_CLEAR_COLOR;
|
|
|
4895
5253
|
createWavefrontMeshAcceleration,
|
|
4896
5254
|
createWavefrontPathTracingComputeConfig,
|
|
4897
5255
|
createWavefrontPathTracingComputeRenderer,
|
|
5256
|
+
createWavefrontPathTracingComputeShaderSource,
|
|
4898
5257
|
createWavefrontPathTracingPlan,
|
|
5258
|
+
createWavefrontReferenceRay,
|
|
4899
5259
|
defaultRendererClearColor,
|
|
4900
5260
|
defaultRendererWorkerProfile,
|
|
4901
5261
|
estimateWavefrontPathTracingMemory,
|
|
4902
5262
|
getRendererWorkerManifest,
|
|
4903
5263
|
getRendererWorkerProfile,
|
|
5264
|
+
intersectWavefrontReferenceTriangle,
|
|
4904
5265
|
normalizeWavefrontMesh,
|
|
4905
5266
|
normalizeWavefrontSceneObject,
|
|
4906
5267
|
packWavefrontBvhNodes,
|
|
4907
5268
|
packWavefrontSceneObjects,
|
|
4908
5269
|
packWavefrontTriangles,
|
|
5270
|
+
renderWavefrontPathTracingComputeFrame,
|
|
4909
5271
|
rendererAccelerationStructureUpdateClasses,
|
|
4910
5272
|
rendererDebugOwner,
|
|
4911
5273
|
rendererRayTracingStageOrder,
|
|
4912
5274
|
rendererRepresentationBands,
|
|
4913
5275
|
rendererWavefrontBufferSchemaVersion,
|
|
5276
|
+
rendererWavefrontComputeMode,
|
|
5277
|
+
rendererWavefrontComputeStatsStride,
|
|
5278
|
+
rendererWavefrontComputeWorkgroupSize,
|
|
4914
5279
|
rendererWavefrontHitTypes,
|
|
4915
5280
|
rendererWavefrontPassOrder,
|
|
4916
5281
|
rendererWavefrontQueuePairStrategy,
|
|
@@ -4920,6 +5285,7 @@ var defaultRendererClearColor = DEFAULT_CLEAR_COLOR;
|
|
|
4920
5285
|
rendererWorkerQueueClass,
|
|
4921
5286
|
supportsWavefrontPathTracingCompute,
|
|
4922
5287
|
supportsWebGpu,
|
|
5288
|
+
traceWavefrontReferenceTriangles,
|
|
4923
5289
|
wavefrontMaterialKinds,
|
|
4924
5290
|
wavefrontPathTracingComputeLimits,
|
|
4925
5291
|
wavefrontSceneObjectKinds
|