@plasius/gpu-lighting 0.2.5 → 0.2.7

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
@@ -41,6 +41,14 @@ var techniqueSpecs = {
41
41
  denoise: "denoise.job.wgsl"
42
42
  }
43
43
  },
44
+ wavefront: {
45
+ description: "Renderer-aligned wavefront lighting jobs for terminal radiance and continuation scattering.",
46
+ prelude: "prelude.wgsl",
47
+ jobs: {
48
+ accumulateTerminalRadiance: "accumulate-terminal-radiance.job.wgsl",
49
+ scatterContinuations: "scatter-continuations.job.wgsl"
50
+ }
51
+ },
44
52
  volumetrics: {
45
53
  description: "Froxel volumetric lighting for fog, shafts, and participating media shadows.",
46
54
  prelude: "prelude.wgsl",
@@ -1049,6 +1057,684 @@ function createWavefrontEnvironmentLightingOptions(options = {}) {
1049
1057
  lightingEnvironment: config
1050
1058
  });
1051
1059
  }
1060
+ var lightingWavefrontSchemaVersion = 1;
1061
+ var lightingWavefrontQueuePairStrategy = "ping-pong-active-next";
1062
+ var lightingWavefrontHitTypes = Object.freeze([
1063
+ "surface",
1064
+ "emissive",
1065
+ "environment",
1066
+ "transparent",
1067
+ "miss"
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
+ ]);
1078
+ var lightingWavefrontTerminalHitTypes = Object.freeze([
1079
+ "emissive",
1080
+ "environment",
1081
+ "miss"
1082
+ ]);
1083
+ var lightingWavefrontContinuationHitTypes = Object.freeze([
1084
+ "surface",
1085
+ "transparent"
1086
+ ]);
1087
+ var lightingWavefrontPassOrder = Object.freeze([
1088
+ "accumulateTerminalRadiance",
1089
+ "scatterContinuations"
1090
+ ]);
1091
+ var lightingRequiredRendererWavefrontPassOrder = Object.freeze([
1092
+ "generatePrimaryRays",
1093
+ "intersectActiveQueue",
1094
+ "resolveSurfaceRecords",
1095
+ "accumulateTerminalRadiance",
1096
+ "scatterContinuations",
1097
+ "compactAndSwapQueues"
1098
+ ]);
1099
+ function createLightingWavefrontField(name, type, description) {
1100
+ return Object.freeze({
1101
+ name,
1102
+ type,
1103
+ description
1104
+ });
1105
+ }
1106
+ function createLightingWavefrontRecordContract(recordName, fields) {
1107
+ return Object.freeze({
1108
+ schemaVersion: lightingWavefrontSchemaVersion,
1109
+ recordName,
1110
+ fields: Object.freeze(fields)
1111
+ });
1112
+ }
1113
+ var lightingWavefrontBufferContracts = Object.freeze({
1114
+ ray: createLightingWavefrontRecordContract(
1115
+ "RayRecord",
1116
+ [
1117
+ createLightingWavefrontField("rayId", "u32", "Stable ray identifier."),
1118
+ createLightingWavefrontField("parentRayId", "u32", "Parent ray identifier."),
1119
+ createLightingWavefrontField("sourcePixelId", "u32", "Source pixel/sample owner."),
1120
+ createLightingWavefrontField("sampleId", "u32", "Per-pixel sample slot."),
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
+ ),
1148
+ createLightingWavefrontField("origin", "vec3<f32>", "Ray origin."),
1149
+ createLightingWavefrontField("direction", "vec3<f32>", "Normalized ray direction."),
1150
+ createLightingWavefrontField("throughput", "vec3<f32>", "Accumulated path throughput.")
1151
+ ]
1152
+ ),
1153
+ hit: createLightingWavefrontRecordContract(
1154
+ "HitRecord",
1155
+ [
1156
+ createLightingWavefrontField("rayId", "u32", "Stable ray identifier."),
1157
+ createLightingWavefrontField("sourcePixelId", "u32", "Source pixel/sample owner."),
1158
+ createLightingWavefrontField("hitType", "u32", "Surface, emissive, environment, transparent, or miss."),
1159
+ createLightingWavefrontField("distance", "f32", "Nearest-hit distance."),
1160
+ createLightingWavefrontField("entityId", "u32", "Owning entity identifier."),
1161
+ createLightingWavefrontField("instanceId", "u32", "Owning instance identifier."),
1162
+ createLightingWavefrontField("primitiveId", "u32", "Primitive identifier."),
1163
+ createLightingWavefrontField("materialId", "u32", "Resolved material identifier."),
1164
+ createLightingWavefrontField("barycentrics", "vec3<f32>", "Triangle barycentrics."),
1165
+ createLightingWavefrontField("uv", "vec2<f32>", "Resolved UV coordinates."),
1166
+ createLightingWavefrontField("geometricNormal", "vec3<f32>", "Geometric surface normal."),
1167
+ createLightingWavefrontField("shadingNormal", "vec3<f32>", "Interpolated shading normal."),
1168
+ createLightingWavefrontField("frontFace", "u32", "Front-face classification.")
1169
+ ]
1170
+ ),
1171
+ surface: createLightingWavefrontRecordContract(
1172
+ "SurfaceRecord",
1173
+ [
1174
+ createLightingWavefrontField("rayId", "u32", "Stable ray identifier."),
1175
+ createLightingWavefrontField("entityId", "u32", "Owning entity identifier."),
1176
+ createLightingWavefrontField("materialRefId", "u32", "Renderer material reference id."),
1177
+ createLightingWavefrontField("mediumRefId", "u32", "Renderer medium reference id."),
1178
+ createLightingWavefrontField("geometricNormal", "vec3<f32>", "Geometric surface normal."),
1179
+ createLightingWavefrontField("shadingNormal", "vec3<f32>", "Interpolated shading normal."),
1180
+ createLightingWavefrontField("uv", "vec2<f32>", "Resolved UV coordinates."),
1181
+ createLightingWavefrontField("tangentFrame", "mat3x3<f32>", "Resolved tangent frame.")
1182
+ ]
1183
+ ),
1184
+ materialReference: createLightingWavefrontRecordContract(
1185
+ "MaterialReferenceRecord",
1186
+ [
1187
+ createLightingWavefrontField("materialRefId", "u32", "Lighting-visible material reference id."),
1188
+ createLightingWavefrontField("materialId", "u32", "Source material identifier."),
1189
+ createLightingWavefrontField("shadingModel", "u32", "Renderer shading-model discriminator."),
1190
+ createLightingWavefrontField("textureSetId", "u32", "Resolved texture-set identifier."),
1191
+ createLightingWavefrontField("flags", "u32", "Renderer material flags.")
1192
+ ]
1193
+ ),
1194
+ mediumReference: createLightingWavefrontRecordContract(
1195
+ "MediumReferenceRecord",
1196
+ [
1197
+ createLightingWavefrontField("mediumRefId", "u32", "Lighting-visible medium reference id."),
1198
+ createLightingWavefrontField("mediumId", "u32", "Source medium identifier."),
1199
+ createLightingWavefrontField("phaseModel", "u32", "Phase-function discriminator."),
1200
+ createLightingWavefrontField("absorption", "vec3<f32>", "Medium absorption coefficients."),
1201
+ createLightingWavefrontField("scattering", "vec3<f32>", "Medium scattering coefficients.")
1202
+ ]
1203
+ ),
1204
+ accumulation: createLightingWavefrontRecordContract(
1205
+ "AccumulationRecord",
1206
+ [
1207
+ createLightingWavefrontField("sourcePixelId", "u32", "Source pixel/sample owner."),
1208
+ createLightingWavefrontField("sampleCount", "u32", "Accumulated sample count."),
1209
+ createLightingWavefrontField("radiance", "vec3<f32>", "Accumulated radiance."),
1210
+ createLightingWavefrontField("throughput", "vec3<f32>", "Last surviving throughput."),
1211
+ createLightingWavefrontField("resetEpoch", "u32", "Renderer accumulation reset epoch.")
1212
+ ]
1213
+ )
1214
+ });
1215
+ var lightingWavefrontTerminationPolicy = Object.freeze({
1216
+ terminalHitTypes: lightingWavefrontTerminalHitTypes,
1217
+ continuationHitTypes: lightingWavefrontContinuationHitTypes,
1218
+ emissive: Object.freeze({
1219
+ action: "accumulate-and-stop",
1220
+ contributesRadiance: true
1221
+ }),
1222
+ environment: Object.freeze({
1223
+ action: "accumulate-and-stop",
1224
+ contributesRadiance: true
1225
+ }),
1226
+ miss: Object.freeze({
1227
+ action: "accumulate-environment-or-dark-stop",
1228
+ contributesRadiance: true
1229
+ })
1230
+ });
1231
+ var defaultWavefrontDarkRadiance = Object.freeze([1e-4, 1e-4, 1e-4]);
1232
+ var wavefrontEventKinds = Object.freeze([
1233
+ "diffuse",
1234
+ "reflection",
1235
+ "refraction",
1236
+ "transparency",
1237
+ "terminate"
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
+ }
1250
+ function normalizeWavefrontHitType(value) {
1251
+ return lightingWavefrontHitTypes.includes(value) ? value : "surface";
1252
+ }
1253
+ function normalizeWavefrontEventKind(value) {
1254
+ return wavefrontEventKinds.includes(value) ? value : null;
1255
+ }
1256
+ function normalizeVec3(value, fallback = [0, 0, 0]) {
1257
+ if (!Array.isArray(value) || value.length < 3) {
1258
+ return [...fallback];
1259
+ }
1260
+ return [
1261
+ Number.isFinite(value[0]) ? value[0] : fallback[0],
1262
+ Number.isFinite(value[1]) ? value[1] : fallback[1],
1263
+ Number.isFinite(value[2]) ? value[2] : fallback[2]
1264
+ ];
1265
+ }
1266
+ function clampUnit(value, fallback = 0) {
1267
+ return Math.max(0, Math.min(1, readFinite(value, fallback)));
1268
+ }
1269
+ function saturateVec3(value) {
1270
+ return value.map((component) => Math.max(0, component));
1271
+ }
1272
+ function scaleVec3(value, scalar) {
1273
+ return value.map((component) => component * scalar);
1274
+ }
1275
+ function addVec3(left, right) {
1276
+ return left.map((component, index) => component + right[index]);
1277
+ }
1278
+ function multiplyVec3(left, right) {
1279
+ return left.map((component, index) => component * right[index]);
1280
+ }
1281
+ function dotVec3(left, right) {
1282
+ return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
1283
+ }
1284
+ function lengthVec3(value) {
1285
+ return Math.hypot(value[0], value[1], value[2]);
1286
+ }
1287
+ function normalizeDirection(value, fallback = [0, 1, 0]) {
1288
+ const vector = normalizeVec3(value, fallback);
1289
+ const length = lengthVec3(vector);
1290
+ if (!Number.isFinite(length) || length <= 1e-6) {
1291
+ return [...fallback];
1292
+ }
1293
+ return vector.map((component) => component / length);
1294
+ }
1295
+ function mixVec3(left, right, factor) {
1296
+ return left.map(
1297
+ (component, index) => component * (1 - factor) + right[index] * factor
1298
+ );
1299
+ }
1300
+ function reflectDirection(direction, normal) {
1301
+ const scale = 2 * dotVec3(direction, normal);
1302
+ return normalizeDirection(
1303
+ [
1304
+ direction[0] - scale * normal[0],
1305
+ direction[1] - scale * normal[1],
1306
+ direction[2] - scale * normal[2]
1307
+ ],
1308
+ normal
1309
+ );
1310
+ }
1311
+ function refractDirection(direction, normal, etaRatio) {
1312
+ const cosTheta = Math.min(-dotVec3(direction, normal), 1);
1313
+ const rOutPerp = scaleVec3(addVec3(direction, scaleVec3(normal, cosTheta)), etaRatio);
1314
+ const rOutPerpLengthSquared = dotVec3(rOutPerp, rOutPerp);
1315
+ const parallelFactor = 1 - rOutPerpLengthSquared;
1316
+ if (parallelFactor <= 0) {
1317
+ return null;
1318
+ }
1319
+ const rOutParallel = scaleVec3(normal, -Math.sqrt(parallelFactor));
1320
+ return normalizeDirection(addVec3(rOutPerp, rOutParallel), direction);
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
+ }
1434
+ function createWavefrontLightingPlan(options = {}) {
1435
+ const maxDepth = Math.max(1, Math.trunc(readFinite(options.maxDepth, 4)));
1436
+ const queueCapacity = Math.max(
1437
+ 1,
1438
+ Math.trunc(readFinite(options.queueCapacity, 4096))
1439
+ );
1440
+ const explicitLightSampling = Boolean(options.explicitLightSampling);
1441
+ const visibilityProbeMode = normalizeWavefrontVisibilityProbeMode(
1442
+ options.visibilityProbeMode ?? (explicitLightSampling ? "mis-balanced" : "disabled")
1443
+ );
1444
+ const accumulationResetEpoch = Math.max(
1445
+ 0,
1446
+ Math.trunc(readFinite(options.accumulationResetEpoch, 0))
1447
+ );
1448
+ return Object.freeze({
1449
+ schemaVersion: lightingWavefrontSchemaVersion,
1450
+ maxDepth,
1451
+ queueCapacity,
1452
+ explicitLightSampling,
1453
+ visibilityProbeMode,
1454
+ rayKinds: lightingWavefrontRayKinds,
1455
+ accumulationResetEpoch,
1456
+ queueLayout: Object.freeze({
1457
+ strategy: lightingWavefrontQueuePairStrategy,
1458
+ compactAfterScatter: true,
1459
+ queues: Object.freeze([
1460
+ Object.freeze({ name: "active", role: "current-bounce" }),
1461
+ Object.freeze({ name: "next", role: "next-bounce" })
1462
+ ])
1463
+ }),
1464
+ bufferContracts: lightingWavefrontBufferContracts,
1465
+ terminationPolicy: lightingWavefrontTerminationPolicy,
1466
+ requiredRendererPassOrder: lightingRequiredRendererWavefrontPassOrder,
1467
+ lightingPasses: Object.freeze([
1468
+ Object.freeze({
1469
+ key: "accumulateTerminalRadiance",
1470
+ stage: "accumulateTerminalRadiance",
1471
+ reads: Object.freeze([
1472
+ "ray",
1473
+ "hit",
1474
+ "surface",
1475
+ "materialReference",
1476
+ "mediumReference"
1477
+ ]),
1478
+ writes: Object.freeze(["accumulation"]),
1479
+ terminalHitTypes: lightingWavefrontTerminalHitTypes
1480
+ }),
1481
+ Object.freeze({
1482
+ key: "scatterContinuations",
1483
+ stage: "scatterContinuations",
1484
+ reads: Object.freeze([
1485
+ "ray",
1486
+ "hit",
1487
+ "surface",
1488
+ "materialReference",
1489
+ "mediumReference"
1490
+ ]),
1491
+ writes: Object.freeze(["ray"]),
1492
+ continuationHitTypes: lightingWavefrontContinuationHitTypes,
1493
+ explicitLightSampling,
1494
+ visibilityProbeMode
1495
+ })
1496
+ ])
1497
+ });
1498
+ }
1499
+ function evaluateWavefrontTerminalRadiance(options = {}) {
1500
+ const hitType = normalizeWavefrontHitType(options.hitType);
1501
+ const throughput = saturateVec3(normalizeVec3(options.throughput, [1, 1, 1]));
1502
+ const emission = saturateVec3(normalizeVec3(options.emission, [0, 0, 0]));
1503
+ const environmentRadiance = saturateVec3(
1504
+ normalizeVec3(options.environmentRadiance, [0, 0, 0])
1505
+ );
1506
+ const missRadiance = saturateVec3(
1507
+ normalizeVec3(options.missRadiance, defaultWavefrontDarkRadiance)
1508
+ );
1509
+ const environmentLuminance = colorLuminance(environmentRadiance);
1510
+ let source = "none";
1511
+ let rawRadiance = [0, 0, 0];
1512
+ if (hitType === "emissive") {
1513
+ source = "emissive";
1514
+ rawRadiance = emission;
1515
+ } else if (hitType === "environment") {
1516
+ source = "environment";
1517
+ rawRadiance = environmentRadiance;
1518
+ } else if (hitType === "miss") {
1519
+ source = environmentLuminance > 1e-6 ? "environment" : "dark";
1520
+ rawRadiance = environmentLuminance > 1e-6 ? environmentRadiance : missRadiance;
1521
+ }
1522
+ const radiance = multiplyVec3(throughput, rawRadiance);
1523
+ const terminated = lightingWavefrontTerminalHitTypes.includes(hitType);
1524
+ return Object.freeze({
1525
+ hitType,
1526
+ source,
1527
+ terminated,
1528
+ radiance: Object.freeze(radiance),
1529
+ nearDarkSample: source === "dark" && colorLuminance(radiance) <= colorLuminance(defaultWavefrontDarkRadiance)
1530
+ });
1531
+ }
1532
+ function evaluateWavefrontContinuationEvent(options = {}) {
1533
+ const hitType = normalizeWavefrontHitType(options.hitType);
1534
+ const bounceIndex = Math.max(0, Math.trunc(readFinite(options.bounceIndex, 0)));
1535
+ const maxDepth = Math.max(1, Math.trunc(readFinite(options.maxDepth, 4)));
1536
+ const throughput = saturateVec3(normalizeVec3(options.throughput, [1, 1, 1]));
1537
+ const albedo = saturateVec3(normalizeVec3(options.albedo, [0.8, 0.8, 0.8]));
1538
+ const transmission = saturateVec3(
1539
+ normalizeVec3(options.transmission, [0, 0, 0])
1540
+ );
1541
+ const shadingNormal = normalizeDirection(options.shadingNormal, [0, 1, 0]);
1542
+ const viewDirection = normalizeDirection(options.viewDirection, [0, 0, 1]);
1543
+ const incomingDirection = normalizeDirection(
1544
+ scaleVec3(viewDirection, -1),
1545
+ [0, 0, -1]
1546
+ );
1547
+ const frontFace = options.frontFace !== false;
1548
+ const orientedNormal = frontFace ? shadingNormal : scaleVec3(shadingNormal, -1);
1549
+ const metalness = clampUnit(options.metalness, 0);
1550
+ const roughness = clampUnit(options.roughness, 0.5);
1551
+ const opacity = clampUnit(options.opacity, 1);
1552
+ const refractiveIndex = Math.max(1, readFinite(options.refractiveIndex ?? options.ior, 1.45));
1553
+ const transmissionStrength = Math.max(...transmission);
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;
1557
+ let nextDirection = incomingDirection;
1558
+ let attenuation;
1559
+ if (!lightingWavefrontContinuationHitTypes.includes(hitType) || bounceIndex >= maxDepth - 1) {
1560
+ eventKind = "terminate";
1561
+ attenuation = [0, 0, 0];
1562
+ } else if (eventKind === "reflection") {
1563
+ nextDirection = reflectDirection(incomingDirection, orientedNormal);
1564
+ attenuation = mixVec3([0.04, 0.04, 0.04], albedo, metalness);
1565
+ } else if (eventKind === "refraction") {
1566
+ const etaRatio = frontFace ? 1 / refractiveIndex : refractiveIndex;
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
+ }
1581
+ } else if (eventKind === "transparency") {
1582
+ nextDirection = incomingDirection;
1583
+ const transparencyWeight = Math.max(1 - opacity, transmissionStrength, 0.05);
1584
+ attenuation = transmissionStrength > 1e-3 ? transmission : [transparencyWeight, transparencyWeight, transparencyWeight];
1585
+ } else {
1586
+ nextDirection = normalizeDirection(
1587
+ addVec3(orientedNormal, albedo.map((component) => component - 0.5)),
1588
+ orientedNormal
1589
+ );
1590
+ attenuation = scaleVec3(albedo, Math.max(0.05, 1 - metalness));
1591
+ }
1592
+ const nextThroughput = multiplyVec3(throughput, saturateVec3(attenuation));
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
+ });
1601
+ return Object.freeze({
1602
+ hitType,
1603
+ requestedEventKind,
1604
+ eventKind,
1605
+ continueTracing,
1606
+ totalInternalReflection,
1607
+ nextDirection: Object.freeze(nextDirection),
1608
+ attenuation: Object.freeze(saturateVec3(attenuation)),
1609
+ nextThroughput: Object.freeze(nextThroughput),
1610
+ mediumState,
1611
+ explicitLightSamplingEnabled: Boolean(options.explicitLightSampling)
1612
+ });
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
+ }
1052
1738
  var lightingImportanceLevels = Object.freeze([
1053
1739
  "low",
1054
1740
  "medium",
@@ -1614,6 +2300,91 @@ var lightingWorkerSpecPresets = {
1614
2300
  }
1615
2301
  }
1616
2302
  },
2303
+ wavefront: {
2304
+ suggestedAllocationIds: [
2305
+ "lighting.wavefront.active-queue",
2306
+ "lighting.wavefront.next-queue",
2307
+ "lighting.wavefront.accumulation"
2308
+ ],
2309
+ jobs: {
2310
+ accumulateTerminalRadiance: {
2311
+ domain: "lighting",
2312
+ importance: "critical",
2313
+ levels: buildWorkerBudgetLevels(
2314
+ "lighting.wavefront.accumulateTerminalRadiance",
2315
+ lightingWorkerQueueClass,
2316
+ {
2317
+ low: {
2318
+ estimatedCostMs: 0.7,
2319
+ maxDispatchesPerFrame: 1,
2320
+ maxJobsPerDispatch: 32,
2321
+ cadenceDivisor: 2,
2322
+ workgroupScale: 0.5,
2323
+ maxQueueDepth: 96
2324
+ },
2325
+ medium: {
2326
+ estimatedCostMs: 1.2,
2327
+ maxDispatchesPerFrame: 1,
2328
+ maxJobsPerDispatch: 64,
2329
+ cadenceDivisor: 1,
2330
+ workgroupScale: 0.75,
2331
+ maxQueueDepth: 192
2332
+ },
2333
+ high: {
2334
+ estimatedCostMs: 1.8,
2335
+ maxDispatchesPerFrame: 2,
2336
+ maxJobsPerDispatch: 128,
2337
+ cadenceDivisor: 1,
2338
+ workgroupScale: 1,
2339
+ maxQueueDepth: 256
2340
+ }
2341
+ }
2342
+ ),
2343
+ suggestedAllocationIds: [
2344
+ "lighting.wavefront.accumulation",
2345
+ "lighting.wavefront.active-queue"
2346
+ ]
2347
+ },
2348
+ scatterContinuations: {
2349
+ domain: "lighting",
2350
+ importance: "critical",
2351
+ levels: buildWorkerBudgetLevels(
2352
+ "lighting.wavefront.scatterContinuations",
2353
+ lightingWorkerQueueClass,
2354
+ {
2355
+ low: {
2356
+ estimatedCostMs: 0.8,
2357
+ maxDispatchesPerFrame: 1,
2358
+ maxJobsPerDispatch: 32,
2359
+ cadenceDivisor: 2,
2360
+ workgroupScale: 0.5,
2361
+ maxQueueDepth: 96
2362
+ },
2363
+ medium: {
2364
+ estimatedCostMs: 1.4,
2365
+ maxDispatchesPerFrame: 1,
2366
+ maxJobsPerDispatch: 64,
2367
+ cadenceDivisor: 1,
2368
+ workgroupScale: 0.75,
2369
+ maxQueueDepth: 192
2370
+ },
2371
+ high: {
2372
+ estimatedCostMs: 2.1,
2373
+ maxDispatchesPerFrame: 2,
2374
+ maxJobsPerDispatch: 128,
2375
+ cadenceDivisor: 1,
2376
+ workgroupScale: 1,
2377
+ maxQueueDepth: 256
2378
+ }
2379
+ }
2380
+ ),
2381
+ suggestedAllocationIds: [
2382
+ "lighting.wavefront.active-queue",
2383
+ "lighting.wavefront.next-queue"
2384
+ ]
2385
+ }
2386
+ }
2387
+ },
1617
2388
  volumetrics: {
1618
2389
  suggestedAllocationIds: [
1619
2390
  "lighting.volumetrics.froxel-grid",
@@ -1823,6 +2594,13 @@ var lightingWorkerDagSpecs = {
1823
2594
  accumulate: { priority: 3, dependencies: ["pathTrace"] },
1824
2595
  denoise: { priority: 2, dependencies: ["accumulate"] }
1825
2596
  },
2597
+ wavefront: {
2598
+ accumulateTerminalRadiance: { priority: 3, dependencies: [] },
2599
+ scatterContinuations: {
2600
+ priority: 2,
2601
+ dependencies: ["accumulateTerminalRadiance"]
2602
+ }
2603
+ },
1826
2604
  volumetrics: {
1827
2605
  volumetricShadow: { priority: 3, dependencies: [] },
1828
2606
  froxelIntegrate: { priority: 2, dependencies: ["volumetricShadow"] }
@@ -1856,6 +2634,16 @@ function resolveLightingQualityDimensions(techniqueName, jobKey) {
1856
2634
  "pathtracer.pathTrace": { rayTracing: 1, lightingSamples: 1 },
1857
2635
  "pathtracer.accumulate": { temporalReuse: 1, updateCadence: 0.4 },
1858
2636
  "pathtracer.denoise": { temporalReuse: 1, shading: 0.4 },
2637
+ "wavefront.accumulateTerminalRadiance": {
2638
+ rayTracing: 1,
2639
+ lightingSamples: 1,
2640
+ temporalReuse: 0.4
2641
+ },
2642
+ "wavefront.scatterContinuations": {
2643
+ rayTracing: 1,
2644
+ shading: 0.7,
2645
+ updateCadence: 0.5
2646
+ },
1859
2647
  "volumetrics.froxelIntegrate": {
1860
2648
  lightingSamples: 0.6,
1861
2649
  shading: 0.4,
@@ -1900,6 +2688,15 @@ function resolveLightingImportanceSignals(techniqueName, jobKey) {
1900
2688
  },
1901
2689
  "pathtracer.accumulate": { visible: true },
1902
2690
  "pathtracer.denoise": { visible: true },
2691
+ "wavefront.accumulateTerminalRadiance": {
2692
+ visible: true,
2693
+ shadowSignificance: "high",
2694
+ reflectionSignificance: "high"
2695
+ },
2696
+ "wavefront.scatterContinuations": {
2697
+ visible: true,
2698
+ reflectionSignificance: "high"
2699
+ },
1903
2700
  "volumetrics.froxelIntegrate": { visible: true },
1904
2701
  "volumetrics.volumetricShadow": { visible: true, shadowSignificance: "high" },
1905
2702
  "hdri.irradianceConvolution": { visible: false },
@@ -2169,9 +2966,18 @@ export {
2169
2966
  createLightingBandPlan,
2170
2967
  createLightingProfileModeLadder,
2171
2968
  createWavefrontEnvironmentLightingOptions,
2969
+ createWavefrontLightingPlan,
2970
+ createWavefrontRayPayload,
2971
+ createWavefrontReferenceFixture,
2972
+ createWavefrontVisibilityProbeRay,
2172
2973
  defaultAdaptiveLightingProfilePolicy,
2173
2974
  defaultLightingProfile,
2174
2975
  defaultLightingTechnique,
2976
+ evaluateWavefrontContinuationEvent,
2977
+ evaluateWavefrontMaterialReference,
2978
+ evaluateWavefrontMediumState,
2979
+ evaluateWavefrontTerminalRadiance,
2980
+ evaluateWavefrontVisibilityProbe,
2175
2981
  getLightingProfile,
2176
2982
  getLightingProfileWorkerManifest,
2177
2983
  getLightingTechnique,
@@ -2190,8 +2996,19 @@ export {
2190
2996
  lightingProfileModeOrder,
2191
2997
  lightingProfileNames,
2192
2998
  lightingProfiles,
2999
+ lightingRequiredRendererWavefrontPassOrder,
2193
3000
  lightingTechniqueNames,
2194
3001
  lightingTechniques,
3002
+ lightingWavefrontBufferContracts,
3003
+ lightingWavefrontContinuationHitTypes,
3004
+ lightingWavefrontHitTypes,
3005
+ lightingWavefrontPassOrder,
3006
+ lightingWavefrontQueuePairStrategy,
3007
+ lightingWavefrontRayKinds,
3008
+ lightingWavefrontSchemaVersion,
3009
+ lightingWavefrontTerminalHitTypes,
3010
+ lightingWavefrontTerminationPolicy,
3011
+ lightingWavefrontVisibilityProbeModes,
2195
3012
  lightingWorkerManifests,
2196
3013
  lightingWorkerQueueClass,
2197
3014
  loadLightingJobs,