@plasius/gpu-renderer 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 +69 -20
- package/README.md +40 -7
- package/dist/index.cjs +2651 -403
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2649 -403
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.d.ts +173 -5
- package/src/index.js +52 -0
- package/src/wavefront-compute.js +2650 -417
- package/src/wavefront-frame-runtime.js +167 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,144 @@
|
|
|
1
|
+
// src/wavefront-frame-runtime.js
|
|
2
|
+
function createGpuParallelismCounters() {
|
|
3
|
+
return {
|
|
4
|
+
directDispatches: 0,
|
|
5
|
+
directWorkgroups: 0,
|
|
6
|
+
directShaderInvocations: 0,
|
|
7
|
+
multiWorkgroupDispatches: 0,
|
|
8
|
+
largestDirectWorkgroupsPerDispatch: 0,
|
|
9
|
+
indirectDispatches: 0,
|
|
10
|
+
estimatedIndirectWorkgroupsUpperBound: 0,
|
|
11
|
+
estimatedIndirectShaderInvocationsUpperBound: 0,
|
|
12
|
+
indirectDispatchesWithMultiWorkgroupCapacity: 0,
|
|
13
|
+
largestEstimatedIndirectWorkgroupsPerDispatch: 0
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function countDispatchWorkgroups(groups) {
|
|
17
|
+
return groups.reduce((product, value) => {
|
|
18
|
+
const numeric = Number(value ?? 1);
|
|
19
|
+
const count = Number.isFinite(numeric) ? Math.max(1, Math.trunc(numeric)) : 1;
|
|
20
|
+
return product * count;
|
|
21
|
+
}, 1);
|
|
22
|
+
}
|
|
23
|
+
function recordDirectDispatch(parallelism, groups, invocationsPerWorkgroup = 1) {
|
|
24
|
+
const workgroups = countDispatchWorkgroups(groups);
|
|
25
|
+
parallelism.directDispatches += 1;
|
|
26
|
+
parallelism.directWorkgroups += workgroups;
|
|
27
|
+
parallelism.directShaderInvocations += workgroups * invocationsPerWorkgroup;
|
|
28
|
+
parallelism.largestDirectWorkgroupsPerDispatch = Math.max(
|
|
29
|
+
parallelism.largestDirectWorkgroupsPerDispatch,
|
|
30
|
+
workgroups
|
|
31
|
+
);
|
|
32
|
+
if (workgroups > 1) {
|
|
33
|
+
parallelism.multiWorkgroupDispatches += 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function recordIndirectDispatch(parallelism, estimatedWorkgroupsUpperBound, invocationsPerWorkgroup = 1) {
|
|
37
|
+
const workgroups = Math.max(1, Math.trunc(Number(estimatedWorkgroupsUpperBound) || 1));
|
|
38
|
+
parallelism.indirectDispatches += 1;
|
|
39
|
+
parallelism.estimatedIndirectWorkgroupsUpperBound += workgroups;
|
|
40
|
+
parallelism.estimatedIndirectShaderInvocationsUpperBound += workgroups * invocationsPerWorkgroup;
|
|
41
|
+
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch = Math.max(
|
|
42
|
+
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
43
|
+
workgroups
|
|
44
|
+
);
|
|
45
|
+
if (workgroups > 1) {
|
|
46
|
+
parallelism.indirectDispatchesWithMultiWorkgroupCapacity += 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function createGpuParallelismDiagnostics(adapterDiagnostics, counters) {
|
|
50
|
+
const totalEstimatedWorkgroupsUpperBound = counters.directWorkgroups + counters.estimatedIndirectWorkgroupsUpperBound;
|
|
51
|
+
const totalEstimatedShaderInvocationsUpperBound = counters.directShaderInvocations + counters.estimatedIndirectShaderInvocationsUpperBound;
|
|
52
|
+
const exposesMultiWorkgroupParallelism = counters.multiWorkgroupDispatches > 0 || counters.indirectDispatchesWithMultiWorkgroupCapacity > 0;
|
|
53
|
+
return Object.freeze({
|
|
54
|
+
...adapterDiagnostics,
|
|
55
|
+
directDispatches: counters.directDispatches,
|
|
56
|
+
directWorkgroups: counters.directWorkgroups,
|
|
57
|
+
directShaderInvocations: counters.directShaderInvocations,
|
|
58
|
+
multiWorkgroupDispatches: counters.multiWorkgroupDispatches,
|
|
59
|
+
largestDirectWorkgroupsPerDispatch: counters.largestDirectWorkgroupsPerDispatch,
|
|
60
|
+
indirectDispatches: counters.indirectDispatches,
|
|
61
|
+
estimatedIndirectWorkgroupsUpperBound: counters.estimatedIndirectWorkgroupsUpperBound,
|
|
62
|
+
estimatedIndirectShaderInvocationsUpperBound: counters.estimatedIndirectShaderInvocationsUpperBound,
|
|
63
|
+
indirectDispatchesWithMultiWorkgroupCapacity: counters.indirectDispatchesWithMultiWorkgroupCapacity,
|
|
64
|
+
largestEstimatedIndirectWorkgroupsPerDispatch: counters.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
65
|
+
totalEstimatedWorkgroupsUpperBound,
|
|
66
|
+
totalEstimatedShaderInvocationsUpperBound,
|
|
67
|
+
exposesMultiWorkgroupParallelism,
|
|
68
|
+
likelyUsesMoreThanOnePhysicalGpuCore: null,
|
|
69
|
+
coreUtilizationStatus: "not-exposed-by-webgpu"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function createGpuWorkerJobDiagnostics(parallelism, commandSubmissions, frameTimeMs, awaitedGpuCompletion) {
|
|
73
|
+
const directDispatchesCompleted = Math.max(0, Number(parallelism?.directDispatches ?? 0));
|
|
74
|
+
const indirectDispatchesCompleted = Math.max(
|
|
75
|
+
0,
|
|
76
|
+
Number(parallelism?.indirectDispatches ?? 0)
|
|
77
|
+
);
|
|
78
|
+
const completedPerFrame = directDispatchesCompleted + indirectDispatchesCompleted;
|
|
79
|
+
const completedPerSubmission = commandSubmissions > 0 ? completedPerFrame / commandSubmissions : completedPerFrame;
|
|
80
|
+
const completedPerSecond = awaitedGpuCompletion && frameTimeMs > 0 ? completedPerFrame * 1e3 / frameTimeMs : null;
|
|
81
|
+
return Object.freeze({
|
|
82
|
+
completedPerFrame,
|
|
83
|
+
completedPerSecond,
|
|
84
|
+
completedPerSubmission,
|
|
85
|
+
directDispatchesCompleted,
|
|
86
|
+
indirectDispatchesCompleted,
|
|
87
|
+
frameTimeMs,
|
|
88
|
+
awaitedGpuCompletion
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function createGpuSubmissionBatcher({
|
|
92
|
+
device,
|
|
93
|
+
frameIndex,
|
|
94
|
+
maxFramePassesPerSubmission,
|
|
95
|
+
startingSubmissionCount = 0,
|
|
96
|
+
labelPrefix = "plasius.wavefront.frame"
|
|
97
|
+
}) {
|
|
98
|
+
let encodedFramePasses = 0;
|
|
99
|
+
let submissionCount = 0;
|
|
100
|
+
let encoder = createCommandEncoder();
|
|
101
|
+
function createCommandEncoder() {
|
|
102
|
+
return device.createCommandEncoder({
|
|
103
|
+
label: `${labelPrefix}.${frameIndex}.batched.${startingSubmissionCount + submissionCount + 1}`
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function submitCurrentEncoder() {
|
|
107
|
+
if (encodedFramePasses <= 0) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
device.queue.submit([encoder.finish()]);
|
|
111
|
+
submissionCount += 1;
|
|
112
|
+
encodedFramePasses = 0;
|
|
113
|
+
encoder = createCommandEncoder();
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return Object.freeze({
|
|
117
|
+
reserve(passCount = 1) {
|
|
118
|
+
if (encodedFramePasses > 0 && encodedFramePasses + passCount > maxFramePassesPerSubmission) {
|
|
119
|
+
submitCurrentEncoder();
|
|
120
|
+
}
|
|
121
|
+
encodedFramePasses += passCount;
|
|
122
|
+
return encoder;
|
|
123
|
+
},
|
|
124
|
+
flush() {
|
|
125
|
+
submitCurrentEncoder();
|
|
126
|
+
return submissionCount;
|
|
127
|
+
},
|
|
128
|
+
getSubmissionCount() {
|
|
129
|
+
return submissionCount;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
1
134
|
// src/wavefront-compute.js
|
|
2
135
|
var DEFAULT_WIDTH = 1280;
|
|
3
136
|
var DEFAULT_HEIGHT = 720;
|
|
4
137
|
var DEFAULT_MAX_DEPTH = 6;
|
|
5
138
|
var DEFAULT_TILE_SIZE = 128;
|
|
6
139
|
var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
140
|
+
var MAX_SAMPLES_PER_PIXEL = 256;
|
|
141
|
+
var DEFAULT_BRDF_LUT_SIZE = 256;
|
|
7
142
|
var DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
8
143
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
9
144
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
@@ -12,22 +147,27 @@ var rendererWavefrontComputeMode = "webgpu-compute";
|
|
|
12
147
|
var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
13
148
|
var rendererWavefrontComputeStatsStride = 8;
|
|
14
149
|
var RAY_RECORD_BYTES = 80;
|
|
15
|
-
var HIT_RECORD_BYTES =
|
|
16
|
-
var SCENE_OBJECT_RECORD_BYTES =
|
|
150
|
+
var HIT_RECORD_BYTES = 256;
|
|
151
|
+
var SCENE_OBJECT_RECORD_BYTES = 144;
|
|
17
152
|
var MESH_VERTEX_RECORD_BYTES = 48;
|
|
18
|
-
var MESH_RANGE_RECORD_BYTES =
|
|
19
|
-
var TRIANGLE_RECORD_BYTES =
|
|
153
|
+
var MESH_RANGE_RECORD_BYTES = 240;
|
|
154
|
+
var TRIANGLE_RECORD_BYTES = 352;
|
|
155
|
+
var GPU_MATERIAL_RECORD_BYTES = 192;
|
|
20
156
|
var BVH_NODE_RECORD_BYTES = 48;
|
|
21
157
|
var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
22
158
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
23
159
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
24
160
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
25
161
|
var PATH_VERTEX_RECORD_BYTES = 16;
|
|
26
|
-
var
|
|
162
|
+
var GPU_SUBMITTED_WORK_TIMEOUT_MS = 5e3;
|
|
163
|
+
var GPU_READBACK_COMPLETION_TIMEOUT_MS = 6e4;
|
|
164
|
+
var GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 6e4;
|
|
165
|
+
var CONFIG_BUFFER_BYTES = 320;
|
|
27
166
|
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
28
167
|
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
29
168
|
var COUNTER_BUFFER_BYTES = 32;
|
|
30
169
|
var TRACE_STORAGE_BUFFER_BINDINGS = 10;
|
|
170
|
+
var BRDF_LUT_UPLOAD_CACHE = /* @__PURE__ */ new Map();
|
|
31
171
|
var MATERIAL_DIFFUSE = 0;
|
|
32
172
|
var MATERIAL_METAL = 1;
|
|
33
173
|
var MATERIAL_DIELECTRIC = 2;
|
|
@@ -62,6 +202,7 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
62
202
|
meshVertexRecordBytes: MESH_VERTEX_RECORD_BYTES,
|
|
63
203
|
meshRangeRecordBytes: MESH_RANGE_RECORD_BYTES,
|
|
64
204
|
triangleRecordBytes: TRIANGLE_RECORD_BYTES,
|
|
205
|
+
materialRecordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
65
206
|
bvhNodeRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
66
207
|
bvhLeafReferenceRecordBytes: BVH_LEAF_REF_RECORD_BYTES,
|
|
67
208
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
@@ -145,6 +286,32 @@ function asColor(value, fallback = [1, 1, 1, 1]) {
|
|
|
145
286
|
clamp(readFiniteNumber("color[3]", value[3], fallback[3] ?? 1), 0, 1)
|
|
146
287
|
];
|
|
147
288
|
}
|
|
289
|
+
function deriveLegacySheenColor(baseColor, sheen, sheenTint) {
|
|
290
|
+
const sheenStrength = clamp(Number(sheen) || 0, 0, 1);
|
|
291
|
+
if (sheenStrength <= 0) {
|
|
292
|
+
return [0, 0, 0, 1];
|
|
293
|
+
}
|
|
294
|
+
const tint = clamp(Number(sheenTint) || 0, 0, 1);
|
|
295
|
+
const base = asColor(baseColor, [1, 1, 1, 1]);
|
|
296
|
+
return [
|
|
297
|
+
clamp((1 - tint) * sheenStrength + base[0] * tint * sheenStrength, 0, 1),
|
|
298
|
+
clamp((1 - tint) * sheenStrength + base[1] * tint * sheenStrength, 0, 1),
|
|
299
|
+
clamp((1 - tint) * sheenStrength + base[2] * tint * sheenStrength, 0, 1),
|
|
300
|
+
1
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
function resolveSheenColor(input, fallbackBaseColor) {
|
|
304
|
+
if (input?.sheenColor || input?.material?.sheenColor) {
|
|
305
|
+
return asColor(input.sheenColor ?? input.material?.sheenColor, [0, 0, 0, 1]).map(
|
|
306
|
+
(value, index) => index < 3 ? clamp(value, 0, 1) : 1
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
return deriveLegacySheenColor(
|
|
310
|
+
fallbackBaseColor,
|
|
311
|
+
input?.sheen ?? input?.material?.sheen,
|
|
312
|
+
input?.sheenTint ?? input?.material?.sheenTint
|
|
313
|
+
);
|
|
314
|
+
}
|
|
148
315
|
function resolveEnvironmentMap(input = null) {
|
|
149
316
|
const source = input && typeof input === "object" ? input : null;
|
|
150
317
|
const hasTexture = Boolean(source?.view || source?.texture || source?.data);
|
|
@@ -154,6 +321,11 @@ function resolveEnvironmentMap(input = null) {
|
|
|
154
321
|
enabled: hasTexture && source?.enabled !== false,
|
|
155
322
|
width,
|
|
156
323
|
height,
|
|
324
|
+
mipLevelCount: readPositiveInteger(
|
|
325
|
+
"environmentMap.mipLevelCount",
|
|
326
|
+
source?.mipLevelCount,
|
|
327
|
+
1
|
|
328
|
+
),
|
|
157
329
|
format: typeof source?.format === "string" ? source.format : "rgba16float",
|
|
158
330
|
projection: typeof source?.projection === "string" ? source.projection : "equirectangular",
|
|
159
331
|
texture: source?.texture ?? null,
|
|
@@ -165,7 +337,8 @@ function resolveEnvironmentMap(input = null) {
|
|
|
165
337
|
ambientStrength: Math.max(
|
|
166
338
|
0,
|
|
167
339
|
readFiniteNumber("environmentMap.ambientStrength", source?.ambientStrength, 0.32)
|
|
168
|
-
)
|
|
340
|
+
),
|
|
341
|
+
hasImportanceData: source?.hasImportanceData === true
|
|
169
342
|
});
|
|
170
343
|
}
|
|
171
344
|
function resolveDeferredPathResolve(options = {}) {
|
|
@@ -339,7 +512,8 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
339
512
|
input.halfExtent ?? input.halfExtents ?? input.extents ?? bounds?.halfExtent,
|
|
340
513
|
[0.5, 0.5, 0.5]
|
|
341
514
|
).map((value) => Math.max(value, 1e-3));
|
|
342
|
-
const
|
|
515
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
516
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
343
517
|
const color = asColor(
|
|
344
518
|
input.color ?? input.baseColor ?? input.albedo ?? input.material?.color ?? input.material?.baseColor,
|
|
345
519
|
[0.72, 0.72, 0.68, 1]
|
|
@@ -348,10 +522,22 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
348
522
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
349
523
|
[0, 0, 0, 1]
|
|
350
524
|
);
|
|
525
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
526
|
+
const transmission = clamp(
|
|
527
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
528
|
+
0,
|
|
529
|
+
1
|
|
530
|
+
);
|
|
531
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
532
|
+
const specularColor = asColor(
|
|
533
|
+
input.specularColor ?? input.material?.specularColor,
|
|
534
|
+
[1, 1, 1, 1]
|
|
535
|
+
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
536
|
+
const resolvedMaterialKind = emission[0] > 0 || emission[1] > 0 || emission[2] > 0 ? MATERIAL_EMISSIVE : materialKindInput === void 0 || materialKindInput === null ? transmission > 1e-3 || opacity < 0.999 ? MATERIAL_TRANSPARENT : materialKind : materialKind;
|
|
351
537
|
return Object.freeze({
|
|
352
538
|
id: readNonNegativeInteger("id", input.id, index + 1),
|
|
353
539
|
kind,
|
|
354
|
-
materialKind:
|
|
540
|
+
materialKind: resolvedMaterialKind,
|
|
355
541
|
flags: readNonNegativeInteger("flags", input.flags, 0),
|
|
356
542
|
center: Object.freeze(center),
|
|
357
543
|
halfExtent: Object.freeze(halfExtent),
|
|
@@ -359,8 +545,24 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
359
545
|
emission: Object.freeze(emission),
|
|
360
546
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
361
547
|
metallic: clamp(readFiniteNumber("metallic", input.metallic ?? input.material?.metallic, 0), 0, 1),
|
|
362
|
-
opacity
|
|
363
|
-
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3)
|
|
548
|
+
opacity,
|
|
549
|
+
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
550
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
551
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
552
|
+
sheenColor: Object.freeze(sheenColor),
|
|
553
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
554
|
+
clearcoatRoughness: clamp(
|
|
555
|
+
readFiniteNumber(
|
|
556
|
+
"clearcoatRoughness",
|
|
557
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
558
|
+
0.08
|
|
559
|
+
),
|
|
560
|
+
0,
|
|
561
|
+
1
|
|
562
|
+
),
|
|
563
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
564
|
+
specularColor: Object.freeze(specularColor),
|
|
565
|
+
transmission
|
|
364
566
|
});
|
|
365
567
|
}
|
|
366
568
|
function createDefaultWavefrontSceneObjects() {
|
|
@@ -432,7 +634,8 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
432
634
|
input.uvs ?? input.texcoords ?? input.uv,
|
|
433
635
|
(value) => readFiniteNumber("mesh uv", value, 0)
|
|
434
636
|
) : null;
|
|
435
|
-
const
|
|
637
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
638
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
436
639
|
const color = asColor(
|
|
437
640
|
input.color ?? input.baseColor ?? input.albedo ?? input.material?.color ?? input.material?.baseColor,
|
|
438
641
|
[0.72, 0.72, 0.68, 1]
|
|
@@ -441,13 +644,25 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
441
644
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
442
645
|
[0, 0, 0, 1]
|
|
443
646
|
);
|
|
647
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
648
|
+
const transmission = clamp(
|
|
649
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
650
|
+
0,
|
|
651
|
+
1
|
|
652
|
+
);
|
|
653
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
654
|
+
const specularColor = asColor(
|
|
655
|
+
input.specularColor ?? input.material?.specularColor,
|
|
656
|
+
[1, 1, 1, 1]
|
|
657
|
+
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
658
|
+
const resolvedMaterialKind = emission[0] > 0 || emission[1] > 0 || emission[2] > 0 ? MATERIAL_EMISSIVE : materialKindInput === void 0 || materialKindInput === null ? transmission > 1e-3 || opacity < 0.999 ? MATERIAL_TRANSPARENT : materialKind : materialKind;
|
|
444
659
|
return Object.freeze({
|
|
445
660
|
id: readNonNegativeInteger("mesh id", input.id, meshIndex + 1),
|
|
446
661
|
positions: Object.freeze(Array.from(positions, (value) => readFiniteNumber("mesh position", value, 0))),
|
|
447
662
|
indices: Object.freeze(indices),
|
|
448
663
|
normals: normals ? Object.freeze(normals) : null,
|
|
449
664
|
uvs: uvs ? Object.freeze(uvs) : null,
|
|
450
|
-
materialKind:
|
|
665
|
+
materialKind: resolvedMaterialKind,
|
|
451
666
|
flags: readNonNegativeInteger("mesh flags", input.flags, 0),
|
|
452
667
|
materialRefId: readNonNegativeInteger(
|
|
453
668
|
"mesh materialRefId",
|
|
@@ -463,10 +678,167 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
463
678
|
emission: Object.freeze(emission),
|
|
464
679
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
465
680
|
metallic: clamp(readFiniteNumber("metallic", input.metallic ?? input.material?.metallic, 0), 0, 1),
|
|
466
|
-
opacity
|
|
467
|
-
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3)
|
|
681
|
+
opacity,
|
|
682
|
+
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
683
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
684
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
685
|
+
sheenColor: Object.freeze(sheenColor),
|
|
686
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
687
|
+
clearcoatRoughness: clamp(
|
|
688
|
+
readFiniteNumber(
|
|
689
|
+
"clearcoatRoughness",
|
|
690
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
691
|
+
0.08
|
|
692
|
+
),
|
|
693
|
+
0,
|
|
694
|
+
1
|
|
695
|
+
),
|
|
696
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
697
|
+
specularColor: Object.freeze(specularColor),
|
|
698
|
+
transmission,
|
|
699
|
+
baseColorTexture: input.baseColorTexture ?? input.material?.baseColorTexture ?? null,
|
|
700
|
+
metallicRoughnessTexture: input.metallicRoughnessTexture ?? input.material?.metallicRoughnessTexture ?? null,
|
|
701
|
+
normalTexture: input.normalTexture ?? input.material?.normalTexture ?? null,
|
|
702
|
+
occlusionTexture: input.occlusionTexture ?? input.material?.occlusionTexture ?? null,
|
|
703
|
+
emissiveTexture: input.emissiveTexture ?? input.material?.emissiveTexture ?? null
|
|
468
704
|
});
|
|
469
705
|
}
|
|
706
|
+
function clampUnit(value) {
|
|
707
|
+
return clamp(Number(value) || 0, 0, 1);
|
|
708
|
+
}
|
|
709
|
+
function srgbToLinear(value) {
|
|
710
|
+
const channel = clampUnit(value);
|
|
711
|
+
if (channel <= 0.04045) {
|
|
712
|
+
return channel / 12.92;
|
|
713
|
+
}
|
|
714
|
+
return ((channel + 0.055) / 1.055) ** 2.4;
|
|
715
|
+
}
|
|
716
|
+
function sampleTextureRgba(texture, uv = [0, 0], colorSpace = "linear") {
|
|
717
|
+
if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || !texture.data || texture.width <= 0 || texture.height <= 0) {
|
|
718
|
+
return [1, 1, 1, 1];
|
|
719
|
+
}
|
|
720
|
+
const u = (uv[0] % 1 + 1) % 1;
|
|
721
|
+
const v = (uv[1] % 1 + 1) % 1;
|
|
722
|
+
const x = Math.min(texture.width - 1, Math.max(0, Math.round(u * (texture.width - 1))));
|
|
723
|
+
const y = Math.min(texture.height - 1, Math.max(0, Math.round((1 - v) * (texture.height - 1))));
|
|
724
|
+
const offset = (y * texture.width + x) * 4;
|
|
725
|
+
const data = texture.data;
|
|
726
|
+
const scale2 = resolveTextureSampleScale(data);
|
|
727
|
+
const defaultChannel = scale2 === 1 ? 1 : Math.round(1 / scale2);
|
|
728
|
+
const color = [
|
|
729
|
+
(data[offset] ?? defaultChannel) * scale2,
|
|
730
|
+
(data[offset + 1] ?? defaultChannel) * scale2,
|
|
731
|
+
(data[offset + 2] ?? defaultChannel) * scale2,
|
|
732
|
+
(data[offset + 3] ?? defaultChannel) * scale2
|
|
733
|
+
];
|
|
734
|
+
if (colorSpace === "srgb") {
|
|
735
|
+
return [srgbToLinear(color[0]), srgbToLinear(color[1]), srgbToLinear(color[2]), color[3]];
|
|
736
|
+
}
|
|
737
|
+
return color;
|
|
738
|
+
}
|
|
739
|
+
function resolveTextureSampleScale(data) {
|
|
740
|
+
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
|
|
741
|
+
return 1 / 255;
|
|
742
|
+
}
|
|
743
|
+
if (data instanceof Uint16Array) {
|
|
744
|
+
return 1 / 65535;
|
|
745
|
+
}
|
|
746
|
+
if (Array.isArray(data) && data.some((value) => Number(value) > 1)) {
|
|
747
|
+
return 1 / 255;
|
|
748
|
+
}
|
|
749
|
+
return 1;
|
|
750
|
+
}
|
|
751
|
+
function normalizeVectorOrFallback(vector, fallback) {
|
|
752
|
+
return normalize(Array.isArray(vector) ? vector : fallback, fallback);
|
|
753
|
+
}
|
|
754
|
+
function buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, fallbackNormal) {
|
|
755
|
+
const edge1 = subtract(v1, v0);
|
|
756
|
+
const edge2 = subtract(v2, v0);
|
|
757
|
+
const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
|
|
758
|
+
const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
|
|
759
|
+
const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
|
|
760
|
+
if (Math.abs(determinant) < 1e-6) {
|
|
761
|
+
const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
762
|
+
const tangent2 = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
|
|
763
|
+
const bitangent2 = normalize(cross(fallbackNormal, tangent2), [0, 0, 1]);
|
|
764
|
+
return { tangent: tangent2, bitangent: bitangent2 };
|
|
765
|
+
}
|
|
766
|
+
const inverse = 1 / determinant;
|
|
767
|
+
const tangent = normalize(
|
|
768
|
+
[
|
|
769
|
+
inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
|
|
770
|
+
inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
|
|
771
|
+
inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1])
|
|
772
|
+
],
|
|
773
|
+
[1, 0, 0]
|
|
774
|
+
);
|
|
775
|
+
const bitangent = normalize(
|
|
776
|
+
[
|
|
777
|
+
inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
|
|
778
|
+
inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
|
|
779
|
+
inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0])
|
|
780
|
+
],
|
|
781
|
+
[0, 0, 1]
|
|
782
|
+
);
|
|
783
|
+
return { tangent, bitangent };
|
|
784
|
+
}
|
|
785
|
+
function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
|
|
786
|
+
if (!normalTexture) {
|
|
787
|
+
return normalizeVectorOrFallback(normal, [0, 1, 0]);
|
|
788
|
+
}
|
|
789
|
+
const sample = sampleTextureRgba(normalTexture, uv, "linear");
|
|
790
|
+
const strength = clampUnit(normalTexture.scale ?? 1);
|
|
791
|
+
const tangentNormal = normalize(
|
|
792
|
+
[
|
|
793
|
+
(sample[0] * 2 - 1) * strength,
|
|
794
|
+
(sample[1] * 2 - 1) * strength,
|
|
795
|
+
1 + (sample[2] * 2 - 1 - 1) * strength
|
|
796
|
+
],
|
|
797
|
+
[0, 0, 1]
|
|
798
|
+
);
|
|
799
|
+
return normalize(
|
|
800
|
+
[
|
|
801
|
+
tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
|
|
802
|
+
tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
|
|
803
|
+
tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2]
|
|
804
|
+
],
|
|
805
|
+
normal
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
function sampleBaseColor(mesh, uv) {
|
|
809
|
+
const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
|
|
810
|
+
return [
|
|
811
|
+
clampUnit(mesh.color[0] * sample[0]),
|
|
812
|
+
clampUnit(mesh.color[1] * sample[1]),
|
|
813
|
+
clampUnit(mesh.color[2] * sample[2]),
|
|
814
|
+
clampUnit((mesh.color[3] ?? 1) * sample[3])
|
|
815
|
+
];
|
|
816
|
+
}
|
|
817
|
+
function sampleSurfaceMaterial(mesh, uv) {
|
|
818
|
+
const textureSample = mesh.metallicRoughnessTexture ? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear") : [1, 1, 1, 1];
|
|
819
|
+
return {
|
|
820
|
+
roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
|
|
821
|
+
metallic: clamp(mesh.metallic * textureSample[2], 0, 1)
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function averageColors(colors) {
|
|
825
|
+
const count = Math.max(colors.length, 1);
|
|
826
|
+
return colors.reduce(
|
|
827
|
+
(accumulator, color) => [
|
|
828
|
+
accumulator[0] + color[0] / count,
|
|
829
|
+
accumulator[1] + color[1] / count,
|
|
830
|
+
accumulator[2] + color[2] / count,
|
|
831
|
+
accumulator[3] + color[3] / count
|
|
832
|
+
],
|
|
833
|
+
[0, 0, 0, 0]
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
function averageNumbers(values, fallback = 0) {
|
|
837
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
838
|
+
return fallback;
|
|
839
|
+
}
|
|
840
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
841
|
+
}
|
|
470
842
|
function createMeshTriangleRecords(meshes) {
|
|
471
843
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
472
844
|
let nextTriangleId = 0;
|
|
@@ -487,6 +859,16 @@ function createMeshTriangleRecords(meshes) {
|
|
|
487
859
|
const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
|
|
488
860
|
const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
|
|
489
861
|
const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
|
|
862
|
+
const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
|
|
863
|
+
const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
|
|
864
|
+
const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
|
|
865
|
+
const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
|
|
866
|
+
const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
|
|
867
|
+
const sampledMaterials = [
|
|
868
|
+
sampleSurfaceMaterial(mesh, uv0),
|
|
869
|
+
sampleSurfaceMaterial(mesh, uv1),
|
|
870
|
+
sampleSurfaceMaterial(mesh, uv2)
|
|
871
|
+
];
|
|
490
872
|
const bounds = triangleBounds(v0, v1, v2);
|
|
491
873
|
triangles.push(
|
|
492
874
|
Object.freeze({
|
|
@@ -496,18 +878,42 @@ function createMeshTriangleRecords(meshes) {
|
|
|
496
878
|
flags: mesh.flags,
|
|
497
879
|
materialRefId: mesh.materialRefId,
|
|
498
880
|
mediumRefId: mesh.mediumRefId,
|
|
881
|
+
materialSlot: meshIndex,
|
|
499
882
|
v0: Object.freeze(v0),
|
|
500
883
|
v1: Object.freeze(v1),
|
|
501
884
|
v2: Object.freeze(v2),
|
|
502
|
-
n0: Object.freeze(
|
|
503
|
-
n1: Object.freeze(
|
|
504
|
-
n2: Object.freeze(
|
|
885
|
+
n0: Object.freeze(shadedN0),
|
|
886
|
+
n1: Object.freeze(shadedN1),
|
|
887
|
+
n2: Object.freeze(shadedN2),
|
|
505
888
|
uv0: Object.freeze(uv0),
|
|
506
889
|
uv1: Object.freeze(uv1),
|
|
507
890
|
uv2: Object.freeze(uv2),
|
|
508
|
-
color:
|
|
891
|
+
color: Object.freeze(averageColors(sampledColors)),
|
|
509
892
|
emission: mesh.emission,
|
|
510
|
-
material: Object.freeze([
|
|
893
|
+
material: Object.freeze([
|
|
894
|
+
averageNumbers(sampledMaterials.map((sample) => sample.roughness), mesh.roughness),
|
|
895
|
+
averageNumbers(sampledMaterials.map((sample) => sample.metallic), mesh.metallic),
|
|
896
|
+
mesh.opacity,
|
|
897
|
+
mesh.ior
|
|
898
|
+
]),
|
|
899
|
+
materialResponse: Object.freeze([
|
|
900
|
+
mesh.sheenColor[0] ?? 0,
|
|
901
|
+
mesh.sheenColor[1] ?? 0,
|
|
902
|
+
mesh.sheenColor[2] ?? 0,
|
|
903
|
+
mesh.clearcoat
|
|
904
|
+
]),
|
|
905
|
+
materialExtension: Object.freeze([
|
|
906
|
+
mesh.clearcoatRoughness,
|
|
907
|
+
mesh.specular,
|
|
908
|
+
mesh.transmission,
|
|
909
|
+
0
|
|
910
|
+
]),
|
|
911
|
+
specularColor: Object.freeze([
|
|
912
|
+
mesh.specularColor[0] ?? 1,
|
|
913
|
+
mesh.specularColor[1] ?? 1,
|
|
914
|
+
mesh.specularColor[2] ?? 1,
|
|
915
|
+
1
|
|
916
|
+
]),
|
|
511
917
|
bounds: Object.freeze({
|
|
512
918
|
min: Object.freeze(bounds.min),
|
|
513
919
|
max: Object.freeze(bounds.max)
|
|
@@ -616,6 +1022,220 @@ function nextPowerOfTwo(value) {
|
|
|
616
1022
|
}
|
|
617
1023
|
return 2 ** Math.ceil(Math.log2(value));
|
|
618
1024
|
}
|
|
1025
|
+
function textureComponentToByte(value, fallback) {
|
|
1026
|
+
const numeric = Number(value);
|
|
1027
|
+
if (!Number.isFinite(numeric)) {
|
|
1028
|
+
return fallback;
|
|
1029
|
+
}
|
|
1030
|
+
if (numeric >= 0 && numeric <= 1) {
|
|
1031
|
+
return Math.max(0, Math.min(255, Math.round(numeric * 255)));
|
|
1032
|
+
}
|
|
1033
|
+
return Math.max(0, Math.min(255, Math.round(numeric)));
|
|
1034
|
+
}
|
|
1035
|
+
function createSolidTextureSample(width, height, rgba) {
|
|
1036
|
+
const data = new Uint8Array(width * height * 4);
|
|
1037
|
+
for (let offset = 0; offset < data.length; offset += 4) {
|
|
1038
|
+
data[offset] = rgba[0];
|
|
1039
|
+
data[offset + 1] = rgba[1];
|
|
1040
|
+
data[offset + 2] = rgba[2];
|
|
1041
|
+
data[offset + 3] = rgba[3];
|
|
1042
|
+
}
|
|
1043
|
+
return Object.freeze({
|
|
1044
|
+
width,
|
|
1045
|
+
height,
|
|
1046
|
+
data
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
function normalizeTextureSampleInput(texture, fallbackColor) {
|
|
1050
|
+
if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || texture.width <= 0 || texture.height <= 0) {
|
|
1051
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
1052
|
+
}
|
|
1053
|
+
const pixelCount = Math.trunc(texture.width) * Math.trunc(texture.height) * 4;
|
|
1054
|
+
const source = ArrayBuffer.isView(texture.data) || Array.isArray(texture.data) ? texture.data : null;
|
|
1055
|
+
if (!source || source.length < pixelCount) {
|
|
1056
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
1057
|
+
}
|
|
1058
|
+
const data = new Uint8Array(pixelCount);
|
|
1059
|
+
for (let index = 0; index < pixelCount; index += 1) {
|
|
1060
|
+
data[index] = textureComponentToByte(source[index], fallbackColor[index % 4]);
|
|
1061
|
+
}
|
|
1062
|
+
return Object.freeze({
|
|
1063
|
+
width: Math.trunc(texture.width),
|
|
1064
|
+
height: Math.trunc(texture.height),
|
|
1065
|
+
data
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
function buildTextureAtlas(textures, fallbackColor) {
|
|
1069
|
+
const padding = 1;
|
|
1070
|
+
const defaultTexture = createSolidTextureSample(1, 1, fallbackColor);
|
|
1071
|
+
const uniqueEntries = [{ source: null, texture: defaultTexture }];
|
|
1072
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
1073
|
+
for (const texture of Array.isArray(textures) ? textures : []) {
|
|
1074
|
+
if (!texture || bySource.has(texture)) {
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
const normalized = normalizeTextureSampleInput(texture, fallbackColor);
|
|
1078
|
+
bySource.set(texture, uniqueEntries.length);
|
|
1079
|
+
uniqueEntries.push({ source: texture, texture: normalized });
|
|
1080
|
+
}
|
|
1081
|
+
const totalArea = uniqueEntries.reduce((sum, entry) => {
|
|
1082
|
+
return sum + (entry.texture.width + padding * 2) * (entry.texture.height + padding * 2);
|
|
1083
|
+
}, 0);
|
|
1084
|
+
const maxTileWidth = uniqueEntries.reduce((maxWidth, entry) => {
|
|
1085
|
+
return Math.max(maxWidth, entry.texture.width + padding * 2);
|
|
1086
|
+
}, 1);
|
|
1087
|
+
const targetWidth = Math.max(
|
|
1088
|
+
maxTileWidth,
|
|
1089
|
+
nextPowerOfTwo(Math.max(maxTileWidth, Math.ceil(Math.sqrt(totalArea))))
|
|
1090
|
+
);
|
|
1091
|
+
let cursorX = 0;
|
|
1092
|
+
let cursorY = 0;
|
|
1093
|
+
let rowHeight = 0;
|
|
1094
|
+
let atlasWidth = 0;
|
|
1095
|
+
const placements = uniqueEntries.map((entry) => {
|
|
1096
|
+
const tileWidth = entry.texture.width + padding * 2;
|
|
1097
|
+
const tileHeight = entry.texture.height + padding * 2;
|
|
1098
|
+
if (cursorX > 0 && cursorX + tileWidth > targetWidth) {
|
|
1099
|
+
cursorX = 0;
|
|
1100
|
+
cursorY += rowHeight;
|
|
1101
|
+
rowHeight = 0;
|
|
1102
|
+
}
|
|
1103
|
+
const placement = Object.freeze({
|
|
1104
|
+
x: cursorX,
|
|
1105
|
+
y: cursorY,
|
|
1106
|
+
tileWidth,
|
|
1107
|
+
tileHeight,
|
|
1108
|
+
width: entry.texture.width,
|
|
1109
|
+
height: entry.texture.height
|
|
1110
|
+
});
|
|
1111
|
+
cursorX += tileWidth;
|
|
1112
|
+
atlasWidth = Math.max(atlasWidth, cursorX);
|
|
1113
|
+
rowHeight = Math.max(rowHeight, tileHeight);
|
|
1114
|
+
return placement;
|
|
1115
|
+
});
|
|
1116
|
+
const atlasHeight = Math.max(1, cursorY + rowHeight);
|
|
1117
|
+
const atlasData = new Uint8Array(Math.max(1, atlasWidth * atlasHeight * 4));
|
|
1118
|
+
const writePixel = (x, y, rgba) => {
|
|
1119
|
+
const offset = (y * atlasWidth + x) * 4;
|
|
1120
|
+
atlasData[offset] = rgba[0];
|
|
1121
|
+
atlasData[offset + 1] = rgba[1];
|
|
1122
|
+
atlasData[offset + 2] = rgba[2];
|
|
1123
|
+
atlasData[offset + 3] = rgba[3];
|
|
1124
|
+
};
|
|
1125
|
+
const rects = placements.map((placement, entryIndex) => {
|
|
1126
|
+
const { texture } = uniqueEntries[entryIndex];
|
|
1127
|
+
for (let y = 0; y < placement.tileHeight; y += 1) {
|
|
1128
|
+
for (let x = 0; x < placement.tileWidth; x += 1) {
|
|
1129
|
+
const sampleX = Math.max(0, Math.min(texture.width - 1, x - padding));
|
|
1130
|
+
const sampleY = Math.max(0, Math.min(texture.height - 1, y - padding));
|
|
1131
|
+
const sourceOffset = (sampleY * texture.width + sampleX) * 4;
|
|
1132
|
+
writePixel(placement.x + x, placement.y + y, texture.data.slice(sourceOffset, sourceOffset + 4));
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return Object.freeze([
|
|
1136
|
+
(placement.x + padding) / Math.max(1, atlasWidth),
|
|
1137
|
+
(placement.y + padding) / Math.max(1, atlasHeight),
|
|
1138
|
+
placement.width / Math.max(1, atlasWidth),
|
|
1139
|
+
placement.height / Math.max(1, atlasHeight)
|
|
1140
|
+
]);
|
|
1141
|
+
});
|
|
1142
|
+
const rectBySource = /* @__PURE__ */ new Map();
|
|
1143
|
+
uniqueEntries.forEach((entry, index) => {
|
|
1144
|
+
if (entry.source) {
|
|
1145
|
+
rectBySource.set(entry.source, rects[index]);
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
return Object.freeze({
|
|
1149
|
+
width: Math.max(1, atlasWidth),
|
|
1150
|
+
height: Math.max(1, atlasHeight),
|
|
1151
|
+
data: atlasData,
|
|
1152
|
+
defaultRect: rects[0],
|
|
1153
|
+
resolveRect(texture) {
|
|
1154
|
+
return rectBySource.get(texture) ?? rects[0];
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
function createWavefrontGpuMaterialSource(meshes = []) {
|
|
1159
|
+
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
1160
|
+
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1161
|
+
const baseColorAtlas = buildTextureAtlas(
|
|
1162
|
+
normalized.map((mesh) => mesh.baseColorTexture),
|
|
1163
|
+
[255, 255, 255, 255]
|
|
1164
|
+
);
|
|
1165
|
+
const metallicRoughnessAtlas = buildTextureAtlas(
|
|
1166
|
+
normalized.map((mesh) => mesh.metallicRoughnessTexture),
|
|
1167
|
+
[255, 255, 255, 255]
|
|
1168
|
+
);
|
|
1169
|
+
const normalAtlas = buildTextureAtlas(
|
|
1170
|
+
normalized.map((mesh) => mesh.normalTexture),
|
|
1171
|
+
[128, 128, 255, 255]
|
|
1172
|
+
);
|
|
1173
|
+
const occlusionAtlas = buildTextureAtlas(
|
|
1174
|
+
normalized.map((mesh) => mesh.occlusionTexture),
|
|
1175
|
+
[255, 255, 255, 255]
|
|
1176
|
+
);
|
|
1177
|
+
const emissiveAtlas = buildTextureAtlas(
|
|
1178
|
+
normalized.map((mesh) => mesh.emissiveTexture),
|
|
1179
|
+
[255, 255, 255, 255]
|
|
1180
|
+
);
|
|
1181
|
+
const bytes = new ArrayBuffer(Math.max(1, normalized.length) * GPU_MATERIAL_RECORD_BYTES);
|
|
1182
|
+
const floatView = new Float32Array(bytes);
|
|
1183
|
+
normalized.forEach((mesh, meshIndex) => {
|
|
1184
|
+
const byteOffset = meshIndex * GPU_MATERIAL_RECORD_BYTES;
|
|
1185
|
+
writeVec4(floatView, byteOffset, mesh.color);
|
|
1186
|
+
writeVec4(floatView, byteOffset + 16, mesh.emission);
|
|
1187
|
+
writeVec4(floatView, byteOffset + 32, [
|
|
1188
|
+
mesh.roughness,
|
|
1189
|
+
mesh.metallic,
|
|
1190
|
+
mesh.opacity,
|
|
1191
|
+
mesh.ior
|
|
1192
|
+
]);
|
|
1193
|
+
writeVec4(floatView, byteOffset + 48, [
|
|
1194
|
+
mesh.sheenColor[0] ?? 0,
|
|
1195
|
+
mesh.sheenColor[1] ?? 0,
|
|
1196
|
+
mesh.sheenColor[2] ?? 0,
|
|
1197
|
+
mesh.clearcoat
|
|
1198
|
+
]);
|
|
1199
|
+
writeVec4(floatView, byteOffset + 64, [
|
|
1200
|
+
mesh.clearcoatRoughness,
|
|
1201
|
+
mesh.specular,
|
|
1202
|
+
mesh.transmission,
|
|
1203
|
+
0
|
|
1204
|
+
]);
|
|
1205
|
+
writeVec4(floatView, byteOffset + 80, [
|
|
1206
|
+
mesh.specularColor[0] ?? 1,
|
|
1207
|
+
mesh.specularColor[1] ?? 1,
|
|
1208
|
+
mesh.specularColor[2] ?? 1,
|
|
1209
|
+
1
|
|
1210
|
+
]);
|
|
1211
|
+
writeVec4(floatView, byteOffset + 96, baseColorAtlas.resolveRect(mesh.baseColorTexture));
|
|
1212
|
+
writeVec4(
|
|
1213
|
+
floatView,
|
|
1214
|
+
byteOffset + 112,
|
|
1215
|
+
metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1216
|
+
);
|
|
1217
|
+
writeVec4(floatView, byteOffset + 128, normalAtlas.resolveRect(mesh.normalTexture));
|
|
1218
|
+
writeVec4(floatView, byteOffset + 144, occlusionAtlas.resolveRect(mesh.occlusionTexture));
|
|
1219
|
+
writeVec4(floatView, byteOffset + 160, emissiveAtlas.resolveRect(mesh.emissiveTexture));
|
|
1220
|
+
writeVec4(floatView, byteOffset + 176, [
|
|
1221
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1222
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1223
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1224
|
+
0
|
|
1225
|
+
]);
|
|
1226
|
+
});
|
|
1227
|
+
return Object.freeze({
|
|
1228
|
+
buffer: bytes,
|
|
1229
|
+
count: normalized.length,
|
|
1230
|
+
recordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
1231
|
+
records: Object.freeze(normalized),
|
|
1232
|
+
baseColorAtlas,
|
|
1233
|
+
metallicRoughnessAtlas,
|
|
1234
|
+
normalAtlas,
|
|
1235
|
+
occlusionAtlas,
|
|
1236
|
+
emissiveAtlas
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
619
1239
|
function estimateBvhLeafSortCapacity(triangleCount) {
|
|
620
1240
|
return triangleCount <= 0 ? 0 : nextPowerOfTwo(triangleCount);
|
|
621
1241
|
}
|
|
@@ -673,9 +1293,10 @@ function resolveAccelerationBuildMode(options = {}) {
|
|
|
673
1293
|
}
|
|
674
1294
|
return mode;
|
|
675
1295
|
}
|
|
676
|
-
function createWavefrontGpuMeshSource(meshes = []) {
|
|
1296
|
+
function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput = null) {
|
|
677
1297
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
678
1298
|
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1299
|
+
const gpuMaterialSource = gpuMaterialSourceInput ?? createWavefrontGpuMaterialSource(normalized);
|
|
679
1300
|
const vertexCount = normalized.reduce((count, mesh) => count + mesh.positions.length / 3, 0);
|
|
680
1301
|
const indexCount = normalized.reduce((count, mesh) => count + mesh.indices.length, 0);
|
|
681
1302
|
const triangleCount = Math.floor(indexCount / 3);
|
|
@@ -727,7 +1348,7 @@ function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
727
1348
|
meshUints[meshOffset + 8] = mesh.indices.length / 3;
|
|
728
1349
|
meshUints[meshOffset + 9] = meshVertexBase;
|
|
729
1350
|
meshUints[meshOffset + 10] = meshVertexCount;
|
|
730
|
-
meshUints[meshOffset + 11] =
|
|
1351
|
+
meshUints[meshOffset + 11] = meshIndex;
|
|
731
1352
|
const floatOffset = meshOffset;
|
|
732
1353
|
writeVec4(meshFloats, floatOffset * 4 + 48, mesh.color);
|
|
733
1354
|
writeVec4(meshFloats, floatOffset * 4 + 64, mesh.emission);
|
|
@@ -737,6 +1358,55 @@ function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
737
1358
|
mesh.opacity,
|
|
738
1359
|
mesh.ior
|
|
739
1360
|
]);
|
|
1361
|
+
writeVec4(meshFloats, floatOffset * 4 + 96, [
|
|
1362
|
+
mesh.sheenColor[0] ?? 0,
|
|
1363
|
+
mesh.sheenColor[1] ?? 0,
|
|
1364
|
+
mesh.sheenColor[2] ?? 0,
|
|
1365
|
+
mesh.clearcoat
|
|
1366
|
+
]);
|
|
1367
|
+
writeVec4(meshFloats, floatOffset * 4 + 112, [
|
|
1368
|
+
mesh.clearcoatRoughness,
|
|
1369
|
+
mesh.specular,
|
|
1370
|
+
mesh.transmission,
|
|
1371
|
+
0
|
|
1372
|
+
]);
|
|
1373
|
+
writeVec4(meshFloats, floatOffset * 4 + 128, [
|
|
1374
|
+
mesh.specularColor[0] ?? 1,
|
|
1375
|
+
mesh.specularColor[1] ?? 1,
|
|
1376
|
+
mesh.specularColor[2] ?? 1,
|
|
1377
|
+
1
|
|
1378
|
+
]);
|
|
1379
|
+
writeVec4(
|
|
1380
|
+
meshFloats,
|
|
1381
|
+
floatOffset * 4 + 144,
|
|
1382
|
+
gpuMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
|
|
1383
|
+
);
|
|
1384
|
+
writeVec4(
|
|
1385
|
+
meshFloats,
|
|
1386
|
+
floatOffset * 4 + 160,
|
|
1387
|
+
gpuMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1388
|
+
);
|
|
1389
|
+
writeVec4(
|
|
1390
|
+
meshFloats,
|
|
1391
|
+
floatOffset * 4 + 176,
|
|
1392
|
+
gpuMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
|
|
1393
|
+
);
|
|
1394
|
+
writeVec4(
|
|
1395
|
+
meshFloats,
|
|
1396
|
+
floatOffset * 4 + 192,
|
|
1397
|
+
gpuMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
|
|
1398
|
+
);
|
|
1399
|
+
writeVec4(
|
|
1400
|
+
meshFloats,
|
|
1401
|
+
floatOffset * 4 + 208,
|
|
1402
|
+
gpuMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
|
|
1403
|
+
);
|
|
1404
|
+
writeVec4(meshFloats, floatOffset * 4 + 224, [
|
|
1405
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1406
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1407
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1408
|
+
0
|
|
1409
|
+
]);
|
|
740
1410
|
vertexCursor += meshVertexCount;
|
|
741
1411
|
indexCursor += mesh.indices.length;
|
|
742
1412
|
triangleCursor += mesh.indices.length / 3;
|
|
@@ -1039,12 +1709,14 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1039
1709
|
options.environmentPortalCapacity,
|
|
1040
1710
|
0
|
|
1041
1711
|
);
|
|
1712
|
+
const materialCapacity = readNonNegativeInteger("materialCapacity", options.materialCapacity, 0);
|
|
1042
1713
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
1043
1714
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
1044
1715
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
1045
1716
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
1046
1717
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
1047
1718
|
const triangleBytes = triangleCapacity * TRIANGLE_RECORD_BYTES;
|
|
1719
|
+
const materialTableBytes = materialCapacity * GPU_MATERIAL_RECORD_BYTES;
|
|
1048
1720
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
1049
1721
|
const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
|
|
1050
1722
|
const emissiveTriangleMetadataBytes = emissiveTriangleCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -1057,6 +1729,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1057
1729
|
pathVertexBytes,
|
|
1058
1730
|
sceneObjectBytes,
|
|
1059
1731
|
triangleBytes,
|
|
1732
|
+
materialTableBytes,
|
|
1060
1733
|
bvhNodeBytes,
|
|
1061
1734
|
bvhLeafReferenceBytes,
|
|
1062
1735
|
emissiveTriangleMetadataBytes,
|
|
@@ -1064,7 +1737,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1064
1737
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
1065
1738
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
1066
1739
|
indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
|
|
1067
|
-
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + pathVertexBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1740
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + pathVertexBytes + sceneObjectBytes + triangleBytes + materialTableBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1068
1741
|
});
|
|
1069
1742
|
}
|
|
1070
1743
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -1078,7 +1751,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1078
1751
|
const samplesPerPixel = clamp(
|
|
1079
1752
|
readPositiveInteger("samplesPerPixel", options.samplesPerPixel, DEFAULT_SAMPLES_PER_PIXEL),
|
|
1080
1753
|
1,
|
|
1081
|
-
|
|
1754
|
+
MAX_SAMPLES_PER_PIXEL
|
|
1082
1755
|
);
|
|
1083
1756
|
const maxFramePassesPerSubmission = clamp(
|
|
1084
1757
|
readPositiveInteger(
|
|
@@ -1096,7 +1769,8 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1096
1769
|
);
|
|
1097
1770
|
const meshes = normalizeMeshes(options);
|
|
1098
1771
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
1099
|
-
const
|
|
1772
|
+
const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
|
|
1773
|
+
const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
|
|
1100
1774
|
const meshAcceleration = accelerationBuildMode === "cpu-debug" ? createWavefrontMeshAcceleration(meshes) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
1101
1775
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
1102
1776
|
meshes,
|
|
@@ -1168,6 +1842,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1168
1842
|
accelerationBuildMode,
|
|
1169
1843
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
1170
1844
|
gpuMeshSource,
|
|
1845
|
+
gpuMaterialSource,
|
|
1171
1846
|
meshAcceleration,
|
|
1172
1847
|
emissiveTriangleIndices,
|
|
1173
1848
|
emissiveTriangleCount: emissiveTriangleIndices.count,
|
|
@@ -1198,6 +1873,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1198
1873
|
maxDepth,
|
|
1199
1874
|
sceneObjectCapacity,
|
|
1200
1875
|
triangleCapacity,
|
|
1876
|
+
materialCapacity: gpuMaterialSource.count,
|
|
1201
1877
|
bvhNodeCapacity,
|
|
1202
1878
|
bvhLeafSortCapacity,
|
|
1203
1879
|
emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
|
|
@@ -1274,6 +1950,24 @@ function packWavefrontSceneObjects(sceneObjects, capacity = sceneObjects.length)
|
|
|
1274
1950
|
object.opacity,
|
|
1275
1951
|
object.ior
|
|
1276
1952
|
]);
|
|
1953
|
+
writeVec4(floatView, byteOffset + 96, [
|
|
1954
|
+
object.sheenColor[0] ?? 0,
|
|
1955
|
+
object.sheenColor[1] ?? 0,
|
|
1956
|
+
object.sheenColor[2] ?? 0,
|
|
1957
|
+
object.clearcoat
|
|
1958
|
+
]);
|
|
1959
|
+
writeVec4(floatView, byteOffset + 112, [
|
|
1960
|
+
object.clearcoatRoughness,
|
|
1961
|
+
object.specular,
|
|
1962
|
+
object.transmission,
|
|
1963
|
+
0
|
|
1964
|
+
]);
|
|
1965
|
+
writeVec4(floatView, byteOffset + 128, [
|
|
1966
|
+
object.specularColor[0] ?? 1,
|
|
1967
|
+
object.specularColor[1] ?? 1,
|
|
1968
|
+
object.specularColor[2] ?? 1,
|
|
1969
|
+
1
|
|
1970
|
+
]);
|
|
1277
1971
|
});
|
|
1278
1972
|
return Object.freeze({
|
|
1279
1973
|
buffer: bytes,
|
|
@@ -1298,7 +1992,7 @@ function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1298
1992
|
uintView[u32 + 3] = triangle.flags;
|
|
1299
1993
|
uintView[u32 + 4] = triangle.materialRefId;
|
|
1300
1994
|
uintView[u32 + 5] = triangle.mediumRefId;
|
|
1301
|
-
uintView[u32 + 6] = 0;
|
|
1995
|
+
uintView[u32 + 6] = triangle.materialSlot ?? 0;
|
|
1302
1996
|
uintView[u32 + 7] = 0;
|
|
1303
1997
|
writeVec4(floatView, byteOffset + 32, [...triangle.v0, 0]);
|
|
1304
1998
|
writeVec4(floatView, byteOffset + 48, [...triangle.v1, 0]);
|
|
@@ -1311,6 +2005,15 @@ function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1311
2005
|
writeVec4(floatView, byteOffset + 160, triangle.color);
|
|
1312
2006
|
writeVec4(floatView, byteOffset + 176, triangle.emission);
|
|
1313
2007
|
writeVec4(floatView, byteOffset + 192, triangle.material);
|
|
2008
|
+
writeVec4(floatView, byteOffset + 208, triangle.materialResponse);
|
|
2009
|
+
writeVec4(floatView, byteOffset + 224, triangle.materialExtension ?? [0.08, 1, 0, 0]);
|
|
2010
|
+
writeVec4(floatView, byteOffset + 240, triangle.specularColor ?? [1, 1, 1, 1]);
|
|
2011
|
+
writeVec4(floatView, byteOffset + 256, triangle.baseColorAtlas ?? [0, 0, 1, 1]);
|
|
2012
|
+
writeVec4(floatView, byteOffset + 272, triangle.metallicRoughnessAtlas ?? [0, 0, 1, 1]);
|
|
2013
|
+
writeVec4(floatView, byteOffset + 288, triangle.normalAtlas ?? [0, 0, 1, 1]);
|
|
2014
|
+
writeVec4(floatView, byteOffset + 304, triangle.occlusionAtlas ?? [0, 0, 1, 1]);
|
|
2015
|
+
writeVec4(floatView, byteOffset + 320, triangle.emissiveAtlas ?? [0, 0, 1, 1]);
|
|
2016
|
+
writeVec4(floatView, byteOffset + 336, triangle.textureSettings ?? [1, 1, 1, 0]);
|
|
1314
2017
|
});
|
|
1315
2018
|
return Object.freeze({
|
|
1316
2019
|
buffer: bytes,
|
|
@@ -1404,6 +2107,12 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1404
2107
|
0,
|
|
1405
2108
|
0
|
|
1406
2109
|
]);
|
|
2110
|
+
writeVec4(floatView, 304, [
|
|
2111
|
+
config.environmentMap.width ?? 1,
|
|
2112
|
+
config.environmentMap.height ?? 1,
|
|
2113
|
+
config.environmentMap.mipLevelCount ?? 1,
|
|
2114
|
+
config.environmentMap.hasImportanceData ? 1 : 0
|
|
2115
|
+
]);
|
|
1407
2116
|
return bytes;
|
|
1408
2117
|
}
|
|
1409
2118
|
function createTiles(width, height, tileSize) {
|
|
@@ -1577,7 +2286,8 @@ function intersectWavefrontReferenceTriangle(ray, triangle, options = {}) {
|
|
|
1577
2286
|
position: Object.freeze(position),
|
|
1578
2287
|
color: triangle.color,
|
|
1579
2288
|
emission: triangle.emission,
|
|
1580
|
-
material: triangle.material
|
|
2289
|
+
material: triangle.material,
|
|
2290
|
+
materialResponse: triangle.materialResponse
|
|
1581
2291
|
});
|
|
1582
2292
|
}
|
|
1583
2293
|
function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
@@ -1603,7 +2313,8 @@ function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
|
1603
2313
|
position: Object.freeze(add(ray.origin, scale(ray.direction, 1e3))),
|
|
1604
2314
|
color: Object.freeze([0, 0, 0, 0]),
|
|
1605
2315
|
emission: radiance,
|
|
1606
|
-
material: Object.freeze([1, 0, 1, 1])
|
|
2316
|
+
material: Object.freeze([1, 0, 1, 1]),
|
|
2317
|
+
materialResponse: Object.freeze([0, 0, 0, 0])
|
|
1607
2318
|
});
|
|
1608
2319
|
}
|
|
1609
2320
|
function traceWavefrontReferenceTriangles(config, ray, triangles, options = {}) {
|
|
@@ -1682,40 +2393,24 @@ function environmentMapIntegerScale(data) {
|
|
|
1682
2393
|
}
|
|
1683
2394
|
return 1;
|
|
1684
2395
|
}
|
|
1685
|
-
function
|
|
1686
|
-
if (!
|
|
1687
|
-
return
|
|
2396
|
+
function environmentMapHasSamplingData(environmentMap) {
|
|
2397
|
+
if (!environmentMap || !environmentMap.data) {
|
|
2398
|
+
return false;
|
|
1688
2399
|
}
|
|
1689
|
-
const
|
|
1690
|
-
|
|
2400
|
+
const width = Math.max(1, environmentMap.width ?? 1);
|
|
2401
|
+
const height = Math.max(1, environmentMap.height ?? 1);
|
|
2402
|
+
return environmentMap.data.length >= width * height * 4;
|
|
1691
2403
|
}
|
|
1692
|
-
function
|
|
1693
|
-
const width = Math.max(1,
|
|
1694
|
-
const height = Math.max(1,
|
|
1695
|
-
const
|
|
1696
|
-
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2404
|
+
function createRgba8TextureUpload(source) {
|
|
2405
|
+
const width = Math.max(1, Math.trunc(source.width));
|
|
2406
|
+
const height = Math.max(1, Math.trunc(source.height));
|
|
2407
|
+
const bytesPerRow = alignTo(width * 4, 256);
|
|
1697
2408
|
const bytes = new Uint8Array(bytesPerRow * height);
|
|
1698
|
-
const data =
|
|
1699
|
-
const integerScale = environmentMapIntegerScale(data);
|
|
1700
|
-
const view = new DataView(bytes.buffer);
|
|
1701
|
-
const writeComponent = (targetOffset, sourceOffset, fallback) => {
|
|
1702
|
-
view.setUint16(
|
|
1703
|
-
targetOffset,
|
|
1704
|
-
float32ToFloat16Bits(
|
|
1705
|
-
readEnvironmentMapComponent(data, sourceOffset, fallback, integerScale)
|
|
1706
|
-
),
|
|
1707
|
-
true
|
|
1708
|
-
);
|
|
1709
|
-
};
|
|
2409
|
+
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
1710
2410
|
for (let y = 0; y < height; y += 1) {
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
writeComponent(targetOffset, sourceOffset, fallbackColor[0]);
|
|
1715
|
-
writeComponent(targetOffset + 2, sourceOffset + 1, fallbackColor[1]);
|
|
1716
|
-
writeComponent(targetOffset + 4, sourceOffset + 2, fallbackColor[2]);
|
|
1717
|
-
writeComponent(targetOffset + 6, sourceOffset + 3, fallbackColor[3] ?? 1);
|
|
1718
|
-
}
|
|
2411
|
+
const sourceOffset = y * width * 4;
|
|
2412
|
+
const targetOffset = y * bytesPerRow;
|
|
2413
|
+
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
1719
2414
|
}
|
|
1720
2415
|
return Object.freeze({
|
|
1721
2416
|
bytes,
|
|
@@ -1724,6 +2419,320 @@ function createEnvironmentMapUploadBytes(environmentMap, fallbackColor) {
|
|
|
1724
2419
|
height
|
|
1725
2420
|
});
|
|
1726
2421
|
}
|
|
2422
|
+
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
2423
|
+
if (!data || index >= data.length) {
|
|
2424
|
+
return fallback;
|
|
2425
|
+
}
|
|
2426
|
+
const value = Number(data[index]);
|
|
2427
|
+
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
2428
|
+
}
|
|
2429
|
+
function buildOrthonormalBasis(normal) {
|
|
2430
|
+
const tangentFallback = Math.abs(normal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
2431
|
+
const tangent = normalize(cross(tangentFallback, normal), [1, 0, 0]);
|
|
2432
|
+
const bitangent = normalize(cross(normal, tangent), [0, 0, 1]);
|
|
2433
|
+
return { tangent, bitangent };
|
|
2434
|
+
}
|
|
2435
|
+
function localToWorld(local, normal) {
|
|
2436
|
+
const basis = buildOrthonormalBasis(normal);
|
|
2437
|
+
return normalize(
|
|
2438
|
+
add(
|
|
2439
|
+
add(scale(basis.tangent, local[0]), scale(basis.bitangent, local[1])),
|
|
2440
|
+
scale(normal, local[2])
|
|
2441
|
+
),
|
|
2442
|
+
normal
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
function radicalInverseVdc(bits) {
|
|
2446
|
+
let value = bits >>> 0;
|
|
2447
|
+
value = (value << 16 | value >>> 16) >>> 0;
|
|
2448
|
+
value = ((value & 1431655765) << 1 | (value & 2863311530) >>> 1) >>> 0;
|
|
2449
|
+
value = ((value & 858993459) << 2 | (value & 3435973836) >>> 2) >>> 0;
|
|
2450
|
+
value = ((value & 252645135) << 4 | (value & 4042322160) >>> 4) >>> 0;
|
|
2451
|
+
value = ((value & 16711935) << 8 | (value & 4278255360) >>> 8) >>> 0;
|
|
2452
|
+
return value * 23283064365386963e-26;
|
|
2453
|
+
}
|
|
2454
|
+
function hammersley(index, count) {
|
|
2455
|
+
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
2456
|
+
}
|
|
2457
|
+
function importanceSampleGgx(sample, roughness, normal) {
|
|
2458
|
+
const alpha = Math.max(roughness * roughness, 1e-4);
|
|
2459
|
+
const phi = 2 * Math.PI * sample[0];
|
|
2460
|
+
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
2461
|
+
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
2462
|
+
const halfVector = localToWorld(
|
|
2463
|
+
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
2464
|
+
normal
|
|
2465
|
+
);
|
|
2466
|
+
return normalize(halfVector, normal);
|
|
2467
|
+
}
|
|
2468
|
+
function geometrySchlickGgx(nDotV, roughness) {
|
|
2469
|
+
const k = (roughness + 1) * (roughness + 1) / 8;
|
|
2470
|
+
return nDotV / Math.max(nDotV * (1 - k) + k, 1e-6);
|
|
2471
|
+
}
|
|
2472
|
+
function geometrySmith(nDotV, nDotL, roughness) {
|
|
2473
|
+
return geometrySchlickGgx(nDotV, roughness) * geometrySchlickGgx(nDotL, roughness);
|
|
2474
|
+
}
|
|
2475
|
+
function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
2476
|
+
const viewDirection = [Math.sqrt(Math.max(0, 1 - nDotV * nDotV)), 0, nDotV];
|
|
2477
|
+
const normal = [0, 0, 1];
|
|
2478
|
+
let scaleTerm = 0;
|
|
2479
|
+
let biasTerm = 0;
|
|
2480
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
2481
|
+
const xi = hammersley(index, sampleCount);
|
|
2482
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2483
|
+
const vDotH = Math.max(dot(viewDirection, halfVector), 0);
|
|
2484
|
+
const lightDirection = normalize(
|
|
2485
|
+
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
2486
|
+
normal
|
|
2487
|
+
);
|
|
2488
|
+
const nDotL = Math.max(lightDirection[2], 0);
|
|
2489
|
+
const nDotH = Math.max(halfVector[2], 0);
|
|
2490
|
+
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2493
|
+
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
2494
|
+
const visibility = geometry * vDotH / Math.max(nDotH * nDotV, 1e-6);
|
|
2495
|
+
const fresnel = (1 - vDotH) ** 5;
|
|
2496
|
+
scaleTerm += (1 - fresnel) * visibility;
|
|
2497
|
+
biasTerm += fresnel * visibility;
|
|
2498
|
+
}
|
|
2499
|
+
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
2500
|
+
}
|
|
2501
|
+
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount = 1024) {
|
|
2502
|
+
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
2503
|
+
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
2504
|
+
if (cached) {
|
|
2505
|
+
return cached;
|
|
2506
|
+
}
|
|
2507
|
+
const width = Math.max(1, Math.trunc(size));
|
|
2508
|
+
const height = Math.max(1, Math.trunc(size));
|
|
2509
|
+
const rowBytes = width * 8;
|
|
2510
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2511
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
2512
|
+
const view = new DataView(bytes.buffer);
|
|
2513
|
+
for (let y = 0; y < height; y += 1) {
|
|
2514
|
+
const roughness = (y + 0.5) / height;
|
|
2515
|
+
for (let x = 0; x < width; x += 1) {
|
|
2516
|
+
const nDotV = Math.max((x + 0.5) / width, 1e-4);
|
|
2517
|
+
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
2518
|
+
const offset = y * bytesPerRow + x * 8;
|
|
2519
|
+
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
2520
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
2521
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
2522
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
2526
|
+
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
2527
|
+
return upload;
|
|
2528
|
+
}
|
|
2529
|
+
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
2530
|
+
const width = Math.max(1, environmentMap.width);
|
|
2531
|
+
const height = Math.max(1, environmentMap.height);
|
|
2532
|
+
const pixels = new Float32Array(width * height * 4);
|
|
2533
|
+
const data = environmentMap.data;
|
|
2534
|
+
const integerScale = environmentMapIntegerScale(data);
|
|
2535
|
+
for (let index = 0; index < width * height; index += 1) {
|
|
2536
|
+
const sourceOffset = index * 4;
|
|
2537
|
+
const targetOffset = index * 4;
|
|
2538
|
+
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
2539
|
+
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
2540
|
+
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
2541
|
+
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
2542
|
+
}
|
|
2543
|
+
return pixels;
|
|
2544
|
+
}
|
|
2545
|
+
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
2546
|
+
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
2547
|
+
const theta = v * Math.PI;
|
|
2548
|
+
const sinTheta = Math.sin(theta);
|
|
2549
|
+
return [
|
|
2550
|
+
Math.cos(angle) * sinTheta,
|
|
2551
|
+
Math.cos(theta),
|
|
2552
|
+
Math.sin(angle) * sinTheta
|
|
2553
|
+
];
|
|
2554
|
+
}
|
|
2555
|
+
function sampleEnvironmentPixelsBilinear(pixels, width, height, u, v) {
|
|
2556
|
+
const wrappedU = (u % 1 + 1) % 1;
|
|
2557
|
+
const clampedV = clamp(v, 0, 1);
|
|
2558
|
+
const x = wrappedU * width - 0.5;
|
|
2559
|
+
const y = clampedV * height - 0.5;
|
|
2560
|
+
const x0 = (Math.floor(x) % width + width) % width;
|
|
2561
|
+
const y0 = clamp(Math.floor(y), 0, height - 1);
|
|
2562
|
+
const x1 = (x0 + 1) % width;
|
|
2563
|
+
const y1 = clamp(y0 + 1, 0, height - 1);
|
|
2564
|
+
const tx = x - Math.floor(x);
|
|
2565
|
+
const ty = y - Math.floor(y);
|
|
2566
|
+
const read = (px, py) => {
|
|
2567
|
+
const offset = (py * width + px) * 4;
|
|
2568
|
+
return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]];
|
|
2569
|
+
};
|
|
2570
|
+
const a = read(x0, y0);
|
|
2571
|
+
const b = read(x1, y0);
|
|
2572
|
+
const c = read(x0, y1);
|
|
2573
|
+
const d = read(x1, y1);
|
|
2574
|
+
const mixPair = (first, second, factor) => first * (1 - factor) + second * factor;
|
|
2575
|
+
return [
|
|
2576
|
+
mixPair(mixPair(a[0], b[0], tx), mixPair(c[0], d[0], tx), ty),
|
|
2577
|
+
mixPair(mixPair(a[1], b[1], tx), mixPair(c[1], d[1], tx), ty),
|
|
2578
|
+
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
2579
|
+
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty)
|
|
2580
|
+
];
|
|
2581
|
+
}
|
|
2582
|
+
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
2583
|
+
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
2584
|
+
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
2585
|
+
const u = ((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI) + 0.5 + rotationTurns) % 1 + 1) % 1;
|
|
2586
|
+
const v = Math.acos(clamp(unitDirection[1], -1, 1)) / Math.PI;
|
|
2587
|
+
return [u, clamp(v, 0, 1)];
|
|
2588
|
+
}
|
|
2589
|
+
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
2590
|
+
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
2591
|
+
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
2592
|
+
}
|
|
2593
|
+
function createFloat16RgbaUploadFromLevels(levels) {
|
|
2594
|
+
return levels.map((level) => {
|
|
2595
|
+
const rowBytes = level.width * 8;
|
|
2596
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2597
|
+
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
2598
|
+
const view = new DataView(bytes.buffer);
|
|
2599
|
+
for (let y = 0; y < level.height; y += 1) {
|
|
2600
|
+
for (let x = 0; x < level.width; x += 1) {
|
|
2601
|
+
const sourceOffset = (y * level.width + x) * 4;
|
|
2602
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
2603
|
+
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
2604
|
+
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
2605
|
+
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
2606
|
+
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
return Object.freeze({ bytes, bytesPerRow, width: level.width, height: level.height });
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
function createPrefilteredEnvironmentLevels(environmentMap, fallbackColor) {
|
|
2613
|
+
const sourcePixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
2614
|
+
const sourceWidth = Math.max(1, environmentMap.width);
|
|
2615
|
+
const sourceHeight = Math.max(1, environmentMap.height);
|
|
2616
|
+
const mipLevelCount = Math.max(1, Math.floor(Math.log2(Math.max(sourceWidth, sourceHeight))) + 1);
|
|
2617
|
+
const levels = [
|
|
2618
|
+
Object.freeze({
|
|
2619
|
+
width: sourceWidth,
|
|
2620
|
+
height: sourceHeight,
|
|
2621
|
+
data: sourcePixels
|
|
2622
|
+
})
|
|
2623
|
+
];
|
|
2624
|
+
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
2625
|
+
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
2626
|
+
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
2627
|
+
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
2628
|
+
const data = new Float32Array(width * height * 4);
|
|
2629
|
+
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
2630
|
+
for (let y = 0; y < height; y += 1) {
|
|
2631
|
+
for (let x = 0; x < width; x += 1) {
|
|
2632
|
+
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
2633
|
+
const normal = normalize(direction, [0, 1, 0]);
|
|
2634
|
+
const viewDirection = normal;
|
|
2635
|
+
let totalWeight = 0;
|
|
2636
|
+
const accum = [0, 0, 0];
|
|
2637
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
2638
|
+
const xi = hammersley(sampleIndex, sampleCount);
|
|
2639
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2640
|
+
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
2641
|
+
const lightDirection = normalize(
|
|
2642
|
+
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
2643
|
+
normal
|
|
2644
|
+
);
|
|
2645
|
+
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
2646
|
+
if (nDotL <= 1e-6) {
|
|
2647
|
+
continue;
|
|
2648
|
+
}
|
|
2649
|
+
const radiance = sampleEnvironmentRadiance(
|
|
2650
|
+
sourcePixels,
|
|
2651
|
+
sourceWidth,
|
|
2652
|
+
sourceHeight,
|
|
2653
|
+
lightDirection,
|
|
2654
|
+
environmentMap.rotationRadians
|
|
2655
|
+
);
|
|
2656
|
+
accum[0] += radiance[0] * nDotL;
|
|
2657
|
+
accum[1] += radiance[1] * nDotL;
|
|
2658
|
+
accum[2] += radiance[2] * nDotL;
|
|
2659
|
+
totalWeight += nDotL;
|
|
2660
|
+
}
|
|
2661
|
+
const offset = (y * width + x) * 4;
|
|
2662
|
+
data[offset] = accum[0] / Math.max(totalWeight, 1e-6);
|
|
2663
|
+
data[offset + 1] = accum[1] / Math.max(totalWeight, 1e-6);
|
|
2664
|
+
data[offset + 2] = accum[2] / Math.max(totalWeight, 1e-6);
|
|
2665
|
+
data[offset + 3] = 1;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
levels.push(Object.freeze({ width, height, data }));
|
|
2669
|
+
}
|
|
2670
|
+
return Object.freeze({
|
|
2671
|
+
levels,
|
|
2672
|
+
mipLevelCount,
|
|
2673
|
+
width: sourceWidth,
|
|
2674
|
+
height: sourceHeight
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
function createEnvironmentSamplingTables(environmentMap, fallbackColor) {
|
|
2678
|
+
if (!environmentMapHasSamplingData(environmentMap)) {
|
|
2679
|
+
return Object.freeze({
|
|
2680
|
+
width: 1,
|
|
2681
|
+
height: 1,
|
|
2682
|
+
pdf: new Float32Array([1]),
|
|
2683
|
+
marginalCdf: new Float32Array([1]),
|
|
2684
|
+
conditionalCdf: new Float32Array([1]),
|
|
2685
|
+
hasImportanceData: false
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
const pixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
2689
|
+
const width = Math.max(1, environmentMap.width);
|
|
2690
|
+
const height = Math.max(1, environmentMap.height);
|
|
2691
|
+
const pdf = new Float32Array(width * height);
|
|
2692
|
+
const marginalCdf = new Float32Array(height);
|
|
2693
|
+
const conditionalCdf = new Float32Array(width * height);
|
|
2694
|
+
const rowSums = new Float32Array(height);
|
|
2695
|
+
let totalWeight = 0;
|
|
2696
|
+
for (let y = 0; y < height; y += 1) {
|
|
2697
|
+
const theta = (y + 0.5) / height * Math.PI;
|
|
2698
|
+
const sinTheta = Math.max(Math.sin(theta), 1e-4);
|
|
2699
|
+
let rowWeight = 0;
|
|
2700
|
+
for (let x = 0; x < width; x += 1) {
|
|
2701
|
+
const offset = (y * width + x) * 4;
|
|
2702
|
+
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
2703
|
+
const weight = Math.max(luminance * sinTheta, 1e-6);
|
|
2704
|
+
pdf[y * width + x] = weight;
|
|
2705
|
+
rowWeight += weight;
|
|
2706
|
+
conditionalCdf[y * width + x] = rowWeight;
|
|
2707
|
+
}
|
|
2708
|
+
rowSums[y] = rowWeight;
|
|
2709
|
+
totalWeight += rowWeight;
|
|
2710
|
+
if (rowWeight > 0) {
|
|
2711
|
+
for (let x = 0; x < width; x += 1) {
|
|
2712
|
+
conditionalCdf[y * width + x] /= rowWeight;
|
|
2713
|
+
}
|
|
2714
|
+
} else {
|
|
2715
|
+
for (let x = 0; x < width; x += 1) {
|
|
2716
|
+
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
marginalCdf[y] = totalWeight;
|
|
2720
|
+
}
|
|
2721
|
+
for (let y = 0; y < height; y += 1) {
|
|
2722
|
+
marginalCdf[y] /= Math.max(totalWeight, 1e-6);
|
|
2723
|
+
}
|
|
2724
|
+
for (let index = 0; index < pdf.length; index += 1) {
|
|
2725
|
+
pdf[index] /= Math.max(totalWeight, 1e-6);
|
|
2726
|
+
}
|
|
2727
|
+
return Object.freeze({
|
|
2728
|
+
width,
|
|
2729
|
+
height,
|
|
2730
|
+
pdf,
|
|
2731
|
+
marginalCdf,
|
|
2732
|
+
conditionalCdf,
|
|
2733
|
+
hasImportanceData: true
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
1727
2736
|
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
1728
2737
|
if (environmentMap.view) {
|
|
1729
2738
|
return Object.freeze({
|
|
@@ -1733,10 +2742,14 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1733
2742
|
addressModeU: "repeat",
|
|
1734
2743
|
addressModeV: "clamp-to-edge",
|
|
1735
2744
|
magFilter: "linear",
|
|
1736
|
-
minFilter: "linear"
|
|
2745
|
+
minFilter: "linear",
|
|
2746
|
+
mipmapFilter: "linear"
|
|
1737
2747
|
}),
|
|
1738
2748
|
texture: null,
|
|
1739
|
-
ownsTexture: false
|
|
2749
|
+
ownsTexture: false,
|
|
2750
|
+
width: Math.max(1, environmentMap.width),
|
|
2751
|
+
height: Math.max(1, environmentMap.height),
|
|
2752
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
1740
2753
|
});
|
|
1741
2754
|
}
|
|
1742
2755
|
if (environmentMap.texture && typeof environmentMap.texture.createView === "function") {
|
|
@@ -1747,15 +2760,91 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1747
2760
|
addressModeU: "repeat",
|
|
1748
2761
|
addressModeV: "clamp-to-edge",
|
|
1749
2762
|
magFilter: "linear",
|
|
1750
|
-
minFilter: "linear"
|
|
2763
|
+
minFilter: "linear",
|
|
2764
|
+
mipmapFilter: "linear"
|
|
1751
2765
|
}),
|
|
1752
2766
|
texture: environmentMap.texture,
|
|
1753
|
-
ownsTexture: false
|
|
2767
|
+
ownsTexture: false,
|
|
2768
|
+
width: Math.max(1, environmentMap.width),
|
|
2769
|
+
height: Math.max(1, environmentMap.height),
|
|
2770
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
1754
2771
|
});
|
|
1755
2772
|
}
|
|
1756
|
-
const
|
|
2773
|
+
const prefiltered = createPrefilteredEnvironmentLevels(environmentMap, fallbackColor);
|
|
2774
|
+
const uploads = createFloat16RgbaUploadFromLevels(prefiltered.levels);
|
|
1757
2775
|
const texture = device.createTexture({
|
|
1758
2776
|
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
2777
|
+
size: { width: prefiltered.width, height: prefiltered.height },
|
|
2778
|
+
format: "rgba16float",
|
|
2779
|
+
mipLevelCount: prefiltered.mipLevelCount,
|
|
2780
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
2781
|
+
});
|
|
2782
|
+
uploads.forEach((upload, mipLevel) => {
|
|
2783
|
+
device.queue.writeTexture(
|
|
2784
|
+
{ texture, mipLevel },
|
|
2785
|
+
upload.bytes,
|
|
2786
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
2787
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
2788
|
+
);
|
|
2789
|
+
});
|
|
2790
|
+
return Object.freeze({
|
|
2791
|
+
view: texture.createView(),
|
|
2792
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
2793
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
2794
|
+
addressModeU: "repeat",
|
|
2795
|
+
addressModeV: "clamp-to-edge",
|
|
2796
|
+
magFilter: "linear",
|
|
2797
|
+
minFilter: "linear",
|
|
2798
|
+
mipmapFilter: "linear"
|
|
2799
|
+
}),
|
|
2800
|
+
texture,
|
|
2801
|
+
ownsTexture: true,
|
|
2802
|
+
width: prefiltered.width,
|
|
2803
|
+
height: prefiltered.height,
|
|
2804
|
+
mipLevelCount: prefiltered.mipLevelCount
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
function createEnvironmentSamplingTextureResource(device, constants, environmentMap, fallbackColor) {
|
|
2808
|
+
const tables = createEnvironmentSamplingTables(environmentMap, fallbackColor);
|
|
2809
|
+
const rowBytes = tables.width * 8;
|
|
2810
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2811
|
+
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
2812
|
+
const view = new DataView(bytes.buffer);
|
|
2813
|
+
for (let y = 0; y < tables.height; y += 1) {
|
|
2814
|
+
for (let x = 0; x < tables.width; x += 1) {
|
|
2815
|
+
const probability = tables.pdf[y * tables.width + x];
|
|
2816
|
+
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
2817
|
+
const marginal = tables.marginalCdf[y];
|
|
2818
|
+
const offset = y * bytesPerRow + x * 8;
|
|
2819
|
+
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
2820
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
2821
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
2822
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
const texture = device.createTexture({
|
|
2826
|
+
label: "plasius.wavefront.environmentSampling",
|
|
2827
|
+
size: { width: tables.width, height: tables.height },
|
|
2828
|
+
format: "rgba16float",
|
|
2829
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
2830
|
+
});
|
|
2831
|
+
device.queue.writeTexture(
|
|
2832
|
+
{ texture },
|
|
2833
|
+
bytes,
|
|
2834
|
+
{ bytesPerRow, rowsPerImage: tables.height },
|
|
2835
|
+
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
2836
|
+
);
|
|
2837
|
+
return Object.freeze({
|
|
2838
|
+
view: texture.createView(),
|
|
2839
|
+
texture,
|
|
2840
|
+
ownsTexture: true,
|
|
2841
|
+
hasImportanceData: tables.hasImportanceData
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE) {
|
|
2845
|
+
const upload = createBrdfLutUploadBytes(size);
|
|
2846
|
+
const texture = device.createTexture({
|
|
2847
|
+
label: "plasius.wavefront.brdfLut",
|
|
1759
2848
|
size: { width: upload.width, height: upload.height },
|
|
1760
2849
|
format: "rgba16float",
|
|
1761
2850
|
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
@@ -1768,14 +2857,36 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1768
2857
|
);
|
|
1769
2858
|
return Object.freeze({
|
|
1770
2859
|
view: texture.createView(),
|
|
1771
|
-
sampler:
|
|
1772
|
-
label: "plasius.wavefront.
|
|
1773
|
-
addressModeU: "
|
|
2860
|
+
sampler: device.createSampler({
|
|
2861
|
+
label: "plasius.wavefront.brdfLutSampler",
|
|
2862
|
+
addressModeU: "clamp-to-edge",
|
|
1774
2863
|
addressModeV: "clamp-to-edge",
|
|
1775
2864
|
magFilter: "linear",
|
|
1776
2865
|
minFilter: "linear"
|
|
1777
2866
|
}),
|
|
1778
2867
|
texture,
|
|
2868
|
+
ownsTexture: true,
|
|
2869
|
+
width: upload.width,
|
|
2870
|
+
height: upload.height
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
2874
|
+
const upload = createRgba8TextureUpload(atlas);
|
|
2875
|
+
const texture = device.createTexture({
|
|
2876
|
+
label,
|
|
2877
|
+
size: { width: upload.width, height: upload.height },
|
|
2878
|
+
format: "rgba8unorm",
|
|
2879
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
2880
|
+
});
|
|
2881
|
+
device.queue.writeTexture(
|
|
2882
|
+
{ texture },
|
|
2883
|
+
upload.bytes,
|
|
2884
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
2885
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
2886
|
+
);
|
|
2887
|
+
return Object.freeze({
|
|
2888
|
+
texture,
|
|
2889
|
+
view: texture.createView(),
|
|
1779
2890
|
ownsTexture: true
|
|
1780
2891
|
});
|
|
1781
2892
|
}
|
|
@@ -1821,6 +2932,24 @@ ${diagnostics}` : "";
|
|
|
1821
2932
|
});
|
|
1822
2933
|
}
|
|
1823
2934
|
}
|
|
2935
|
+
async function assertShaderModuleCompiles(shaderModule, label) {
|
|
2936
|
+
if (typeof shaderModule?.compilationInfo !== "function") {
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
const info = await shaderModule.compilationInfo();
|
|
2940
|
+
const messages = Array.isArray(info?.messages) ? info.messages : [];
|
|
2941
|
+
const errors = messages.filter((message) => message?.type === "error");
|
|
2942
|
+
if (errors.length <= 0) {
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
const diagnostics = errors.map((message) => {
|
|
2946
|
+
const line = Number.isFinite(message.lineNum) ? message.lineNum : "?";
|
|
2947
|
+
const column = Number.isFinite(message.linePos) ? message.linePos : "?";
|
|
2948
|
+
return `line ${line}:${column} ${message.message}`;
|
|
2949
|
+
}).join("\n");
|
|
2950
|
+
throw new Error(`WGSL compilation preflight failed for ${label}:
|
|
2951
|
+
${diagnostics}`);
|
|
2952
|
+
}
|
|
1824
2953
|
async function createRenderPipeline(device, descriptor) {
|
|
1825
2954
|
if (typeof device.createRenderPipelineAsync === "function") {
|
|
1826
2955
|
return device.createRenderPipelineAsync(descriptor);
|
|
@@ -1829,6 +2958,7 @@ async function createRenderPipeline(device, descriptor) {
|
|
|
1829
2958
|
}
|
|
1830
2959
|
var WAVEFRONT_COMPUTE_WGSL = `
|
|
1831
2960
|
const RAY_FLAG_GUIDED_EMISSIVE: u32 = 1u;
|
|
2961
|
+
const RAY_FLAG_DELTA_SAMPLE: u32 = 2u;
|
|
1832
2962
|
|
|
1833
2963
|
struct RayRecord {
|
|
1834
2964
|
rayId: u32,
|
|
@@ -1854,11 +2984,12 @@ struct HitRecord {
|
|
|
1854
2984
|
primitiveId: u32,
|
|
1855
2985
|
materialRefId: u32,
|
|
1856
2986
|
mediumRefId: u32,
|
|
2987
|
+
materialSlot: u32,
|
|
1857
2988
|
pad0: u32,
|
|
1858
2989
|
pad1: u32,
|
|
1859
|
-
pad2: u32,
|
|
1860
2990
|
distance: f32,
|
|
1861
|
-
|
|
2991
|
+
occlusion: f32,
|
|
2992
|
+
pad2: vec2<f32>,
|
|
1862
2993
|
position: vec4<f32>,
|
|
1863
2994
|
geometricNormal: vec4<f32>,
|
|
1864
2995
|
shadingNormal: vec4<f32>,
|
|
@@ -1867,6 +2998,9 @@ struct HitRecord {
|
|
|
1867
2998
|
color: vec4<f32>,
|
|
1868
2999
|
emission: vec4<f32>,
|
|
1869
3000
|
material: vec4<f32>,
|
|
3001
|
+
materialResponse: vec4<f32>,
|
|
3002
|
+
materialExtension: vec4<f32>,
|
|
3003
|
+
specularColor: vec4<f32>,
|
|
1870
3004
|
};
|
|
1871
3005
|
|
|
1872
3006
|
struct SceneObject {
|
|
@@ -1879,6 +3013,9 @@ struct SceneObject {
|
|
|
1879
3013
|
color: vec4<f32>,
|
|
1880
3014
|
emission: vec4<f32>,
|
|
1881
3015
|
material: vec4<f32>,
|
|
3016
|
+
materialResponse: vec4<f32>,
|
|
3017
|
+
materialExtension: vec4<f32>,
|
|
3018
|
+
specularColor: vec4<f32>,
|
|
1882
3019
|
};
|
|
1883
3020
|
|
|
1884
3021
|
struct TriangleRecord {
|
|
@@ -1888,7 +3025,7 @@ struct TriangleRecord {
|
|
|
1888
3025
|
flags: u32,
|
|
1889
3026
|
materialRefId: u32,
|
|
1890
3027
|
mediumRefId: u32,
|
|
1891
|
-
|
|
3028
|
+
materialSlot: u32,
|
|
1892
3029
|
pad1: u32,
|
|
1893
3030
|
v0: vec4<f32>,
|
|
1894
3031
|
v1: vec4<f32>,
|
|
@@ -1901,6 +3038,15 @@ struct TriangleRecord {
|
|
|
1901
3038
|
color: vec4<f32>,
|
|
1902
3039
|
emission: vec4<f32>,
|
|
1903
3040
|
material: vec4<f32>,
|
|
3041
|
+
materialResponse: vec4<f32>,
|
|
3042
|
+
materialExtension: vec4<f32>,
|
|
3043
|
+
specularColor: vec4<f32>,
|
|
3044
|
+
baseColorAtlas: vec4<f32>,
|
|
3045
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3046
|
+
normalAtlas: vec4<f32>,
|
|
3047
|
+
occlusionAtlas: vec4<f32>,
|
|
3048
|
+
emissiveAtlas: vec4<f32>,
|
|
3049
|
+
textureSettings: vec4<f32>,
|
|
1904
3050
|
};
|
|
1905
3051
|
|
|
1906
3052
|
struct BvhNode {
|
|
@@ -1921,10 +3067,10 @@ struct BvhLeafRef {
|
|
|
1921
3067
|
|
|
1922
3068
|
struct ScatterResult {
|
|
1923
3069
|
direction: vec4<f32>,
|
|
3070
|
+
pdf: f32,
|
|
1924
3071
|
flags: u32,
|
|
1925
3072
|
pad0: u32,
|
|
1926
3073
|
pad1: u32,
|
|
1927
|
-
pad2: u32,
|
|
1928
3074
|
};
|
|
1929
3075
|
|
|
1930
3076
|
struct MeshVertex {
|
|
@@ -1945,10 +3091,19 @@ struct MeshRange {
|
|
|
1945
3091
|
triangleCount: u32,
|
|
1946
3092
|
firstVertex: u32,
|
|
1947
3093
|
vertexCount: u32,
|
|
1948
|
-
|
|
3094
|
+
materialSlot: u32,
|
|
1949
3095
|
color: vec4<f32>,
|
|
1950
3096
|
emission: vec4<f32>,
|
|
1951
3097
|
material: vec4<f32>,
|
|
3098
|
+
materialResponse: vec4<f32>,
|
|
3099
|
+
materialExtension: vec4<f32>,
|
|
3100
|
+
specularColor: vec4<f32>,
|
|
3101
|
+
baseColorAtlas: vec4<f32>,
|
|
3102
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3103
|
+
normalAtlas: vec4<f32>,
|
|
3104
|
+
occlusionAtlas: vec4<f32>,
|
|
3105
|
+
emissiveAtlas: vec4<f32>,
|
|
3106
|
+
textureSettings: vec4<f32>,
|
|
1952
3107
|
};
|
|
1953
3108
|
|
|
1954
3109
|
struct FrameConfig {
|
|
@@ -1989,6 +3144,7 @@ struct FrameConfig {
|
|
|
1989
3144
|
_portalPad1: u32,
|
|
1990
3145
|
environmentMapSettings: vec4<f32>,
|
|
1991
3146
|
pathResolveSettings: vec4<f32>,
|
|
3147
|
+
environmentMapMeta: vec4<f32>,
|
|
1992
3148
|
};
|
|
1993
3149
|
|
|
1994
3150
|
struct Counters {
|
|
@@ -2051,6 +3207,15 @@ struct EnvironmentPortal {
|
|
|
2051
3207
|
@group(0) @binding(20) var environmentMapTexture: texture_2d<f32>;
|
|
2052
3208
|
@group(0) @binding(21) var environmentMapSampler: sampler;
|
|
2053
3209
|
@group(0) @binding(22) var<storage, read_write> pathVertices: array<vec4<f32>>;
|
|
3210
|
+
@group(0) @binding(23) var baseColorAtlasTexture: texture_2d<f32>;
|
|
3211
|
+
@group(0) @binding(24) var metallicRoughnessAtlasTexture: texture_2d<f32>;
|
|
3212
|
+
@group(0) @binding(25) var normalAtlasTexture: texture_2d<f32>;
|
|
3213
|
+
@group(0) @binding(26) var occlusionAtlasTexture: texture_2d<f32>;
|
|
3214
|
+
@group(0) @binding(27) var emissiveAtlasTexture: texture_2d<f32>;
|
|
3215
|
+
@group(0) @binding(28) var materialAtlasSampler: sampler;
|
|
3216
|
+
@group(0) @binding(29) var brdfLutTexture: texture_2d<f32>;
|
|
3217
|
+
@group(0) @binding(30) var brdfLutSampler: sampler;
|
|
3218
|
+
@group(0) @binding(31) var environmentSamplingTexture: texture_2d<f32>;
|
|
2054
3219
|
|
|
2055
3220
|
fn hash_u32(value: u32) -> u32 {
|
|
2056
3221
|
var x = value;
|
|
@@ -2087,6 +3252,146 @@ fn safe_normalize(value: vec3<f32>, fallback: vec3<f32>) -> vec3<f32> {
|
|
|
2087
3252
|
return value / len;
|
|
2088
3253
|
}
|
|
2089
3254
|
|
|
3255
|
+
struct TangentBasis {
|
|
3256
|
+
tangent: vec3<f32>,
|
|
3257
|
+
bitangent: vec3<f32>,
|
|
3258
|
+
};
|
|
3259
|
+
|
|
3260
|
+
struct SurfaceMaterialSample {
|
|
3261
|
+
color: vec4<f32>,
|
|
3262
|
+
emission: vec4<f32>,
|
|
3263
|
+
material: vec4<f32>,
|
|
3264
|
+
materialResponse: vec4<f32>,
|
|
3265
|
+
materialExtension: vec4<f32>,
|
|
3266
|
+
specularColor: vec4<f32>,
|
|
3267
|
+
shadingNormal: vec3<f32>,
|
|
3268
|
+
occlusion: f32,
|
|
3269
|
+
};
|
|
3270
|
+
|
|
3271
|
+
fn srgb_to_linear_channel(value: f32) -> f32 {
|
|
3272
|
+
if (value <= 0.04045) {
|
|
3273
|
+
return value / 12.92;
|
|
3274
|
+
}
|
|
3275
|
+
return pow((value + 0.055) / 1.055, 2.4);
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
fn srgb_to_linear_vec3(value: vec3<f32>) -> vec3<f32> {
|
|
3279
|
+
return vec3<f32>(
|
|
3280
|
+
srgb_to_linear_channel(value.x),
|
|
3281
|
+
srgb_to_linear_channel(value.y),
|
|
3282
|
+
srgb_to_linear_channel(value.z)
|
|
3283
|
+
);
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
fn wrap_uv(uv: vec2<f32>) -> vec2<f32> {
|
|
3287
|
+
return fract(fract(uv) + vec2<f32>(1.0));
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
fn atlas_sample_uv(rect: vec4<f32>, uv: vec2<f32>) -> vec2<f32> {
|
|
3291
|
+
let local = wrap_uv(uv);
|
|
3292
|
+
let clamped = clamp(local, vec2<f32>(0.001), vec2<f32>(0.999));
|
|
3293
|
+
return rect.xy + clamped * rect.zw;
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
fn sample_atlas(textureRef: texture_2d<f32>, rect: vec4<f32>, uv: vec2<f32>) -> vec4<f32> {
|
|
3297
|
+
return textureSampleLevel(textureRef, materialAtlasSampler, atlas_sample_uv(rect, uv), 0.0);
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
fn build_triangle_tangent_basis(
|
|
3301
|
+
triangle: TriangleRecord,
|
|
3302
|
+
fallbackNormal: vec3<f32>
|
|
3303
|
+
) -> TangentBasis {
|
|
3304
|
+
let edge1 = triangle.v1.xyz - triangle.v0.xyz;
|
|
3305
|
+
let edge2 = triangle.v2.xyz - triangle.v0.xyz;
|
|
3306
|
+
let uv0 = triangle.uv0uv1.xy;
|
|
3307
|
+
let uv1 = triangle.uv0uv1.zw;
|
|
3308
|
+
let uv2 = triangle.uv2Pad.xy;
|
|
3309
|
+
let deltaUv1 = uv1 - uv0;
|
|
3310
|
+
let deltaUv2 = uv2 - uv0;
|
|
3311
|
+
let determinant = deltaUv1.x * deltaUv2.y - deltaUv1.y * deltaUv2.x;
|
|
3312
|
+
if (abs(determinant) <= 0.000001) {
|
|
3313
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(fallbackNormal.y) >= 0.999);
|
|
3314
|
+
let tangent = safe_normalize(cross(tangentFallback, fallbackNormal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3315
|
+
let bitangent = safe_normalize(cross(fallbackNormal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3316
|
+
return TangentBasis(tangent, bitangent);
|
|
3317
|
+
}
|
|
3318
|
+
let inverse = 1.0 / determinant;
|
|
3319
|
+
let tangent = safe_normalize(
|
|
3320
|
+
inverse * (edge1 * deltaUv2.y - edge2 * deltaUv1.y),
|
|
3321
|
+
vec3<f32>(1.0, 0.0, 0.0)
|
|
3322
|
+
);
|
|
3323
|
+
let bitangent = safe_normalize(
|
|
3324
|
+
inverse * (-edge1 * deltaUv2.x + edge2 * deltaUv1.x),
|
|
3325
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3326
|
+
);
|
|
3327
|
+
return TangentBasis(tangent, bitangent);
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
fn sample_surface_material(
|
|
3331
|
+
triangle: TriangleRecord,
|
|
3332
|
+
uv: vec2<f32>,
|
|
3333
|
+
geometricNormal: vec3<f32>,
|
|
3334
|
+
shadingNormal: vec3<f32>
|
|
3335
|
+
) -> SurfaceMaterialSample {
|
|
3336
|
+
let baseColorTexel = sample_atlas(baseColorAtlasTexture, triangle.baseColorAtlas, uv);
|
|
3337
|
+
let baseColor = vec4<f32>(
|
|
3338
|
+
clamp(triangle.color.rgb * srgb_to_linear_vec3(baseColorTexel.rgb), vec3<f32>(0.0), vec3<f32>(1.0)),
|
|
3339
|
+
clamp(triangle.color.a * baseColorTexel.a, 0.0, 1.0)
|
|
3340
|
+
);
|
|
3341
|
+
let metallicRoughnessTexel = sample_atlas(
|
|
3342
|
+
metallicRoughnessAtlasTexture,
|
|
3343
|
+
triangle.metallicRoughnessAtlas,
|
|
3344
|
+
uv
|
|
3345
|
+
);
|
|
3346
|
+
let normalTexel = sample_atlas(normalAtlasTexture, triangle.normalAtlas, uv);
|
|
3347
|
+
let occlusionTexel = sample_atlas(occlusionAtlasTexture, triangle.occlusionAtlas, uv);
|
|
3348
|
+
let emissiveTexel = sample_atlas(emissiveAtlasTexture, triangle.emissiveAtlas, uv);
|
|
3349
|
+
let normalScale = clamp(triangle.textureSettings.x, 0.0, 1.0);
|
|
3350
|
+
let tangentBasis = build_triangle_tangent_basis(triangle, geometricNormal);
|
|
3351
|
+
let tangentNormal = safe_normalize(
|
|
3352
|
+
vec3<f32>(
|
|
3353
|
+
(normalTexel.x * 2.0 - 1.0) * normalScale,
|
|
3354
|
+
(normalTexel.y * 2.0 - 1.0) * normalScale,
|
|
3355
|
+
1.0 + ((normalTexel.z * 2.0 - 1.0) - 1.0) * normalScale
|
|
3356
|
+
),
|
|
3357
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3358
|
+
);
|
|
3359
|
+
let mappedNormal = safe_normalize(
|
|
3360
|
+
tangentBasis.tangent * tangentNormal.x +
|
|
3361
|
+
tangentBasis.bitangent * tangentNormal.y +
|
|
3362
|
+
shadingNormal * tangentNormal.z,
|
|
3363
|
+
shadingNormal
|
|
3364
|
+
);
|
|
3365
|
+
let emission = vec4<f32>(
|
|
3366
|
+
max(
|
|
3367
|
+
triangle.emission.rgb *
|
|
3368
|
+
srgb_to_linear_vec3(emissiveTexel.rgb) *
|
|
3369
|
+
max(triangle.textureSettings.z, 0.0),
|
|
3370
|
+
vec3<f32>(0.0)
|
|
3371
|
+
),
|
|
3372
|
+
clamp(triangle.emission.a * emissiveTexel.a, 0.0, 1.0)
|
|
3373
|
+
);
|
|
3374
|
+
return SurfaceMaterialSample(
|
|
3375
|
+
baseColor,
|
|
3376
|
+
emission,
|
|
3377
|
+
vec4<f32>(
|
|
3378
|
+
clamp(triangle.material.x * metallicRoughnessTexel.y, 0.0, 1.0),
|
|
3379
|
+
clamp(triangle.material.y * metallicRoughnessTexel.z, 0.0, 1.0),
|
|
3380
|
+
clamp(triangle.material.z * baseColor.a, 0.0, 1.0),
|
|
3381
|
+
clamp(triangle.material.w, 1.0, 3.0)
|
|
3382
|
+
),
|
|
3383
|
+
triangle.materialResponse,
|
|
3384
|
+
triangle.materialExtension,
|
|
3385
|
+
triangle.specularColor,
|
|
3386
|
+
repair_shading_normal(geometricNormal, mappedNormal),
|
|
3387
|
+
clamp(
|
|
3388
|
+
mix(1.0, occlusionTexel.x, clamp(triangle.textureSettings.y, 0.0, 1.0)),
|
|
3389
|
+
0.0,
|
|
3390
|
+
1.0
|
|
3391
|
+
)
|
|
3392
|
+
);
|
|
3393
|
+
}
|
|
3394
|
+
|
|
2090
3395
|
fn saturate(value: f32) -> f32 {
|
|
2091
3396
|
return clamp(value, 0.0, 1.0);
|
|
2092
3397
|
}
|
|
@@ -2095,6 +3400,10 @@ fn max_component(value: vec3<f32>) -> f32 {
|
|
|
2095
3400
|
return max(max(value.x, value.y), value.z);
|
|
2096
3401
|
}
|
|
2097
3402
|
|
|
3403
|
+
fn radiance_luminance(value: vec3<f32>) -> f32 {
|
|
3404
|
+
return dot(value, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
3405
|
+
}
|
|
3406
|
+
|
|
2098
3407
|
fn environment_map_enabled() -> bool {
|
|
2099
3408
|
return config.environmentMapSettings.x > 0.5;
|
|
2100
3409
|
}
|
|
@@ -2223,6 +3532,343 @@ fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
|
2223
3532
|
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
2224
3533
|
}
|
|
2225
3534
|
|
|
3535
|
+
fn direct_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
3536
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3537
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
3538
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
3539
|
+
if (
|
|
3540
|
+
config.environmentPortalCount > 0u &&
|
|
3541
|
+
config.environmentPortalMode == 2u &&
|
|
3542
|
+
!portalHit
|
|
3543
|
+
) {
|
|
3544
|
+
return vec3<f32>(0.0);
|
|
3545
|
+
}
|
|
3546
|
+
return base_environment_radiance(rayDirection) *
|
|
3547
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
fn radical_inverse_vdc(bitsValue: u32) -> f32 {
|
|
3551
|
+
var bits = bitsValue;
|
|
3552
|
+
bits = (bits << 16u) | (bits >> 16u);
|
|
3553
|
+
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xaaaaaaaau) >> 1u);
|
|
3554
|
+
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xccccccccu) >> 2u);
|
|
3555
|
+
bits = ((bits & 0x0f0f0f0fu) << 4u) | ((bits & 0xf0f0f0f0u) >> 4u);
|
|
3556
|
+
bits = ((bits & 0x00ff00ffu) << 8u) | ((bits & 0xff00ff00u) >> 8u);
|
|
3557
|
+
return f32(bits) * 2.3283064365386963e-10;
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
fn hammersley_2d(index: u32, count: u32) -> vec2<f32> {
|
|
3561
|
+
return vec2<f32>(f32(index) / max(f32(count), 1.0), radical_inverse_vdc(index));
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
fn build_basis_tangent(normal: vec3<f32>) -> vec3<f32> {
|
|
3565
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(normal.y) >= 0.999);
|
|
3566
|
+
return safe_normalize(cross(tangentFallback, normal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
fn local_to_world(local: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
3570
|
+
let tangent = build_basis_tangent(normal);
|
|
3571
|
+
let bitangent = safe_normalize(cross(normal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3572
|
+
return safe_normalize(tangent * local.x + bitangent * local.y + normal * local.z, normal);
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
fn cosine_sample_hemisphere(sample: vec2<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
3576
|
+
let phi = 6.28318530718 * sample.x;
|
|
3577
|
+
let radius = sqrt(sample.y);
|
|
3578
|
+
let x = cos(phi) * radius;
|
|
3579
|
+
let y = sin(phi) * radius;
|
|
3580
|
+
let z = sqrt(max(0.0, 1.0 - sample.y));
|
|
3581
|
+
return local_to_world(vec3<f32>(x, y, z), normal);
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
fn importance_sample_ggx(sample: vec2<f32>, roughness: f32, normal: vec3<f32>) -> vec3<f32> {
|
|
3585
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
3586
|
+
let phi = 6.28318530718 * sample.x;
|
|
3587
|
+
let cosTheta = sqrt((1.0 - sample.y) / max(1.0 + (alpha * alpha - 1.0) * sample.y, 0.0001));
|
|
3588
|
+
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3589
|
+
let localHalf = vec3<f32>(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
|
3590
|
+
return local_to_world(localHalf, normal);
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
fn distribution_ggx(normal: vec3<f32>, halfVector: vec3<f32>, roughness: f32) -> f32 {
|
|
3594
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
3595
|
+
let alpha2 = alpha * alpha;
|
|
3596
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
3597
|
+
let denominator = nDotH * nDotH * (alpha2 - 1.0) + 1.0;
|
|
3598
|
+
return alpha2 / max(3.14159265359 * denominator * denominator, 0.000001);
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
fn geometry_schlick_ggx(nDotValue: f32, roughness: f32) -> f32 {
|
|
3602
|
+
let k = ((roughness + 1.0) * (roughness + 1.0)) / 8.0;
|
|
3603
|
+
return nDotValue / max(nDotValue * (1.0 - k) + k, 0.000001);
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
fn geometry_smith(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
3607
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
3608
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
3609
|
+
return geometry_schlick_ggx(nDotV, roughness) * geometry_schlick_ggx(nDotL, roughness);
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
fn fresnel_schlick(cosine: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
3613
|
+
return f0 + (vec3<f32>(1.0) - f0) * pow(1.0 - cosine, 5.0);
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
fn sample_brdf_lut(nDotV: f32, roughness: f32) -> vec2<f32> {
|
|
3617
|
+
let uv = vec2<f32>(clamp(nDotV, 0.0, 1.0), clamp(roughness, 0.0, 1.0));
|
|
3618
|
+
return textureSampleLevel(brdfLutTexture, brdfLutSampler, uv, 0.0).xy;
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
fn prefiltered_environment_radiance(direction: vec3<f32>, roughness: f32) -> vec3<f32> {
|
|
3622
|
+
let uv = environment_map_uv(direction);
|
|
3623
|
+
let maxLevel = max(config.environmentMapMeta.z - 1.0, 0.0);
|
|
3624
|
+
let lod = clamp(roughness, 0.0, 1.0) * maxLevel;
|
|
3625
|
+
let texel = max(textureSampleLevel(environmentMapTexture, environmentMapSampler, uv, lod).rgb, vec3<f32>(0.0));
|
|
3626
|
+
return texel * max(config.environmentMapSettings.y, 0.0);
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3629
|
+
fn environment_pdf_dimensions() -> vec2<u32> {
|
|
3630
|
+
return vec2<u32>(
|
|
3631
|
+
max(u32(config.environmentMapMeta.x), 1u),
|
|
3632
|
+
max(u32(config.environmentMapMeta.y), 1u)
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
fn environment_importance_sampling_enabled() -> bool {
|
|
3637
|
+
return config.environmentMapMeta.w > 0.5;
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
fn uniform_sphere_pdf() -> f32 {
|
|
3641
|
+
return 1.0 / (4.0 * 3.14159265359);
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
fn sample_uniform_sphere_direction(sample: vec2<f32>) -> vec3<f32> {
|
|
3645
|
+
let z = 1.0 - 2.0 * sample.y;
|
|
3646
|
+
let radial = sqrt(max(1.0 - z * z, 0.0));
|
|
3647
|
+
let phi = sample.x * 6.28318530718;
|
|
3648
|
+
return vec3<f32>(cos(phi) * radial, z, sin(phi) * radial);
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
fn environment_sampling_texel(x: u32, y: u32) -> vec4<f32> {
|
|
3652
|
+
return textureLoad(environmentSamplingTexture, vec2<i32>(i32(x), i32(y)), 0);
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
fn environment_pdf_texel(x: u32, y: u32) -> f32 {
|
|
3656
|
+
return environment_sampling_texel(x, y).x;
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
fn environment_row_cdf_texel(y: u32) -> f32 {
|
|
3660
|
+
return environment_sampling_texel(0u, y).z;
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
fn environment_column_cdf_texel(x: u32, y: u32) -> f32 {
|
|
3664
|
+
return environment_sampling_texel(x, y).y;
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
fn environment_direction_pdf(direction: vec3<f32>) -> f32 {
|
|
3668
|
+
if (!environment_importance_sampling_enabled()) {
|
|
3669
|
+
return uniform_sphere_pdf();
|
|
3670
|
+
}
|
|
3671
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3672
|
+
let uv = environment_map_uv(rayDirection);
|
|
3673
|
+
let dimensions = environment_pdf_dimensions();
|
|
3674
|
+
let width = max(f32(dimensions.x), 1.0);
|
|
3675
|
+
let height = max(f32(dimensions.y), 1.0);
|
|
3676
|
+
let x = min(u32(uv.x * width), dimensions.x - 1u);
|
|
3677
|
+
let y = min(u32(uv.y * height), dimensions.y - 1u);
|
|
3678
|
+
let discretePdf = max(environment_pdf_texel(x, y), 0.0);
|
|
3679
|
+
let sinTheta = sqrt(max(1.0 - rayDirection.y * rayDirection.y, 0.0));
|
|
3680
|
+
let solidAngle = max((2.0 * 3.14159265359 * 3.14159265359 * sinTheta) / (width * height), 0.000001);
|
|
3681
|
+
return discretePdf / solidAngle;
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
fn sample_row_cdf(count: u32, sampleValue: f32) -> u32 {
|
|
3685
|
+
if (count == 0u) {
|
|
3686
|
+
return 0u;
|
|
3687
|
+
}
|
|
3688
|
+
var low = 0u;
|
|
3689
|
+
var high = count - 1u;
|
|
3690
|
+
loop {
|
|
3691
|
+
if (low >= high) {
|
|
3692
|
+
break;
|
|
3693
|
+
}
|
|
3694
|
+
let mid = (low + high) / 2u;
|
|
3695
|
+
let cdfValue = environment_row_cdf_texel(mid);
|
|
3696
|
+
if (sampleValue <= cdfValue) {
|
|
3697
|
+
high = mid;
|
|
3698
|
+
} else {
|
|
3699
|
+
low = mid + 1u;
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
return min(low, count - 1u);
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
fn sample_column_cdf(row: u32, count: u32, sampleValue: f32) -> u32 {
|
|
3706
|
+
if (count == 0u) {
|
|
3707
|
+
return 0u;
|
|
3708
|
+
}
|
|
3709
|
+
var low = 0u;
|
|
3710
|
+
var high = count - 1u;
|
|
3711
|
+
loop {
|
|
3712
|
+
if (low >= high) {
|
|
3713
|
+
break;
|
|
3714
|
+
}
|
|
3715
|
+
let mid = (low + high) / 2u;
|
|
3716
|
+
let cdfValue = environment_column_cdf_texel(mid, row);
|
|
3717
|
+
if (sampleValue <= cdfValue) {
|
|
3718
|
+
high = mid;
|
|
3719
|
+
} else {
|
|
3720
|
+
low = mid + 1u;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
return min(low, count - 1u);
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3726
|
+
struct EnvironmentSample {
|
|
3727
|
+
direction: vec3<f32>,
|
|
3728
|
+
radiance: vec3<f32>,
|
|
3729
|
+
pdf: f32,
|
|
3730
|
+
};
|
|
3731
|
+
|
|
3732
|
+
fn sample_environment_importance(sample: vec2<f32>) -> EnvironmentSample {
|
|
3733
|
+
if (!environment_importance_sampling_enabled()) {
|
|
3734
|
+
let direction = sample_uniform_sphere_direction(sample);
|
|
3735
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), uniform_sphere_pdf());
|
|
3736
|
+
}
|
|
3737
|
+
let dimensions = environment_pdf_dimensions();
|
|
3738
|
+
let row = sample_row_cdf(dimensions.y, sample.y);
|
|
3739
|
+
let column = sample_column_cdf(row, dimensions.x, sample.x);
|
|
3740
|
+
let uv = vec2<f32>(
|
|
3741
|
+
(f32(column) + 0.5) / max(f32(dimensions.x), 1.0),
|
|
3742
|
+
(f32(row) + 0.5) / max(f32(dimensions.y), 1.0)
|
|
3743
|
+
);
|
|
3744
|
+
let theta = uv.y * 3.14159265359;
|
|
3745
|
+
let phi = (uv.x - 0.5 - config.environmentMapSettings.z / 6.28318530718) * 6.28318530718;
|
|
3746
|
+
let sinTheta = sin(theta);
|
|
3747
|
+
let direction = vec3<f32>(cos(phi) * sinTheta, cos(theta), sin(phi) * sinTheta);
|
|
3748
|
+
let pdf = environment_direction_pdf(direction);
|
|
3749
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), pdf);
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
fn power_heuristic(pdfA: f32, pdfB: f32) -> f32 {
|
|
3753
|
+
let a2 = pdfA * pdfA;
|
|
3754
|
+
let b2 = pdfB * pdfB;
|
|
3755
|
+
return a2 / max(a2 + b2, 0.000001);
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
fn visible_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
3759
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3760
|
+
let visible = !scene_visibility_blocked(origin, rayDirection, 1000000.0);
|
|
3761
|
+
return select(vec3<f32>(0.0), direct_environment_radiance(origin, rayDirection), visible);
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
fn glossy_environment_direction(
|
|
3765
|
+
incidentDirection: vec3<f32>,
|
|
3766
|
+
normal: vec3<f32>,
|
|
3767
|
+
roughness: f32,
|
|
3768
|
+
normalBlendScale: f32
|
|
3769
|
+
) -> vec3<f32> {
|
|
3770
|
+
let reflectionDirection = reflect(incidentDirection, normal);
|
|
3771
|
+
let blend = clamp(roughness * roughness * normalBlendScale, 0.0, 0.92);
|
|
3772
|
+
return safe_normalize(mix(reflectionDirection, normal, blend), normal);
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
fn surface_glossiness(hit: HitRecord) -> f32 {
|
|
3776
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3777
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
3778
|
+
let sheen = clamp(max_component(hit.materialResponse.xyz), 0.0, 1.0);
|
|
3779
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
3780
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
3781
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
3782
|
+
let baseGloss =
|
|
3783
|
+
max(
|
|
3784
|
+
clearcoat,
|
|
3785
|
+
max(sheen * 0.72, max(specularWeight * (0.38 + metallic * 0.62), transmission))
|
|
3786
|
+
);
|
|
3787
|
+
return clamp(baseGloss * (1.0 - roughness * 0.72) + metallic * (1.0 - roughness) * 0.35, 0.0, 1.0);
|
|
3788
|
+
}
|
|
3789
|
+
|
|
3790
|
+
fn surface_specular_f0(hit: HitRecord, surfaceColor: vec3<f32>) -> vec3<f32> {
|
|
3791
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
3792
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
3793
|
+
let specularColor = clamp(hit.specularColor.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
3794
|
+
let dielectricF0 = vec3<f32>(0.04) * specularWeight * specularColor;
|
|
3795
|
+
return mix(dielectricF0, surfaceColor, metallic);
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
fn surface_bsdf_sampling_weights(hit: HitRecord) -> vec3<f32> {
|
|
3799
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
3800
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
3801
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
3802
|
+
let diffuseWeight = clamp(
|
|
3803
|
+
(1.0 - metallic) * max(1.0 - specularWeight * 0.5 - clearcoat * 0.25, 0.15),
|
|
3804
|
+
0.0,
|
|
3805
|
+
1.0
|
|
3806
|
+
);
|
|
3807
|
+
let specWeight = clamp(max(metallic, specularWeight * 0.75) * (1.0 - clearcoat * 0.5), 0.0, 1.0);
|
|
3808
|
+
let clearcoatWeight = clamp(clearcoat, 0.0, 1.0);
|
|
3809
|
+
let totalWeight = max(diffuseWeight + specWeight + clearcoatWeight, 0.000001);
|
|
3810
|
+
return vec3<f32>(
|
|
3811
|
+
diffuseWeight / totalWeight,
|
|
3812
|
+
specWeight / totalWeight,
|
|
3813
|
+
clearcoatWeight / totalWeight
|
|
3814
|
+
);
|
|
3815
|
+
}
|
|
3816
|
+
|
|
3817
|
+
fn evaluate_surface_bsdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> vec3<f32> {
|
|
3818
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
3819
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
3820
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3821
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
3822
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
3823
|
+
let clearcoatRoughness = clamp(hit.materialExtension.x, 0.0, 1.0);
|
|
3824
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
3825
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
3826
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
3827
|
+
if (nDotV <= 0.0 || nDotL <= 0.0) {
|
|
3828
|
+
return vec3<f32>(0.0);
|
|
3829
|
+
}
|
|
3830
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
3831
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
3832
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
3833
|
+
let fresnel = fresnel_schlick(vDotH, f0);
|
|
3834
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
3835
|
+
let geometry = geometry_smith(normal, viewDirection, lightDirection, roughness);
|
|
3836
|
+
let specular = (distribution * geometry * fresnel) / max(4.0 * nDotV * nDotL, 0.000001);
|
|
3837
|
+
let diffuseWeight = (1.0 - metallic) * (1.0 - clearcoat * 0.24) * (1.0 - clamp(max_component(fresnel), 0.0, 0.98));
|
|
3838
|
+
let diffuse = surfaceColor * diffuseWeight / 3.14159265359;
|
|
3839
|
+
let clearcoatHalf = safe_normalize(viewDirection + lightDirection, normal);
|
|
3840
|
+
let clearcoatDistribution = distribution_ggx(normal, clearcoatHalf, max(clearcoatRoughness, 0.02));
|
|
3841
|
+
let clearcoatGeometry = geometry_smith(normal, viewDirection, lightDirection, max(clearcoatRoughness, 0.02));
|
|
3842
|
+
let clearcoatFresnel = fresnel_schlick(saturate(dot(viewDirection, clearcoatHalf)), vec3<f32>(0.04));
|
|
3843
|
+
let clearcoatTerm =
|
|
3844
|
+
(clearcoatDistribution * clearcoatGeometry * clearcoatFresnel) /
|
|
3845
|
+
max(4.0 * nDotV * nDotL, 0.000001) *
|
|
3846
|
+
clearcoat;
|
|
3847
|
+
return (diffuse + specular + clearcoatTerm) * mix(0.42, 1.0, occlusion);
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
fn diffuse_pdf(normal: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
3851
|
+
return saturate(dot(normal, lightDirection)) / 3.14159265359;
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
fn ggx_pdf(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
3855
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
3856
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
3857
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
3858
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
3859
|
+
return (distribution * nDotH) / max(4.0 * vDotH, 0.000001);
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
fn evaluate_surface_bsdf_pdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
3863
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
3864
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3865
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
3866
|
+
let diffuseTerm = diffuse_pdf(normal, lightDirection);
|
|
3867
|
+
let specTerm = ggx_pdf(normal, viewDirection, lightDirection, max(roughness, 0.02));
|
|
3868
|
+
let clearcoatTerm = ggx_pdf(normal, viewDirection, lightDirection, max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02));
|
|
3869
|
+
return weights.x * diffuseTerm + weights.y * specTerm + weights.z * clearcoatTerm;
|
|
3870
|
+
}
|
|
3871
|
+
|
|
2226
3872
|
fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
2227
3873
|
let portalScale = environment_portal_radiance_scale(origin, safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0)));
|
|
2228
3874
|
if (
|
|
@@ -2238,9 +3884,45 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
2238
3884
|
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
2239
3885
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
2240
3886
|
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
3887
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
2241
3888
|
let materialEnergy = select(0.68, 0.92, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2242
3889
|
let transparentEnergy = select(materialEnergy, 0.9, hit.hitType == 3u);
|
|
2243
|
-
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy;
|
|
3890
|
+
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy * mix(0.55, 1.0, occlusion);
|
|
3891
|
+
}
|
|
3892
|
+
|
|
3893
|
+
fn bounded_path_response_luminance(ray: RayRecord, hit: HitRecord) -> f32 {
|
|
3894
|
+
let daylightFloor = max(config.pathResolveSettings.y, 0.0) * 0.08;
|
|
3895
|
+
let hdriFloor = max(config.environmentMapSettings.w, 0.0) * 0.02;
|
|
3896
|
+
let sceneFloor = max(daylightFloor, hdriFloor);
|
|
3897
|
+
if (sceneFloor <= 0.000001) {
|
|
3898
|
+
return 0.0;
|
|
3899
|
+
}
|
|
3900
|
+
let bounceRatio = select(
|
|
3901
|
+
0.0,
|
|
3902
|
+
f32(ray.bounce) / max(f32(config.maxDepth - 1u), 1.0),
|
|
3903
|
+
config.maxDepth > 1u
|
|
3904
|
+
);
|
|
3905
|
+
let bounceScale = 1.0 - bounceRatio * 0.55;
|
|
3906
|
+
let materialScale = select(1.0, 0.34, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
3907
|
+
let transparentScale = select(materialScale, 0.58, hit.hitType == 3u);
|
|
3908
|
+
let opacityScale = mix(0.55, 1.0, clamp(hit.material.z, 0.0, 1.0));
|
|
3909
|
+
return sceneFloor * bounceScale * transparentScale * opacityScale;
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
fn stabilize_surface_path_response(ray: RayRecord, hit: HitRecord, response: vec3<f32>) -> vec3<f32> {
|
|
3913
|
+
let minimumLuminance = bounded_path_response_luminance(ray, hit);
|
|
3914
|
+
let responseLuminance = radiance_luminance(response);
|
|
3915
|
+
if (minimumLuminance <= 0.000001 || responseLuminance >= minimumLuminance) {
|
|
3916
|
+
return response;
|
|
3917
|
+
}
|
|
3918
|
+
let tintBase = max(response, max(hit.color.xyz * 0.65, config.ambientColor.xyz * 0.35));
|
|
3919
|
+
let tint = tintBase / max(max_component(tintBase), 0.0001);
|
|
3920
|
+
let lifted = select(
|
|
3921
|
+
tint * minimumLuminance,
|
|
3922
|
+
response * (minimumLuminance / max(responseLuminance, 0.0001)),
|
|
3923
|
+
responseLuminance > 0.0001
|
|
3924
|
+
);
|
|
3925
|
+
return clamp(lifted, vec3<f32>(0.0), vec3<f32>(0.98));
|
|
2244
3926
|
}
|
|
2245
3927
|
|
|
2246
3928
|
fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
@@ -2259,12 +3941,24 @@ fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
|
2259
3941
|
return clamp_sample_radiance(sunTint * baseline * skyFacing * directionalWeight * 0.04);
|
|
2260
3942
|
}
|
|
2261
3943
|
|
|
2262
|
-
fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
3944
|
+
fn terminal_surface_environment_source(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2263
3945
|
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2264
|
-
let
|
|
2265
|
-
|
|
2266
|
-
|
|
3946
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
3947
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3948
|
+
let glossiness = surface_glossiness(hit);
|
|
3949
|
+
let normalEnvironment = gated_environment_radiance(origin, normal);
|
|
3950
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
3951
|
+
let reflectionDirection = glossy_environment_direction(
|
|
3952
|
+
ray.direction.xyz,
|
|
3953
|
+
normal,
|
|
3954
|
+
roughness,
|
|
3955
|
+
mix(0.88, 0.38, glossiness)
|
|
2267
3956
|
);
|
|
3957
|
+
let reflectionEnvironment = prefiltered_environment_radiance(reflectionDirection, roughness);
|
|
3958
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
3959
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
3960
|
+
let brdfTerm = sample_brdf_lut(saturate(dot(normal, viewDirection)), roughness);
|
|
3961
|
+
let specularEnvironment = reflectionEnvironment * (f0 * brdfTerm.x + vec3<f32>(brdfTerm.y));
|
|
2268
3962
|
let sunlitFloor = sunlit_baseline_radiance(normal);
|
|
2269
3963
|
let ambientFloor = select(
|
|
2270
3964
|
max(config.ambientColor.xyz, sunlitFloor * 0.82),
|
|
@@ -2276,17 +3970,23 @@ fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
|
2276
3970
|
max(config.environmentMapSettings.w, max(0.12, config.pathResolveSettings.y * 0.42)),
|
|
2277
3971
|
environment_map_enabled()
|
|
2278
3972
|
);
|
|
2279
|
-
let
|
|
3973
|
+
let glossyEnvironment = max(
|
|
3974
|
+
normalEnvironment,
|
|
3975
|
+
max(reflectionEnvironment * mix(0.24, 0.92, glossiness), specularEnvironment)
|
|
3976
|
+
);
|
|
3977
|
+
let environmentFloor = max(ambientFloor, max(sunlitFloor, glossyEnvironment * environmentInfluence));
|
|
2280
3978
|
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
2281
3979
|
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
2282
3980
|
}
|
|
2283
3981
|
|
|
2284
3982
|
fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2285
3983
|
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
3984
|
+
let occlusion = mix(0.75, 1.0, clamp(hit.occlusion, 0.0, 1.0));
|
|
2286
3985
|
return clamp_sample_radiance(
|
|
2287
3986
|
ray.throughput.xyz *
|
|
2288
3987
|
surfaceColor *
|
|
2289
|
-
terminal_surface_environment_source(hit)
|
|
3988
|
+
terminal_surface_environment_source(ray, hit) *
|
|
3989
|
+
occlusion
|
|
2290
3990
|
);
|
|
2291
3991
|
}
|
|
2292
3992
|
|
|
@@ -2319,6 +4019,10 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2319
4019
|
);
|
|
2320
4020
|
let area = max(portal.position.w, 0.0001);
|
|
2321
4021
|
let distanceFalloff = clamp(area / max(distanceSquared, area * 0.25), 0.0, 2.5);
|
|
4022
|
+
let traceDistance = max(sqrt(distanceSquared) - 0.01, 0.01);
|
|
4023
|
+
if (scene_visibility_blocked(origin, direction, traceDistance)) {
|
|
4024
|
+
continue;
|
|
4025
|
+
}
|
|
2322
4026
|
irradiance = irradiance +
|
|
2323
4027
|
portal.color.rgb *
|
|
2324
4028
|
portal.normal.w *
|
|
@@ -2330,48 +4034,79 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2330
4034
|
return irradiance;
|
|
2331
4035
|
}
|
|
2332
4036
|
|
|
2333
|
-
fn
|
|
2334
|
-
let
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
);
|
|
2348
|
-
let environmentIrradianceScale = select(
|
|
2349
|
-
max(0.16, config.pathResolveSettings.y * 0.45),
|
|
2350
|
-
max(config.environmentMapSettings.w, max(0.16, config.pathResolveSettings.y * 0.45)),
|
|
2351
|
-
environment_map_enabled()
|
|
4037
|
+
fn visibility_test_ray(origin: vec3<f32>, direction: vec3<f32>) -> RayRecord {
|
|
4038
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4039
|
+
return RayRecord(
|
|
4040
|
+
0u,
|
|
4041
|
+
0u,
|
|
4042
|
+
0u,
|
|
4043
|
+
0u,
|
|
4044
|
+
0u,
|
|
4045
|
+
0u,
|
|
4046
|
+
0u,
|
|
4047
|
+
0u,
|
|
4048
|
+
vec4<f32>(origin, 1.0),
|
|
4049
|
+
vec4<f32>(rayDirection, 0.0),
|
|
4050
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2352
4051
|
);
|
|
2353
|
-
|
|
4052
|
+
}
|
|
2354
4053
|
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
);
|
|
2359
|
-
let sunFacing = saturate(dot(normal, sunDirection));
|
|
2360
|
-
let sunRadiance = gated_environment_radiance(origin, sunDirection);
|
|
2361
|
-
let sunIrradiance = sunRadiance * sunFacing * 0.2;
|
|
2362
|
-
let portalIrradiance = direct_environment_portal_irradiance(origin, normal);
|
|
4054
|
+
fn scene_visibility_blocked(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> bool {
|
|
4055
|
+
let testRay = visibility_test_ray(origin, direction);
|
|
4056
|
+
let nearest = max(maxDistance, 0.001);
|
|
2363
4057
|
|
|
2364
|
-
|
|
2365
|
-
|
|
4058
|
+
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
4059
|
+
let object = sceneObjects[objectIndex];
|
|
4060
|
+
var current = no_candidate();
|
|
4061
|
+
if (object.kind == 1u) {
|
|
4062
|
+
current = intersect_sphere(testRay, object);
|
|
4063
|
+
} else if (object.kind == 2u) {
|
|
4064
|
+
current = intersect_box(testRay, object);
|
|
4065
|
+
}
|
|
4066
|
+
if (current.hit == 1u && current.distance < nearest) {
|
|
4067
|
+
return true;
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
2366
4070
|
|
|
2367
|
-
let
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
let specularTint = mix(vec3<f32>(0.04), surfaceColor, metallic);
|
|
2371
|
-
let specular = specularTint * sunRadiance * specularFacing * select(0.16, 0.48, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
4071
|
+
let meshCandidate = intersect_bvh(testRay, nearest);
|
|
4072
|
+
return meshCandidate.hit == 1u && meshCandidate.distance < nearest;
|
|
4073
|
+
}
|
|
2372
4074
|
|
|
2373
|
-
|
|
2374
|
-
|
|
4075
|
+
fn surface_direct_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
4076
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4077
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4078
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4079
|
+
let lightSample = sample_environment_importance(vec2<f32>(
|
|
4080
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 41u)),
|
|
4081
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 43u))
|
|
4082
|
+
));
|
|
4083
|
+
if (lightSample.pdf <= 0.000001) {
|
|
4084
|
+
return vec3<f32>(0.0);
|
|
4085
|
+
}
|
|
4086
|
+
let lightDirection = safe_normalize(lightSample.direction, normal);
|
|
4087
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4088
|
+
if (nDotL <= 0.000001) {
|
|
4089
|
+
return vec3<f32>(0.0);
|
|
4090
|
+
}
|
|
4091
|
+
if (scene_visibility_blocked(origin, lightDirection, 1000000.0)) {
|
|
4092
|
+
return vec3<f32>(0.0);
|
|
4093
|
+
}
|
|
4094
|
+
let incidentRadiance = direct_environment_radiance(origin, lightDirection);
|
|
4095
|
+
if (max_component(incidentRadiance) <= 0.000001) {
|
|
4096
|
+
return vec3<f32>(0.0);
|
|
4097
|
+
}
|
|
4098
|
+
let bsdf = evaluate_surface_bsdf(hit, viewDirection, lightDirection);
|
|
4099
|
+
if (max_component(bsdf) <= 0.000001) {
|
|
4100
|
+
return vec3<f32>(0.0);
|
|
4101
|
+
}
|
|
4102
|
+
let bsdfPdf = evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection);
|
|
4103
|
+
let misWeight = power_heuristic(lightSample.pdf, bsdfPdf);
|
|
4104
|
+
let contribution =
|
|
4105
|
+
ray.throughput.xyz *
|
|
4106
|
+
bsdf *
|
|
4107
|
+
incidentRadiance *
|
|
4108
|
+
(nDotL * misWeight / max(lightSample.pdf, 0.000001));
|
|
4109
|
+
return clamp_sample_radiance(contribution);
|
|
2375
4110
|
}
|
|
2376
4111
|
|
|
2377
4112
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -2390,7 +4125,16 @@ fn default_mesh_range() -> MeshRange {
|
|
|
2390
4125
|
0u,
|
|
2391
4126
|
vec4<f32>(0.72, 0.72, 0.68, 1.0),
|
|
2392
4127
|
vec4<f32>(0.0),
|
|
2393
|
-
vec4<f32>(0.72, 0.0, 1.0, 1.45)
|
|
4128
|
+
vec4<f32>(0.72, 0.0, 1.0, 1.45),
|
|
4129
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4130
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
4131
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4132
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4133
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4134
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4135
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4136
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4137
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
2394
4138
|
);
|
|
2395
4139
|
}
|
|
2396
4140
|
|
|
@@ -2486,7 +4230,7 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2486
4230
|
mesh.flags,
|
|
2487
4231
|
mesh.materialRefId,
|
|
2488
4232
|
mesh.mediumRefId,
|
|
2489
|
-
|
|
4233
|
+
mesh.materialSlot,
|
|
2490
4234
|
0u,
|
|
2491
4235
|
vec4<f32>(vertex0.position.xyz, 0.0),
|
|
2492
4236
|
vec4<f32>(vertex1.position.xyz, 0.0),
|
|
@@ -2498,7 +4242,16 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2498
4242
|
vec4<f32>(uv2, 0.0, 0.0),
|
|
2499
4243
|
mesh.color,
|
|
2500
4244
|
mesh.emission,
|
|
2501
|
-
mesh.material
|
|
4245
|
+
mesh.material,
|
|
4246
|
+
mesh.materialResponse,
|
|
4247
|
+
mesh.materialExtension,
|
|
4248
|
+
mesh.specularColor,
|
|
4249
|
+
mesh.baseColorAtlas,
|
|
4250
|
+
mesh.metallicRoughnessAtlas,
|
|
4251
|
+
mesh.normalAtlas,
|
|
4252
|
+
mesh.occlusionAtlas,
|
|
4253
|
+
mesh.emissiveAtlas,
|
|
4254
|
+
mesh.textureSettings
|
|
2502
4255
|
);
|
|
2503
4256
|
|
|
2504
4257
|
let leafBase = config.triangleCount - 1u;
|
|
@@ -2657,7 +4410,8 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2657
4410
|
0u,
|
|
2658
4411
|
0u,
|
|
2659
4412
|
-1.0,
|
|
2660
|
-
|
|
4413
|
+
1.0,
|
|
4414
|
+
vec2<f32>(0.0),
|
|
2661
4415
|
vec4<f32>(ray.origin.xyz + ray.direction.xyz * 1000.0, 1.0),
|
|
2662
4416
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
2663
4417
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
@@ -2665,7 +4419,10 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2665
4419
|
vec4<f32>(0.0),
|
|
2666
4420
|
vec4<f32>(radiance, 1.0),
|
|
2667
4421
|
vec4<f32>(0.0),
|
|
2668
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
4422
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
4423
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4424
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
4425
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2669
4426
|
);
|
|
2670
4427
|
}
|
|
2671
4428
|
|
|
@@ -2960,6 +4717,19 @@ fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
|
|
|
2960
4717
|
return value / (vec3<f32>(1.0) + value);
|
|
2961
4718
|
}
|
|
2962
4719
|
|
|
4720
|
+
fn denoise_sample_count() -> f32 {
|
|
4721
|
+
return clamp(1.0 / max(config.projectionAndSampling.z, 0.000001), 1.0, 256.0);
|
|
4722
|
+
}
|
|
4723
|
+
|
|
4724
|
+
fn denoise_strength() -> f32 {
|
|
4725
|
+
let spp = denoise_sample_count();
|
|
4726
|
+
return clamp(0.44 / sqrt(spp), 0.08, 0.44);
|
|
4727
|
+
}
|
|
4728
|
+
|
|
4729
|
+
fn denoise_kernel_radius() -> i32 {
|
|
4730
|
+
return select(1i, 2i, denoise_sample_count() < 2.5);
|
|
4731
|
+
}
|
|
4732
|
+
|
|
2963
4733
|
@compute @workgroup_size(64)
|
|
2964
4734
|
fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
2965
4735
|
let index = globalId.x;
|
|
@@ -2998,7 +4768,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2998
4768
|
vec4<f32>(0.0),
|
|
2999
4769
|
vec4<f32>(0.0),
|
|
3000
4770
|
vec4<f32>(0.0),
|
|
3001
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
4771
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
4772
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4773
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
4774
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
3002
4775
|
);
|
|
3003
4776
|
var candidate = no_candidate();
|
|
3004
4777
|
var hitTriangle = TriangleRecord(
|
|
@@ -3020,7 +4793,16 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3020
4793
|
vec4<f32>(0.0),
|
|
3021
4794
|
vec4<f32>(0.0),
|
|
3022
4795
|
vec4<f32>(0.0),
|
|
3023
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
4796
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
4797
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4798
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
4799
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
4800
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4801
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4802
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4803
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4804
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4805
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
3024
4806
|
);
|
|
3025
4807
|
|
|
3026
4808
|
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
@@ -3053,16 +4835,28 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3053
4835
|
let position = ray.origin.xyz + ray.direction.xyz * candidate.distance;
|
|
3054
4836
|
let hitMaterialKind = select(hitObject.materialKind, hitTriangle.materialKind, candidate.triangleIndex != 0xffffffffu);
|
|
3055
4837
|
let hitObjectId = select(hitObject.objectId, hitTriangle.meshId, candidate.triangleIndex != 0xffffffffu);
|
|
3056
|
-
let
|
|
3057
|
-
|
|
3058
|
-
|
|
4838
|
+
let meshSurface = sample_surface_material(
|
|
4839
|
+
hitTriangle,
|
|
4840
|
+
candidate.uv,
|
|
4841
|
+
candidate.geometricNormal,
|
|
4842
|
+
candidate.shadingNormal
|
|
4843
|
+
);
|
|
4844
|
+
let hitColor = select(hitObject.color, meshSurface.color, candidate.triangleIndex != 0xffffffffu);
|
|
4845
|
+
let hitEmission = select(hitObject.emission, meshSurface.emission, candidate.triangleIndex != 0xffffffffu);
|
|
4846
|
+
let hitMaterial = select(hitObject.material, meshSurface.material, candidate.triangleIndex != 0xffffffffu);
|
|
4847
|
+
let hitMaterialResponse = select(hitObject.materialResponse, meshSurface.materialResponse, candidate.triangleIndex != 0xffffffffu);
|
|
4848
|
+
let hitMaterialExtension = select(hitObject.materialExtension, meshSurface.materialExtension, candidate.triangleIndex != 0xffffffffu);
|
|
4849
|
+
let hitSpecularColor = select(hitObject.specularColor, meshSurface.specularColor, candidate.triangleIndex != 0xffffffffu);
|
|
4850
|
+
let hitShadingNormal = select(candidate.shadingNormal, meshSurface.shadingNormal, candidate.triangleIndex != 0xffffffffu);
|
|
3059
4851
|
let hitPrimitiveId = select(candidate.primitiveId, hitTriangle.triangleId, candidate.triangleIndex != 0xffffffffu);
|
|
3060
4852
|
let hitMaterialRefId = select(candidate.materialRefId, hitTriangle.materialRefId, candidate.triangleIndex != 0xffffffffu);
|
|
3061
4853
|
let hitMediumRefId = select(candidate.mediumRefId, hitTriangle.mediumRefId, candidate.triangleIndex != 0xffffffffu);
|
|
4854
|
+
let hitMaterialSlot = select(0u, hitTriangle.materialSlot, candidate.triangleIndex != 0xffffffffu);
|
|
4855
|
+
let hitOcclusion = select(1.0, meshSurface.occlusion, candidate.triangleIndex != 0xffffffffu);
|
|
3062
4856
|
var hitType = 0u;
|
|
3063
4857
|
if (hitMaterialKind == 4u || emission_power(hitEmission) > 0.0001) {
|
|
3064
4858
|
hitType = 1u;
|
|
3065
|
-
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999) {
|
|
4859
|
+
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999 || hitMaterialExtension.z > 0.001) {
|
|
3066
4860
|
hitType = 3u;
|
|
3067
4861
|
}
|
|
3068
4862
|
atomicAdd(&counters.hitCount, 1u);
|
|
@@ -3076,19 +4870,23 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3076
4870
|
hitPrimitiveId,
|
|
3077
4871
|
hitMaterialRefId,
|
|
3078
4872
|
hitMediumRefId,
|
|
3079
|
-
|
|
4873
|
+
hitMaterialSlot,
|
|
3080
4874
|
0u,
|
|
3081
4875
|
0u,
|
|
3082
4876
|
candidate.distance,
|
|
3083
|
-
|
|
4877
|
+
hitOcclusion,
|
|
4878
|
+
vec2<f32>(0.0),
|
|
3084
4879
|
vec4<f32>(position, 1.0),
|
|
3085
4880
|
vec4<f32>(candidate.geometricNormal, 0.0),
|
|
3086
|
-
vec4<f32>(
|
|
4881
|
+
vec4<f32>(hitShadingNormal, 0.0),
|
|
3087
4882
|
vec4<f32>(candidate.barycentric, 0.0),
|
|
3088
4883
|
vec4<f32>(candidate.uv, 0.0, 0.0),
|
|
3089
4884
|
hitColor,
|
|
3090
4885
|
hitEmission,
|
|
3091
|
-
hitMaterial
|
|
4886
|
+
hitMaterial,
|
|
4887
|
+
hitMaterialResponse,
|
|
4888
|
+
hitMaterialExtension,
|
|
4889
|
+
hitSpecularColor
|
|
3092
4890
|
);
|
|
3093
4891
|
}
|
|
3094
4892
|
|
|
@@ -3157,60 +4955,106 @@ fn sample_environment_portal_direction(hit: HitRecord, seed: u32, fallback: vec3
|
|
|
3157
4955
|
}
|
|
3158
4956
|
|
|
3159
4957
|
fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult {
|
|
4958
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4959
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
3160
4960
|
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3161
|
-
|
|
4961
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
4962
|
+
if (hit.materialKind == 1u && roughness <= 0.02) {
|
|
3162
4963
|
return ScatterResult(
|
|
3163
|
-
vec4<f32>(
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
hit.shadingNormal.xyz
|
|
3167
|
-
),
|
|
3168
|
-
0.0
|
|
3169
|
-
),
|
|
3170
|
-
0u,
|
|
3171
|
-
0u,
|
|
4964
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
4965
|
+
1.0,
|
|
4966
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
3172
4967
|
0u,
|
|
3173
4968
|
0u
|
|
3174
4969
|
);
|
|
3175
4970
|
}
|
|
3176
4971
|
|
|
3177
|
-
if (hit.materialKind == 2u || hit.materialKind == 3u) {
|
|
4972
|
+
if (hit.materialKind == 2u || hit.materialKind == 3u || transmission > 0.001) {
|
|
3178
4973
|
let ior = max(hit.material.w, 1.01);
|
|
3179
4974
|
let etaRatio = select(ior, 1.0 / ior, hit.frontFace == 1u);
|
|
3180
|
-
let cosTheta = min(dot(-ray.direction.xyz,
|
|
4975
|
+
let cosTheta = min(dot(-ray.direction.xyz, normal), 1.0);
|
|
3181
4976
|
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3182
4977
|
let cannotRefract = etaRatio * sinTheta > 1.0;
|
|
3183
4978
|
let reflectChance = schlick(cosTheta, etaRatio);
|
|
3184
|
-
|
|
3185
|
-
|
|
4979
|
+
let transmissionReflectChance = select(
|
|
4980
|
+
reflectChance,
|
|
4981
|
+
max(reflectChance, 1.0 - transmission),
|
|
4982
|
+
transmission > 0.001
|
|
4983
|
+
);
|
|
4984
|
+
if (cannotRefract || random01(seed + 23u) < transmissionReflectChance) {
|
|
4985
|
+
return ScatterResult(
|
|
4986
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
4987
|
+
1.0,
|
|
4988
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
4989
|
+
0u,
|
|
4990
|
+
0u
|
|
4991
|
+
);
|
|
3186
4992
|
}
|
|
3187
|
-
return ScatterResult(
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
let
|
|
3197
|
-
let
|
|
3198
|
-
|
|
3199
|
-
let
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
)
|
|
4993
|
+
return ScatterResult(
|
|
4994
|
+
vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
|
|
4995
|
+
1.0,
|
|
4996
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
4997
|
+
0u,
|
|
4998
|
+
0u
|
|
4999
|
+
);
|
|
5000
|
+
}
|
|
5001
|
+
|
|
5002
|
+
let guidedEmissiveAvailable = config.emissiveTriangleCount > 0u;
|
|
5003
|
+
let guidedPortalAvailable =
|
|
5004
|
+
config.environmentPortalCount > 0u && config.environmentPortalMode != 0u;
|
|
5005
|
+
let guidedSelector = random01(seed + 17u);
|
|
5006
|
+
if (guidedEmissiveAvailable && guidedSelector < 0.18) {
|
|
5007
|
+
let guidedDirection = sample_emissive_triangle_direction(hit, seed + 101u, normal);
|
|
5008
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5009
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5010
|
+
return ScatterResult(
|
|
5011
|
+
vec4<f32>(guidedDirection, 0.0),
|
|
5012
|
+
guidedPdf,
|
|
5013
|
+
RAY_FLAG_GUIDED_EMISSIVE,
|
|
5014
|
+
0u,
|
|
5015
|
+
0u
|
|
5016
|
+
);
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
if (guidedPortalAvailable && guidedSelector < 0.32) {
|
|
5020
|
+
let guidedDirection = sample_environment_portal_direction(hit, seed + 131u, normal);
|
|
5021
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5022
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5023
|
+
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf, 0u, 0u, 0u);
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
|
|
5027
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
5028
|
+
let selector = random01(seed + 31u);
|
|
5029
|
+
var lightDirection = normal;
|
|
5030
|
+
if (selector < weights.x) {
|
|
5031
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5032
|
+
vec2<f32>(random01(seed + 37u), random01(seed + 41u)),
|
|
5033
|
+
normal
|
|
5034
|
+
);
|
|
5035
|
+
} else if (selector < weights.x + weights.y) {
|
|
5036
|
+
let halfVector = importance_sample_ggx(
|
|
5037
|
+
vec2<f32>(random01(seed + 47u), random01(seed + 53u)),
|
|
5038
|
+
max(roughness, 0.02),
|
|
5039
|
+
normal
|
|
5040
|
+
);
|
|
5041
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5042
|
+
} else {
|
|
5043
|
+
let halfVector = importance_sample_ggx(
|
|
5044
|
+
vec2<f32>(random01(seed + 59u), random01(seed + 61u)),
|
|
5045
|
+
max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02),
|
|
5046
|
+
normal
|
|
5047
|
+
);
|
|
5048
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5049
|
+
}
|
|
5050
|
+
if (dot(normal, lightDirection) <= 0.000001) {
|
|
5051
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5052
|
+
vec2<f32>(random01(seed + 67u), random01(seed + 71u)),
|
|
5053
|
+
normal
|
|
5054
|
+
);
|
|
5055
|
+
}
|
|
5056
|
+
let pdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection), 0.000001);
|
|
5057
|
+
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf, 0u, 0u, 0u);
|
|
3214
5058
|
}
|
|
3215
5059
|
|
|
3216
5060
|
@compute @workgroup_size(64)
|
|
@@ -3240,10 +5084,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3240
5084
|
}
|
|
3241
5085
|
|
|
3242
5086
|
if (hit.hitType == 2u) {
|
|
5087
|
+
var sourceRadiance = hit.color.xyz;
|
|
5088
|
+
if ((ray.flags & RAY_FLAG_DELTA_SAMPLE) == 0u) {
|
|
5089
|
+
let bsdfPdf = max(ray.throughput.w, 0.000001);
|
|
5090
|
+
let lightPdf = environment_direction_pdf(ray.direction.xyz);
|
|
5091
|
+
let misWeight = power_heuristic(bsdfPdf, lightPdf);
|
|
5092
|
+
sourceRadiance = sourceRadiance * misWeight;
|
|
5093
|
+
}
|
|
3243
5094
|
if (deferred_path_resolve_enabled()) {
|
|
3244
|
-
record_deferred_terminal_source(ray,
|
|
5095
|
+
record_deferred_terminal_source(ray, sourceRadiance);
|
|
3245
5096
|
} else {
|
|
3246
|
-
contribution = clamp_sample_radiance(ray.throughput.xyz *
|
|
5097
|
+
contribution = clamp_sample_radiance(ray.throughput.xyz * sourceRadiance);
|
|
3247
5098
|
accumulation[ray.rayId] =
|
|
3248
5099
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3249
5100
|
}
|
|
@@ -3251,13 +5102,13 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3251
5102
|
return;
|
|
3252
5103
|
}
|
|
3253
5104
|
|
|
3254
|
-
let response = surface_path_response(hit);
|
|
5105
|
+
let response = stabilize_surface_path_response(ray, hit, surface_path_response(hit));
|
|
3255
5106
|
record_deferred_path_response(ray, response);
|
|
3256
5107
|
|
|
3257
5108
|
let shouldEstimateDirectEnvironment =
|
|
3258
|
-
!deferred_path_resolve_enabled() &&
|
|
3259
5109
|
(hit.materialKind == 0u || hit.materialKind == 1u) &&
|
|
3260
|
-
hit.material.z >= 0.95
|
|
5110
|
+
hit.material.z >= 0.95 &&
|
|
5111
|
+
ray.bounce < 2u;
|
|
3261
5112
|
if (shouldEstimateDirectEnvironment) {
|
|
3262
5113
|
let directEnvironment = surface_direct_environment_contribution(ray, hit);
|
|
3263
5114
|
accumulation[ray.rayId] =
|
|
@@ -3266,7 +5117,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3266
5117
|
|
|
3267
5118
|
if (ray.bounce + 1u >= config.maxDepth) {
|
|
3268
5119
|
if (deferred_path_resolve_enabled()) {
|
|
3269
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5120
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3270
5121
|
} else {
|
|
3271
5122
|
let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
3272
5123
|
accumulation[ray.rayId] =
|
|
@@ -3281,7 +5132,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3281
5132
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
3282
5133
|
if (nextIndex >= config.tilePixelCount) {
|
|
3283
5134
|
if (deferred_path_resolve_enabled()) {
|
|
3284
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5135
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3285
5136
|
} else {
|
|
3286
5137
|
let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
3287
5138
|
accumulation[ray.rayId] =
|
|
@@ -3302,7 +5153,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3302
5153
|
0u,
|
|
3303
5154
|
vec4<f32>(offset_origin(hit.position.xyz, hit.shadingNormal.xyz), 1.0),
|
|
3304
5155
|
scatter.direction,
|
|
3305
|
-
vec4<f32>(throughput,
|
|
5156
|
+
vec4<f32>(throughput, scatter.pdf)
|
|
3306
5157
|
);
|
|
3307
5158
|
}
|
|
3308
5159
|
|
|
@@ -3371,8 +5222,11 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3371
5222
|
|
|
3372
5223
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3373
5224
|
let center = textureLoad(denoiseInputRadiance, pixel, 0).xyz;
|
|
3374
|
-
|
|
3375
|
-
|
|
5225
|
+
let strength = denoise_strength();
|
|
5226
|
+
let kernelRadius = denoise_kernel_radius();
|
|
5227
|
+
let centerWeight = 1.7 - strength * 0.35;
|
|
5228
|
+
var sum = center * centerWeight;
|
|
5229
|
+
var totalWeight = centerWeight;
|
|
3376
5230
|
let centerRange = denoise_range_space(center);
|
|
3377
5231
|
|
|
3378
5232
|
for (var oy = -2i; oy <= 2i; oy = oy + 1i) {
|
|
@@ -3380,13 +5234,16 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3380
5234
|
if (ox == 0i && oy == 0i) {
|
|
3381
5235
|
continue;
|
|
3382
5236
|
}
|
|
5237
|
+
if (abs(ox) > kernelRadius || abs(oy) > kernelRadius) {
|
|
5238
|
+
continue;
|
|
5239
|
+
}
|
|
3383
5240
|
let sx = clamp(i32(x) + ox, 0i, i32(config.canvasWidth) - 1i);
|
|
3384
5241
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3385
5242
|
let sampleColor = textureLoad(denoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3386
5243
|
let colorDistance = length(denoise_range_space(sampleColor) - centerRange);
|
|
3387
|
-
let rangeWeight = 1.0 / (1.0 + colorDistance *
|
|
3388
|
-
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * 0.24);
|
|
3389
|
-
let diagonalWeight = select(1.0, 0.
|
|
5244
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (11.0 + strength * 6.0));
|
|
5245
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.62 + strength * 0.24));
|
|
5246
|
+
let diagonalWeight = select(1.0, 0.92, abs(ox) + abs(oy) > 1i);
|
|
3390
5247
|
let weight = rangeWeight * diagonalWeight * distanceWeight;
|
|
3391
5248
|
sum = sum + sampleColor * weight;
|
|
3392
5249
|
totalWeight = totalWeight + weight;
|
|
@@ -3394,8 +5251,9 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3394
5251
|
}
|
|
3395
5252
|
|
|
3396
5253
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3397
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3398
|
-
let
|
|
5254
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.1);
|
|
5255
|
+
let blend = min(0.3, strength * (0.62 + outlier * 0.12));
|
|
5256
|
+
let color = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3399
5257
|
textureStore(denoisedRadianceImage, pixel, vec4<f32>(color, 1.0));
|
|
3400
5258
|
}
|
|
3401
5259
|
|
|
@@ -3409,8 +5267,10 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3409
5267
|
|
|
3410
5268
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3411
5269
|
let center = textureLoad(finalDenoiseInputRadiance, pixel, 0).xyz;
|
|
3412
|
-
|
|
3413
|
-
|
|
5270
|
+
let strength = denoise_strength();
|
|
5271
|
+
let centerWeight = 1.35 - strength * 0.25;
|
|
5272
|
+
var sum = center * centerWeight;
|
|
5273
|
+
var totalWeight = centerWeight;
|
|
3414
5274
|
let centerRange = denoise_range_space(center);
|
|
3415
5275
|
|
|
3416
5276
|
for (var oy = -1i; oy <= 1i; oy = oy + 1i) {
|
|
@@ -3422,8 +5282,8 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3422
5282
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3423
5283
|
let sampleColor = textureLoad(finalDenoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3424
5284
|
let colorDistance = length(denoise_range_space(sampleColor) - centerRange);
|
|
3425
|
-
let rangeWeight = 1.0 / (1.0 + colorDistance *
|
|
3426
|
-
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * 0.
|
|
5285
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (12.0 + strength * 8.0));
|
|
5286
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.82 + strength * 0.28));
|
|
3427
5287
|
let weight = rangeWeight * distanceWeight;
|
|
3428
5288
|
sum = sum + sampleColor * weight;
|
|
3429
5289
|
totalWeight = totalWeight + weight;
|
|
@@ -3431,8 +5291,9 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3431
5291
|
}
|
|
3432
5292
|
|
|
3433
5293
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3434
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3435
|
-
let
|
|
5294
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.2);
|
|
5295
|
+
let blend = min(0.18, strength * (0.42 + outlier * 0.08));
|
|
5296
|
+
let radiance = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3436
5297
|
textureStore(denoisedOutputImage, pixel, vec4<f32>(tone_map_radiance(radiance), 1.0));
|
|
3437
5298
|
}
|
|
3438
5299
|
`;
|
|
@@ -3499,104 +5360,34 @@ function readGpuLimit(adapter, device, name) {
|
|
|
3499
5360
|
const deviceValue = Number(device?.limits?.[name]);
|
|
3500
5361
|
return Number.isFinite(deviceValue) ? deviceValue : null;
|
|
3501
5362
|
}
|
|
3502
|
-
function createAdapterInfoSnapshot(adapter) {
|
|
3503
|
-
const info = adapter?.info;
|
|
3504
|
-
if (!info || typeof info !== "object") {
|
|
3505
|
-
return null;
|
|
3506
|
-
}
|
|
3507
|
-
return Object.freeze({
|
|
3508
|
-
vendor: typeof info.vendor === "string" ? info.vendor : "",
|
|
3509
|
-
architecture: typeof info.architecture === "string" ? info.architecture : "",
|
|
3510
|
-
device: typeof info.device === "string" ? info.device : "",
|
|
3511
|
-
description: typeof info.description === "string" ? info.description : ""
|
|
3512
|
-
});
|
|
3513
|
-
}
|
|
3514
|
-
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
3515
|
-
return Object.freeze({
|
|
3516
|
-
physicalCoreCount: null,
|
|
3517
|
-
physicalCoreCountAvailable: false,
|
|
3518
|
-
physicalCoreCountUnavailableReason: "WebGPU does not expose physical GPU core counts.",
|
|
3519
|
-
adapterInfo: createAdapterInfoSnapshot(adapter),
|
|
3520
|
-
adapterLimits: Object.freeze({
|
|
3521
|
-
maxComputeInvocationsPerWorkgroup: readGpuLimit(adapter, device, "maxComputeInvocationsPerWorkgroup"),
|
|
3522
|
-
maxComputeWorkgroupSizeX: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeX"),
|
|
3523
|
-
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
3524
|
-
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
3525
|
-
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
3526
|
-
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
3527
|
-
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize")
|
|
3528
|
-
}),
|
|
3529
|
-
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
3530
|
-
});
|
|
3531
|
-
}
|
|
3532
|
-
function createGpuParallelismCounters() {
|
|
3533
|
-
return {
|
|
3534
|
-
directDispatches: 0,
|
|
3535
|
-
directWorkgroups: 0,
|
|
3536
|
-
directShaderInvocations: 0,
|
|
3537
|
-
multiWorkgroupDispatches: 0,
|
|
3538
|
-
largestDirectWorkgroupsPerDispatch: 0,
|
|
3539
|
-
indirectDispatches: 0,
|
|
3540
|
-
estimatedIndirectWorkgroupsUpperBound: 0,
|
|
3541
|
-
estimatedIndirectShaderInvocationsUpperBound: 0,
|
|
3542
|
-
indirectDispatchesWithMultiWorkgroupCapacity: 0,
|
|
3543
|
-
largestEstimatedIndirectWorkgroupsPerDispatch: 0
|
|
3544
|
-
};
|
|
3545
|
-
}
|
|
3546
|
-
function countDispatchWorkgroups(groups) {
|
|
3547
|
-
return groups.reduce((product, value) => {
|
|
3548
|
-
const numeric = Number(value ?? 1);
|
|
3549
|
-
const count = Number.isFinite(numeric) ? Math.max(1, Math.trunc(numeric)) : 1;
|
|
3550
|
-
return product * count;
|
|
3551
|
-
}, 1);
|
|
3552
|
-
}
|
|
3553
|
-
function recordDirectDispatch(parallelism, groups, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3554
|
-
const workgroups = countDispatchWorkgroups(groups);
|
|
3555
|
-
parallelism.directDispatches += 1;
|
|
3556
|
-
parallelism.directWorkgroups += workgroups;
|
|
3557
|
-
parallelism.directShaderInvocations += workgroups * invocationsPerWorkgroup;
|
|
3558
|
-
parallelism.largestDirectWorkgroupsPerDispatch = Math.max(
|
|
3559
|
-
parallelism.largestDirectWorkgroupsPerDispatch,
|
|
3560
|
-
workgroups
|
|
3561
|
-
);
|
|
3562
|
-
if (workgroups > 1) {
|
|
3563
|
-
parallelism.multiWorkgroupDispatches += 1;
|
|
3564
|
-
}
|
|
3565
|
-
}
|
|
3566
|
-
function recordIndirectDispatch(parallelism, estimatedWorkgroupsUpperBound, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3567
|
-
const workgroups = Math.max(1, Math.trunc(Number(estimatedWorkgroupsUpperBound) || 1));
|
|
3568
|
-
parallelism.indirectDispatches += 1;
|
|
3569
|
-
parallelism.estimatedIndirectWorkgroupsUpperBound += workgroups;
|
|
3570
|
-
parallelism.estimatedIndirectShaderInvocationsUpperBound += workgroups * invocationsPerWorkgroup;
|
|
3571
|
-
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch = Math.max(
|
|
3572
|
-
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3573
|
-
workgroups
|
|
3574
|
-
);
|
|
3575
|
-
if (workgroups > 1) {
|
|
3576
|
-
parallelism.indirectDispatchesWithMultiWorkgroupCapacity += 1;
|
|
3577
|
-
}
|
|
3578
|
-
}
|
|
3579
|
-
function createGpuParallelismDiagnostics(adapterDiagnostics, counters) {
|
|
3580
|
-
const totalEstimatedWorkgroupsUpperBound = counters.directWorkgroups + counters.estimatedIndirectWorkgroupsUpperBound;
|
|
3581
|
-
const totalEstimatedShaderInvocationsUpperBound = counters.directShaderInvocations + counters.estimatedIndirectShaderInvocationsUpperBound;
|
|
3582
|
-
const exposesMultiWorkgroupParallelism = counters.multiWorkgroupDispatches > 0 || counters.indirectDispatchesWithMultiWorkgroupCapacity > 0;
|
|
3583
|
-
return Object.freeze({
|
|
3584
|
-
...adapterDiagnostics,
|
|
3585
|
-
directDispatches: counters.directDispatches,
|
|
3586
|
-
directWorkgroups: counters.directWorkgroups,
|
|
3587
|
-
directShaderInvocations: counters.directShaderInvocations,
|
|
3588
|
-
multiWorkgroupDispatches: counters.multiWorkgroupDispatches,
|
|
3589
|
-
largestDirectWorkgroupsPerDispatch: counters.largestDirectWorkgroupsPerDispatch,
|
|
3590
|
-
indirectDispatches: counters.indirectDispatches,
|
|
3591
|
-
estimatedIndirectWorkgroupsUpperBound: counters.estimatedIndirectWorkgroupsUpperBound,
|
|
3592
|
-
estimatedIndirectShaderInvocationsUpperBound: counters.estimatedIndirectShaderInvocationsUpperBound,
|
|
3593
|
-
indirectDispatchesWithMultiWorkgroupCapacity: counters.indirectDispatchesWithMultiWorkgroupCapacity,
|
|
3594
|
-
largestEstimatedIndirectWorkgroupsPerDispatch: counters.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3595
|
-
totalEstimatedWorkgroupsUpperBound,
|
|
3596
|
-
totalEstimatedShaderInvocationsUpperBound,
|
|
3597
|
-
exposesMultiWorkgroupParallelism,
|
|
3598
|
-
likelyUsesMoreThanOnePhysicalGpuCore: null,
|
|
3599
|
-
coreUtilizationStatus: "not-exposed-by-webgpu"
|
|
5363
|
+
function createAdapterInfoSnapshot(adapter) {
|
|
5364
|
+
const info = adapter?.info;
|
|
5365
|
+
if (!info || typeof info !== "object") {
|
|
5366
|
+
return null;
|
|
5367
|
+
}
|
|
5368
|
+
return Object.freeze({
|
|
5369
|
+
vendor: typeof info.vendor === "string" ? info.vendor : "",
|
|
5370
|
+
architecture: typeof info.architecture === "string" ? info.architecture : "",
|
|
5371
|
+
device: typeof info.device === "string" ? info.device : "",
|
|
5372
|
+
description: typeof info.description === "string" ? info.description : ""
|
|
5373
|
+
});
|
|
5374
|
+
}
|
|
5375
|
+
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
5376
|
+
return Object.freeze({
|
|
5377
|
+
physicalCoreCount: null,
|
|
5378
|
+
physicalCoreCountAvailable: false,
|
|
5379
|
+
physicalCoreCountUnavailableReason: "WebGPU does not expose physical GPU core counts.",
|
|
5380
|
+
adapterInfo: createAdapterInfoSnapshot(adapter),
|
|
5381
|
+
adapterLimits: Object.freeze({
|
|
5382
|
+
maxComputeInvocationsPerWorkgroup: readGpuLimit(adapter, device, "maxComputeInvocationsPerWorkgroup"),
|
|
5383
|
+
maxComputeWorkgroupSizeX: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeX"),
|
|
5384
|
+
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
5385
|
+
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
5386
|
+
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
5387
|
+
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
5388
|
+
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize")
|
|
5389
|
+
}),
|
|
5390
|
+
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
3600
5391
|
});
|
|
3601
5392
|
}
|
|
3602
5393
|
function createEnvironmentMapSnapshot(environmentMap) {
|
|
@@ -3604,12 +5395,38 @@ function createEnvironmentMapSnapshot(environmentMap) {
|
|
|
3604
5395
|
enabled: environmentMap.enabled,
|
|
3605
5396
|
width: environmentMap.width,
|
|
3606
5397
|
height: environmentMap.height,
|
|
5398
|
+
mipLevelCount: environmentMap.mipLevelCount ?? 1,
|
|
3607
5399
|
projection: environmentMap.projection,
|
|
3608
5400
|
intensity: environmentMap.intensity,
|
|
3609
5401
|
rotationRadians: environmentMap.rotationRadians,
|
|
3610
|
-
ambientStrength: environmentMap.ambientStrength
|
|
5402
|
+
ambientStrength: environmentMap.ambientStrength,
|
|
5403
|
+
hasImportanceData: environmentMap.hasImportanceData === true
|
|
3611
5404
|
});
|
|
3612
5405
|
}
|
|
5406
|
+
function nowMs() {
|
|
5407
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
5408
|
+
return performance.now();
|
|
5409
|
+
}
|
|
5410
|
+
return Date.now();
|
|
5411
|
+
}
|
|
5412
|
+
function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs = null) {
|
|
5413
|
+
if (Number.isFinite(overrideTimeoutMs)) {
|
|
5414
|
+
return Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
5415
|
+
}
|
|
5416
|
+
const samplesPerPixel = Math.max(
|
|
5417
|
+
1,
|
|
5418
|
+
Number(config?.renderedSamplesPerPixel ?? config?.samplesPerPixel ?? 1)
|
|
5419
|
+
);
|
|
5420
|
+
const maxDepth = Math.max(1, Number(config?.maxDepth ?? 1));
|
|
5421
|
+
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
5422
|
+
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
5423
|
+
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
5424
|
+
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
5425
|
+
return Math.min(
|
|
5426
|
+
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
5427
|
+
GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5
|
|
5428
|
+
);
|
|
5429
|
+
}
|
|
3613
5430
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
3614
5431
|
assertAnalyticDisplayQualityPolicy(options);
|
|
3615
5432
|
const constants = getGpuUsageConstants();
|
|
@@ -3814,6 +5631,60 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3814
5631
|
config.environmentMap,
|
|
3815
5632
|
config.environmentColor
|
|
3816
5633
|
);
|
|
5634
|
+
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
5635
|
+
device,
|
|
5636
|
+
constants,
|
|
5637
|
+
config.environmentMap,
|
|
5638
|
+
config.environmentColor
|
|
5639
|
+
);
|
|
5640
|
+
config = Object.freeze({
|
|
5641
|
+
...config,
|
|
5642
|
+
environmentMap: Object.freeze({
|
|
5643
|
+
...config.environmentMap,
|
|
5644
|
+
width: environmentMapResource.width,
|
|
5645
|
+
height: environmentMapResource.height,
|
|
5646
|
+
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
5647
|
+
hasImportanceData: environmentSamplingResource.hasImportanceData
|
|
5648
|
+
})
|
|
5649
|
+
});
|
|
5650
|
+
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
5651
|
+
const baseColorAtlasResource = createAtlasTextureResource(
|
|
5652
|
+
device,
|
|
5653
|
+
constants,
|
|
5654
|
+
config.gpuMaterialSource.baseColorAtlas,
|
|
5655
|
+
"plasius.wavefront.materialAtlas.baseColor"
|
|
5656
|
+
);
|
|
5657
|
+
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
5658
|
+
device,
|
|
5659
|
+
constants,
|
|
5660
|
+
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
5661
|
+
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
5662
|
+
);
|
|
5663
|
+
const normalAtlasResource = createAtlasTextureResource(
|
|
5664
|
+
device,
|
|
5665
|
+
constants,
|
|
5666
|
+
config.gpuMaterialSource.normalAtlas,
|
|
5667
|
+
"plasius.wavefront.materialAtlas.normal"
|
|
5668
|
+
);
|
|
5669
|
+
const occlusionAtlasResource = createAtlasTextureResource(
|
|
5670
|
+
device,
|
|
5671
|
+
constants,
|
|
5672
|
+
config.gpuMaterialSource.occlusionAtlas,
|
|
5673
|
+
"plasius.wavefront.materialAtlas.occlusion"
|
|
5674
|
+
);
|
|
5675
|
+
const emissiveAtlasResource = createAtlasTextureResource(
|
|
5676
|
+
device,
|
|
5677
|
+
constants,
|
|
5678
|
+
config.gpuMaterialSource.emissiveAtlas,
|
|
5679
|
+
"plasius.wavefront.materialAtlas.emissive"
|
|
5680
|
+
);
|
|
5681
|
+
const materialAtlasSampler = device.createSampler({
|
|
5682
|
+
label: "plasius.wavefront.materialAtlasSampler",
|
|
5683
|
+
addressModeU: "clamp-to-edge",
|
|
5684
|
+
addressModeV: "clamp-to-edge",
|
|
5685
|
+
magFilter: "linear",
|
|
5686
|
+
minFilter: "linear"
|
|
5687
|
+
});
|
|
3817
5688
|
const traceBindGroupLayout = device.createBindGroupLayout({
|
|
3818
5689
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
3819
5690
|
entries: [
|
|
@@ -3843,7 +5714,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3843
5714
|
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
3844
5715
|
{ binding: 20, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
3845
5716
|
{ binding: 21, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
3846
|
-
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } }
|
|
5717
|
+
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } },
|
|
5718
|
+
{ binding: 23, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5719
|
+
{ binding: 24, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5720
|
+
{ binding: 25, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5721
|
+
{ binding: 26, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5722
|
+
{ binding: 27, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5723
|
+
{ binding: 28, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
5724
|
+
{ binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5725
|
+
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
5726
|
+
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
|
|
3847
5727
|
]
|
|
3848
5728
|
});
|
|
3849
5729
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -3922,6 +5802,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3922
5802
|
label: "plasius.wavefront.computeShader",
|
|
3923
5803
|
code: WAVEFRONT_COMPUTE_WGSL
|
|
3924
5804
|
});
|
|
5805
|
+
await assertShaderModuleCompiles(computeShader, "plasius.wavefront.computeShader");
|
|
3925
5806
|
const pipelines = {
|
|
3926
5807
|
prepareMeshTrianglesAndLeaves: await createComputePipeline(
|
|
3927
5808
|
device,
|
|
@@ -4020,7 +5901,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4020
5901
|
{ binding: 19, resource: { buffer: environmentPortalBuffer } },
|
|
4021
5902
|
{ binding: 20, resource: environmentMapResource.view },
|
|
4022
5903
|
{ binding: 21, resource: environmentMapResource.sampler },
|
|
4023
|
-
{ binding: 22, resource: { buffer: pathVertexBuffer } }
|
|
5904
|
+
{ binding: 22, resource: { buffer: pathVertexBuffer } },
|
|
5905
|
+
{ binding: 23, resource: baseColorAtlasResource.view },
|
|
5906
|
+
{ binding: 24, resource: metallicRoughnessAtlasResource.view },
|
|
5907
|
+
{ binding: 25, resource: normalAtlasResource.view },
|
|
5908
|
+
{ binding: 26, resource: occlusionAtlasResource.view },
|
|
5909
|
+
{ binding: 27, resource: emissiveAtlasResource.view },
|
|
5910
|
+
{ binding: 28, resource: materialAtlasSampler },
|
|
5911
|
+
{ binding: 29, resource: brdfLutResource.view },
|
|
5912
|
+
{ binding: 30, resource: brdfLutResource.sampler },
|
|
5913
|
+
{ binding: 31, resource: environmentSamplingResource.view }
|
|
4024
5914
|
]
|
|
4025
5915
|
});
|
|
4026
5916
|
}
|
|
@@ -4073,6 +5963,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4073
5963
|
outputView,
|
|
4074
5964
|
"plasius.wavefront.bind.denoise.scratchToOutput"
|
|
4075
5965
|
);
|
|
5966
|
+
const denoiseDirectResolveBindGroup = createDenoiseResolveBindGroup(
|
|
5967
|
+
radianceView,
|
|
5968
|
+
outputView,
|
|
5969
|
+
"plasius.wavefront.bind.denoise.radianceToOutput"
|
|
5970
|
+
);
|
|
4076
5971
|
const presentBindGroupLayout = device.createBindGroupLayout({
|
|
4077
5972
|
label: "plasius.wavefront.presentBindGroupLayout",
|
|
4078
5973
|
entries: [
|
|
@@ -4110,23 +6005,128 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4110
6005
|
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
4111
6006
|
let accelerationBuildCount = 0;
|
|
4112
6007
|
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA;
|
|
6008
|
+
let lastCompletedFrameTimeMs = null;
|
|
6009
|
+
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
4113
6010
|
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
4114
6011
|
gpuAdapterParallelism,
|
|
4115
6012
|
createGpuParallelismCounters()
|
|
4116
6013
|
);
|
|
6014
|
+
function resolveRenderedSamplesPerPixel(renderOptions = {}, awaitGPUCompletion = true) {
|
|
6015
|
+
const targetSamplesPerPixel = clamp(
|
|
6016
|
+
readPositiveInteger(
|
|
6017
|
+
"samplesPerPixel",
|
|
6018
|
+
renderOptions.samplesPerPixel,
|
|
6019
|
+
config.samplesPerPixel
|
|
6020
|
+
),
|
|
6021
|
+
1,
|
|
6022
|
+
config.samplesPerPixel
|
|
6023
|
+
);
|
|
6024
|
+
const frameTimeBudgetMs = Number.isFinite(renderOptions.frameTimeBudgetMs) ? Math.max(0, Number(renderOptions.frameTimeBudgetMs)) : null;
|
|
6025
|
+
const minimumSamplesPerPixel = clamp(
|
|
6026
|
+
readPositiveInteger(
|
|
6027
|
+
"minimumSamplesPerPixel",
|
|
6028
|
+
renderOptions.minimumSamplesPerPixel,
|
|
6029
|
+
frameTimeBudgetMs !== null && targetSamplesPerPixel > 1 ? 1 : targetSamplesPerPixel
|
|
6030
|
+
),
|
|
6031
|
+
1,
|
|
6032
|
+
targetSamplesPerPixel
|
|
6033
|
+
);
|
|
6034
|
+
if (frameTimeBudgetMs === null || !awaitGPUCompletion || targetSamplesPerPixel <= minimumSamplesPerPixel) {
|
|
6035
|
+
return Object.freeze({
|
|
6036
|
+
renderedSamplesPerPixel: targetSamplesPerPixel,
|
|
6037
|
+
targetSamplesPerPixel,
|
|
6038
|
+
minimumSamplesPerPixel,
|
|
6039
|
+
frameTimeBudgetMs,
|
|
6040
|
+
budgetConstrained: false
|
|
6041
|
+
});
|
|
6042
|
+
}
|
|
6043
|
+
const estimatedSampleTimeMs = Number.isFinite(lastCompletedFrameTimeMs) && lastCompletedFrameTimeMs > 0 ? lastCompletedFrameTimeMs / Math.max(1, lastCompletedSamplesPerPixel) : null;
|
|
6044
|
+
if (!Number.isFinite(estimatedSampleTimeMs) || estimatedSampleTimeMs <= 0) {
|
|
6045
|
+
return Object.freeze({
|
|
6046
|
+
renderedSamplesPerPixel: minimumSamplesPerPixel,
|
|
6047
|
+
targetSamplesPerPixel,
|
|
6048
|
+
minimumSamplesPerPixel,
|
|
6049
|
+
frameTimeBudgetMs,
|
|
6050
|
+
budgetConstrained: minimumSamplesPerPixel < targetSamplesPerPixel
|
|
6051
|
+
});
|
|
6052
|
+
}
|
|
6053
|
+
const budgetLimitedSamples = clamp(
|
|
6054
|
+
Math.floor(frameTimeBudgetMs / estimatedSampleTimeMs),
|
|
6055
|
+
minimumSamplesPerPixel,
|
|
6056
|
+
targetSamplesPerPixel
|
|
6057
|
+
);
|
|
6058
|
+
return Object.freeze({
|
|
6059
|
+
renderedSamplesPerPixel: budgetLimitedSamples,
|
|
6060
|
+
targetSamplesPerPixel,
|
|
6061
|
+
minimumSamplesPerPixel,
|
|
6062
|
+
frameTimeBudgetMs,
|
|
6063
|
+
budgetConstrained: budgetLimitedSamples < targetSamplesPerPixel
|
|
6064
|
+
});
|
|
6065
|
+
}
|
|
6066
|
+
function createFrameStats({
|
|
6067
|
+
frameIndex,
|
|
6068
|
+
accelerationBuildSubmitted,
|
|
6069
|
+
frameSubmissionCount,
|
|
6070
|
+
parallelismCounters,
|
|
6071
|
+
renderedSamplesPerPixel,
|
|
6072
|
+
targetSamplesPerPixel,
|
|
6073
|
+
frameTimeBudgetMs,
|
|
6074
|
+
budgetConstrained
|
|
6075
|
+
}) {
|
|
6076
|
+
lastGpuParallelism = createGpuParallelismDiagnostics(gpuAdapterParallelism, parallelismCounters);
|
|
6077
|
+
const commandSubmissions = frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0);
|
|
6078
|
+
return Object.freeze({
|
|
6079
|
+
frame,
|
|
6080
|
+
frameIndex,
|
|
6081
|
+
width: config.width,
|
|
6082
|
+
height: config.height,
|
|
6083
|
+
maxDepth: config.maxDepth,
|
|
6084
|
+
tiles: tiles.length,
|
|
6085
|
+
tileSize: config.tileSize,
|
|
6086
|
+
samplesPerPixel: targetSamplesPerPixel,
|
|
6087
|
+
renderedSamplesPerPixel,
|
|
6088
|
+
frameTimeBudgetMs,
|
|
6089
|
+
budgetConstrained,
|
|
6090
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6091
|
+
screenRays: config.width * config.height,
|
|
6092
|
+
primaryRays: config.width * config.height * renderedSamplesPerPixel,
|
|
6093
|
+
sceneObjectCount: config.sceneObjectCount,
|
|
6094
|
+
triangleCount: config.triangleCount,
|
|
6095
|
+
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6096
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
6097
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
6098
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6099
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
6100
|
+
bvhNodeCount: config.bvhNodeCount,
|
|
6101
|
+
displayQuality: config.displayQuality,
|
|
6102
|
+
accelerationBuildMode: config.accelerationBuildMode,
|
|
6103
|
+
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
6104
|
+
accelerationBuildSubmitted,
|
|
6105
|
+
accelerationBuilt,
|
|
6106
|
+
accelerationBuildCount,
|
|
6107
|
+
commandSubmissions,
|
|
6108
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
6109
|
+
gpuParallelism: lastGpuParallelism,
|
|
6110
|
+
memory: config.memory
|
|
6111
|
+
});
|
|
6112
|
+
}
|
|
6113
|
+
function writeFrameConfigSlot(slot, tile, frameIndex, buildRange = {}) {
|
|
6114
|
+
if (slot >= frameConfigSlotCount) {
|
|
6115
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
6116
|
+
}
|
|
6117
|
+
const offset = slot * configBufferStride;
|
|
6118
|
+
device.queue.writeBuffer(
|
|
6119
|
+
configBuffer,
|
|
6120
|
+
offset,
|
|
6121
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
6122
|
+
);
|
|
6123
|
+
return offset;
|
|
6124
|
+
}
|
|
4117
6125
|
function createFrameConfigWriter(frameIndex) {
|
|
4118
6126
|
let slot = 0;
|
|
4119
6127
|
return (tile, buildRange = {}) => {
|
|
4120
|
-
|
|
4121
|
-
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
4122
|
-
}
|
|
4123
|
-
const offset = slot * configBufferStride;
|
|
6128
|
+
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
4124
6129
|
slot += 1;
|
|
4125
|
-
device.queue.writeBuffer(
|
|
4126
|
-
configBuffer,
|
|
4127
|
-
offset,
|
|
4128
|
-
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
4129
|
-
);
|
|
4130
6130
|
return offset;
|
|
4131
6131
|
};
|
|
4132
6132
|
}
|
|
@@ -4171,7 +6171,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4171
6171
|
passEncoder.setPipeline(pipelines.prepareMeshTrianglesAndLeaves);
|
|
4172
6172
|
const prepareWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4173
6173
|
passEncoder.dispatchWorkgroups(prepareWorkgroups);
|
|
4174
|
-
recordDirectDispatch(parallelism, [prepareWorkgroups]);
|
|
6174
|
+
recordDirectDispatch(parallelism, [prepareWorkgroups], WORKGROUP_SIZE);
|
|
4175
6175
|
passEncoder.setPipeline(pipelines.sortBvhLeafRefs);
|
|
4176
6176
|
for (let stageIndex = 0; stageIndex < config.bvhSortStages.length; stageIndex += 1) {
|
|
4177
6177
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [
|
|
@@ -4179,13 +6179,13 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4179
6179
|
]);
|
|
4180
6180
|
const sortWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4181
6181
|
passEncoder.dispatchWorkgroups(sortWorkgroups);
|
|
4182
|
-
recordDirectDispatch(parallelism, [sortWorkgroups]);
|
|
6182
|
+
recordDirectDispatch(parallelism, [sortWorkgroups], WORKGROUP_SIZE);
|
|
4183
6183
|
}
|
|
4184
6184
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [0]);
|
|
4185
6185
|
passEncoder.setPipeline(pipelines.writeSortedBvhLeaves);
|
|
4186
6186
|
const leafWriteWorkgroups = Math.ceil(config.triangleCount / WORKGROUP_SIZE);
|
|
4187
6187
|
passEncoder.dispatchWorkgroups(leafWriteWorkgroups);
|
|
4188
|
-
recordDirectDispatch(parallelism, [leafWriteWorkgroups]);
|
|
6188
|
+
recordDirectDispatch(parallelism, [leafWriteWorkgroups], WORKGROUP_SIZE);
|
|
4189
6189
|
passEncoder.setPipeline(pipelines.buildBvhInternalLevel);
|
|
4190
6190
|
for (let levelIndex = 0; levelIndex < config.bvhBuildLevels.length; levelIndex += 1) {
|
|
4191
6191
|
const buildLevel = config.bvhBuildLevels[levelIndex];
|
|
@@ -4194,7 +6194,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4194
6194
|
]);
|
|
4195
6195
|
const levelWorkgroups = Math.ceil(buildLevel.count / WORKGROUP_SIZE);
|
|
4196
6196
|
passEncoder.dispatchWorkgroups(levelWorkgroups);
|
|
4197
|
-
recordDirectDispatch(parallelism, [levelWorkgroups]);
|
|
6197
|
+
recordDirectDispatch(parallelism, [levelWorkgroups], WORKGROUP_SIZE);
|
|
4198
6198
|
}
|
|
4199
6199
|
passEncoder.end();
|
|
4200
6200
|
device.queue.submit([encoder.finish()]);
|
|
@@ -4210,7 +6210,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4210
6210
|
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4211
6211
|
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
4212
6212
|
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
4213
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6213
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4214
6214
|
generatePass.end();
|
|
4215
6215
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
4216
6216
|
encoder.copyBufferToBuffer(
|
|
@@ -4226,10 +6226,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4226
6226
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
4227
6227
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
4228
6228
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4229
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6229
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4230
6230
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
4231
6231
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4232
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6232
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4233
6233
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
4234
6234
|
passEncoder.dispatchWorkgroups(1);
|
|
4235
6235
|
recordDirectDispatch(parallelism, [1], 1);
|
|
@@ -4244,30 +6244,45 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4244
6244
|
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4245
6245
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
4246
6246
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
4247
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6247
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4248
6248
|
passEncoder.end();
|
|
4249
6249
|
}
|
|
4250
|
-
function encodeDenoise(encoder, configOffset, parallelism) {
|
|
6250
|
+
function encodeDenoise(encoder, configOffset, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4251
6251
|
if (!config.denoise) {
|
|
4252
6252
|
return;
|
|
4253
6253
|
}
|
|
4254
6254
|
const denoiseWorkgroupsX = Math.ceil(config.width / 8);
|
|
4255
6255
|
const denoiseWorkgroupsY = Math.ceil(config.height / 8);
|
|
4256
|
-
const
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
6256
|
+
const useTwoPassDenoise = renderedSamplesPerPixel < 4;
|
|
6257
|
+
if (useTwoPassDenoise) {
|
|
6258
|
+
const radiancePass = encoder.beginComputePass({
|
|
6259
|
+
label: "plasius.wavefront.denoiseRadiancePass"
|
|
6260
|
+
});
|
|
6261
|
+
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
6262
|
+
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
6263
|
+
radiancePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
6264
|
+
recordDirectDispatch(
|
|
6265
|
+
parallelism,
|
|
6266
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6267
|
+
WORKGROUP_SIZE
|
|
6268
|
+
);
|
|
6269
|
+
radiancePass.end();
|
|
6270
|
+
}
|
|
4264
6271
|
const resolvePass = encoder.beginComputePass({
|
|
4265
6272
|
label: "plasius.wavefront.denoiseResolvePass"
|
|
4266
6273
|
});
|
|
4267
|
-
resolvePass.setBindGroup(
|
|
6274
|
+
resolvePass.setBindGroup(
|
|
6275
|
+
0,
|
|
6276
|
+
useTwoPassDenoise ? denoiseResolveBindGroup : denoiseDirectResolveBindGroup,
|
|
6277
|
+
[configOffset]
|
|
6278
|
+
);
|
|
4268
6279
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
4269
6280
|
resolvePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
4270
|
-
recordDirectDispatch(
|
|
6281
|
+
recordDirectDispatch(
|
|
6282
|
+
parallelism,
|
|
6283
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6284
|
+
WORKGROUP_SIZE
|
|
6285
|
+
);
|
|
4271
6286
|
resolvePass.end();
|
|
4272
6287
|
}
|
|
4273
6288
|
function encodePresent(encoder) {
|
|
@@ -4288,98 +6303,213 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4288
6303
|
passEncoder.draw(3);
|
|
4289
6304
|
passEncoder.end();
|
|
4290
6305
|
}
|
|
4291
|
-
function dispatchFrame(frameIndex, parallelism) {
|
|
6306
|
+
function dispatchFrame(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4292
6307
|
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
6308
|
+
const batch = createGpuSubmissionBatcher({
|
|
6309
|
+
device,
|
|
6310
|
+
frameIndex,
|
|
6311
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission
|
|
4297
6312
|
});
|
|
4298
|
-
function submitCurrentEncoder() {
|
|
4299
|
-
if (encodedFramePasses <= 0) {
|
|
4300
|
-
return;
|
|
4301
|
-
}
|
|
4302
|
-
device.queue.submit([encoder.finish()]);
|
|
4303
|
-
submissionCount += 1;
|
|
4304
|
-
encodedFramePasses = 0;
|
|
4305
|
-
encoder = device.createCommandEncoder({
|
|
4306
|
-
label: `plasius.wavefront.frame.${frameIndex}.batched.${submissionCount + 1}`
|
|
4307
|
-
});
|
|
4308
|
-
}
|
|
4309
|
-
function reserveEncoder(passCount = 1) {
|
|
4310
|
-
if (encodedFramePasses > 0 && encodedFramePasses + passCount > config.maxFramePassesPerSubmission) {
|
|
4311
|
-
submitCurrentEncoder();
|
|
4312
|
-
}
|
|
4313
|
-
encodedFramePasses += passCount;
|
|
4314
|
-
return encoder;
|
|
4315
|
-
}
|
|
4316
6313
|
for (const tile of tiles) {
|
|
4317
|
-
for (let sampleIndex = 0; sampleIndex <
|
|
6314
|
+
for (let sampleIndex = 0; sampleIndex < renderedSamplesPerPixel; sampleIndex += 1) {
|
|
4318
6315
|
const configOffset = writeFrameConfig(tile, {
|
|
4319
6316
|
sampleIndex,
|
|
4320
|
-
sampleWeight: 1 /
|
|
6317
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
4321
6318
|
});
|
|
4322
|
-
encodeTileSample(
|
|
6319
|
+
encodeTileSample(
|
|
6320
|
+
batch.reserve(config.maxDepth + 1),
|
|
6321
|
+
tile,
|
|
6322
|
+
configOffset,
|
|
6323
|
+
parallelism
|
|
6324
|
+
);
|
|
4323
6325
|
if (config.deferredPathResolve) {
|
|
4324
|
-
encodeTileOutput(
|
|
6326
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
4325
6327
|
}
|
|
4326
6328
|
}
|
|
4327
6329
|
if (!config.deferredPathResolve) {
|
|
4328
6330
|
const outputConfigOffset = writeFrameConfig(tile, {
|
|
4329
6331
|
sampleIndex: 0,
|
|
4330
|
-
sampleWeight: 1 /
|
|
6332
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
4331
6333
|
});
|
|
4332
|
-
encodeTileOutput(
|
|
6334
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
4333
6335
|
}
|
|
4334
6336
|
}
|
|
4335
6337
|
if (config.denoise) {
|
|
4336
6338
|
const denoiseConfigOffset = writeFrameConfig(
|
|
4337
6339
|
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
4338
|
-
{ sampleIndex: 0, sampleWeight: 1 /
|
|
6340
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6341
|
+
);
|
|
6342
|
+
const denoisePassCount = renderedSamplesPerPixel < 4 ? 2 : 1;
|
|
6343
|
+
encodeDenoise(
|
|
6344
|
+
batch.reserve(denoisePassCount),
|
|
6345
|
+
denoiseConfigOffset,
|
|
6346
|
+
parallelism,
|
|
6347
|
+
renderedSamplesPerPixel
|
|
4339
6348
|
);
|
|
4340
|
-
encodeDenoise(reserveEncoder(), denoiseConfigOffset, parallelism);
|
|
4341
6349
|
}
|
|
4342
|
-
encodePresent(
|
|
4343
|
-
|
|
4344
|
-
return submissionCount;
|
|
6350
|
+
encodePresent(batch.reserve(1));
|
|
6351
|
+
return batch.flush();
|
|
4345
6352
|
}
|
|
4346
|
-
function renderOnce() {
|
|
6353
|
+
function renderOnce(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
6354
|
+
const frameStartTimeMs = nowMs();
|
|
4347
6355
|
frame += 1;
|
|
4348
6356
|
const frameIndex = frame + config.frameIndex;
|
|
6357
|
+
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
4349
6358
|
const parallelismCounters = createGpuParallelismCounters();
|
|
4350
6359
|
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
4351
|
-
const frameSubmissionCount = dispatchFrame(
|
|
4352
|
-
|
|
6360
|
+
const frameSubmissionCount = dispatchFrame(
|
|
6361
|
+
frameIndex,
|
|
6362
|
+
parallelismCounters,
|
|
6363
|
+
samplingPlan.renderedSamplesPerPixel
|
|
6364
|
+
);
|
|
6365
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
4353
6366
|
return Object.freeze({
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
6367
|
+
...createFrameStats({
|
|
6368
|
+
frameIndex,
|
|
6369
|
+
accelerationBuildSubmitted,
|
|
6370
|
+
frameSubmissionCount,
|
|
6371
|
+
parallelismCounters,
|
|
6372
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
6373
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
6374
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
6375
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
6376
|
+
}),
|
|
6377
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
6378
|
+
lastGpuParallelism,
|
|
6379
|
+
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
6380
|
+
frameTimeMs,
|
|
6381
|
+
false
|
|
6382
|
+
)
|
|
6383
|
+
});
|
|
6384
|
+
}
|
|
6385
|
+
async function waitForSubmittedGpuWork(options2 = {}) {
|
|
6386
|
+
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
6387
|
+
return true;
|
|
6388
|
+
}
|
|
6389
|
+
const timeoutMs = Math.max(
|
|
6390
|
+
1,
|
|
6391
|
+
Number.isFinite(options2.timeoutMs) ? Number(options2.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
6392
|
+
);
|
|
6393
|
+
const allowTimeout = options2.allowTimeout !== false;
|
|
6394
|
+
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
6395
|
+
() => ({ status: "done" }),
|
|
6396
|
+
(error) => {
|
|
6397
|
+
throw error;
|
|
6398
|
+
}
|
|
6399
|
+
);
|
|
6400
|
+
const lossPromise = typeof device.lost?.then === "function" ? device.lost.then((info) => {
|
|
6401
|
+
throw new Error(
|
|
6402
|
+
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
6403
|
+
);
|
|
6404
|
+
}) : null;
|
|
6405
|
+
let timeoutHandle = null;
|
|
6406
|
+
let resolveTimeoutPromise = null;
|
|
6407
|
+
let timeoutSettled = false;
|
|
6408
|
+
const settleTimeoutPromise = (value) => {
|
|
6409
|
+
if (timeoutSettled) {
|
|
6410
|
+
return;
|
|
6411
|
+
}
|
|
6412
|
+
timeoutSettled = true;
|
|
6413
|
+
resolveTimeoutPromise?.(value);
|
|
6414
|
+
};
|
|
6415
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
6416
|
+
resolveTimeoutPromise = resolve;
|
|
6417
|
+
timeoutHandle = setTimeout(() => settleTimeoutPromise({ status: "timeout" }), timeoutMs);
|
|
6418
|
+
});
|
|
6419
|
+
let result;
|
|
6420
|
+
try {
|
|
6421
|
+
result = await Promise.race(
|
|
6422
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
6423
|
+
);
|
|
6424
|
+
} finally {
|
|
6425
|
+
if (timeoutHandle !== null) {
|
|
6426
|
+
clearTimeout(timeoutHandle);
|
|
6427
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
6428
|
+
}
|
|
6429
|
+
}
|
|
6430
|
+
if (result?.status === "timeout") {
|
|
6431
|
+
if (!allowTimeout) {
|
|
6432
|
+
throw new Error(`Timed out after ${timeoutMs} ms waiting for submitted GPU work.`);
|
|
6433
|
+
}
|
|
6434
|
+
console.warn(
|
|
6435
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
|
|
6436
|
+
);
|
|
6437
|
+
return false;
|
|
6438
|
+
}
|
|
6439
|
+
return true;
|
|
6440
|
+
}
|
|
6441
|
+
function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
6442
|
+
const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
|
|
6443
|
+
const denoisePassCount = config.denoise ? renderedSamplesPerPixel < 4 ? 2 : 1 : 0;
|
|
6444
|
+
const tailPassCount = denoisePassCount + 1;
|
|
6445
|
+
const sampleBatchSize = Math.max(
|
|
6446
|
+
1,
|
|
6447
|
+
Math.floor(
|
|
6448
|
+
Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) / Math.max(samplePassesPerSample, 1)
|
|
6449
|
+
)
|
|
6450
|
+
);
|
|
6451
|
+
let submissionCount = 0;
|
|
6452
|
+
for (const tile of tiles) {
|
|
6453
|
+
for (let sampleStart = 0; sampleStart < renderedSamplesPerPixel; sampleStart += sampleBatchSize) {
|
|
6454
|
+
const sampleEnd = Math.min(renderedSamplesPerPixel, sampleStart + sampleBatchSize);
|
|
6455
|
+
const batch = createGpuSubmissionBatcher({
|
|
6456
|
+
device,
|
|
6457
|
+
frameIndex,
|
|
6458
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6459
|
+
startingSubmissionCount: submissionCount
|
|
6460
|
+
});
|
|
6461
|
+
let slot = 0;
|
|
6462
|
+
for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
|
|
6463
|
+
const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6464
|
+
sampleIndex,
|
|
6465
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
6466
|
+
});
|
|
6467
|
+
slot += 1;
|
|
6468
|
+
encodeTileSample(
|
|
6469
|
+
batch.reserve(config.maxDepth + 1),
|
|
6470
|
+
tile,
|
|
6471
|
+
configOffset,
|
|
6472
|
+
parallelism
|
|
6473
|
+
);
|
|
6474
|
+
if (config.deferredPathResolve) {
|
|
6475
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
6476
|
+
}
|
|
6477
|
+
}
|
|
6478
|
+
if (!config.deferredPathResolve && sampleEnd >= renderedSamplesPerPixel) {
|
|
6479
|
+
const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6480
|
+
sampleIndex: 0,
|
|
6481
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
6482
|
+
});
|
|
6483
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
6484
|
+
}
|
|
6485
|
+
batch.flush();
|
|
6486
|
+
submissionCount += batch.getSubmissionCount();
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
6489
|
+
const tail = createGpuSubmissionBatcher({
|
|
6490
|
+
device,
|
|
6491
|
+
frameIndex,
|
|
4361
6492
|
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
4362
|
-
|
|
4363
|
-
primaryRays: config.width * config.height * config.samplesPerPixel,
|
|
4364
|
-
sceneObjectCount: config.sceneObjectCount,
|
|
4365
|
-
triangleCount: config.triangleCount,
|
|
4366
|
-
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
4367
|
-
environmentPortalCount: config.environmentPortalCount,
|
|
4368
|
-
environmentPortalMode: config.environmentPortalMode,
|
|
4369
|
-
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4370
|
-
deferredPathResolve: config.deferredPathResolve,
|
|
4371
|
-
bvhNodeCount: config.bvhNodeCount,
|
|
4372
|
-
displayQuality: config.displayQuality,
|
|
4373
|
-
accelerationBuildMode: config.accelerationBuildMode,
|
|
4374
|
-
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
4375
|
-
accelerationBuildSubmitted,
|
|
4376
|
-
accelerationBuilt,
|
|
4377
|
-
accelerationBuildCount,
|
|
4378
|
-
commandSubmissions: frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
4379
|
-
frameConfigSlots: frameConfigSlotCount,
|
|
4380
|
-
gpuParallelism: lastGpuParallelism,
|
|
4381
|
-
memory: config.memory
|
|
6493
|
+
startingSubmissionCount: submissionCount
|
|
4382
6494
|
});
|
|
6495
|
+
if (config.denoise) {
|
|
6496
|
+
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
6497
|
+
0,
|
|
6498
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
6499
|
+
frameIndex,
|
|
6500
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6501
|
+
);
|
|
6502
|
+
encodeDenoise(
|
|
6503
|
+
tail.reserve(denoisePassCount),
|
|
6504
|
+
denoiseConfigOffset,
|
|
6505
|
+
parallelism,
|
|
6506
|
+
renderedSamplesPerPixel
|
|
6507
|
+
);
|
|
6508
|
+
}
|
|
6509
|
+
encodePresent(tail.reserve(1));
|
|
6510
|
+
tail.flush();
|
|
6511
|
+
submissionCount += tail.getSubmissionCount();
|
|
6512
|
+
return submissionCount;
|
|
4383
6513
|
}
|
|
4384
6514
|
async function readOutputProbe(optionsForProbe = {}) {
|
|
4385
6515
|
const mapMode = constants.map;
|
|
@@ -4393,6 +6523,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4393
6523
|
size: 256,
|
|
4394
6524
|
usage: constants.buffer.COPY_DST | constants.buffer.MAP_READ
|
|
4395
6525
|
});
|
|
6526
|
+
await waitForSubmittedGpuWork({
|
|
6527
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
6528
|
+
allowTimeout: false
|
|
6529
|
+
});
|
|
4396
6530
|
const encoder = device.createCommandEncoder({
|
|
4397
6531
|
label: "plasius.wavefront.outputProbe.copy"
|
|
4398
6532
|
});
|
|
@@ -4402,6 +6536,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4402
6536
|
{ width: 1, height: 1, depthOrArrayLayers: 1 }
|
|
4403
6537
|
);
|
|
4404
6538
|
device.queue.submit([encoder.finish()]);
|
|
6539
|
+
await waitForSubmittedGpuWork({
|
|
6540
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
6541
|
+
allowTimeout: false
|
|
6542
|
+
});
|
|
4405
6543
|
await readback.mapAsync(mapMode.READ);
|
|
4406
6544
|
const bytes = new Uint8Array(readback.getMappedRange()).slice(0, 4);
|
|
4407
6545
|
readback.unmap();
|
|
@@ -4414,7 +6552,57 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4414
6552
|
});
|
|
4415
6553
|
}
|
|
4416
6554
|
async function renderFrame(renderOptions = {}) {
|
|
4417
|
-
const
|
|
6555
|
+
const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
|
|
6556
|
+
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
6557
|
+
const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
6558
|
+
const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
|
|
6559
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6560
|
+
tiles.length,
|
|
6561
|
+
renderOptions.submittedWorkTimeoutMs
|
|
6562
|
+
);
|
|
6563
|
+
const frameStartTimeMs = nowMs();
|
|
6564
|
+
const submissionWaitOptions = awaitGPUCompletion ? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false } : { timeoutMs: submittedWorkTimeoutMs };
|
|
6565
|
+
let frameStats;
|
|
6566
|
+
if (useThrottledHighSamplePath) {
|
|
6567
|
+
frame += 1;
|
|
6568
|
+
const frameIndex = frame + config.frameIndex;
|
|
6569
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
6570
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
6571
|
+
const frameSubmissionCount = dispatchFrameAwaitingGpu(
|
|
6572
|
+
frameIndex,
|
|
6573
|
+
parallelismCounters,
|
|
6574
|
+
samplingPlan.renderedSamplesPerPixel
|
|
6575
|
+
);
|
|
6576
|
+
frameStats = createFrameStats({
|
|
6577
|
+
frameIndex,
|
|
6578
|
+
accelerationBuildSubmitted,
|
|
6579
|
+
frameSubmissionCount,
|
|
6580
|
+
parallelismCounters,
|
|
6581
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
6582
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
6583
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
6584
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
6585
|
+
});
|
|
6586
|
+
} else {
|
|
6587
|
+
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
6588
|
+
}
|
|
6589
|
+
if (awaitGPUCompletion) {
|
|
6590
|
+
await waitForSubmittedGpuWork(submissionWaitOptions);
|
|
6591
|
+
}
|
|
6592
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
6593
|
+
if (awaitGPUCompletion) {
|
|
6594
|
+
lastCompletedFrameTimeMs = frameTimeMs;
|
|
6595
|
+
lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
|
|
6596
|
+
}
|
|
6597
|
+
frameStats = Object.freeze({
|
|
6598
|
+
...frameStats,
|
|
6599
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
6600
|
+
frameStats.gpuParallelism,
|
|
6601
|
+
frameStats.commandSubmissions,
|
|
6602
|
+
frameTimeMs,
|
|
6603
|
+
awaitGPUCompletion
|
|
6604
|
+
)
|
|
6605
|
+
});
|
|
4418
6606
|
const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
4419
6607
|
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
4420
6608
|
return Object.freeze({
|
|
@@ -4435,10 +6623,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4435
6623
|
queueOverflow: 0
|
|
4436
6624
|
});
|
|
4437
6625
|
}
|
|
4438
|
-
function
|
|
4439
|
-
|
|
4440
|
-
packedScene = nextPackedScene;
|
|
4441
|
-
config = createWavefrontPathTracingComputeConfig({
|
|
6626
|
+
function rebuildLiveConfig(overrides = {}) {
|
|
6627
|
+
return createWavefrontPathTracingComputeConfig({
|
|
4442
6628
|
...options,
|
|
4443
6629
|
canvas,
|
|
4444
6630
|
width: config.width,
|
|
@@ -4449,26 +6635,23 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4449
6635
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4450
6636
|
sceneObjects: packedScene.objects,
|
|
4451
6637
|
camera: activeCameraOptions,
|
|
4452
|
-
|
|
6638
|
+
environmentMap: {
|
|
6639
|
+
...config.environmentMap
|
|
6640
|
+
},
|
|
6641
|
+
frameIndex: config.frameIndex,
|
|
6642
|
+
...overrides
|
|
4453
6643
|
});
|
|
6644
|
+
}
|
|
6645
|
+
function updateSceneObjects(sceneObjects) {
|
|
6646
|
+
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
6647
|
+
packedScene = nextPackedScene;
|
|
6648
|
+
config = rebuildLiveConfig();
|
|
4454
6649
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
4455
6650
|
return config;
|
|
4456
6651
|
}
|
|
4457
6652
|
function updateCamera(cameraOptions = {}) {
|
|
4458
6653
|
activeCameraOptions = cameraOptions;
|
|
4459
|
-
config =
|
|
4460
|
-
...options,
|
|
4461
|
-
canvas,
|
|
4462
|
-
width: config.width,
|
|
4463
|
-
height: config.height,
|
|
4464
|
-
maxDepth: config.maxDepth,
|
|
4465
|
-
tileSize: config.tileSize,
|
|
4466
|
-
samplesPerPixel: config.samplesPerPixel,
|
|
4467
|
-
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4468
|
-
sceneObjects: packedScene.objects,
|
|
4469
|
-
camera: activeCameraOptions,
|
|
4470
|
-
frameIndex: config.frameIndex
|
|
4471
|
-
});
|
|
6654
|
+
config = rebuildLiveConfig();
|
|
4472
6655
|
return config;
|
|
4473
6656
|
}
|
|
4474
6657
|
function getSnapshot() {
|
|
@@ -4523,6 +6706,25 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4523
6706
|
if (environmentMapResource.ownsTexture) {
|
|
4524
6707
|
environmentMapResource.texture?.destroy?.();
|
|
4525
6708
|
}
|
|
6709
|
+
if (environmentSamplingResource.ownsTexture) {
|
|
6710
|
+
environmentSamplingResource.texture?.destroy?.();
|
|
6711
|
+
}
|
|
6712
|
+
brdfLutResource.texture?.destroy?.();
|
|
6713
|
+
if (baseColorAtlasResource.ownsTexture) {
|
|
6714
|
+
baseColorAtlasResource.texture?.destroy?.();
|
|
6715
|
+
}
|
|
6716
|
+
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
6717
|
+
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
6718
|
+
}
|
|
6719
|
+
if (normalAtlasResource.ownsTexture) {
|
|
6720
|
+
normalAtlasResource.texture?.destroy?.();
|
|
6721
|
+
}
|
|
6722
|
+
if (occlusionAtlasResource.ownsTexture) {
|
|
6723
|
+
occlusionAtlasResource.texture?.destroy?.();
|
|
6724
|
+
}
|
|
6725
|
+
if (emissiveAtlasResource.ownsTexture) {
|
|
6726
|
+
emissiveAtlasResource.texture?.destroy?.();
|
|
6727
|
+
}
|
|
4526
6728
|
context.unconfigure?.();
|
|
4527
6729
|
}
|
|
4528
6730
|
return Object.freeze({
|
|
@@ -4675,6 +6877,48 @@ var rendererAccelerationStructurePolicies = Object.freeze(
|
|
|
4675
6877
|
})
|
|
4676
6878
|
)
|
|
4677
6879
|
);
|
|
6880
|
+
function clampWavefrontAdaptiveSamplesPerPixel(value) {
|
|
6881
|
+
if (!Number.isFinite(value)) {
|
|
6882
|
+
return 1;
|
|
6883
|
+
}
|
|
6884
|
+
return Math.max(1, Math.min(256, Math.round(value)));
|
|
6885
|
+
}
|
|
6886
|
+
function createWavefrontAdaptiveSamplingLevels(options = {}) {
|
|
6887
|
+
const requestedSamplesPerPixel = clampWavefrontAdaptiveSamplesPerPixel(
|
|
6888
|
+
options.samplesPerPixel ?? 1
|
|
6889
|
+
);
|
|
6890
|
+
const minimumSamplesPerPixel = Math.min(
|
|
6891
|
+
requestedSamplesPerPixel,
|
|
6892
|
+
clampWavefrontAdaptiveSamplesPerPixel(options.minimumSamplesPerPixel ?? 1)
|
|
6893
|
+
);
|
|
6894
|
+
const frameTimeBudgetMs = Number.isFinite(options.frameTimeBudgetMs) ? Math.max(0, Number(options.frameTimeBudgetMs)) : 0;
|
|
6895
|
+
const levels = /* @__PURE__ */ new Set([minimumSamplesPerPixel, requestedSamplesPerPixel]);
|
|
6896
|
+
let currentSamplesPerPixel = minimumSamplesPerPixel;
|
|
6897
|
+
while (currentSamplesPerPixel < requestedSamplesPerPixel) {
|
|
6898
|
+
levels.add(currentSamplesPerPixel);
|
|
6899
|
+
currentSamplesPerPixel *= 2;
|
|
6900
|
+
}
|
|
6901
|
+
levels.add(Math.min(currentSamplesPerPixel, requestedSamplesPerPixel));
|
|
6902
|
+
return Object.freeze({
|
|
6903
|
+
requestedSamplesPerPixel,
|
|
6904
|
+
minimumSamplesPerPixel,
|
|
6905
|
+
frameTimeBudgetMs,
|
|
6906
|
+
levels: Object.freeze(
|
|
6907
|
+
[...levels].sort((left, right) => left - right).map(
|
|
6908
|
+
(samplesPerPixel) => Object.freeze({
|
|
6909
|
+
id: `${samplesPerPixel}spp`,
|
|
6910
|
+
label: `${samplesPerPixel} spp`,
|
|
6911
|
+
estimatedCostMs: samplesPerPixel,
|
|
6912
|
+
config: Object.freeze({
|
|
6913
|
+
samplesPerPixel,
|
|
6914
|
+
frameTimeBudgetMs,
|
|
6915
|
+
minimumSamplesPerPixel
|
|
6916
|
+
})
|
|
6917
|
+
})
|
|
6918
|
+
)
|
|
6919
|
+
)
|
|
6920
|
+
});
|
|
6921
|
+
}
|
|
4678
6922
|
function createWavefrontField(name, type, description) {
|
|
4679
6923
|
return Object.freeze({
|
|
4680
6924
|
name,
|
|
@@ -5851,9 +8095,11 @@ export {
|
|
|
5851
8095
|
createGpuRenderer,
|
|
5852
8096
|
createRayTracingRenderPlan,
|
|
5853
8097
|
createRendererDebugHooks,
|
|
8098
|
+
createWavefrontAdaptiveSamplingLevels,
|
|
5854
8099
|
createWavefrontBvhBuildLevels,
|
|
5855
8100
|
createWavefrontBvhSortStages,
|
|
5856
8101
|
createWavefrontEmissiveTriangleIndexSource,
|
|
8102
|
+
createWavefrontGpuMaterialSource,
|
|
5857
8103
|
createWavefrontGpuMeshSource,
|
|
5858
8104
|
createWavefrontMeshAcceleration,
|
|
5859
8105
|
createWavefrontPathTracingComputeConfig,
|