@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.js
CHANGED
|
@@ -7,6 +7,9 @@ var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
|
7
7
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
8
8
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
9
9
|
var WORKGROUP_SIZE = 64;
|
|
10
|
+
var rendererWavefrontComputeMode = "webgpu-compute";
|
|
11
|
+
var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
12
|
+
var rendererWavefrontComputeStatsStride = 8;
|
|
10
13
|
var RAY_RECORD_BYTES = 80;
|
|
11
14
|
var HIT_RECORD_BYTES = 208;
|
|
12
15
|
var SCENE_OBJECT_RECORD_BYTES = 96;
|
|
@@ -19,7 +22,9 @@ var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
|
19
22
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
20
23
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
21
24
|
var CONFIG_BUFFER_BYTES = 272;
|
|
22
|
-
var
|
|
25
|
+
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
26
|
+
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
27
|
+
var COUNTER_BUFFER_BYTES = 32;
|
|
23
28
|
var TRACE_STORAGE_BUFFER_BINDINGS = 9;
|
|
24
29
|
var MATERIAL_DIFFUSE = 0;
|
|
25
30
|
var MATERIAL_METAL = 1;
|
|
@@ -59,7 +64,9 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
59
64
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
60
65
|
emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
61
66
|
environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
62
|
-
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES
|
|
67
|
+
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES,
|
|
68
|
+
counterRecordBytes: COUNTER_BUFFER_BYTES,
|
|
69
|
+
indirectDispatchRecordBytes: INDIRECT_DISPATCH_ARGS_BYTES
|
|
63
70
|
});
|
|
64
71
|
var wavefrontSceneObjectKinds = Object.freeze({
|
|
65
72
|
sphere: OBJECT_KIND_SPHERE,
|
|
@@ -167,6 +174,25 @@ function normalize(value, fallback = [0, 0, 1]) {
|
|
|
167
174
|
}
|
|
168
175
|
return [value[0] / length, value[1] / length, value[2] / length];
|
|
169
176
|
}
|
|
177
|
+
function hashUint32(value) {
|
|
178
|
+
let x = value >>> 0;
|
|
179
|
+
x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
|
|
180
|
+
x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
|
|
181
|
+
return (x >>> 16 ^ x) >>> 0;
|
|
182
|
+
}
|
|
183
|
+
function mixSeed(pixelId, sampleId, bounce, frameIndex, dimension) {
|
|
184
|
+
let x = (pixelId >>> 0) * 747796405 ^ (sampleId >>> 0) * 2891336453 ^ (bounce >>> 0) * 277803737 ^ (frameIndex >>> 0) * 1442695041 ^ (dimension >>> 0) * 1597334677;
|
|
185
|
+
x >>>= 0;
|
|
186
|
+
x ^= x >>> 16;
|
|
187
|
+
x = x * 2146121005 >>> 0;
|
|
188
|
+
x ^= x >>> 15;
|
|
189
|
+
x = x * 2221713035 >>> 0;
|
|
190
|
+
x ^= x >>> 16;
|
|
191
|
+
return x >>> 0;
|
|
192
|
+
}
|
|
193
|
+
function random01FromSeed(seed) {
|
|
194
|
+
return (hashUint32(seed) & 16777215) / 16777215;
|
|
195
|
+
}
|
|
170
196
|
function getArrayLikeLength(value) {
|
|
171
197
|
return Array.isArray(value) || ArrayBuffer.isView(value) ? value.length : 0;
|
|
172
198
|
}
|
|
@@ -765,6 +791,29 @@ function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
|
|
|
765
791
|
exposure: Math.max(1e-4, readFiniteNumber("environmentLighting.exposure", source.exposure, DEFAULT_ENVIRONMENT_LIGHTING.exposure))
|
|
766
792
|
});
|
|
767
793
|
}
|
|
794
|
+
function evaluateReferenceEnvironmentRadiance(config, origin, direction) {
|
|
795
|
+
void origin;
|
|
796
|
+
const rayDirection = normalize(direction, [0, 1, 0]);
|
|
797
|
+
const upFactor = clamp(rayDirection[1] * 0.5 + 0.5, 0, 1);
|
|
798
|
+
const sunDirection = normalize(
|
|
799
|
+
config.environmentLighting?.sunDirection ?? DEFAULT_ENVIRONMENT_LIGHTING.sunDirection,
|
|
800
|
+
DEFAULT_ENVIRONMENT_LIGHTING.sunDirection
|
|
801
|
+
);
|
|
802
|
+
const sunGlow = Math.pow(clamp(dot(rayDirection, sunDirection), 0, 1), 192);
|
|
803
|
+
const horizonColor = config.environmentLighting?.horizonColor ?? DEFAULT_ENVIRONMENT_LIGHTING.horizonColor;
|
|
804
|
+
const zenithColor = config.environmentLighting?.zenithColor ?? DEFAULT_ENVIRONMENT_LIGHTING.zenithColor;
|
|
805
|
+
const sunColor = config.environmentLighting?.sunColor ?? DEFAULT_ENVIRONMENT_LIGHTING.sunColor;
|
|
806
|
+
const intensity = Math.max(
|
|
807
|
+
1e-4,
|
|
808
|
+
Number(config.environmentLighting?.intensity ?? DEFAULT_ENVIRONMENT_LIGHTING.intensity)
|
|
809
|
+
);
|
|
810
|
+
return Object.freeze([
|
|
811
|
+
(horizonColor[0] * (1 - upFactor) + zenithColor[0] * upFactor + sunColor[0] * sunGlow) * intensity,
|
|
812
|
+
(horizonColor[1] * (1 - upFactor) + zenithColor[1] * upFactor + sunColor[1] * sunGlow) * intensity,
|
|
813
|
+
(horizonColor[2] * (1 - upFactor) + zenithColor[2] * upFactor + sunColor[2] * sunGlow) * intensity,
|
|
814
|
+
1
|
|
815
|
+
]);
|
|
816
|
+
}
|
|
768
817
|
function resolveEnvironmentPortalMode(value, hasPortals) {
|
|
769
818
|
if (value === void 0 || value === null) {
|
|
770
819
|
return hasPortals ? 2 : 0;
|
|
@@ -968,7 +1017,8 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
968
1017
|
environmentPortalBytes,
|
|
969
1018
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
970
1019
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
971
|
-
|
|
1020
|
+
indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
|
|
1021
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
972
1022
|
});
|
|
973
1023
|
}
|
|
974
1024
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -1045,6 +1095,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1045
1095
|
environmentPortals.length > 0
|
|
1046
1096
|
);
|
|
1047
1097
|
return Object.freeze({
|
|
1098
|
+
mode: rendererWavefrontComputeMode,
|
|
1048
1099
|
width,
|
|
1049
1100
|
height,
|
|
1050
1101
|
maxDepth,
|
|
@@ -1099,6 +1150,9 @@ function getGpuUsageConstants() {
|
|
|
1099
1150
|
if (typeof GPUBufferUsage === "undefined" || typeof GPUTextureUsage === "undefined" || typeof GPUShaderStage === "undefined") {
|
|
1100
1151
|
throw new Error("WebGPU runtime unavailable. Required GPU constants are missing.");
|
|
1101
1152
|
}
|
|
1153
|
+
if (typeof GPUBufferUsage.INDIRECT !== "number") {
|
|
1154
|
+
throw new Error("WebGPU runtime unavailable. GPUBufferUsage.INDIRECT is missing.");
|
|
1155
|
+
}
|
|
1102
1156
|
return {
|
|
1103
1157
|
buffer: GPUBufferUsage,
|
|
1104
1158
|
texture: GPUTextureUsage,
|
|
@@ -1293,6 +1347,209 @@ function createTiles(width, height, tileSize) {
|
|
|
1293
1347
|
}
|
|
1294
1348
|
return Object.freeze(tiles);
|
|
1295
1349
|
}
|
|
1350
|
+
function normalizeReferenceTile(config, tileInput = {}) {
|
|
1351
|
+
const tileX = clamp(
|
|
1352
|
+
readNonNegativeInteger("tile.x", tileInput.x, 0),
|
|
1353
|
+
0,
|
|
1354
|
+
Math.max(0, config.width - 1)
|
|
1355
|
+
);
|
|
1356
|
+
const tileY = clamp(
|
|
1357
|
+
readNonNegativeInteger("tile.y", tileInput.y, 0),
|
|
1358
|
+
0,
|
|
1359
|
+
Math.max(0, config.height - 1)
|
|
1360
|
+
);
|
|
1361
|
+
const tileWidth = clamp(
|
|
1362
|
+
readPositiveInteger("tile.width", tileInput.width, config.width - tileX),
|
|
1363
|
+
1,
|
|
1364
|
+
config.width - tileX
|
|
1365
|
+
);
|
|
1366
|
+
const tileHeight = clamp(
|
|
1367
|
+
readPositiveInteger("tile.height", tileInput.height, config.height - tileY),
|
|
1368
|
+
1,
|
|
1369
|
+
config.height - tileY
|
|
1370
|
+
);
|
|
1371
|
+
return Object.freeze({
|
|
1372
|
+
x: tileX,
|
|
1373
|
+
y: tileY,
|
|
1374
|
+
width: tileWidth,
|
|
1375
|
+
height: tileHeight
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
function repairReferenceShadingNormal(geometricNormal, shadingNormal) {
|
|
1379
|
+
const normal = normalize(shadingNormal, geometricNormal);
|
|
1380
|
+
return dot(normal, geometricNormal) < 0 ? scale(normal, -1) : normal;
|
|
1381
|
+
}
|
|
1382
|
+
function readOptionalMaxDistance(value) {
|
|
1383
|
+
if (value === void 0 || value === null) {
|
|
1384
|
+
return Number.POSITIVE_INFINITY;
|
|
1385
|
+
}
|
|
1386
|
+
const numeric = Number(value);
|
|
1387
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
1388
|
+
throw new Error("maxDistance must be a positive finite number when provided.");
|
|
1389
|
+
}
|
|
1390
|
+
return numeric;
|
|
1391
|
+
}
|
|
1392
|
+
function createWavefrontReferenceRay(config, options = {}) {
|
|
1393
|
+
if (!config || typeof config !== "object") {
|
|
1394
|
+
throw new Error("config must be a wavefront path tracing config.");
|
|
1395
|
+
}
|
|
1396
|
+
const tile = normalizeReferenceTile(config, options.tile);
|
|
1397
|
+
const tilePixelCount = tile.width * tile.height;
|
|
1398
|
+
const pixelIndex = readNonNegativeInteger("pixelIndex", options.pixelIndex, 0);
|
|
1399
|
+
if (pixelIndex >= tilePixelCount) {
|
|
1400
|
+
throw new Error(`pixelIndex ${pixelIndex} exceeds tile capacity ${tilePixelCount}.`);
|
|
1401
|
+
}
|
|
1402
|
+
const sampleIndex = readNonNegativeInteger("sampleIndex", options.sampleIndex, 0);
|
|
1403
|
+
const frameIndex = readNonNegativeInteger("frameIndex", options.frameIndex, config.frameIndex ?? 0);
|
|
1404
|
+
const jitterScale = clamp(readFiniteNumber("jitterScale", options.jitterScale, 0.35), 0, 1);
|
|
1405
|
+
const localX = pixelIndex % tile.width;
|
|
1406
|
+
const localY = Math.floor(pixelIndex / tile.width);
|
|
1407
|
+
const pixelX = tile.x + localX;
|
|
1408
|
+
const pixelY = tile.y + localY;
|
|
1409
|
+
const sourcePixelId = pixelY * config.width + pixelX;
|
|
1410
|
+
const jitterX = random01FromSeed(mixSeed(sourcePixelId, sampleIndex, 0, frameIndex, 1)) - 0.5;
|
|
1411
|
+
const jitterY = random01FromSeed(mixSeed(sourcePixelId, sampleIndex, 0, frameIndex, 2)) - 0.5;
|
|
1412
|
+
const ndcX = (pixelX + 0.5 + jitterX * jitterScale) / config.width * 2 - 1;
|
|
1413
|
+
const ndcY = 1 - (pixelY + 0.5 + jitterY * jitterScale) / config.height * 2;
|
|
1414
|
+
const viewX = ndcX * config.camera.tanHalfFovY * config.camera.aspect;
|
|
1415
|
+
const viewY = ndcY * config.camera.tanHalfFovY;
|
|
1416
|
+
const direction = normalize(
|
|
1417
|
+
add(
|
|
1418
|
+
add(config.camera.forward, scale(config.camera.right, viewX)),
|
|
1419
|
+
scale(config.camera.up, viewY)
|
|
1420
|
+
),
|
|
1421
|
+
config.camera.forward
|
|
1422
|
+
);
|
|
1423
|
+
return Object.freeze({
|
|
1424
|
+
rayId: pixelIndex,
|
|
1425
|
+
parentRayId: 4294967295,
|
|
1426
|
+
sourcePixelId,
|
|
1427
|
+
sampleId: sampleIndex,
|
|
1428
|
+
bounce: 0,
|
|
1429
|
+
mediumRefId: 0,
|
|
1430
|
+
flags: 0,
|
|
1431
|
+
origin: Object.freeze([...config.camera.position]),
|
|
1432
|
+
direction: Object.freeze(direction),
|
|
1433
|
+
throughput: Object.freeze([1, 1, 1, 1]),
|
|
1434
|
+
pixelX,
|
|
1435
|
+
pixelY
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
function intersectWavefrontReferenceTriangle(ray, triangle, options = {}) {
|
|
1439
|
+
if (!ray || typeof ray !== "object") {
|
|
1440
|
+
throw new Error("ray must be a wavefront reference ray.");
|
|
1441
|
+
}
|
|
1442
|
+
if (!triangle || typeof triangle !== "object") {
|
|
1443
|
+
throw new Error("triangle must be a wavefront triangle record.");
|
|
1444
|
+
}
|
|
1445
|
+
const maxDistance = readOptionalMaxDistance(options.maxDistance);
|
|
1446
|
+
const triangleIndex = readNonNegativeInteger("triangleIndex", options.triangleIndex, 0);
|
|
1447
|
+
const edge1 = subtract(triangle.v1, triangle.v0);
|
|
1448
|
+
const edge2 = subtract(triangle.v2, triangle.v0);
|
|
1449
|
+
const pvec = cross(ray.direction, edge2);
|
|
1450
|
+
const determinant = dot(edge1, pvec);
|
|
1451
|
+
if (Math.abs(determinant) < 1e-7) {
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
const invDet = 1 / determinant;
|
|
1455
|
+
const tvec = subtract(ray.origin, triangle.v0);
|
|
1456
|
+
const u = dot(tvec, pvec) * invDet;
|
|
1457
|
+
if (u < 0 || u > 1) {
|
|
1458
|
+
return null;
|
|
1459
|
+
}
|
|
1460
|
+
const qvec = cross(tvec, edge1);
|
|
1461
|
+
const v = dot(ray.direction, qvec) * invDet;
|
|
1462
|
+
if (v < 0 || u + v > 1) {
|
|
1463
|
+
return null;
|
|
1464
|
+
}
|
|
1465
|
+
const distance = dot(edge2, qvec) * invDet;
|
|
1466
|
+
if (distance <= 1e-3 || distance > maxDistance) {
|
|
1467
|
+
return null;
|
|
1468
|
+
}
|
|
1469
|
+
const geometric = normalize(cross(edge1, edge2), [0, 1, 0]);
|
|
1470
|
+
const frontFace = dot(ray.direction, geometric) < 0;
|
|
1471
|
+
const orientedGeometric = frontFace ? geometric : scale(geometric, -1);
|
|
1472
|
+
const w = 1 - u - v;
|
|
1473
|
+
const interpolated = [
|
|
1474
|
+
triangle.n0[0] * w + triangle.n1[0] * u + triangle.n2[0] * v,
|
|
1475
|
+
triangle.n0[1] * w + triangle.n1[1] * u + triangle.n2[1] * v,
|
|
1476
|
+
triangle.n0[2] * w + triangle.n1[2] * u + triangle.n2[2] * v
|
|
1477
|
+
];
|
|
1478
|
+
const shadingNormal = repairReferenceShadingNormal(orientedGeometric, interpolated);
|
|
1479
|
+
const uv = [
|
|
1480
|
+
triangle.uv0[0] * w + triangle.uv1[0] * u + triangle.uv2[0] * v,
|
|
1481
|
+
triangle.uv0[1] * w + triangle.uv1[1] * u + triangle.uv2[1] * v
|
|
1482
|
+
];
|
|
1483
|
+
const position = add(ray.origin, scale(ray.direction, distance));
|
|
1484
|
+
return Object.freeze({
|
|
1485
|
+
hitType: "surface",
|
|
1486
|
+
rayId: ray.rayId,
|
|
1487
|
+
sourcePixelId: ray.sourcePixelId,
|
|
1488
|
+
distance,
|
|
1489
|
+
entityId: triangle.meshId,
|
|
1490
|
+
instanceId: 0,
|
|
1491
|
+
primitiveId: triangle.triangleId,
|
|
1492
|
+
materialId: triangle.materialKind,
|
|
1493
|
+
materialRefId: triangle.materialRefId,
|
|
1494
|
+
mediumRefId: triangle.mediumRefId,
|
|
1495
|
+
barycentrics: Object.freeze([w, u, v]),
|
|
1496
|
+
uv: Object.freeze(uv),
|
|
1497
|
+
geometricNormal: Object.freeze(orientedGeometric),
|
|
1498
|
+
shadingNormal: Object.freeze(shadingNormal),
|
|
1499
|
+
frontFace,
|
|
1500
|
+
triangleIndex,
|
|
1501
|
+
triangleId: triangle.triangleId,
|
|
1502
|
+
position: Object.freeze(position),
|
|
1503
|
+
color: triangle.color,
|
|
1504
|
+
emission: triangle.emission,
|
|
1505
|
+
material: triangle.material
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
1509
|
+
const radiance = evaluateReferenceEnvironmentRadiance(config, ray.origin, ray.direction);
|
|
1510
|
+
return Object.freeze({
|
|
1511
|
+
hitType: "environment",
|
|
1512
|
+
rayId: ray.rayId,
|
|
1513
|
+
sourcePixelId: ray.sourcePixelId,
|
|
1514
|
+
distance: -1,
|
|
1515
|
+
entityId: 0,
|
|
1516
|
+
instanceId: 0,
|
|
1517
|
+
primitiveId: 0,
|
|
1518
|
+
materialId: 0,
|
|
1519
|
+
materialRefId: 0,
|
|
1520
|
+
mediumRefId: 0,
|
|
1521
|
+
barycentrics: Object.freeze([0, 0, 0]),
|
|
1522
|
+
uv: Object.freeze([0, 0]),
|
|
1523
|
+
geometricNormal: Object.freeze(scale(ray.direction, -1)),
|
|
1524
|
+
shadingNormal: Object.freeze(scale(ray.direction, -1)),
|
|
1525
|
+
frontFace: true,
|
|
1526
|
+
triangleIndex: -1,
|
|
1527
|
+
triangleId: -1,
|
|
1528
|
+
position: Object.freeze(add(ray.origin, scale(ray.direction, 1e3))),
|
|
1529
|
+
color: Object.freeze([0, 0, 0, 0]),
|
|
1530
|
+
emission: radiance,
|
|
1531
|
+
material: Object.freeze([1, 0, 1, 1])
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
function traceWavefrontReferenceTriangles(config, ray, triangles, options = {}) {
|
|
1535
|
+
if (!config || typeof config !== "object") {
|
|
1536
|
+
throw new Error("config must be a wavefront path tracing config.");
|
|
1537
|
+
}
|
|
1538
|
+
const source = Array.isArray(triangles) ? triangles : [];
|
|
1539
|
+
let nearestHit = null;
|
|
1540
|
+
let nearestDistance = readOptionalMaxDistance(options.maxDistance);
|
|
1541
|
+
source.forEach((triangle, index) => {
|
|
1542
|
+
const hit = intersectWavefrontReferenceTriangle(ray, triangle, {
|
|
1543
|
+
maxDistance: Number.isFinite(nearestDistance) ? nearestDistance : void 0,
|
|
1544
|
+
triangleIndex: index
|
|
1545
|
+
});
|
|
1546
|
+
if (hit && hit.distance < nearestDistance) {
|
|
1547
|
+
nearestDistance = hit.distance;
|
|
1548
|
+
nearestHit = hit;
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
return nearestHit ?? createWavefrontReferenceEnvironmentHit(config, ray);
|
|
1552
|
+
}
|
|
1296
1553
|
function clampTileSizeForDevice(config, device) {
|
|
1297
1554
|
const limit = Number(device?.limits?.maxStorageBufferBindingSize);
|
|
1298
1555
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
@@ -1532,6 +1789,10 @@ struct Counters {
|
|
|
1532
1789
|
nextCount: atomic<u32>,
|
|
1533
1790
|
terminatedCount: atomic<u32>,
|
|
1534
1791
|
hitCount: atomic<u32>,
|
|
1792
|
+
dispatchX: u32,
|
|
1793
|
+
dispatchY: u32,
|
|
1794
|
+
dispatchZ: u32,
|
|
1795
|
+
dispatchPad: u32,
|
|
1535
1796
|
};
|
|
1536
1797
|
|
|
1537
1798
|
struct Candidate {
|
|
@@ -1694,6 +1955,18 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
1694
1955
|
return environment_radiance(origin, direction);
|
|
1695
1956
|
}
|
|
1696
1957
|
|
|
1958
|
+
fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
1959
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
1960
|
+
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
1961
|
+
let normalEnvironment = gated_environment_radiance(
|
|
1962
|
+
hit.position.xyz + normal * 0.003,
|
|
1963
|
+
normal
|
|
1964
|
+
);
|
|
1965
|
+
let environmentFloor = max(config.ambientColor.xyz, normalEnvironment * 0.12);
|
|
1966
|
+
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
1967
|
+
return clamp_sample_radiance(ray.throughput.xyz * surfaceColor * environmentFloor * materialFloor);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1697
1970
|
fn default_mesh_range() -> MeshRange {
|
|
1698
1971
|
return MeshRange(
|
|
1699
1972
|
0u,
|
|
@@ -2265,6 +2538,17 @@ fn tone_map_radiance(value: vec3<f32>) -> vec3<f32> {
|
|
|
2265
2538
|
return pow(clamp(mapped, vec3<f32>(0.0), vec3<f32>(1.0)), vec3<f32>(1.0 / 2.2));
|
|
2266
2539
|
}
|
|
2267
2540
|
|
|
2541
|
+
fn ray_workgroups_for_count(rayCount: u32) -> u32 {
|
|
2542
|
+
return max(1u, (rayCount + 63u) / 64u);
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
fn write_active_dispatch_args(activeCount: u32) {
|
|
2546
|
+
counters.dispatchX = ray_workgroups_for_count(activeCount);
|
|
2547
|
+
counters.dispatchY = 1u;
|
|
2548
|
+
counters.dispatchZ = 1u;
|
|
2549
|
+
counters.dispatchPad = 0u;
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2268
2552
|
fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
|
|
2269
2553
|
return value / (vec3<f32>(1.0) + value);
|
|
2270
2554
|
}
|
|
@@ -2277,6 +2561,7 @@ fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2277
2561
|
atomicStore(&counters.nextCount, 0u);
|
|
2278
2562
|
atomicStore(&counters.terminatedCount, 0u);
|
|
2279
2563
|
atomicStore(&counters.hitCount, 0u);
|
|
2564
|
+
write_active_dispatch_args(config.tilePixelCount);
|
|
2280
2565
|
}
|
|
2281
2566
|
if (index >= config.tilePixelCount) {
|
|
2282
2567
|
return;
|
|
@@ -2552,9 +2837,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2552
2837
|
}
|
|
2553
2838
|
|
|
2554
2839
|
if (ray.bounce + 1u >= config.maxDepth) {
|
|
2840
|
+
let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
2555
2841
|
accumulation[ray.rayId] =
|
|
2556
|
-
accumulation[ray.rayId] +
|
|
2557
|
-
vec4<f32>(ray.throughput.xyz * config.ambientColor.xyz * sample_weight(), 1.0);
|
|
2842
|
+
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
2558
2843
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2559
2844
|
return;
|
|
2560
2845
|
}
|
|
@@ -2563,6 +2848,10 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2563
2848
|
let scatter = scatter_direction(ray, hit, seed);
|
|
2564
2849
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
2565
2850
|
if (nextIndex >= config.tilePixelCount) {
|
|
2851
|
+
let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
2852
|
+
accumulation[ray.rayId] =
|
|
2853
|
+
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
2854
|
+
atomicAdd(&counters.terminatedCount, 1u);
|
|
2566
2855
|
return;
|
|
2567
2856
|
}
|
|
2568
2857
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
@@ -2591,8 +2880,10 @@ fn compactAndSwapQueues(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2591
2880
|
return;
|
|
2592
2881
|
}
|
|
2593
2882
|
let nextCount = atomicLoad(&counters.nextCount);
|
|
2594
|
-
|
|
2883
|
+
let activeCount = min(nextCount, config.tilePixelCount);
|
|
2884
|
+
atomicStore(&counters.activeCount, activeCount);
|
|
2595
2885
|
atomicStore(&counters.nextCount, 0u);
|
|
2886
|
+
write_active_dispatch_args(activeCount);
|
|
2596
2887
|
}
|
|
2597
2888
|
|
|
2598
2889
|
@compute @workgroup_size(64)
|
|
@@ -2870,10 +3161,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2870
3161
|
);
|
|
2871
3162
|
const counterBuffer = createBuffer(
|
|
2872
3163
|
device,
|
|
2873
|
-
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
3164
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
|
|
2874
3165
|
COUNTER_BUFFER_BYTES,
|
|
2875
3166
|
"plasius.wavefront.counters"
|
|
2876
3167
|
);
|
|
3168
|
+
const activeDispatchBuffer = createBuffer(
|
|
3169
|
+
device,
|
|
3170
|
+
constants.buffer.INDIRECT | constants.buffer.COPY_DST,
|
|
3171
|
+
INDIRECT_DISPATCH_ARGS_BYTES,
|
|
3172
|
+
"plasius.wavefront.activeDispatchArgs"
|
|
3173
|
+
);
|
|
2877
3174
|
let packedScene = packWavefrontSceneObjects(config.sceneObjects, config.sceneObjectCapacity);
|
|
2878
3175
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
2879
3176
|
const packedTriangles = packWavefrontTriangles(
|
|
@@ -3295,24 +3592,34 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3295
3592
|
return true;
|
|
3296
3593
|
}
|
|
3297
3594
|
function encodeTileSample(encoder, tile, configOffset) {
|
|
3298
|
-
const
|
|
3299
|
-
label: "plasius.wavefront.
|
|
3595
|
+
const generatePass = encoder.beginComputePass({
|
|
3596
|
+
label: "plasius.wavefront.generatePrimaryRaysPass"
|
|
3300
3597
|
});
|
|
3301
3598
|
const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3599
|
+
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3600
|
+
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
3601
|
+
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
3602
|
+
generatePass.end();
|
|
3306
3603
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
3604
|
+
encoder.copyBufferToBuffer(
|
|
3605
|
+
counterBuffer,
|
|
3606
|
+
COUNTER_DISPATCH_ARGS_OFFSET,
|
|
3607
|
+
activeDispatchBuffer,
|
|
3608
|
+
0,
|
|
3609
|
+
INDIRECT_DISPATCH_ARGS_BYTES
|
|
3610
|
+
);
|
|
3611
|
+
const passEncoder = encoder.beginComputePass({
|
|
3612
|
+
label: `plasius.wavefront.bounce.${bounceIndex}`
|
|
3613
|
+
});
|
|
3307
3614
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
3308
3615
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
3309
|
-
passEncoder.
|
|
3616
|
+
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
3310
3617
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
3311
|
-
passEncoder.
|
|
3618
|
+
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
3312
3619
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
3313
3620
|
passEncoder.dispatchWorkgroups(1);
|
|
3621
|
+
passEncoder.end();
|
|
3314
3622
|
}
|
|
3315
|
-
passEncoder.end();
|
|
3316
3623
|
}
|
|
3317
3624
|
function encodeTileOutput(encoder, tile, configOffset) {
|
|
3318
3625
|
const passEncoder = encoder.beginComputePass({
|
|
@@ -3455,6 +3762,28 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3455
3762
|
luminance: (0.2126 * bytes[0] + 0.7152 * bytes[1] + 0.0722 * bytes[2]) / 255
|
|
3456
3763
|
});
|
|
3457
3764
|
}
|
|
3765
|
+
async function renderFrame(renderOptions = {}) {
|
|
3766
|
+
const frameStats = renderOnce();
|
|
3767
|
+
const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
3768
|
+
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
3769
|
+
return Object.freeze({
|
|
3770
|
+
...frameStats,
|
|
3771
|
+
outputProbe: probe ? Object.freeze({
|
|
3772
|
+
...probe,
|
|
3773
|
+
sampledPixels: 1,
|
|
3774
|
+
nonZeroSamples: maxChannel > 0 ? 1 : 0,
|
|
3775
|
+
maxChannel
|
|
3776
|
+
}) : null,
|
|
3777
|
+
bounces: [],
|
|
3778
|
+
termination: Object.freeze({
|
|
3779
|
+
emissive: 0,
|
|
3780
|
+
environment: 0,
|
|
3781
|
+
ambientFallback: 0,
|
|
3782
|
+
maxDepth: 0
|
|
3783
|
+
}),
|
|
3784
|
+
queueOverflow: 0
|
|
3785
|
+
});
|
|
3786
|
+
}
|
|
3458
3787
|
function updateSceneObjects(sceneObjects) {
|
|
3459
3788
|
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
3460
3789
|
packedScene = nextPackedScene;
|
|
@@ -3513,6 +3842,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3513
3842
|
configBuffer.destroy?.();
|
|
3514
3843
|
bvhBuildConfigBuffer.destroy?.();
|
|
3515
3844
|
counterBuffer.destroy?.();
|
|
3845
|
+
activeDispatchBuffer.destroy?.();
|
|
3516
3846
|
radianceTexture.destroy?.();
|
|
3517
3847
|
denoiseScratchTexture.destroy?.();
|
|
3518
3848
|
outputTexture.destroy?.();
|
|
@@ -3525,12 +3855,32 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3525
3855
|
format,
|
|
3526
3856
|
config,
|
|
3527
3857
|
renderOnce,
|
|
3858
|
+
renderFrame,
|
|
3528
3859
|
readOutputProbe,
|
|
3529
3860
|
updateSceneObjects,
|
|
3530
3861
|
getSnapshot,
|
|
3531
3862
|
destroy
|
|
3532
3863
|
});
|
|
3533
3864
|
}
|
|
3865
|
+
async function renderWavefrontPathTracingComputeFrame(options = {}) {
|
|
3866
|
+
const renderer = await createWavefrontPathTracingComputeRenderer(options);
|
|
3867
|
+
try {
|
|
3868
|
+
return await renderer.renderFrame(options);
|
|
3869
|
+
} finally {
|
|
3870
|
+
renderer.destroy();
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
function createWavefrontPathTracingComputeShaderSource(options = {}) {
|
|
3874
|
+
const workgroupSize = readPositiveInteger(
|
|
3875
|
+
"workgroupSize",
|
|
3876
|
+
options.workgroupSize ?? rendererWavefrontComputeWorkgroupSize,
|
|
3877
|
+
rendererWavefrontComputeWorkgroupSize
|
|
3878
|
+
);
|
|
3879
|
+
if (workgroupSize !== rendererWavefrontComputeWorkgroupSize) {
|
|
3880
|
+
throw new Error(`wavefront mesh compute currently requires workgroupSize=${rendererWavefrontComputeWorkgroupSize}.`);
|
|
3881
|
+
}
|
|
3882
|
+
return WAVEFRONT_COMPUTE_WGSL;
|
|
3883
|
+
}
|
|
3534
3884
|
|
|
3535
3885
|
// src/index.js
|
|
3536
3886
|
var DEFAULT_CLEAR_COLOR = Object.freeze([0.07, 0.11, 0.18, 1]);
|
|
@@ -4830,22 +5180,29 @@ export {
|
|
|
4830
5180
|
createWavefrontMeshAcceleration,
|
|
4831
5181
|
createWavefrontPathTracingComputeConfig,
|
|
4832
5182
|
createWavefrontPathTracingComputeRenderer,
|
|
5183
|
+
createWavefrontPathTracingComputeShaderSource,
|
|
4833
5184
|
createWavefrontPathTracingPlan,
|
|
5185
|
+
createWavefrontReferenceRay,
|
|
4834
5186
|
defaultRendererClearColor,
|
|
4835
5187
|
defaultRendererWorkerProfile,
|
|
4836
5188
|
estimateWavefrontPathTracingMemory,
|
|
4837
5189
|
getRendererWorkerManifest,
|
|
4838
5190
|
getRendererWorkerProfile,
|
|
5191
|
+
intersectWavefrontReferenceTriangle,
|
|
4839
5192
|
normalizeWavefrontMesh,
|
|
4840
5193
|
normalizeWavefrontSceneObject,
|
|
4841
5194
|
packWavefrontBvhNodes,
|
|
4842
5195
|
packWavefrontSceneObjects,
|
|
4843
5196
|
packWavefrontTriangles,
|
|
5197
|
+
renderWavefrontPathTracingComputeFrame,
|
|
4844
5198
|
rendererAccelerationStructureUpdateClasses,
|
|
4845
5199
|
rendererDebugOwner,
|
|
4846
5200
|
rendererRayTracingStageOrder,
|
|
4847
5201
|
rendererRepresentationBands,
|
|
4848
5202
|
rendererWavefrontBufferSchemaVersion,
|
|
5203
|
+
rendererWavefrontComputeMode,
|
|
5204
|
+
rendererWavefrontComputeStatsStride,
|
|
5205
|
+
rendererWavefrontComputeWorkgroupSize,
|
|
4849
5206
|
rendererWavefrontHitTypes,
|
|
4850
5207
|
rendererWavefrontPassOrder,
|
|
4851
5208
|
rendererWavefrontQueuePairStrategy,
|
|
@@ -4855,6 +5212,7 @@ export {
|
|
|
4855
5212
|
rendererWorkerQueueClass,
|
|
4856
5213
|
supportsWavefrontPathTracingCompute,
|
|
4857
5214
|
supportsWebGpu,
|
|
5215
|
+
traceWavefrontReferenceTriangles,
|
|
4858
5216
|
wavefrontMaterialKinds,
|
|
4859
5217
|
wavefrontPathTracingComputeLimits,
|
|
4860
5218
|
wavefrontSceneObjectKinds
|