@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/CHANGELOG.md CHANGED
@@ -20,7 +20,7 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
20
20
  - **Security**
21
21
  - (placeholder)
22
22
 
23
- ## [0.2.5] - 2026-06-15
23
+ ## [0.2.6] - 2026-06-16
24
24
 
25
25
  - **Added**
26
26
  - Added concrete volumetric WGSL kernels for `volumetricShadow` and
@@ -28,14 +28,25 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
28
28
  integration for the published realtime and reference profiles.
29
29
  - Added concrete HDRI/IBL WGSL kernels for `irradianceConvolution`,
30
30
  `specularPrefilter`, and `brdfLut`.
31
+ - Added a renderer-aligned `wavefront` lighting technique with concrete
32
+ WGSL jobs for terminal radiance accumulation and continuation scattering.
33
+ - Added `createWavefrontLightingPlan()`,
34
+ `evaluateWavefrontTerminalRadiance()`, and
35
+ `evaluateWavefrontContinuationEvent()` so downstream packages and tests can
36
+ validate emissive-hit, environment-hit, miss-darkening, reflection,
37
+ refraction, and transparency behavior without reimplementing the contract.
31
38
 
32
39
  - **Changed**
33
40
  - README now documents the delivered volumetrics and HDRI kernel scope with
34
41
  technique-level descriptions instead of leaving those exported jobs implied.
42
+ - Eames capture helpers now resolve workspace roots correctly from both
43
+ ordinary repo checkouts and `git worktree` paths.
35
44
 
36
45
  - **Fixed**
37
46
  - Package tests now fail if any exported `hybrid`, `volumetrics`, or `hdri`
38
47
  job regresses to placeholder text or an empty/no-op `process_job` body.
48
+ - Eames asset-path and capture-bridge tests are now worktree-safe instead of
49
+ assuming the package always lives under a literal `/gpu-lighting/` path.
39
50
 
40
51
  ## [0.2.2] - 2026-06-11
41
52
 
@@ -464,7 +475,7 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
464
475
  [0.1.16]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.1.16
465
476
  [0.1.17]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.1.17
466
477
  [0.1.19]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.1.19
467
- [Unreleased]: https://github.com/Plasius-LTD/gpu-lighting/compare/v0.2.5...HEAD
478
+ [Unreleased]: https://github.com/Plasius-LTD/gpu-lighting/compare/v0.2.6...HEAD
468
479
  [0.2.0]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.2.0
469
480
  [0.2.2]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.2.2
470
- [0.2.5]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.2.5
481
+ [0.2.6]: https://github.com/Plasius-LTD/gpu-lighting/releases/tag/v0.2.6
package/README.md CHANGED
@@ -162,6 +162,43 @@ const profileManifest = getLightingProfileWorkerManifest("realtime");
162
162
  console.log(profileManifest.jobs.map((job) => job.worker.jobType));
163
163
  ```
164
164
 
165
+ ## Wavefront Lighting Contracts
166
+
167
+ `@plasius/gpu-lighting` now also publishes a renderer-aligned `wavefront`
168
+ technique for the first active-ray lighting slice. It keeps the queue layout,
169
+ buffer contract names, and terminal-hit policy aligned with
170
+ `@plasius/gpu-renderer` while owning the lighting-specific WGSL for terminal
171
+ radiance accumulation and continuation scattering.
172
+
173
+ ```js
174
+ import {
175
+ createWavefrontLightingPlan,
176
+ evaluateWavefrontTerminalRadiance,
177
+ loadLightingTechniqueWorkerBundle,
178
+ } from "@plasius/gpu-lighting";
179
+
180
+ const plan = createWavefrontLightingPlan({
181
+ maxDepth: 6,
182
+ queueCapacity: 4096,
183
+ explicitLightSampling: true,
184
+ });
185
+
186
+ const bundle = await loadLightingTechniqueWorkerBundle("wavefront");
187
+ const emissive = evaluateWavefrontTerminalRadiance({
188
+ hitType: "emissive",
189
+ throughput: [0.5, 0.5, 0.5],
190
+ emission: [8, 6, 4],
191
+ });
192
+
193
+ console.log(plan.requiredRendererPassOrder);
194
+ console.log(bundle.jobs.map((job) => job.label));
195
+ console.log(emissive.radiance);
196
+ ```
197
+
198
+ This slice keeps emissive hits, environment hits, and environment-miss dark
199
+ fallbacks on the lighting package surface without reintroducing a depth-first
200
+ shader dependency into the renderer-owned wavefront queue model.
201
+
165
202
  ## Distance-Banded Lighting
166
203
 
167
204
  ```js
package/dist/index.cjs CHANGED
@@ -33,9 +33,12 @@ __export(index_exports, {
33
33
  createLightingBandPlan: () => createLightingBandPlan,
34
34
  createLightingProfileModeLadder: () => createLightingProfileModeLadder,
35
35
  createWavefrontEnvironmentLightingOptions: () => createWavefrontEnvironmentLightingOptions,
36
+ createWavefrontLightingPlan: () => createWavefrontLightingPlan,
36
37
  defaultAdaptiveLightingProfilePolicy: () => defaultAdaptiveLightingProfilePolicy,
37
38
  defaultLightingProfile: () => defaultLightingProfile,
38
39
  defaultLightingTechnique: () => defaultLightingTechnique,
40
+ evaluateWavefrontContinuationEvent: () => evaluateWavefrontContinuationEvent,
41
+ evaluateWavefrontTerminalRadiance: () => evaluateWavefrontTerminalRadiance,
39
42
  getLightingProfile: () => getLightingProfile,
40
43
  getLightingProfileWorkerManifest: () => getLightingProfileWorkerManifest,
41
44
  getLightingTechnique: () => getLightingTechnique,
@@ -54,8 +57,17 @@ __export(index_exports, {
54
57
  lightingProfileModeOrder: () => lightingProfileModeOrder,
55
58
  lightingProfileNames: () => lightingProfileNames,
56
59
  lightingProfiles: () => lightingProfiles,
60
+ lightingRequiredRendererWavefrontPassOrder: () => lightingRequiredRendererWavefrontPassOrder,
57
61
  lightingTechniqueNames: () => lightingTechniqueNames,
58
62
  lightingTechniques: () => lightingTechniques,
63
+ lightingWavefrontBufferContracts: () => lightingWavefrontBufferContracts,
64
+ lightingWavefrontContinuationHitTypes: () => lightingWavefrontContinuationHitTypes,
65
+ lightingWavefrontHitTypes: () => lightingWavefrontHitTypes,
66
+ lightingWavefrontPassOrder: () => lightingWavefrontPassOrder,
67
+ lightingWavefrontQueuePairStrategy: () => lightingWavefrontQueuePairStrategy,
68
+ lightingWavefrontSchemaVersion: () => lightingWavefrontSchemaVersion,
69
+ lightingWavefrontTerminalHitTypes: () => lightingWavefrontTerminalHitTypes,
70
+ lightingWavefrontTerminationPolicy: () => lightingWavefrontTerminationPolicy,
59
71
  lightingWorkerManifests: () => lightingWorkerManifests,
60
72
  lightingWorkerQueueClass: () => lightingWorkerQueueClass,
61
73
  loadLightingJobs: () => loadLightingJobs,
@@ -100,6 +112,14 @@ var techniqueSpecs = {
100
112
  denoise: "denoise.job.wgsl"
101
113
  }
102
114
  },
115
+ wavefront: {
116
+ description: "Renderer-aligned wavefront lighting jobs for terminal radiance and continuation scattering.",
117
+ prelude: "prelude.wgsl",
118
+ jobs: {
119
+ accumulateTerminalRadiance: "accumulate-terminal-radiance.job.wgsl",
120
+ scatterContinuations: "scatter-continuations.job.wgsl"
121
+ }
122
+ },
103
123
  volumetrics: {
104
124
  description: "Froxel volumetric lighting for fog, shafts, and participating media shadows.",
105
125
  prelude: "prelude.wgsl",
@@ -1108,6 +1128,374 @@ function createWavefrontEnvironmentLightingOptions(options = {}) {
1108
1128
  lightingEnvironment: config
1109
1129
  });
1110
1130
  }
1131
+ var lightingWavefrontSchemaVersion = 1;
1132
+ var lightingWavefrontQueuePairStrategy = "ping-pong-active-next";
1133
+ var lightingWavefrontHitTypes = Object.freeze([
1134
+ "surface",
1135
+ "emissive",
1136
+ "environment",
1137
+ "transparent",
1138
+ "miss"
1139
+ ]);
1140
+ var lightingWavefrontTerminalHitTypes = Object.freeze([
1141
+ "emissive",
1142
+ "environment",
1143
+ "miss"
1144
+ ]);
1145
+ var lightingWavefrontContinuationHitTypes = Object.freeze([
1146
+ "surface",
1147
+ "transparent"
1148
+ ]);
1149
+ var lightingWavefrontPassOrder = Object.freeze([
1150
+ "accumulateTerminalRadiance",
1151
+ "scatterContinuations"
1152
+ ]);
1153
+ var lightingRequiredRendererWavefrontPassOrder = Object.freeze([
1154
+ "generatePrimaryRays",
1155
+ "intersectActiveQueue",
1156
+ "resolveSurfaceRecords",
1157
+ "accumulateTerminalRadiance",
1158
+ "scatterContinuations",
1159
+ "compactAndSwapQueues"
1160
+ ]);
1161
+ function createLightingWavefrontField(name, type, description) {
1162
+ return Object.freeze({
1163
+ name,
1164
+ type,
1165
+ description
1166
+ });
1167
+ }
1168
+ function createLightingWavefrontRecordContract(recordName, fields) {
1169
+ return Object.freeze({
1170
+ schemaVersion: lightingWavefrontSchemaVersion,
1171
+ recordName,
1172
+ fields: Object.freeze(fields)
1173
+ });
1174
+ }
1175
+ var lightingWavefrontBufferContracts = Object.freeze({
1176
+ ray: createLightingWavefrontRecordContract(
1177
+ "RayRecord",
1178
+ [
1179
+ createLightingWavefrontField("rayId", "u32", "Stable ray identifier."),
1180
+ createLightingWavefrontField("parentRayId", "u32", "Parent ray identifier."),
1181
+ createLightingWavefrontField("sourcePixelId", "u32", "Source pixel/sample owner."),
1182
+ createLightingWavefrontField("sampleId", "u32", "Per-pixel sample slot."),
1183
+ createLightingWavefrontField("bounce", "u32", "Current bounce depth."),
1184
+ createLightingWavefrontField("origin", "vec3<f32>", "Ray origin."),
1185
+ createLightingWavefrontField("direction", "vec3<f32>", "Normalized ray direction."),
1186
+ createLightingWavefrontField("throughput", "vec3<f32>", "Accumulated path throughput."),
1187
+ createLightingWavefrontField("mediumRefId", "u32", "Current medium reference."),
1188
+ createLightingWavefrontField("flags", "u32", "Renderer-owned ray flags.")
1189
+ ]
1190
+ ),
1191
+ hit: createLightingWavefrontRecordContract(
1192
+ "HitRecord",
1193
+ [
1194
+ createLightingWavefrontField("rayId", "u32", "Stable ray identifier."),
1195
+ createLightingWavefrontField("sourcePixelId", "u32", "Source pixel/sample owner."),
1196
+ createLightingWavefrontField("hitType", "u32", "Surface, emissive, environment, transparent, or miss."),
1197
+ createLightingWavefrontField("distance", "f32", "Nearest-hit distance."),
1198
+ createLightingWavefrontField("entityId", "u32", "Owning entity identifier."),
1199
+ createLightingWavefrontField("instanceId", "u32", "Owning instance identifier."),
1200
+ createLightingWavefrontField("primitiveId", "u32", "Primitive identifier."),
1201
+ createLightingWavefrontField("materialId", "u32", "Resolved material identifier."),
1202
+ createLightingWavefrontField("barycentrics", "vec3<f32>", "Triangle barycentrics."),
1203
+ createLightingWavefrontField("uv", "vec2<f32>", "Resolved UV coordinates."),
1204
+ createLightingWavefrontField("geometricNormal", "vec3<f32>", "Geometric surface normal."),
1205
+ createLightingWavefrontField("shadingNormal", "vec3<f32>", "Interpolated shading normal."),
1206
+ createLightingWavefrontField("frontFace", "u32", "Front-face classification.")
1207
+ ]
1208
+ ),
1209
+ surface: createLightingWavefrontRecordContract(
1210
+ "SurfaceRecord",
1211
+ [
1212
+ createLightingWavefrontField("rayId", "u32", "Stable ray identifier."),
1213
+ createLightingWavefrontField("entityId", "u32", "Owning entity identifier."),
1214
+ createLightingWavefrontField("materialRefId", "u32", "Renderer material reference id."),
1215
+ createLightingWavefrontField("mediumRefId", "u32", "Renderer medium reference id."),
1216
+ createLightingWavefrontField("geometricNormal", "vec3<f32>", "Geometric surface normal."),
1217
+ createLightingWavefrontField("shadingNormal", "vec3<f32>", "Interpolated shading normal."),
1218
+ createLightingWavefrontField("uv", "vec2<f32>", "Resolved UV coordinates."),
1219
+ createLightingWavefrontField("tangentFrame", "mat3x3<f32>", "Resolved tangent frame.")
1220
+ ]
1221
+ ),
1222
+ materialReference: createLightingWavefrontRecordContract(
1223
+ "MaterialReferenceRecord",
1224
+ [
1225
+ createLightingWavefrontField("materialRefId", "u32", "Lighting-visible material reference id."),
1226
+ createLightingWavefrontField("materialId", "u32", "Source material identifier."),
1227
+ createLightingWavefrontField("shadingModel", "u32", "Renderer shading-model discriminator."),
1228
+ createLightingWavefrontField("textureSetId", "u32", "Resolved texture-set identifier."),
1229
+ createLightingWavefrontField("flags", "u32", "Renderer material flags.")
1230
+ ]
1231
+ ),
1232
+ mediumReference: createLightingWavefrontRecordContract(
1233
+ "MediumReferenceRecord",
1234
+ [
1235
+ createLightingWavefrontField("mediumRefId", "u32", "Lighting-visible medium reference id."),
1236
+ createLightingWavefrontField("mediumId", "u32", "Source medium identifier."),
1237
+ createLightingWavefrontField("phaseModel", "u32", "Phase-function discriminator."),
1238
+ createLightingWavefrontField("absorption", "vec3<f32>", "Medium absorption coefficients."),
1239
+ createLightingWavefrontField("scattering", "vec3<f32>", "Medium scattering coefficients.")
1240
+ ]
1241
+ ),
1242
+ accumulation: createLightingWavefrontRecordContract(
1243
+ "AccumulationRecord",
1244
+ [
1245
+ createLightingWavefrontField("sourcePixelId", "u32", "Source pixel/sample owner."),
1246
+ createLightingWavefrontField("sampleCount", "u32", "Accumulated sample count."),
1247
+ createLightingWavefrontField("radiance", "vec3<f32>", "Accumulated radiance."),
1248
+ createLightingWavefrontField("throughput", "vec3<f32>", "Last surviving throughput."),
1249
+ createLightingWavefrontField("resetEpoch", "u32", "Renderer accumulation reset epoch.")
1250
+ ]
1251
+ )
1252
+ });
1253
+ var lightingWavefrontTerminationPolicy = Object.freeze({
1254
+ terminalHitTypes: lightingWavefrontTerminalHitTypes,
1255
+ continuationHitTypes: lightingWavefrontContinuationHitTypes,
1256
+ emissive: Object.freeze({
1257
+ action: "accumulate-and-stop",
1258
+ contributesRadiance: true
1259
+ }),
1260
+ environment: Object.freeze({
1261
+ action: "accumulate-and-stop",
1262
+ contributesRadiance: true
1263
+ }),
1264
+ miss: Object.freeze({
1265
+ action: "accumulate-environment-or-dark-stop",
1266
+ contributesRadiance: true
1267
+ })
1268
+ });
1269
+ var defaultWavefrontDarkRadiance = Object.freeze([1e-4, 1e-4, 1e-4]);
1270
+ var wavefrontEventKinds = Object.freeze([
1271
+ "diffuse",
1272
+ "reflection",
1273
+ "refraction",
1274
+ "transparency",
1275
+ "terminate"
1276
+ ]);
1277
+ function normalizeWavefrontHitType(value) {
1278
+ return lightingWavefrontHitTypes.includes(value) ? value : "surface";
1279
+ }
1280
+ function normalizeWavefrontEventKind(value) {
1281
+ return wavefrontEventKinds.includes(value) ? value : null;
1282
+ }
1283
+ function normalizeVec3(value, fallback = [0, 0, 0]) {
1284
+ if (!Array.isArray(value) || value.length < 3) {
1285
+ return [...fallback];
1286
+ }
1287
+ return [
1288
+ Number.isFinite(value[0]) ? value[0] : fallback[0],
1289
+ Number.isFinite(value[1]) ? value[1] : fallback[1],
1290
+ Number.isFinite(value[2]) ? value[2] : fallback[2]
1291
+ ];
1292
+ }
1293
+ function clampUnit(value, fallback = 0) {
1294
+ return Math.max(0, Math.min(1, readFinite(value, fallback)));
1295
+ }
1296
+ function saturateVec3(value) {
1297
+ return value.map((component) => Math.max(0, component));
1298
+ }
1299
+ function scaleVec3(value, scalar) {
1300
+ return value.map((component) => component * scalar);
1301
+ }
1302
+ function addVec3(left, right) {
1303
+ return left.map((component, index) => component + right[index]);
1304
+ }
1305
+ function multiplyVec3(left, right) {
1306
+ return left.map((component, index) => component * right[index]);
1307
+ }
1308
+ function dotVec3(left, right) {
1309
+ return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
1310
+ }
1311
+ function lengthVec3(value) {
1312
+ return Math.hypot(value[0], value[1], value[2]);
1313
+ }
1314
+ function normalizeDirection(value, fallback = [0, 1, 0]) {
1315
+ const vector = normalizeVec3(value, fallback);
1316
+ const length = lengthVec3(vector);
1317
+ if (!Number.isFinite(length) || length <= 1e-6) {
1318
+ return [...fallback];
1319
+ }
1320
+ return vector.map((component) => component / length);
1321
+ }
1322
+ function mixVec3(left, right, factor) {
1323
+ return left.map(
1324
+ (component, index) => component * (1 - factor) + right[index] * factor
1325
+ );
1326
+ }
1327
+ function reflectDirection(direction, normal) {
1328
+ const scale = 2 * dotVec3(direction, normal);
1329
+ return normalizeDirection(
1330
+ [
1331
+ direction[0] - scale * normal[0],
1332
+ direction[1] - scale * normal[1],
1333
+ direction[2] - scale * normal[2]
1334
+ ],
1335
+ normal
1336
+ );
1337
+ }
1338
+ function refractDirection(direction, normal, etaRatio) {
1339
+ const cosTheta = Math.min(-dotVec3(direction, normal), 1);
1340
+ const rOutPerp = scaleVec3(addVec3(direction, scaleVec3(normal, cosTheta)), etaRatio);
1341
+ const rOutPerpLengthSquared = dotVec3(rOutPerp, rOutPerp);
1342
+ const parallelFactor = 1 - rOutPerpLengthSquared;
1343
+ if (parallelFactor <= 0) {
1344
+ return null;
1345
+ }
1346
+ const rOutParallel = scaleVec3(normal, -Math.sqrt(parallelFactor));
1347
+ return normalizeDirection(addVec3(rOutPerp, rOutParallel), direction);
1348
+ }
1349
+ function createWavefrontLightingPlan(options = {}) {
1350
+ const maxDepth = Math.max(1, Math.trunc(readFinite(options.maxDepth, 4)));
1351
+ const queueCapacity = Math.max(
1352
+ 1,
1353
+ Math.trunc(readFinite(options.queueCapacity, 4096))
1354
+ );
1355
+ const explicitLightSampling = Boolean(options.explicitLightSampling);
1356
+ const accumulationResetEpoch = Math.max(
1357
+ 0,
1358
+ Math.trunc(readFinite(options.accumulationResetEpoch, 0))
1359
+ );
1360
+ return Object.freeze({
1361
+ schemaVersion: lightingWavefrontSchemaVersion,
1362
+ maxDepth,
1363
+ queueCapacity,
1364
+ explicitLightSampling,
1365
+ accumulationResetEpoch,
1366
+ queueLayout: Object.freeze({
1367
+ strategy: lightingWavefrontQueuePairStrategy,
1368
+ compactAfterScatter: true,
1369
+ queues: Object.freeze([
1370
+ Object.freeze({ name: "active", role: "current-bounce" }),
1371
+ Object.freeze({ name: "next", role: "next-bounce" })
1372
+ ])
1373
+ }),
1374
+ bufferContracts: lightingWavefrontBufferContracts,
1375
+ terminationPolicy: lightingWavefrontTerminationPolicy,
1376
+ requiredRendererPassOrder: lightingRequiredRendererWavefrontPassOrder,
1377
+ lightingPasses: Object.freeze([
1378
+ Object.freeze({
1379
+ key: "accumulateTerminalRadiance",
1380
+ stage: "accumulateTerminalRadiance",
1381
+ reads: Object.freeze([
1382
+ "ray",
1383
+ "hit",
1384
+ "surface",
1385
+ "materialReference",
1386
+ "mediumReference"
1387
+ ]),
1388
+ writes: Object.freeze(["accumulation"]),
1389
+ terminalHitTypes: lightingWavefrontTerminalHitTypes
1390
+ }),
1391
+ Object.freeze({
1392
+ key: "scatterContinuations",
1393
+ stage: "scatterContinuations",
1394
+ reads: Object.freeze([
1395
+ "ray",
1396
+ "hit",
1397
+ "surface",
1398
+ "materialReference",
1399
+ "mediumReference"
1400
+ ]),
1401
+ writes: Object.freeze(["ray"]),
1402
+ continuationHitTypes: lightingWavefrontContinuationHitTypes,
1403
+ explicitLightSampling
1404
+ })
1405
+ ])
1406
+ });
1407
+ }
1408
+ function evaluateWavefrontTerminalRadiance(options = {}) {
1409
+ const hitType = normalizeWavefrontHitType(options.hitType);
1410
+ const throughput = saturateVec3(normalizeVec3(options.throughput, [1, 1, 1]));
1411
+ const emission = saturateVec3(normalizeVec3(options.emission, [0, 0, 0]));
1412
+ const environmentRadiance = saturateVec3(
1413
+ normalizeVec3(options.environmentRadiance, [0, 0, 0])
1414
+ );
1415
+ const missRadiance = saturateVec3(
1416
+ normalizeVec3(options.missRadiance, defaultWavefrontDarkRadiance)
1417
+ );
1418
+ const environmentLuminance = colorLuminance(environmentRadiance);
1419
+ let source = "none";
1420
+ let rawRadiance = [0, 0, 0];
1421
+ if (hitType === "emissive") {
1422
+ source = "emissive";
1423
+ rawRadiance = emission;
1424
+ } else if (hitType === "environment") {
1425
+ source = "environment";
1426
+ rawRadiance = environmentRadiance;
1427
+ } else if (hitType === "miss") {
1428
+ source = environmentLuminance > 1e-6 ? "environment" : "dark";
1429
+ rawRadiance = environmentLuminance > 1e-6 ? environmentRadiance : missRadiance;
1430
+ }
1431
+ const radiance = multiplyVec3(throughput, rawRadiance);
1432
+ const terminated = lightingWavefrontTerminalHitTypes.includes(hitType);
1433
+ return Object.freeze({
1434
+ hitType,
1435
+ source,
1436
+ terminated,
1437
+ radiance: Object.freeze(radiance),
1438
+ nearDarkSample: source === "dark" && colorLuminance(radiance) <= colorLuminance(defaultWavefrontDarkRadiance)
1439
+ });
1440
+ }
1441
+ function evaluateWavefrontContinuationEvent(options = {}) {
1442
+ const hitType = normalizeWavefrontHitType(options.hitType);
1443
+ const bounceIndex = Math.max(0, Math.trunc(readFinite(options.bounceIndex, 0)));
1444
+ const maxDepth = Math.max(1, Math.trunc(readFinite(options.maxDepth, 4)));
1445
+ const throughput = saturateVec3(normalizeVec3(options.throughput, [1, 1, 1]));
1446
+ const albedo = saturateVec3(normalizeVec3(options.albedo, [0.8, 0.8, 0.8]));
1447
+ const transmission = saturateVec3(
1448
+ normalizeVec3(options.transmission, [0, 0, 0])
1449
+ );
1450
+ const shadingNormal = normalizeDirection(options.shadingNormal, [0, 1, 0]);
1451
+ const viewDirection = normalizeDirection(options.viewDirection, [0, 0, 1]);
1452
+ const incomingDirection = normalizeDirection(
1453
+ scaleVec3(viewDirection, -1),
1454
+ [0, 0, -1]
1455
+ );
1456
+ const frontFace = options.frontFace !== false;
1457
+ const orientedNormal = frontFace ? shadingNormal : scaleVec3(shadingNormal, -1);
1458
+ const metalness = clampUnit(options.metalness, 0);
1459
+ const roughness = clampUnit(options.roughness, 0.5);
1460
+ const opacity = clampUnit(options.opacity, 1);
1461
+ const refractiveIndex = Math.max(1, readFinite(options.refractiveIndex ?? options.ior, 1.45));
1462
+ const transmissionStrength = Math.max(...transmission);
1463
+ let eventKind = normalizeWavefrontEventKind(options.eventKind) ?? (hitType === "transparent" || opacity < 0.999 ? "transparency" : transmissionStrength > 1e-3 ? "refraction" : metalness >= 0.5 || roughness <= 0.2 ? "reflection" : "diffuse");
1464
+ let nextDirection = incomingDirection;
1465
+ let attenuation;
1466
+ if (!lightingWavefrontContinuationHitTypes.includes(hitType) || bounceIndex >= maxDepth - 1) {
1467
+ eventKind = "terminate";
1468
+ attenuation = [0, 0, 0];
1469
+ } else if (eventKind === "reflection") {
1470
+ nextDirection = reflectDirection(incomingDirection, orientedNormal);
1471
+ attenuation = mixVec3([0.04, 0.04, 0.04], albedo, metalness);
1472
+ } else if (eventKind === "refraction") {
1473
+ const etaRatio = frontFace ? 1 / refractiveIndex : refractiveIndex;
1474
+ nextDirection = refractDirection(incomingDirection, orientedNormal, etaRatio) ?? reflectDirection(incomingDirection, orientedNormal);
1475
+ attenuation = transmissionStrength > 1e-3 ? transmission : [1, 1, 1];
1476
+ } else if (eventKind === "transparency") {
1477
+ nextDirection = incomingDirection;
1478
+ const transparencyWeight = Math.max(1 - opacity, transmissionStrength, 0.05);
1479
+ attenuation = transmissionStrength > 1e-3 ? transmission : [transparencyWeight, transparencyWeight, transparencyWeight];
1480
+ } else {
1481
+ nextDirection = normalizeDirection(
1482
+ addVec3(orientedNormal, albedo.map((component) => component - 0.5)),
1483
+ orientedNormal
1484
+ );
1485
+ attenuation = scaleVec3(albedo, Math.max(0.05, 1 - metalness));
1486
+ }
1487
+ const nextThroughput = multiplyVec3(throughput, saturateVec3(attenuation));
1488
+ const continueTracing = eventKind !== "terminate" && colorLuminance(nextThroughput) > 1e-4;
1489
+ return Object.freeze({
1490
+ hitType,
1491
+ eventKind,
1492
+ continueTracing,
1493
+ nextDirection: Object.freeze(nextDirection),
1494
+ attenuation: Object.freeze(saturateVec3(attenuation)),
1495
+ nextThroughput: Object.freeze(nextThroughput),
1496
+ explicitLightSamplingEnabled: Boolean(options.explicitLightSampling)
1497
+ });
1498
+ }
1111
1499
  var lightingImportanceLevels = Object.freeze([
1112
1500
  "low",
1113
1501
  "medium",
@@ -1673,6 +2061,91 @@ var lightingWorkerSpecPresets = {
1673
2061
  }
1674
2062
  }
1675
2063
  },
2064
+ wavefront: {
2065
+ suggestedAllocationIds: [
2066
+ "lighting.wavefront.active-queue",
2067
+ "lighting.wavefront.next-queue",
2068
+ "lighting.wavefront.accumulation"
2069
+ ],
2070
+ jobs: {
2071
+ accumulateTerminalRadiance: {
2072
+ domain: "lighting",
2073
+ importance: "critical",
2074
+ levels: buildWorkerBudgetLevels(
2075
+ "lighting.wavefront.accumulateTerminalRadiance",
2076
+ lightingWorkerQueueClass,
2077
+ {
2078
+ low: {
2079
+ estimatedCostMs: 0.7,
2080
+ maxDispatchesPerFrame: 1,
2081
+ maxJobsPerDispatch: 32,
2082
+ cadenceDivisor: 2,
2083
+ workgroupScale: 0.5,
2084
+ maxQueueDepth: 96
2085
+ },
2086
+ medium: {
2087
+ estimatedCostMs: 1.2,
2088
+ maxDispatchesPerFrame: 1,
2089
+ maxJobsPerDispatch: 64,
2090
+ cadenceDivisor: 1,
2091
+ workgroupScale: 0.75,
2092
+ maxQueueDepth: 192
2093
+ },
2094
+ high: {
2095
+ estimatedCostMs: 1.8,
2096
+ maxDispatchesPerFrame: 2,
2097
+ maxJobsPerDispatch: 128,
2098
+ cadenceDivisor: 1,
2099
+ workgroupScale: 1,
2100
+ maxQueueDepth: 256
2101
+ }
2102
+ }
2103
+ ),
2104
+ suggestedAllocationIds: [
2105
+ "lighting.wavefront.accumulation",
2106
+ "lighting.wavefront.active-queue"
2107
+ ]
2108
+ },
2109
+ scatterContinuations: {
2110
+ domain: "lighting",
2111
+ importance: "critical",
2112
+ levels: buildWorkerBudgetLevels(
2113
+ "lighting.wavefront.scatterContinuations",
2114
+ lightingWorkerQueueClass,
2115
+ {
2116
+ low: {
2117
+ estimatedCostMs: 0.8,
2118
+ maxDispatchesPerFrame: 1,
2119
+ maxJobsPerDispatch: 32,
2120
+ cadenceDivisor: 2,
2121
+ workgroupScale: 0.5,
2122
+ maxQueueDepth: 96
2123
+ },
2124
+ medium: {
2125
+ estimatedCostMs: 1.4,
2126
+ maxDispatchesPerFrame: 1,
2127
+ maxJobsPerDispatch: 64,
2128
+ cadenceDivisor: 1,
2129
+ workgroupScale: 0.75,
2130
+ maxQueueDepth: 192
2131
+ },
2132
+ high: {
2133
+ estimatedCostMs: 2.1,
2134
+ maxDispatchesPerFrame: 2,
2135
+ maxJobsPerDispatch: 128,
2136
+ cadenceDivisor: 1,
2137
+ workgroupScale: 1,
2138
+ maxQueueDepth: 256
2139
+ }
2140
+ }
2141
+ ),
2142
+ suggestedAllocationIds: [
2143
+ "lighting.wavefront.active-queue",
2144
+ "lighting.wavefront.next-queue"
2145
+ ]
2146
+ }
2147
+ }
2148
+ },
1676
2149
  volumetrics: {
1677
2150
  suggestedAllocationIds: [
1678
2151
  "lighting.volumetrics.froxel-grid",
@@ -1882,6 +2355,13 @@ var lightingWorkerDagSpecs = {
1882
2355
  accumulate: { priority: 3, dependencies: ["pathTrace"] },
1883
2356
  denoise: { priority: 2, dependencies: ["accumulate"] }
1884
2357
  },
2358
+ wavefront: {
2359
+ accumulateTerminalRadiance: { priority: 3, dependencies: [] },
2360
+ scatterContinuations: {
2361
+ priority: 2,
2362
+ dependencies: ["accumulateTerminalRadiance"]
2363
+ }
2364
+ },
1885
2365
  volumetrics: {
1886
2366
  volumetricShadow: { priority: 3, dependencies: [] },
1887
2367
  froxelIntegrate: { priority: 2, dependencies: ["volumetricShadow"] }
@@ -1915,6 +2395,16 @@ function resolveLightingQualityDimensions(techniqueName, jobKey) {
1915
2395
  "pathtracer.pathTrace": { rayTracing: 1, lightingSamples: 1 },
1916
2396
  "pathtracer.accumulate": { temporalReuse: 1, updateCadence: 0.4 },
1917
2397
  "pathtracer.denoise": { temporalReuse: 1, shading: 0.4 },
2398
+ "wavefront.accumulateTerminalRadiance": {
2399
+ rayTracing: 1,
2400
+ lightingSamples: 1,
2401
+ temporalReuse: 0.4
2402
+ },
2403
+ "wavefront.scatterContinuations": {
2404
+ rayTracing: 1,
2405
+ shading: 0.7,
2406
+ updateCadence: 0.5
2407
+ },
1918
2408
  "volumetrics.froxelIntegrate": {
1919
2409
  lightingSamples: 0.6,
1920
2410
  shading: 0.4,
@@ -1959,6 +2449,15 @@ function resolveLightingImportanceSignals(techniqueName, jobKey) {
1959
2449
  },
1960
2450
  "pathtracer.accumulate": { visible: true },
1961
2451
  "pathtracer.denoise": { visible: true },
2452
+ "wavefront.accumulateTerminalRadiance": {
2453
+ visible: true,
2454
+ shadowSignificance: "high",
2455
+ reflectionSignificance: "high"
2456
+ },
2457
+ "wavefront.scatterContinuations": {
2458
+ visible: true,
2459
+ reflectionSignificance: "high"
2460
+ },
1962
2461
  "volumetrics.froxelIntegrate": { visible: true },
1963
2462
  "volumetrics.volumetricShadow": { visible: true, shadowSignificance: "high" },
1964
2463
  "hdri.irradianceConvolution": { visible: false },
@@ -2229,9 +2728,12 @@ async function loadLightingProfileWorkerPlan(profileName = defaultLightingProfil
2229
2728
  createLightingBandPlan,
2230
2729
  createLightingProfileModeLadder,
2231
2730
  createWavefrontEnvironmentLightingOptions,
2731
+ createWavefrontLightingPlan,
2232
2732
  defaultAdaptiveLightingProfilePolicy,
2233
2733
  defaultLightingProfile,
2234
2734
  defaultLightingTechnique,
2735
+ evaluateWavefrontContinuationEvent,
2736
+ evaluateWavefrontTerminalRadiance,
2235
2737
  getLightingProfile,
2236
2738
  getLightingProfileWorkerManifest,
2237
2739
  getLightingTechnique,
@@ -2250,8 +2752,17 @@ async function loadLightingProfileWorkerPlan(profileName = defaultLightingProfil
2250
2752
  lightingProfileModeOrder,
2251
2753
  lightingProfileNames,
2252
2754
  lightingProfiles,
2755
+ lightingRequiredRendererWavefrontPassOrder,
2253
2756
  lightingTechniqueNames,
2254
2757
  lightingTechniques,
2758
+ lightingWavefrontBufferContracts,
2759
+ lightingWavefrontContinuationHitTypes,
2760
+ lightingWavefrontHitTypes,
2761
+ lightingWavefrontPassOrder,
2762
+ lightingWavefrontQueuePairStrategy,
2763
+ lightingWavefrontSchemaVersion,
2764
+ lightingWavefrontTerminalHitTypes,
2765
+ lightingWavefrontTerminationPolicy,
2255
2766
  lightingWorkerManifests,
2256
2767
  lightingWorkerQueueClass,
2257
2768
  loadLightingJobs,