@plasius/gpu-lighting 0.2.5 → 0.2.6

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/src/index.js CHANGED
@@ -40,6 +40,15 @@ const techniqueSpecs = {
40
40
  denoise: "denoise.job.wgsl",
41
41
  },
42
42
  },
43
+ wavefront: {
44
+ description:
45
+ "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
+ },
43
52
  volumetrics: {
44
53
  description:
45
54
  "Froxel volumetric lighting for fog, shafts, and participating media shadows.",
@@ -1128,6 +1137,421 @@ export function createWavefrontEnvironmentLightingOptions(options = {}) {
1128
1137
  });
1129
1138
  }
1130
1139
 
1140
+ export const lightingWavefrontSchemaVersion = 1;
1141
+ export const lightingWavefrontQueuePairStrategy = "ping-pong-active-next";
1142
+ export const lightingWavefrontHitTypes = Object.freeze([
1143
+ "surface",
1144
+ "emissive",
1145
+ "environment",
1146
+ "transparent",
1147
+ "miss",
1148
+ ]);
1149
+ export const lightingWavefrontTerminalHitTypes = Object.freeze([
1150
+ "emissive",
1151
+ "environment",
1152
+ "miss",
1153
+ ]);
1154
+ export const lightingWavefrontContinuationHitTypes = Object.freeze([
1155
+ "surface",
1156
+ "transparent",
1157
+ ]);
1158
+ export const lightingWavefrontPassOrder = Object.freeze([
1159
+ "accumulateTerminalRadiance",
1160
+ "scatterContinuations",
1161
+ ]);
1162
+ export const lightingRequiredRendererWavefrontPassOrder = Object.freeze([
1163
+ "generatePrimaryRays",
1164
+ "intersectActiveQueue",
1165
+ "resolveSurfaceRecords",
1166
+ "accumulateTerminalRadiance",
1167
+ "scatterContinuations",
1168
+ "compactAndSwapQueues",
1169
+ ]);
1170
+
1171
+ function createLightingWavefrontField(name, type, description) {
1172
+ return Object.freeze({
1173
+ name,
1174
+ type,
1175
+ description,
1176
+ });
1177
+ }
1178
+
1179
+ function createLightingWavefrontRecordContract(recordName, fields) {
1180
+ return Object.freeze({
1181
+ schemaVersion: lightingWavefrontSchemaVersion,
1182
+ recordName,
1183
+ fields: Object.freeze(fields),
1184
+ });
1185
+ }
1186
+
1187
+ export const lightingWavefrontBufferContracts = Object.freeze({
1188
+ ray: createLightingWavefrontRecordContract(
1189
+ "RayRecord",
1190
+ [
1191
+ createLightingWavefrontField("rayId", "u32", "Stable ray identifier."),
1192
+ createLightingWavefrontField("parentRayId", "u32", "Parent ray identifier."),
1193
+ createLightingWavefrontField("sourcePixelId", "u32", "Source pixel/sample owner."),
1194
+ createLightingWavefrontField("sampleId", "u32", "Per-pixel sample slot."),
1195
+ createLightingWavefrontField("bounce", "u32", "Current bounce depth."),
1196
+ createLightingWavefrontField("origin", "vec3<f32>", "Ray origin."),
1197
+ createLightingWavefrontField("direction", "vec3<f32>", "Normalized ray direction."),
1198
+ createLightingWavefrontField("throughput", "vec3<f32>", "Accumulated path throughput."),
1199
+ createLightingWavefrontField("mediumRefId", "u32", "Current medium reference."),
1200
+ createLightingWavefrontField("flags", "u32", "Renderer-owned ray flags."),
1201
+ ]
1202
+ ),
1203
+ hit: createLightingWavefrontRecordContract(
1204
+ "HitRecord",
1205
+ [
1206
+ createLightingWavefrontField("rayId", "u32", "Stable ray identifier."),
1207
+ createLightingWavefrontField("sourcePixelId", "u32", "Source pixel/sample owner."),
1208
+ createLightingWavefrontField("hitType", "u32", "Surface, emissive, environment, transparent, or miss."),
1209
+ createLightingWavefrontField("distance", "f32", "Nearest-hit distance."),
1210
+ createLightingWavefrontField("entityId", "u32", "Owning entity identifier."),
1211
+ createLightingWavefrontField("instanceId", "u32", "Owning instance identifier."),
1212
+ createLightingWavefrontField("primitiveId", "u32", "Primitive identifier."),
1213
+ createLightingWavefrontField("materialId", "u32", "Resolved material identifier."),
1214
+ createLightingWavefrontField("barycentrics", "vec3<f32>", "Triangle barycentrics."),
1215
+ createLightingWavefrontField("uv", "vec2<f32>", "Resolved UV coordinates."),
1216
+ createLightingWavefrontField("geometricNormal", "vec3<f32>", "Geometric surface normal."),
1217
+ createLightingWavefrontField("shadingNormal", "vec3<f32>", "Interpolated shading normal."),
1218
+ createLightingWavefrontField("frontFace", "u32", "Front-face classification."),
1219
+ ]
1220
+ ),
1221
+ surface: createLightingWavefrontRecordContract(
1222
+ "SurfaceRecord",
1223
+ [
1224
+ createLightingWavefrontField("rayId", "u32", "Stable ray identifier."),
1225
+ createLightingWavefrontField("entityId", "u32", "Owning entity identifier."),
1226
+ createLightingWavefrontField("materialRefId", "u32", "Renderer material reference id."),
1227
+ createLightingWavefrontField("mediumRefId", "u32", "Renderer medium reference id."),
1228
+ createLightingWavefrontField("geometricNormal", "vec3<f32>", "Geometric surface normal."),
1229
+ createLightingWavefrontField("shadingNormal", "vec3<f32>", "Interpolated shading normal."),
1230
+ createLightingWavefrontField("uv", "vec2<f32>", "Resolved UV coordinates."),
1231
+ createLightingWavefrontField("tangentFrame", "mat3x3<f32>", "Resolved tangent frame."),
1232
+ ]
1233
+ ),
1234
+ materialReference: createLightingWavefrontRecordContract(
1235
+ "MaterialReferenceRecord",
1236
+ [
1237
+ createLightingWavefrontField("materialRefId", "u32", "Lighting-visible material reference id."),
1238
+ createLightingWavefrontField("materialId", "u32", "Source material identifier."),
1239
+ createLightingWavefrontField("shadingModel", "u32", "Renderer shading-model discriminator."),
1240
+ createLightingWavefrontField("textureSetId", "u32", "Resolved texture-set identifier."),
1241
+ createLightingWavefrontField("flags", "u32", "Renderer material flags."),
1242
+ ]
1243
+ ),
1244
+ mediumReference: createLightingWavefrontRecordContract(
1245
+ "MediumReferenceRecord",
1246
+ [
1247
+ createLightingWavefrontField("mediumRefId", "u32", "Lighting-visible medium reference id."),
1248
+ createLightingWavefrontField("mediumId", "u32", "Source medium identifier."),
1249
+ createLightingWavefrontField("phaseModel", "u32", "Phase-function discriminator."),
1250
+ createLightingWavefrontField("absorption", "vec3<f32>", "Medium absorption coefficients."),
1251
+ createLightingWavefrontField("scattering", "vec3<f32>", "Medium scattering coefficients."),
1252
+ ]
1253
+ ),
1254
+ accumulation: createLightingWavefrontRecordContract(
1255
+ "AccumulationRecord",
1256
+ [
1257
+ createLightingWavefrontField("sourcePixelId", "u32", "Source pixel/sample owner."),
1258
+ createLightingWavefrontField("sampleCount", "u32", "Accumulated sample count."),
1259
+ createLightingWavefrontField("radiance", "vec3<f32>", "Accumulated radiance."),
1260
+ createLightingWavefrontField("throughput", "vec3<f32>", "Last surviving throughput."),
1261
+ createLightingWavefrontField("resetEpoch", "u32", "Renderer accumulation reset epoch."),
1262
+ ]
1263
+ ),
1264
+ });
1265
+
1266
+ export const lightingWavefrontTerminationPolicy = Object.freeze({
1267
+ terminalHitTypes: lightingWavefrontTerminalHitTypes,
1268
+ continuationHitTypes: lightingWavefrontContinuationHitTypes,
1269
+ emissive: Object.freeze({
1270
+ action: "accumulate-and-stop",
1271
+ contributesRadiance: true,
1272
+ }),
1273
+ environment: Object.freeze({
1274
+ action: "accumulate-and-stop",
1275
+ contributesRadiance: true,
1276
+ }),
1277
+ miss: Object.freeze({
1278
+ action: "accumulate-environment-or-dark-stop",
1279
+ contributesRadiance: true,
1280
+ }),
1281
+ });
1282
+
1283
+ const defaultWavefrontDarkRadiance = Object.freeze([0.0001, 0.0001, 0.0001]);
1284
+ const wavefrontEventKinds = Object.freeze([
1285
+ "diffuse",
1286
+ "reflection",
1287
+ "refraction",
1288
+ "transparency",
1289
+ "terminate",
1290
+ ]);
1291
+
1292
+ function normalizeWavefrontHitType(value) {
1293
+ return lightingWavefrontHitTypes.includes(value) ? value : "surface";
1294
+ }
1295
+
1296
+ function normalizeWavefrontEventKind(value) {
1297
+ return wavefrontEventKinds.includes(value) ? value : null;
1298
+ }
1299
+
1300
+ function normalizeVec3(value, fallback = [0, 0, 0]) {
1301
+ if (!Array.isArray(value) || value.length < 3) {
1302
+ return [...fallback];
1303
+ }
1304
+ return [
1305
+ Number.isFinite(value[0]) ? value[0] : fallback[0],
1306
+ Number.isFinite(value[1]) ? value[1] : fallback[1],
1307
+ Number.isFinite(value[2]) ? value[2] : fallback[2],
1308
+ ];
1309
+ }
1310
+
1311
+ function clampUnit(value, fallback = 0) {
1312
+ return Math.max(0, Math.min(1, readFinite(value, fallback)));
1313
+ }
1314
+
1315
+ function saturateVec3(value) {
1316
+ return value.map((component) => Math.max(0, component));
1317
+ }
1318
+
1319
+ function scaleVec3(value, scalar) {
1320
+ return value.map((component) => component * scalar);
1321
+ }
1322
+
1323
+ function addVec3(left, right) {
1324
+ return left.map((component, index) => component + right[index]);
1325
+ }
1326
+
1327
+ function multiplyVec3(left, right) {
1328
+ return left.map((component, index) => component * right[index]);
1329
+ }
1330
+
1331
+ function dotVec3(left, right) {
1332
+ return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
1333
+ }
1334
+
1335
+ function lengthVec3(value) {
1336
+ return Math.hypot(value[0], value[1], value[2]);
1337
+ }
1338
+
1339
+ function normalizeDirection(value, fallback = [0, 1, 0]) {
1340
+ const vector = normalizeVec3(value, fallback);
1341
+ const length = lengthVec3(vector);
1342
+ if (!Number.isFinite(length) || length <= 0.000001) {
1343
+ return [...fallback];
1344
+ }
1345
+ return vector.map((component) => component / length);
1346
+ }
1347
+
1348
+ function mixVec3(left, right, factor) {
1349
+ return left.map((component, index) =>
1350
+ component * (1 - factor) + right[index] * factor
1351
+ );
1352
+ }
1353
+
1354
+ function reflectDirection(direction, normal) {
1355
+ const scale = 2 * dotVec3(direction, normal);
1356
+ return normalizeDirection(
1357
+ [
1358
+ direction[0] - scale * normal[0],
1359
+ direction[1] - scale * normal[1],
1360
+ direction[2] - scale * normal[2],
1361
+ ],
1362
+ normal
1363
+ );
1364
+ }
1365
+
1366
+ function refractDirection(direction, normal, etaRatio) {
1367
+ const cosTheta = Math.min(-dotVec3(direction, normal), 1);
1368
+ const rOutPerp = scaleVec3(addVec3(direction, scaleVec3(normal, cosTheta)), etaRatio);
1369
+ const rOutPerpLengthSquared = dotVec3(rOutPerp, rOutPerp);
1370
+ const parallelFactor = 1 - rOutPerpLengthSquared;
1371
+ if (parallelFactor <= 0) {
1372
+ return null;
1373
+ }
1374
+ const rOutParallel = scaleVec3(normal, -Math.sqrt(parallelFactor));
1375
+ return normalizeDirection(addVec3(rOutPerp, rOutParallel), direction);
1376
+ }
1377
+
1378
+ export function createWavefrontLightingPlan(options = {}) {
1379
+ const maxDepth = Math.max(1, Math.trunc(readFinite(options.maxDepth, 4)));
1380
+ const queueCapacity = Math.max(
1381
+ 1,
1382
+ Math.trunc(readFinite(options.queueCapacity, 4096))
1383
+ );
1384
+ const explicitLightSampling = Boolean(options.explicitLightSampling);
1385
+ const accumulationResetEpoch = Math.max(
1386
+ 0,
1387
+ Math.trunc(readFinite(options.accumulationResetEpoch, 0))
1388
+ );
1389
+
1390
+ return Object.freeze({
1391
+ schemaVersion: lightingWavefrontSchemaVersion,
1392
+ maxDepth,
1393
+ queueCapacity,
1394
+ explicitLightSampling,
1395
+ accumulationResetEpoch,
1396
+ queueLayout: Object.freeze({
1397
+ strategy: lightingWavefrontQueuePairStrategy,
1398
+ compactAfterScatter: true,
1399
+ queues: Object.freeze([
1400
+ Object.freeze({ name: "active", role: "current-bounce" }),
1401
+ Object.freeze({ name: "next", role: "next-bounce" }),
1402
+ ]),
1403
+ }),
1404
+ bufferContracts: lightingWavefrontBufferContracts,
1405
+ terminationPolicy: lightingWavefrontTerminationPolicy,
1406
+ requiredRendererPassOrder: lightingRequiredRendererWavefrontPassOrder,
1407
+ lightingPasses: Object.freeze([
1408
+ Object.freeze({
1409
+ key: "accumulateTerminalRadiance",
1410
+ stage: "accumulateTerminalRadiance",
1411
+ reads: Object.freeze([
1412
+ "ray",
1413
+ "hit",
1414
+ "surface",
1415
+ "materialReference",
1416
+ "mediumReference",
1417
+ ]),
1418
+ writes: Object.freeze(["accumulation"]),
1419
+ terminalHitTypes: lightingWavefrontTerminalHitTypes,
1420
+ }),
1421
+ Object.freeze({
1422
+ key: "scatterContinuations",
1423
+ stage: "scatterContinuations",
1424
+ reads: Object.freeze([
1425
+ "ray",
1426
+ "hit",
1427
+ "surface",
1428
+ "materialReference",
1429
+ "mediumReference",
1430
+ ]),
1431
+ writes: Object.freeze(["ray"]),
1432
+ continuationHitTypes: lightingWavefrontContinuationHitTypes,
1433
+ explicitLightSampling,
1434
+ }),
1435
+ ]),
1436
+ });
1437
+ }
1438
+
1439
+ export function evaluateWavefrontTerminalRadiance(options = {}) {
1440
+ const hitType = normalizeWavefrontHitType(options.hitType);
1441
+ const throughput = saturateVec3(normalizeVec3(options.throughput, [1, 1, 1]));
1442
+ const emission = saturateVec3(normalizeVec3(options.emission, [0, 0, 0]));
1443
+ const environmentRadiance = saturateVec3(
1444
+ normalizeVec3(options.environmentRadiance, [0, 0, 0])
1445
+ );
1446
+ const missRadiance = saturateVec3(
1447
+ normalizeVec3(options.missRadiance, defaultWavefrontDarkRadiance)
1448
+ );
1449
+ const environmentLuminance = colorLuminance(environmentRadiance);
1450
+ let source = "none";
1451
+ let rawRadiance = [0, 0, 0];
1452
+
1453
+ if (hitType === "emissive") {
1454
+ source = "emissive";
1455
+ rawRadiance = emission;
1456
+ } else if (hitType === "environment") {
1457
+ source = "environment";
1458
+ rawRadiance = environmentRadiance;
1459
+ } else if (hitType === "miss") {
1460
+ source = environmentLuminance > 0.000001 ? "environment" : "dark";
1461
+ rawRadiance = environmentLuminance > 0.000001 ? environmentRadiance : missRadiance;
1462
+ }
1463
+
1464
+ const radiance = multiplyVec3(throughput, rawRadiance);
1465
+ const terminated = lightingWavefrontTerminalHitTypes.includes(hitType);
1466
+
1467
+ return Object.freeze({
1468
+ hitType,
1469
+ source,
1470
+ terminated,
1471
+ radiance: Object.freeze(radiance),
1472
+ nearDarkSample:
1473
+ source === "dark" && colorLuminance(radiance) <= colorLuminance(defaultWavefrontDarkRadiance),
1474
+ });
1475
+ }
1476
+
1477
+ export function evaluateWavefrontContinuationEvent(options = {}) {
1478
+ const hitType = normalizeWavefrontHitType(options.hitType);
1479
+ const bounceIndex = Math.max(0, Math.trunc(readFinite(options.bounceIndex, 0)));
1480
+ const maxDepth = Math.max(1, Math.trunc(readFinite(options.maxDepth, 4)));
1481
+ const throughput = saturateVec3(normalizeVec3(options.throughput, [1, 1, 1]));
1482
+ const albedo = saturateVec3(normalizeVec3(options.albedo, [0.8, 0.8, 0.8]));
1483
+ const transmission = saturateVec3(
1484
+ normalizeVec3(options.transmission, [0, 0, 0])
1485
+ );
1486
+ const shadingNormal = normalizeDirection(options.shadingNormal, [0, 1, 0]);
1487
+ const viewDirection = normalizeDirection(options.viewDirection, [0, 0, 1]);
1488
+ const incomingDirection = normalizeDirection(
1489
+ scaleVec3(viewDirection, -1),
1490
+ [0, 0, -1]
1491
+ );
1492
+ const frontFace = options.frontFace !== false;
1493
+ const orientedNormal = frontFace
1494
+ ? shadingNormal
1495
+ : scaleVec3(shadingNormal, -1);
1496
+ const metalness = clampUnit(options.metalness, 0);
1497
+ const roughness = clampUnit(options.roughness, 0.5);
1498
+ const opacity = clampUnit(options.opacity, 1);
1499
+ const refractiveIndex = Math.max(1, readFinite(options.refractiveIndex ?? options.ior, 1.45));
1500
+ const transmissionStrength = Math.max(...transmission);
1501
+ let eventKind =
1502
+ normalizeWavefrontEventKind(options.eventKind) ??
1503
+ (hitType === "transparent" || opacity < 0.999
1504
+ ? "transparency"
1505
+ : transmissionStrength > 0.001
1506
+ ? "refraction"
1507
+ : metalness >= 0.5 || roughness <= 0.2
1508
+ ? "reflection"
1509
+ : "diffuse");
1510
+
1511
+ let nextDirection = incomingDirection;
1512
+ let attenuation;
1513
+
1514
+ if (!lightingWavefrontContinuationHitTypes.includes(hitType) || bounceIndex >= maxDepth - 1) {
1515
+ eventKind = "terminate";
1516
+ attenuation = [0, 0, 0];
1517
+ } else if (eventKind === "reflection") {
1518
+ nextDirection = reflectDirection(incomingDirection, orientedNormal);
1519
+ attenuation = mixVec3([0.04, 0.04, 0.04], albedo, metalness);
1520
+ } else if (eventKind === "refraction") {
1521
+ const etaRatio = frontFace ? 1 / refractiveIndex : refractiveIndex;
1522
+ nextDirection =
1523
+ refractDirection(incomingDirection, orientedNormal, etaRatio) ??
1524
+ reflectDirection(incomingDirection, orientedNormal);
1525
+ attenuation = transmissionStrength > 0.001 ? transmission : [1, 1, 1];
1526
+ } else if (eventKind === "transparency") {
1527
+ nextDirection = incomingDirection;
1528
+ const transparencyWeight = Math.max(1 - opacity, transmissionStrength, 0.05);
1529
+ attenuation = transmissionStrength > 0.001
1530
+ ? transmission
1531
+ : [transparencyWeight, transparencyWeight, transparencyWeight];
1532
+ } else {
1533
+ nextDirection = normalizeDirection(
1534
+ addVec3(orientedNormal, albedo.map((component) => component - 0.5)),
1535
+ orientedNormal
1536
+ );
1537
+ attenuation = scaleVec3(albedo, Math.max(0.05, 1 - metalness));
1538
+ }
1539
+
1540
+ const nextThroughput = multiplyVec3(throughput, saturateVec3(attenuation));
1541
+ const continueTracing =
1542
+ eventKind !== "terminate" && colorLuminance(nextThroughput) > 0.0001;
1543
+
1544
+ return Object.freeze({
1545
+ hitType,
1546
+ eventKind,
1547
+ continueTracing,
1548
+ nextDirection: Object.freeze(nextDirection),
1549
+ attenuation: Object.freeze(saturateVec3(attenuation)),
1550
+ nextThroughput: Object.freeze(nextThroughput),
1551
+ explicitLightSamplingEnabled: Boolean(options.explicitLightSampling),
1552
+ });
1553
+ }
1554
+
1131
1555
  const lightingImportanceLevels = Object.freeze([
1132
1556
  "low",
1133
1557
  "medium",
@@ -1725,6 +2149,91 @@ const lightingWorkerSpecPresets = {
1725
2149
  },
1726
2150
  },
1727
2151
  },
2152
+ wavefront: {
2153
+ suggestedAllocationIds: [
2154
+ "lighting.wavefront.active-queue",
2155
+ "lighting.wavefront.next-queue",
2156
+ "lighting.wavefront.accumulation",
2157
+ ],
2158
+ jobs: {
2159
+ accumulateTerminalRadiance: {
2160
+ domain: "lighting",
2161
+ importance: "critical",
2162
+ levels: buildWorkerBudgetLevels(
2163
+ "lighting.wavefront.accumulateTerminalRadiance",
2164
+ lightingWorkerQueueClass,
2165
+ {
2166
+ low: {
2167
+ estimatedCostMs: 0.7,
2168
+ maxDispatchesPerFrame: 1,
2169
+ maxJobsPerDispatch: 32,
2170
+ cadenceDivisor: 2,
2171
+ workgroupScale: 0.5,
2172
+ maxQueueDepth: 96,
2173
+ },
2174
+ medium: {
2175
+ estimatedCostMs: 1.2,
2176
+ maxDispatchesPerFrame: 1,
2177
+ maxJobsPerDispatch: 64,
2178
+ cadenceDivisor: 1,
2179
+ workgroupScale: 0.75,
2180
+ maxQueueDepth: 192,
2181
+ },
2182
+ high: {
2183
+ estimatedCostMs: 1.8,
2184
+ maxDispatchesPerFrame: 2,
2185
+ maxJobsPerDispatch: 128,
2186
+ cadenceDivisor: 1,
2187
+ workgroupScale: 1,
2188
+ maxQueueDepth: 256,
2189
+ },
2190
+ }
2191
+ ),
2192
+ suggestedAllocationIds: [
2193
+ "lighting.wavefront.accumulation",
2194
+ "lighting.wavefront.active-queue",
2195
+ ],
2196
+ },
2197
+ scatterContinuations: {
2198
+ domain: "lighting",
2199
+ importance: "critical",
2200
+ levels: buildWorkerBudgetLevels(
2201
+ "lighting.wavefront.scatterContinuations",
2202
+ lightingWorkerQueueClass,
2203
+ {
2204
+ low: {
2205
+ estimatedCostMs: 0.8,
2206
+ maxDispatchesPerFrame: 1,
2207
+ maxJobsPerDispatch: 32,
2208
+ cadenceDivisor: 2,
2209
+ workgroupScale: 0.5,
2210
+ maxQueueDepth: 96,
2211
+ },
2212
+ medium: {
2213
+ estimatedCostMs: 1.4,
2214
+ maxDispatchesPerFrame: 1,
2215
+ maxJobsPerDispatch: 64,
2216
+ cadenceDivisor: 1,
2217
+ workgroupScale: 0.75,
2218
+ maxQueueDepth: 192,
2219
+ },
2220
+ high: {
2221
+ estimatedCostMs: 2.1,
2222
+ maxDispatchesPerFrame: 2,
2223
+ maxJobsPerDispatch: 128,
2224
+ cadenceDivisor: 1,
2225
+ workgroupScale: 1,
2226
+ maxQueueDepth: 256,
2227
+ },
2228
+ }
2229
+ ),
2230
+ suggestedAllocationIds: [
2231
+ "lighting.wavefront.active-queue",
2232
+ "lighting.wavefront.next-queue",
2233
+ ],
2234
+ },
2235
+ },
2236
+ },
1728
2237
  volumetrics: {
1729
2238
  suggestedAllocationIds: [
1730
2239
  "lighting.volumetrics.froxel-grid",
@@ -1935,6 +2444,13 @@ const lightingWorkerDagSpecs = {
1935
2444
  accumulate: { priority: 3, dependencies: ["pathTrace"] },
1936
2445
  denoise: { priority: 2, dependencies: ["accumulate"] },
1937
2446
  },
2447
+ wavefront: {
2448
+ accumulateTerminalRadiance: { priority: 3, dependencies: [] },
2449
+ scatterContinuations: {
2450
+ priority: 2,
2451
+ dependencies: ["accumulateTerminalRadiance"],
2452
+ },
2453
+ },
1938
2454
  volumetrics: {
1939
2455
  volumetricShadow: { priority: 3, dependencies: [] },
1940
2456
  froxelIntegrate: { priority: 2, dependencies: ["volumetricShadow"] },
@@ -1969,6 +2485,16 @@ function resolveLightingQualityDimensions(techniqueName, jobKey) {
1969
2485
  "pathtracer.pathTrace": { rayTracing: 1, lightingSamples: 1 },
1970
2486
  "pathtracer.accumulate": { temporalReuse: 1, updateCadence: 0.4 },
1971
2487
  "pathtracer.denoise": { temporalReuse: 1, shading: 0.4 },
2488
+ "wavefront.accumulateTerminalRadiance": {
2489
+ rayTracing: 1,
2490
+ lightingSamples: 1,
2491
+ temporalReuse: 0.4,
2492
+ },
2493
+ "wavefront.scatterContinuations": {
2494
+ rayTracing: 1,
2495
+ shading: 0.7,
2496
+ updateCadence: 0.5,
2497
+ },
1972
2498
  "volumetrics.froxelIntegrate": {
1973
2499
  lightingSamples: 0.6,
1974
2500
  shading: 0.4,
@@ -2014,6 +2540,15 @@ function resolveLightingImportanceSignals(techniqueName, jobKey) {
2014
2540
  },
2015
2541
  "pathtracer.accumulate": { visible: true },
2016
2542
  "pathtracer.denoise": { visible: true },
2543
+ "wavefront.accumulateTerminalRadiance": {
2544
+ visible: true,
2545
+ shadowSignificance: "high",
2546
+ reflectionSignificance: "high",
2547
+ },
2548
+ "wavefront.scatterContinuations": {
2549
+ visible: true,
2550
+ reflectionSignificance: "high",
2551
+ },
2017
2552
  "volumetrics.froxelIntegrate": { visible: true },
2018
2553
  "volumetrics.volumetricShadow": { visible: true, shadowSignificance: "high" },
2019
2554
  "hdri.irradianceConvolution": { visible: false },
@@ -0,0 +1,61 @@
1
+ @group(0) @binding(0) var<uniform> wavefrontLightingParams: WavefrontLightingParams;
2
+ @group(0) @binding(1) var<storage, read> activeQueue: array<RayRecord>;
3
+ @group(0) @binding(2) var<storage, read> hitBuffer: array<HitRecord>;
4
+ @group(0) @binding(3) var<storage, read> surfaceBuffer: array<SurfaceRecord>;
5
+ @group(0) @binding(4) var<storage, read> materialRefBuffer: array<MaterialReferenceRecord>;
6
+ @group(0) @binding(5) var<storage, read> mediumRefBuffer: array<MediumReferenceRecord>;
7
+ @group(0) @binding(6) var<storage, read_write> accumulationBuffer: array<AccumulationRecord>;
8
+
9
+ fn resolve_emissive_radiance(
10
+ material: MaterialReferenceRecord,
11
+ surface: SurfaceRecord
12
+ ) -> vec3<f32> {
13
+ let uv_weight = saturate_scalar(surface.uv.x + surface.uv.y);
14
+ let emissive_hint = select(0.0, 1.0, (material.flags & 0x1u) != 0u);
15
+ let base = material_base_albedo(material);
16
+ return base * (0.5 + uv_weight * 0.5 + emissive_hint * 2.0);
17
+ }
18
+
19
+ fn accumulate_terminal_sample(
20
+ ray: RayRecord,
21
+ hit: HitRecord,
22
+ surface: SurfaceRecord,
23
+ material: MaterialReferenceRecord,
24
+ accumulation_index: u32
25
+ ) {
26
+ let emissive_radiance = resolve_emissive_radiance(material, surface);
27
+ let terminal_radiance = terminal_radiance_for_hit(hit, ray, emissive_radiance);
28
+ if (luminance(terminal_radiance) <= LIGHTING_EPSILON) {
29
+ return;
30
+ }
31
+
32
+ accumulationBuffer[accumulation_index].sourcePixelId = ray.sourcePixelId;
33
+ accumulationBuffer[accumulation_index].sampleCount =
34
+ accumulationBuffer[accumulation_index].sampleCount + 1u;
35
+ accumulationBuffer[accumulation_index].resetEpoch =
36
+ wavefrontLightingParams.accumulation_reset_epoch;
37
+ accumulationBuffer[accumulation_index].radiance =
38
+ accumulationBuffer[accumulation_index].radiance + terminal_radiance;
39
+ accumulationBuffer[accumulation_index].throughput = ray.throughput;
40
+ }
41
+
42
+ @compute @workgroup_size(64, 1, 1)
43
+ fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
44
+ let index = global_id.x;
45
+ if (index >= wavefrontLightingParams.active_count) {
46
+ return;
47
+ }
48
+
49
+ let ray = activeQueue[index];
50
+ let hit = hitBuffer[index];
51
+ if (!is_terminal_hit_type(hit.hitType)) {
52
+ return;
53
+ }
54
+
55
+ let surface = surfaceBuffer[index];
56
+ let material = materialRefBuffer[surface.materialRefId];
57
+ let _medium = mediumRefBuffer[surface.mediumRefId];
58
+ let accumulation_index = min(ray.sourcePixelId, wavefrontLightingParams.active_count - 1u);
59
+
60
+ accumulate_terminal_sample(ray, hit, surface, material, accumulation_index);
61
+ }