@plasius/gpu-lighting 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1066,6 +1066,15 @@ var lightingWavefrontHitTypes = Object.freeze([
1066
1066
  "transparent",
1067
1067
  "miss"
1068
1068
  ]);
1069
+ var lightingWavefrontRayKinds = Object.freeze([
1070
+ "path",
1071
+ "visibility-probe"
1072
+ ]);
1073
+ var lightingWavefrontVisibilityProbeModes = Object.freeze([
1074
+ "disabled",
1075
+ "mis-balanced",
1076
+ "exclusive-emissive"
1077
+ ]);
1069
1078
  var lightingWavefrontTerminalHitTypes = Object.freeze([
1070
1079
  "emissive",
1071
1080
  "environment",
@@ -1110,11 +1119,35 @@ var lightingWavefrontBufferContracts = Object.freeze({
1110
1119
  createLightingWavefrontField("sourcePixelId", "u32", "Source pixel/sample owner."),
1111
1120
  createLightingWavefrontField("sampleId", "u32", "Per-pixel sample slot."),
1112
1121
  createLightingWavefrontField("bounce", "u32", "Current bounce depth."),
1122
+ createLightingWavefrontField("mediumRefId", "u32", "Current medium reference."),
1123
+ createLightingWavefrontField(
1124
+ "mediumStackDepth",
1125
+ "u32",
1126
+ "Depth of the bounded nested medium stack."
1127
+ ),
1128
+ createLightingWavefrontField(
1129
+ "flags",
1130
+ "u32",
1131
+ "Renderer-owned ray flags. Low bits may encode ray kind metadata."
1132
+ ),
1133
+ createLightingWavefrontField(
1134
+ "mediumStack0",
1135
+ "vec4<u32>",
1136
+ "Lower half of the bounded nested medium stack."
1137
+ ),
1138
+ createLightingWavefrontField(
1139
+ "mediumStack1",
1140
+ "vec4<u32>",
1141
+ "Upper half of the bounded nested medium stack."
1142
+ ),
1143
+ createLightingWavefrontField(
1144
+ "spectralState",
1145
+ "vec4<f32>",
1146
+ "Spectral transport payload for wavelength-driven reference validation."
1147
+ ),
1113
1148
  createLightingWavefrontField("origin", "vec3<f32>", "Ray origin."),
1114
1149
  createLightingWavefrontField("direction", "vec3<f32>", "Normalized ray direction."),
1115
- createLightingWavefrontField("throughput", "vec3<f32>", "Accumulated path throughput."),
1116
- createLightingWavefrontField("mediumRefId", "u32", "Current medium reference."),
1117
- createLightingWavefrontField("flags", "u32", "Renderer-owned ray flags.")
1150
+ createLightingWavefrontField("throughput", "vec3<f32>", "Accumulated path throughput.")
1118
1151
  ]
1119
1152
  ),
1120
1153
  hit: createLightingWavefrontRecordContract(
@@ -1203,6 +1236,17 @@ var wavefrontEventKinds = Object.freeze([
1203
1236
  "transparency",
1204
1237
  "terminate"
1205
1238
  ]);
1239
+ var wavefrontRayKindFlagMask = 3;
1240
+ var wavefrontRayKindFlagValues = Object.freeze({
1241
+ path: 0,
1242
+ "visibility-probe": 1
1243
+ });
1244
+ function normalizeWavefrontRayKind(value) {
1245
+ return lightingWavefrontRayKinds.includes(value) ? value : "path";
1246
+ }
1247
+ function normalizeWavefrontVisibilityProbeMode(value) {
1248
+ return lightingWavefrontVisibilityProbeModes.includes(value) ? value : "disabled";
1249
+ }
1206
1250
  function normalizeWavefrontHitType(value) {
1207
1251
  return lightingWavefrontHitTypes.includes(value) ? value : "surface";
1208
1252
  }
@@ -1275,6 +1319,118 @@ function refractDirection(direction, normal, etaRatio) {
1275
1319
  const rOutParallel = scaleVec3(normal, -Math.sqrt(parallelFactor));
1276
1320
  return normalizeDirection(addVec3(rOutPerp, rOutParallel), direction);
1277
1321
  }
1322
+ function normalizeMediumRefId(value) {
1323
+ const mediumRefId = Math.max(0, Math.trunc(readFinite(value, 0)));
1324
+ return Number.isFinite(mediumRefId) ? mediumRefId : 0;
1325
+ }
1326
+ function normalizeMediumStack(value) {
1327
+ if (!Array.isArray(value)) {
1328
+ return [];
1329
+ }
1330
+ return value.map((entry) => normalizeMediumRefId(entry)).filter((entry, index, stack) => entry > 0 && stack.indexOf(entry) === index).slice(0, 4);
1331
+ }
1332
+ function createWavefrontMediumStatePayload(currentMediumRefId, stack) {
1333
+ const normalizedStack = normalizeMediumStack(stack);
1334
+ const stackSlots = [0, 0, 0, 0];
1335
+ normalizedStack.forEach((entry, index) => {
1336
+ stackSlots[index] = entry;
1337
+ });
1338
+ return Object.freeze({
1339
+ currentMediumRefId,
1340
+ stackDepth: normalizedStack.length,
1341
+ stack: Object.freeze(normalizedStack),
1342
+ stackSlots: Object.freeze(stackSlots)
1343
+ });
1344
+ }
1345
+ function evaluateWavefrontMediumState(options = {}) {
1346
+ const currentMediumRefId = normalizeMediumRefId(
1347
+ options.currentMediumRefId ?? options.mediumRefId
1348
+ );
1349
+ const surfaceMediumRefId = normalizeMediumRefId(options.surfaceMediumRefId);
1350
+ const stack = normalizeMediumStack(options.mediumStack);
1351
+ const frontFace = options.frontFace !== false;
1352
+ const eventKind = normalizeWavefrontEventKind(options.eventKind) ?? "transparency";
1353
+ if (surfaceMediumRefId === 0 || eventKind !== "refraction" && eventKind !== "transparency") {
1354
+ return Object.freeze({
1355
+ ...createWavefrontMediumStatePayload(currentMediumRefId, stack),
1356
+ enteredMediumRefId: 0,
1357
+ exitedMediumRefId: 0
1358
+ });
1359
+ }
1360
+ let nextStack = [...stack];
1361
+ let nextMediumRefId = currentMediumRefId;
1362
+ let enteredMediumRefId = 0;
1363
+ let exitedMediumRefId = 0;
1364
+ const stackTop = nextStack.at(-1) ?? 0;
1365
+ if (frontFace) {
1366
+ if (stackTop !== surfaceMediumRefId) {
1367
+ nextStack.push(surfaceMediumRefId);
1368
+ nextStack = nextStack.slice(-4);
1369
+ }
1370
+ nextMediumRefId = surfaceMediumRefId;
1371
+ enteredMediumRefId = surfaceMediumRefId;
1372
+ } else if (stackTop === surfaceMediumRefId) {
1373
+ nextStack.pop();
1374
+ nextMediumRefId = nextStack.at(-1) ?? 0;
1375
+ exitedMediumRefId = surfaceMediumRefId;
1376
+ }
1377
+ return Object.freeze({
1378
+ ...createWavefrontMediumStatePayload(nextMediumRefId, nextStack),
1379
+ enteredMediumRefId,
1380
+ exitedMediumRefId
1381
+ });
1382
+ }
1383
+ function encodeWavefrontRayFlags(flags, rayKind) {
1384
+ const normalizedFlags = Math.max(0, Math.trunc(readFinite(flags, 0)));
1385
+ const rayKindValue = wavefrontRayKindFlagValues[rayKind];
1386
+ return normalizedFlags & ~wavefrontRayKindFlagMask | rayKindValue;
1387
+ }
1388
+ function createWavefrontRayPayload(options = {}) {
1389
+ const rayKind = normalizeWavefrontRayKind(options.rayKind);
1390
+ const mediumState = evaluateWavefrontMediumState({
1391
+ currentMediumRefId: options.mediumRefId,
1392
+ mediumStack: options.mediumStack
1393
+ });
1394
+ const spectralState = Object.freeze(
1395
+ normalizeVec3(options.spectralState, [550, 1, 0]).concat(
1396
+ readFinite(options.spectralWeight, 0)
1397
+ )
1398
+ );
1399
+ const mediumStack0 = Object.freeze([
1400
+ mediumState.stackSlots[0],
1401
+ mediumState.stackSlots[1],
1402
+ mediumState.stackSlots[2],
1403
+ mediumState.stackSlots[3]
1404
+ ]);
1405
+ const mediumStack1 = Object.freeze([0, 0, 0, 0]);
1406
+ return Object.freeze({
1407
+ rayId: Math.max(0, Math.trunc(readFinite(options.rayId, 0))),
1408
+ parentRayId: Math.max(0, Math.trunc(readFinite(options.parentRayId, 0))),
1409
+ sourcePixelId: Math.max(0, Math.trunc(readFinite(options.sourcePixelId, 0))),
1410
+ sampleId: Math.max(0, Math.trunc(readFinite(options.sampleId, 0))),
1411
+ bounce: Math.max(0, Math.trunc(readFinite(options.bounce, 0))),
1412
+ origin: Object.freeze(normalizeVec3(options.origin, [0, 0, 0])),
1413
+ direction: Object.freeze(normalizeDirection(options.direction, [0, 0, -1])),
1414
+ throughput: Object.freeze(
1415
+ saturateVec3(normalizeVec3(options.throughput, [1, 1, 1]))
1416
+ ),
1417
+ mediumRefId: mediumState.currentMediumRefId,
1418
+ mediumStackDepth: mediumState.stackDepth,
1419
+ mediumStack: mediumState.stack,
1420
+ mediumStackSlots: mediumState.stackSlots,
1421
+ mediumStack0,
1422
+ mediumStack1,
1423
+ spectralState,
1424
+ rayKind,
1425
+ flags: encodeWavefrontRayFlags(options.flags, rayKind)
1426
+ });
1427
+ }
1428
+ function createWavefrontVisibilityProbeRay(options = {}) {
1429
+ return createWavefrontRayPayload({
1430
+ ...options,
1431
+ rayKind: "visibility-probe"
1432
+ });
1433
+ }
1278
1434
  function createWavefrontLightingPlan(options = {}) {
1279
1435
  const maxDepth = Math.max(1, Math.trunc(readFinite(options.maxDepth, 4)));
1280
1436
  const queueCapacity = Math.max(
@@ -1282,6 +1438,9 @@ function createWavefrontLightingPlan(options = {}) {
1282
1438
  Math.trunc(readFinite(options.queueCapacity, 4096))
1283
1439
  );
1284
1440
  const explicitLightSampling = Boolean(options.explicitLightSampling);
1441
+ const visibilityProbeMode = normalizeWavefrontVisibilityProbeMode(
1442
+ options.visibilityProbeMode ?? (explicitLightSampling ? "mis-balanced" : "disabled")
1443
+ );
1285
1444
  const accumulationResetEpoch = Math.max(
1286
1445
  0,
1287
1446
  Math.trunc(readFinite(options.accumulationResetEpoch, 0))
@@ -1291,6 +1450,8 @@ function createWavefrontLightingPlan(options = {}) {
1291
1450
  maxDepth,
1292
1451
  queueCapacity,
1293
1452
  explicitLightSampling,
1453
+ visibilityProbeMode,
1454
+ rayKinds: lightingWavefrontRayKinds,
1294
1455
  accumulationResetEpoch,
1295
1456
  queueLayout: Object.freeze({
1296
1457
  strategy: lightingWavefrontQueuePairStrategy,
@@ -1329,7 +1490,8 @@ function createWavefrontLightingPlan(options = {}) {
1329
1490
  ]),
1330
1491
  writes: Object.freeze(["ray"]),
1331
1492
  continuationHitTypes: lightingWavefrontContinuationHitTypes,
1332
- explicitLightSampling
1493
+ explicitLightSampling,
1494
+ visibilityProbeMode
1333
1495
  })
1334
1496
  ])
1335
1497
  });
@@ -1389,7 +1551,9 @@ function evaluateWavefrontContinuationEvent(options = {}) {
1389
1551
  const opacity = clampUnit(options.opacity, 1);
1390
1552
  const refractiveIndex = Math.max(1, readFinite(options.refractiveIndex ?? options.ior, 1.45));
1391
1553
  const transmissionStrength = Math.max(...transmission);
1392
- let eventKind = normalizeWavefrontEventKind(options.eventKind) ?? (hitType === "transparent" || opacity < 0.999 ? "transparency" : transmissionStrength > 1e-3 ? "refraction" : metalness >= 0.5 || roughness <= 0.2 ? "reflection" : "diffuse");
1554
+ const requestedEventKind = normalizeWavefrontEventKind(options.eventKind) ?? (hitType === "transparent" || opacity < 0.999 ? "transparency" : transmissionStrength > 1e-3 ? "refraction" : metalness >= 0.5 || roughness <= 0.2 ? "reflection" : "diffuse");
1555
+ let eventKind = requestedEventKind;
1556
+ let totalInternalReflection = false;
1393
1557
  let nextDirection = incomingDirection;
1394
1558
  let attenuation;
1395
1559
  if (!lightingWavefrontContinuationHitTypes.includes(hitType) || bounceIndex >= maxDepth - 1) {
@@ -1400,8 +1564,20 @@ function evaluateWavefrontContinuationEvent(options = {}) {
1400
1564
  attenuation = mixVec3([0.04, 0.04, 0.04], albedo, metalness);
1401
1565
  } else if (eventKind === "refraction") {
1402
1566
  const etaRatio = frontFace ? 1 / refractiveIndex : refractiveIndex;
1403
- nextDirection = refractDirection(incomingDirection, orientedNormal, etaRatio) ?? reflectDirection(incomingDirection, orientedNormal);
1404
- attenuation = transmissionStrength > 1e-3 ? transmission : [1, 1, 1];
1567
+ const refractedDirection = refractDirection(
1568
+ incomingDirection,
1569
+ orientedNormal,
1570
+ etaRatio
1571
+ );
1572
+ if (refractedDirection) {
1573
+ nextDirection = refractedDirection;
1574
+ attenuation = transmissionStrength > 1e-3 ? transmission : [1, 1, 1];
1575
+ } else {
1576
+ totalInternalReflection = true;
1577
+ eventKind = "reflection";
1578
+ nextDirection = reflectDirection(incomingDirection, orientedNormal);
1579
+ attenuation = mixVec3([0.04, 0.04, 0.04], albedo, metalness);
1580
+ }
1405
1581
  } else if (eventKind === "transparency") {
1406
1582
  nextDirection = incomingDirection;
1407
1583
  const transparencyWeight = Math.max(1 - opacity, transmissionStrength, 0.05);
@@ -1415,16 +1591,150 @@ function evaluateWavefrontContinuationEvent(options = {}) {
1415
1591
  }
1416
1592
  const nextThroughput = multiplyVec3(throughput, saturateVec3(attenuation));
1417
1593
  const continueTracing = eventKind !== "terminate" && colorLuminance(nextThroughput) > 1e-4;
1594
+ const mediumState = evaluateWavefrontMediumState({
1595
+ currentMediumRefId: options.currentMediumRefId ?? options.mediumRefId,
1596
+ surfaceMediumRefId: options.surfaceMediumRefId,
1597
+ mediumStack: options.mediumStack,
1598
+ frontFace,
1599
+ eventKind
1600
+ });
1418
1601
  return Object.freeze({
1419
1602
  hitType,
1603
+ requestedEventKind,
1420
1604
  eventKind,
1421
1605
  continueTracing,
1606
+ totalInternalReflection,
1422
1607
  nextDirection: Object.freeze(nextDirection),
1423
1608
  attenuation: Object.freeze(saturateVec3(attenuation)),
1424
1609
  nextThroughput: Object.freeze(nextThroughput),
1610
+ mediumState,
1425
1611
  explicitLightSamplingEnabled: Boolean(options.explicitLightSampling)
1426
1612
  });
1427
1613
  }
1614
+ function evaluateWavefrontVisibilityProbe(options = {}) {
1615
+ const probeMode = normalizeWavefrontVisibilityProbeMode(
1616
+ options.probeMode ?? (options.explicitLightSampling ? "mis-balanced" : "disabled")
1617
+ );
1618
+ const probeRay = createWavefrontVisibilityProbeRay({
1619
+ ...options.probeRay,
1620
+ throughput: options.throughput ?? options.probeRay?.throughput,
1621
+ direction: options.direction ?? options.probeRay?.direction,
1622
+ mediumRefId: options.currentMediumRefId ?? options.probeRay?.mediumRefId,
1623
+ mediumStack: options.mediumStack ?? options.probeRay?.mediumStack
1624
+ });
1625
+ const transparentSegments = Array.isArray(options.transparentSegments) ? options.transparentSegments : [];
1626
+ const transmittance = transparentSegments.reduce(
1627
+ (current, segment) => multiplyVec3(current, saturateVec3(normalizeVec3(segment, [1, 1, 1]))),
1628
+ [1, 1, 1]
1629
+ );
1630
+ const emissiveRadiance = saturateVec3(
1631
+ normalizeVec3(options.emissiveRadiance, [0, 0, 0])
1632
+ );
1633
+ const environmentRadiance = saturateVec3(
1634
+ normalizeVec3(options.environmentRadiance, [0, 0, 0])
1635
+ );
1636
+ const activeEmissiveRadiance = saturateVec3(
1637
+ normalizeVec3(options.activeEmissiveRadiance, [0, 0, 0])
1638
+ );
1639
+ const prefersEnvironment = Boolean(options.prefersEnvironment);
1640
+ const sourceRadiance = prefersEnvironment || colorLuminance(emissiveRadiance) <= 1e-6 ? environmentRadiance : emissiveRadiance;
1641
+ const rawContribution = multiplyVec3(
1642
+ probeRay.throughput,
1643
+ multiplyVec3(sourceRadiance, transmittance)
1644
+ );
1645
+ const activeEmissiveVisible = colorLuminance(activeEmissiveRadiance) > 1e-6;
1646
+ const misWeight = probeMode === "mis-balanced" && activeEmissiveVisible ? 0.5 : 1;
1647
+ const contribution = probeMode === "exclusive-emissive" && activeEmissiveVisible ? [0, 0, 0] : scaleVec3(rawContribution, misWeight);
1648
+ return Object.freeze({
1649
+ probeMode,
1650
+ probeRay,
1651
+ transmittance: Object.freeze(transmittance),
1652
+ misWeight,
1653
+ doubleCountPrevented: activeEmissiveVisible && probeMode !== "disabled",
1654
+ contribution: Object.freeze(contribution)
1655
+ });
1656
+ }
1657
+ function evaluateWavefrontMaterialReference(options = {}) {
1658
+ const material = Object.freeze({
1659
+ albedo: Object.freeze(
1660
+ saturateVec3(normalizeVec3(options.albedo, [0.8, 0.8, 0.8]))
1661
+ ),
1662
+ emission: Object.freeze(
1663
+ saturateVec3(normalizeVec3(options.emission, [0, 0, 0]))
1664
+ ),
1665
+ roughness: clampUnit(options.roughness, 0.5),
1666
+ metalness: clampUnit(options.metalness, 0),
1667
+ opacity: clampUnit(options.opacity, 1),
1668
+ transmission: Object.freeze(
1669
+ saturateVec3(normalizeVec3(options.transmission, [0, 0, 0]))
1670
+ ),
1671
+ refractiveIndex: Math.max(
1672
+ 1,
1673
+ readFinite(options.refractiveIndex ?? options.ior, 1.45)
1674
+ )
1675
+ });
1676
+ const continuation = evaluateWavefrontContinuationEvent({
1677
+ ...options,
1678
+ albedo: material.albedo,
1679
+ roughness: material.roughness,
1680
+ metalness: material.metalness,
1681
+ opacity: material.opacity,
1682
+ transmission: material.transmission,
1683
+ refractiveIndex: material.refractiveIndex
1684
+ });
1685
+ const terminal = evaluateWavefrontTerminalRadiance({
1686
+ ...options,
1687
+ emission: material.emission
1688
+ });
1689
+ return Object.freeze({
1690
+ material,
1691
+ terminal,
1692
+ continuation,
1693
+ throughputUpdate: continuation.nextThroughput
1694
+ });
1695
+ }
1696
+ function createWavefrontReferenceFixture(options = {}) {
1697
+ const tolerance = Math.max(1e-4, readFinite(options.tolerance, 5e-4));
1698
+ const ray = createWavefrontRayPayload({
1699
+ rayId: options.rayId,
1700
+ parentRayId: options.parentRayId,
1701
+ sourcePixelId: options.sourcePixelId,
1702
+ sampleId: options.sampleId,
1703
+ bounce: options.bounceIndex ?? options.bounce,
1704
+ origin: options.origin,
1705
+ direction: options.direction ?? scaleVec3(options.viewDirection ?? [0, 0, 1], -1),
1706
+ throughput: options.throughput,
1707
+ mediumRefId: options.currentMediumRefId ?? options.mediumRefId,
1708
+ mediumStack: options.mediumStack
1709
+ });
1710
+ const reference = evaluateWavefrontMaterialReference(options);
1711
+ const visibilityProbe = options.visibilityProbe === void 0 ? null : evaluateWavefrontVisibilityProbe({
1712
+ ...options.visibilityProbe,
1713
+ throughput: options.visibilityProbe.throughput ?? ray.throughput
1714
+ });
1715
+ const accumulationRadiance = addVec3(
1716
+ reference.terminal.radiance,
1717
+ visibilityProbe?.contribution ?? [0, 0, 0]
1718
+ );
1719
+ return Object.freeze({
1720
+ tolerance,
1721
+ ray,
1722
+ material: reference.material,
1723
+ continuation: reference.continuation,
1724
+ terminal: reference.terminal,
1725
+ visibilityProbe,
1726
+ accumulation: Object.freeze({
1727
+ sourcePixelId: ray.sourcePixelId,
1728
+ sampleCount: reference.terminal.terminated ? 1 : 0,
1729
+ radiance: Object.freeze(accumulationRadiance),
1730
+ throughput: reference.continuation.nextThroughput,
1731
+ resetEpoch: Math.max(
1732
+ 0,
1733
+ Math.trunc(readFinite(options.accumulationResetEpoch, 0))
1734
+ )
1735
+ })
1736
+ });
1737
+ }
1428
1738
  var lightingImportanceLevels = Object.freeze([
1429
1739
  "low",
1430
1740
  "medium",
@@ -2657,11 +2967,17 @@ export {
2657
2967
  createLightingProfileModeLadder,
2658
2968
  createWavefrontEnvironmentLightingOptions,
2659
2969
  createWavefrontLightingPlan,
2970
+ createWavefrontRayPayload,
2971
+ createWavefrontReferenceFixture,
2972
+ createWavefrontVisibilityProbeRay,
2660
2973
  defaultAdaptiveLightingProfilePolicy,
2661
2974
  defaultLightingProfile,
2662
2975
  defaultLightingTechnique,
2663
2976
  evaluateWavefrontContinuationEvent,
2977
+ evaluateWavefrontMaterialReference,
2978
+ evaluateWavefrontMediumState,
2664
2979
  evaluateWavefrontTerminalRadiance,
2980
+ evaluateWavefrontVisibilityProbe,
2665
2981
  getLightingProfile,
2666
2982
  getLightingProfileWorkerManifest,
2667
2983
  getLightingTechnique,
@@ -2688,9 +3004,11 @@ export {
2688
3004
  lightingWavefrontHitTypes,
2689
3005
  lightingWavefrontPassOrder,
2690
3006
  lightingWavefrontQueuePairStrategy,
3007
+ lightingWavefrontRayKinds,
2691
3008
  lightingWavefrontSchemaVersion,
2692
3009
  lightingWavefrontTerminalHitTypes,
2693
3010
  lightingWavefrontTerminationPolicy,
3011
+ lightingWavefrontVisibilityProbeModes,
2694
3012
  lightingWorkerManifests,
2695
3013
  lightingWorkerQueueClass,
2696
3014
  loadLightingJobs,