@plasius/gpu-lighting 0.2.4 → 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 +23 -9
- package/README.md +47 -5
- package/dist/index.cjs +511 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +499 -0
- package/dist/index.js.map +1 -1
- package/dist/techniques/techniques/hdri/brdf-lut.job.wgsl +70 -2
- package/dist/techniques/techniques/hdri/irradiance-convolution.job.wgsl +91 -2
- package/dist/techniques/techniques/hdri/specular-prefilter.job.wgsl +110 -2
- package/dist/techniques/techniques/volumetrics/froxel-integrate.job.wgsl +105 -2
- package/dist/techniques/techniques/volumetrics/volumetric-shadow.job.wgsl +96 -2
- package/dist/techniques/techniques/wavefront/accumulate-terminal-radiance.job.wgsl +61 -0
- package/dist/techniques/techniques/wavefront/prelude.wgsl +229 -0
- package/dist/techniques/techniques/wavefront/scatter-continuations.job.wgsl +136 -0
- package/package.json +2 -1
- package/src/index.js +535 -0
- package/src/techniques/hdri/brdf-lut.job.wgsl +70 -2
- package/src/techniques/hdri/irradiance-convolution.job.wgsl +91 -2
- package/src/techniques/hdri/specular-prefilter.job.wgsl +110 -2
- package/src/techniques/volumetrics/froxel-integrate.job.wgsl +105 -2
- package/src/techniques/volumetrics/volumetric-shadow.job.wgsl +96 -2
- package/src/techniques/wavefront/accumulate-terminal-radiance.job.wgsl +61 -0
- package/src/techniques/wavefront/prelude.wgsl +229 -0
- package/src/techniques/wavefront/scatter-continuations.job.wgsl +136 -0
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 },
|
|
@@ -1,3 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
@group(0) @binding(0) var<uniform> iblPrecomputeParams: IblPrecomputeParams;
|
|
2
|
+
@group(0) @binding(1) var<storage, read_write> hdriBrdfLutOutput: array<vec4<f32>>;
|
|
3
|
+
|
|
4
|
+
fn lut_extent() -> u32 {
|
|
5
|
+
return max(iblPrecomputeParams.sample_count, 1u);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
fn lut_index(pixel: vec2<u32>) -> u32 {
|
|
9
|
+
let extent = lut_extent();
|
|
10
|
+
return pixel.y * extent + pixel.x;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fn saturate(value: f32) -> f32 {
|
|
14
|
+
return clamp(value, 0.0, 1.0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn geometry_schlick_ggx(ndotv: f32, roughness: f32) -> f32 {
|
|
18
|
+
let alpha = max(roughness * roughness, 0.001);
|
|
19
|
+
let k = (alpha + 1.0) * (alpha + 1.0) / 8.0;
|
|
20
|
+
return ndotv / max(ndotv * (1.0 - k) + k, 0.001);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fn geometry_smith(ndotv: f32, ndotl: f32, roughness: f32) -> f32 {
|
|
24
|
+
return geometry_schlick_ggx(ndotv, roughness) * geometry_schlick_ggx(ndotl, roughness);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn fresnel_schlick(cos_theta: f32) -> f32 {
|
|
28
|
+
return pow(1.0 - saturate(cos_theta), 5.0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@compute @workgroup_size(8, 8, 1)
|
|
32
|
+
fn process_job(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
33
|
+
let extent = lut_extent();
|
|
34
|
+
if (global_id.x >= extent || global_id.y >= extent) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let uv = (vec2<f32>(global_id.xy) + vec2<f32>(0.5)) / max(vec2<f32>(f32(extent)), vec2<f32>(1.0));
|
|
39
|
+
let ndotv = saturate(uv.x);
|
|
40
|
+
let roughness = clamp_roughness(uv.y + iblPrecomputeParams.roughness * 0.15);
|
|
41
|
+
let sample_total = max(iblPrecomputeParams.sample_count, 1u);
|
|
42
|
+
var integrated_brdf = vec2<f32>(0.0);
|
|
43
|
+
var sample_index = 0u;
|
|
44
|
+
loop {
|
|
45
|
+
if (sample_index >= sample_total) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let sample_u = (f32(sample_index) + 0.5) / f32(sample_total);
|
|
50
|
+
let sample_v = fract(f32(sample_index) * 0.61803398875);
|
|
51
|
+
let ndotl = saturate(sqrt(sample_u));
|
|
52
|
+
let vdoth = saturate(mix(ndotv, 1.0, sample_v));
|
|
53
|
+
let geometry = geometry_smith(ndotv, ndotl, roughness);
|
|
54
|
+
let fresnel = fresnel_schlick(vdoth);
|
|
55
|
+
integrated_brdf = integrated_brdf + vec2<f32>(
|
|
56
|
+
(1.0 - fresnel) * geometry,
|
|
57
|
+
fresnel * geometry
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
sample_index = sample_index + 1u;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
integrated_brdf =
|
|
64
|
+
integrated_brdf / f32(sample_total) * max(iblPrecomputeParams.exposure_bias, 0.0001);
|
|
65
|
+
hdriBrdfLutOutput[lut_index(global_id.xy)] = vec4<f32>(
|
|
66
|
+
integrated_brdf.x,
|
|
67
|
+
integrated_brdf.y,
|
|
68
|
+
roughness,
|
|
69
|
+
ndotv
|
|
70
|
+
);
|
|
3
71
|
}
|