@plasius/gpu-renderer 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -20
- package/README.md +49 -7
- package/dist/index.cjs +3071 -454
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3069 -454
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.d.ts +228 -8
- package/src/index.js +52 -0
- package/src/wavefront-compute.js +3106 -462
- package/src/wavefront-frame-runtime.js +167 -0
package/dist/index.js
CHANGED
|
@@ -1,33 +1,176 @@
|
|
|
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 = 128;
|
|
142
|
+
var DEFAULT_BRDF_LUT_SAMPLE_COUNT = 256;
|
|
7
143
|
var DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
8
144
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
9
145
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
146
|
+
var DEFAULT_MEDIUM_PHASE_MODEL = 0;
|
|
10
147
|
var WORKGROUP_SIZE = 64;
|
|
11
148
|
var rendererWavefrontComputeMode = "webgpu-compute";
|
|
12
149
|
var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
13
150
|
var rendererWavefrontComputeStatsStride = 8;
|
|
14
151
|
var RAY_RECORD_BYTES = 80;
|
|
15
|
-
var HIT_RECORD_BYTES =
|
|
16
|
-
var SCENE_OBJECT_RECORD_BYTES =
|
|
152
|
+
var HIT_RECORD_BYTES = 256;
|
|
153
|
+
var SCENE_OBJECT_RECORD_BYTES = 160;
|
|
17
154
|
var MESH_VERTEX_RECORD_BYTES = 48;
|
|
18
|
-
var MESH_RANGE_RECORD_BYTES =
|
|
19
|
-
var TRIANGLE_RECORD_BYTES =
|
|
155
|
+
var MESH_RANGE_RECORD_BYTES = 240;
|
|
156
|
+
var TRIANGLE_RECORD_BYTES = 352;
|
|
157
|
+
var GPU_MATERIAL_RECORD_BYTES = 192;
|
|
20
158
|
var BVH_NODE_RECORD_BYTES = 48;
|
|
21
159
|
var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
22
160
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
23
161
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
162
|
+
var MEDIUM_TABLE_ROWS = 2;
|
|
24
163
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
25
164
|
var PATH_VERTEX_RECORD_BYTES = 16;
|
|
26
|
-
var
|
|
165
|
+
var GPU_SUBMITTED_WORK_TIMEOUT_MS = 5e3;
|
|
166
|
+
var GPU_READBACK_COMPLETION_TIMEOUT_MS = 6e4;
|
|
167
|
+
var GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 6e4;
|
|
168
|
+
var CONFIG_BUFFER_BYTES = 320;
|
|
27
169
|
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
28
170
|
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
29
171
|
var COUNTER_BUFFER_BYTES = 32;
|
|
30
172
|
var TRACE_STORAGE_BUFFER_BINDINGS = 10;
|
|
173
|
+
var BRDF_LUT_UPLOAD_CACHE = /* @__PURE__ */ new Map();
|
|
31
174
|
var MATERIAL_DIFFUSE = 0;
|
|
32
175
|
var MATERIAL_METAL = 1;
|
|
33
176
|
var MATERIAL_DIELECTRIC = 2;
|
|
@@ -62,6 +205,7 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
62
205
|
meshVertexRecordBytes: MESH_VERTEX_RECORD_BYTES,
|
|
63
206
|
meshRangeRecordBytes: MESH_RANGE_RECORD_BYTES,
|
|
64
207
|
triangleRecordBytes: TRIANGLE_RECORD_BYTES,
|
|
208
|
+
materialRecordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
65
209
|
bvhNodeRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
66
210
|
bvhLeafReferenceRecordBytes: BVH_LEAF_REF_RECORD_BYTES,
|
|
67
211
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
@@ -145,6 +289,32 @@ function asColor(value, fallback = [1, 1, 1, 1]) {
|
|
|
145
289
|
clamp(readFiniteNumber("color[3]", value[3], fallback[3] ?? 1), 0, 1)
|
|
146
290
|
];
|
|
147
291
|
}
|
|
292
|
+
function deriveLegacySheenColor(baseColor, sheen, sheenTint) {
|
|
293
|
+
const sheenStrength = clamp(Number(sheen) || 0, 0, 1);
|
|
294
|
+
if (sheenStrength <= 0) {
|
|
295
|
+
return [0, 0, 0, 1];
|
|
296
|
+
}
|
|
297
|
+
const tint = clamp(Number(sheenTint) || 0, 0, 1);
|
|
298
|
+
const base = asColor(baseColor, [1, 1, 1, 1]);
|
|
299
|
+
return [
|
|
300
|
+
clamp((1 - tint) * sheenStrength + base[0] * tint * sheenStrength, 0, 1),
|
|
301
|
+
clamp((1 - tint) * sheenStrength + base[1] * tint * sheenStrength, 0, 1),
|
|
302
|
+
clamp((1 - tint) * sheenStrength + base[2] * tint * sheenStrength, 0, 1),
|
|
303
|
+
1
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
function resolveSheenColor(input, fallbackBaseColor) {
|
|
307
|
+
if (input?.sheenColor || input?.material?.sheenColor) {
|
|
308
|
+
return asColor(input.sheenColor ?? input.material?.sheenColor, [0, 0, 0, 1]).map(
|
|
309
|
+
(value, index) => index < 3 ? clamp(value, 0, 1) : 1
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return deriveLegacySheenColor(
|
|
313
|
+
fallbackBaseColor,
|
|
314
|
+
input?.sheen ?? input?.material?.sheen,
|
|
315
|
+
input?.sheenTint ?? input?.material?.sheenTint
|
|
316
|
+
);
|
|
317
|
+
}
|
|
148
318
|
function resolveEnvironmentMap(input = null) {
|
|
149
319
|
const source = input && typeof input === "object" ? input : null;
|
|
150
320
|
const hasTexture = Boolean(source?.view || source?.texture || source?.data);
|
|
@@ -154,6 +324,11 @@ function resolveEnvironmentMap(input = null) {
|
|
|
154
324
|
enabled: hasTexture && source?.enabled !== false,
|
|
155
325
|
width,
|
|
156
326
|
height,
|
|
327
|
+
mipLevelCount: readPositiveInteger(
|
|
328
|
+
"environmentMap.mipLevelCount",
|
|
329
|
+
source?.mipLevelCount,
|
|
330
|
+
1
|
|
331
|
+
),
|
|
157
332
|
format: typeof source?.format === "string" ? source.format : "rgba16float",
|
|
158
333
|
projection: typeof source?.projection === "string" ? source.projection : "equirectangular",
|
|
159
334
|
texture: source?.texture ?? null,
|
|
@@ -165,7 +340,8 @@ function resolveEnvironmentMap(input = null) {
|
|
|
165
340
|
ambientStrength: Math.max(
|
|
166
341
|
0,
|
|
167
342
|
readFiniteNumber("environmentMap.ambientStrength", source?.ambientStrength, 0.32)
|
|
168
|
-
)
|
|
343
|
+
),
|
|
344
|
+
hasImportanceData: source?.hasImportanceData === true
|
|
169
345
|
});
|
|
170
346
|
}
|
|
171
347
|
function resolveDeferredPathResolve(options = {}) {
|
|
@@ -330,6 +506,156 @@ function deriveBounds(input) {
|
|
|
330
506
|
}
|
|
331
507
|
return null;
|
|
332
508
|
}
|
|
509
|
+
function deriveBeerLambertAbsorptionFromAttenuationColor(attenuationColor, attenuationDistance, density = 1) {
|
|
510
|
+
const distance = Number(attenuationDistance);
|
|
511
|
+
const densityScale = Math.max(0, Number(density) || 0);
|
|
512
|
+
if (!Number.isFinite(distance) || distance <= 0 || densityScale <= 0) {
|
|
513
|
+
return [0, 0, 0];
|
|
514
|
+
}
|
|
515
|
+
return attenuationColor.slice(0, 3).map((channel) => {
|
|
516
|
+
const clamped = clamp(Number(channel) || 0, 1e-4, 1);
|
|
517
|
+
return Math.max(0, -Math.log(clamped) / distance * densityScale);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
function readMediumPhaseModel(value) {
|
|
521
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
522
|
+
return Math.max(0, Math.trunc(value));
|
|
523
|
+
}
|
|
524
|
+
switch (String(value ?? "").trim().toLowerCase()) {
|
|
525
|
+
case "isotropic":
|
|
526
|
+
default:
|
|
527
|
+
return DEFAULT_MEDIUM_PHASE_MODEL;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function resolveWavefrontVolumeInput(input) {
|
|
531
|
+
return input?.volume ?? input?.material?.volume ?? null;
|
|
532
|
+
}
|
|
533
|
+
function normalizeWavefrontThickness(input, label) {
|
|
534
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
535
|
+
return Math.max(
|
|
536
|
+
0,
|
|
537
|
+
readFiniteNumber(
|
|
538
|
+
label,
|
|
539
|
+
input?.thickness ?? volume?.thickness ?? input?.material?.thickness,
|
|
540
|
+
0
|
|
541
|
+
)
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
function resolveWavefrontMediumId(input, fallbackId = 1) {
|
|
545
|
+
return input?.mediumRefId ?? input?.mediumId ?? input?.material?.mediumId ?? input?.materialRefId ?? input?.material?.id ?? input?.materialId ?? input?.id ?? fallbackId;
|
|
546
|
+
}
|
|
547
|
+
function deriveWavefrontTransportMedium(input, fallbackId = 1) {
|
|
548
|
+
const resolvedId = resolveWavefrontMediumId(input, fallbackId);
|
|
549
|
+
if (input?.medium) {
|
|
550
|
+
return normalizeWavefrontMedium(
|
|
551
|
+
{
|
|
552
|
+
...input.medium,
|
|
553
|
+
id: input.medium.id ?? input.medium.mediumId ?? resolvedId
|
|
554
|
+
},
|
|
555
|
+
fallbackId
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
559
|
+
if (!volume) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
return normalizeWavefrontMedium(
|
|
563
|
+
{
|
|
564
|
+
id: resolvedId,
|
|
565
|
+
phaseModel: volume.phaseModel,
|
|
566
|
+
density: volume.density,
|
|
567
|
+
attenuationColor: volume.attenuationColor,
|
|
568
|
+
attenuationDistance: volume.attenuationDistance,
|
|
569
|
+
absorption: volume.absorption,
|
|
570
|
+
scattering: volume.scattering
|
|
571
|
+
},
|
|
572
|
+
fallbackId
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
function normalizeWavefrontMedium(input = {}, index = 0) {
|
|
576
|
+
const id = readNonNegativeInteger("medium id", input.id ?? input.mediumId, index);
|
|
577
|
+
const density = Math.max(0, readFiniteNumber("medium density", input.density, 1));
|
|
578
|
+
const attenuationColor = asColor(
|
|
579
|
+
input.attenuationColor ?? input.color ?? input.medium?.attenuationColor,
|
|
580
|
+
[1, 1, 1, 1]
|
|
581
|
+
);
|
|
582
|
+
const attenuationDistance = readFiniteNumber(
|
|
583
|
+
"medium attenuationDistance",
|
|
584
|
+
input.attenuationDistance ?? input.distance ?? input.medium?.attenuationDistance,
|
|
585
|
+
0
|
|
586
|
+
);
|
|
587
|
+
const absorption = Array.isArray(input.absorption) || Array.isArray(input.medium?.absorption) ? asVec3(input.absorption ?? input.medium?.absorption, [0, 0, 0]).map(
|
|
588
|
+
(value) => Math.max(0, Number(value) || 0)
|
|
589
|
+
) : deriveBeerLambertAbsorptionFromAttenuationColor(
|
|
590
|
+
attenuationColor,
|
|
591
|
+
attenuationDistance,
|
|
592
|
+
density
|
|
593
|
+
);
|
|
594
|
+
const scattering = asVec3(
|
|
595
|
+
input.scattering ?? input.medium?.scattering,
|
|
596
|
+
[0, 0, 0]
|
|
597
|
+
).map((value) => Math.max(0, Number(value) || 0));
|
|
598
|
+
return Object.freeze({
|
|
599
|
+
id,
|
|
600
|
+
phaseModel: readMediumPhaseModel(input.phaseModel ?? input.medium?.phaseModel),
|
|
601
|
+
density,
|
|
602
|
+
attenuationColor: Object.freeze(attenuationColor),
|
|
603
|
+
attenuationDistance,
|
|
604
|
+
absorption: Object.freeze(absorption),
|
|
605
|
+
scattering: Object.freeze(scattering)
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
function collectWavefrontMediums(options, meshes, sceneObjects = []) {
|
|
609
|
+
const mediumsById = /* @__PURE__ */ new Map();
|
|
610
|
+
mediumsById.set(
|
|
611
|
+
0,
|
|
612
|
+
Object.freeze({
|
|
613
|
+
id: 0,
|
|
614
|
+
phaseModel: DEFAULT_MEDIUM_PHASE_MODEL,
|
|
615
|
+
density: 0,
|
|
616
|
+
attenuationColor: Object.freeze([1, 1, 1, 1]),
|
|
617
|
+
attenuationDistance: 0,
|
|
618
|
+
absorption: Object.freeze([0, 0, 0]),
|
|
619
|
+
scattering: Object.freeze([0, 0, 0])
|
|
620
|
+
})
|
|
621
|
+
);
|
|
622
|
+
const register = (input, fallbackId = mediumsById.size) => {
|
|
623
|
+
if (!input) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const normalized = normalizeWavefrontMedium(
|
|
627
|
+
typeof input === "object" ? { id: fallbackId, ...input } : { id: fallbackId },
|
|
628
|
+
fallbackId
|
|
629
|
+
);
|
|
630
|
+
const existing = mediumsById.get(normalized.id);
|
|
631
|
+
if (existing && JSON.stringify(existing) !== JSON.stringify(normalized)) {
|
|
632
|
+
throw new Error(`Medium id ${normalized.id} is defined more than once with different values.`);
|
|
633
|
+
}
|
|
634
|
+
mediumsById.set(normalized.id, normalized);
|
|
635
|
+
};
|
|
636
|
+
for (const medium of options.mediums ?? []) {
|
|
637
|
+
register(medium);
|
|
638
|
+
}
|
|
639
|
+
for (const mesh of meshes) {
|
|
640
|
+
register(mesh.medium, mesh.mediumRefId ?? mesh.medium?.id ?? 0);
|
|
641
|
+
}
|
|
642
|
+
for (const mesh of meshes) {
|
|
643
|
+
if ((mesh.mediumRefId ?? 0) > 0 && !mediumsById.has(mesh.mediumRefId)) {
|
|
644
|
+
register({ id: mesh.mediumRefId });
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
for (const object of sceneObjects) {
|
|
648
|
+
register(object.medium, object.mediumRefId ?? object.medium?.id ?? 0);
|
|
649
|
+
}
|
|
650
|
+
for (const object of sceneObjects) {
|
|
651
|
+
if ((object.mediumRefId ?? 0) > 0 && !mediumsById.has(object.mediumRefId)) {
|
|
652
|
+
register({ id: object.mediumRefId });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return Object.freeze(
|
|
656
|
+
Array.from(mediumsById.values()).sort((left, right) => left.id - right.id)
|
|
657
|
+
);
|
|
658
|
+
}
|
|
333
659
|
function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
334
660
|
const bounds = deriveBounds(input);
|
|
335
661
|
const kind = readObjectKind(input.kind ?? input.type ?? (bounds ? "box" : "sphere"));
|
|
@@ -339,7 +665,8 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
339
665
|
input.halfExtent ?? input.halfExtents ?? input.extents ?? bounds?.halfExtent,
|
|
340
666
|
[0.5, 0.5, 0.5]
|
|
341
667
|
).map((value) => Math.max(value, 1e-3));
|
|
342
|
-
const
|
|
668
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
669
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
343
670
|
const color = asColor(
|
|
344
671
|
input.color ?? input.baseColor ?? input.albedo ?? input.material?.color ?? input.material?.baseColor,
|
|
345
672
|
[0.72, 0.72, 0.68, 1]
|
|
@@ -348,19 +675,55 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
348
675
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
349
676
|
[0, 0, 0, 1]
|
|
350
677
|
);
|
|
678
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
679
|
+
const transmission = clamp(
|
|
680
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
681
|
+
0,
|
|
682
|
+
1
|
|
683
|
+
);
|
|
684
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
685
|
+
const specularColor = asColor(
|
|
686
|
+
input.specularColor ?? input.material?.specularColor,
|
|
687
|
+
[1, 1, 1, 1]
|
|
688
|
+
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
689
|
+
const medium = deriveWavefrontTransportMedium(input, index + 1);
|
|
690
|
+
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
691
|
return Object.freeze({
|
|
352
692
|
id: readNonNegativeInteger("id", input.id, index + 1),
|
|
353
693
|
kind,
|
|
354
|
-
materialKind:
|
|
694
|
+
materialKind: resolvedMaterialKind,
|
|
355
695
|
flags: readNonNegativeInteger("flags", input.flags, 0),
|
|
696
|
+
mediumRefId: readNonNegativeInteger(
|
|
697
|
+
"mediumRefId",
|
|
698
|
+
input.mediumRefId ?? medium?.id ?? input.medium?.id ?? input.mediumId,
|
|
699
|
+
0
|
|
700
|
+
),
|
|
701
|
+
medium,
|
|
356
702
|
center: Object.freeze(center),
|
|
357
703
|
halfExtent: Object.freeze(halfExtent),
|
|
358
704
|
color: Object.freeze(color),
|
|
359
705
|
emission: Object.freeze(emission),
|
|
360
706
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
361
707
|
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)
|
|
708
|
+
opacity,
|
|
709
|
+
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
710
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
711
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
712
|
+
sheenColor: Object.freeze(sheenColor),
|
|
713
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
714
|
+
clearcoatRoughness: clamp(
|
|
715
|
+
readFiniteNumber(
|
|
716
|
+
"clearcoatRoughness",
|
|
717
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
718
|
+
0.08
|
|
719
|
+
),
|
|
720
|
+
0,
|
|
721
|
+
1
|
|
722
|
+
),
|
|
723
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
724
|
+
specularColor: Object.freeze(specularColor),
|
|
725
|
+
thickness: normalizeWavefrontThickness(input, "thickness"),
|
|
726
|
+
transmission
|
|
364
727
|
});
|
|
365
728
|
}
|
|
366
729
|
function createDefaultWavefrontSceneObjects() {
|
|
@@ -432,7 +795,8 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
432
795
|
input.uvs ?? input.texcoords ?? input.uv,
|
|
433
796
|
(value) => readFiniteNumber("mesh uv", value, 0)
|
|
434
797
|
) : null;
|
|
435
|
-
const
|
|
798
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
799
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
436
800
|
const color = asColor(
|
|
437
801
|
input.color ?? input.baseColor ?? input.albedo ?? input.material?.color ?? input.material?.baseColor,
|
|
438
802
|
[0.72, 0.72, 0.68, 1]
|
|
@@ -441,13 +805,26 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
441
805
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
442
806
|
[0, 0, 0, 1]
|
|
443
807
|
);
|
|
808
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
809
|
+
const transmission = clamp(
|
|
810
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
811
|
+
0,
|
|
812
|
+
1
|
|
813
|
+
);
|
|
814
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
815
|
+
const specularColor = asColor(
|
|
816
|
+
input.specularColor ?? input.material?.specularColor,
|
|
817
|
+
[1, 1, 1, 1]
|
|
818
|
+
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
819
|
+
const medium = deriveWavefrontTransportMedium(input, meshIndex + 1);
|
|
820
|
+
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
821
|
return Object.freeze({
|
|
445
822
|
id: readNonNegativeInteger("mesh id", input.id, meshIndex + 1),
|
|
446
823
|
positions: Object.freeze(Array.from(positions, (value) => readFiniteNumber("mesh position", value, 0))),
|
|
447
824
|
indices: Object.freeze(indices),
|
|
448
825
|
normals: normals ? Object.freeze(normals) : null,
|
|
449
826
|
uvs: uvs ? Object.freeze(uvs) : null,
|
|
450
|
-
materialKind:
|
|
827
|
+
materialKind: resolvedMaterialKind,
|
|
451
828
|
flags: readNonNegativeInteger("mesh flags", input.flags, 0),
|
|
452
829
|
materialRefId: readNonNegativeInteger(
|
|
453
830
|
"mesh materialRefId",
|
|
@@ -456,17 +833,176 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
456
833
|
),
|
|
457
834
|
mediumRefId: readNonNegativeInteger(
|
|
458
835
|
"mesh mediumRefId",
|
|
459
|
-
input.mediumRefId ?? input.medium?.id ?? input.mediumId,
|
|
836
|
+
input.mediumRefId ?? medium?.id ?? input.medium?.id ?? input.mediumId ?? input.material?.mediumId,
|
|
460
837
|
0
|
|
461
838
|
),
|
|
839
|
+
medium,
|
|
462
840
|
color: Object.freeze(color),
|
|
463
841
|
emission: Object.freeze(emission),
|
|
464
842
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
465
843
|
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)
|
|
844
|
+
opacity,
|
|
845
|
+
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
846
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
847
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
848
|
+
sheenColor: Object.freeze(sheenColor),
|
|
849
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
850
|
+
clearcoatRoughness: clamp(
|
|
851
|
+
readFiniteNumber(
|
|
852
|
+
"clearcoatRoughness",
|
|
853
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
854
|
+
0.08
|
|
855
|
+
),
|
|
856
|
+
0,
|
|
857
|
+
1
|
|
858
|
+
),
|
|
859
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
860
|
+
specularColor: Object.freeze(specularColor),
|
|
861
|
+
thickness: normalizeWavefrontThickness(input, "mesh thickness"),
|
|
862
|
+
transmission,
|
|
863
|
+
baseColorTexture: input.baseColorTexture ?? input.material?.baseColorTexture ?? null,
|
|
864
|
+
metallicRoughnessTexture: input.metallicRoughnessTexture ?? input.material?.metallicRoughnessTexture ?? null,
|
|
865
|
+
normalTexture: input.normalTexture ?? input.material?.normalTexture ?? null,
|
|
866
|
+
occlusionTexture: input.occlusionTexture ?? input.material?.occlusionTexture ?? null,
|
|
867
|
+
emissiveTexture: input.emissiveTexture ?? input.material?.emissiveTexture ?? null
|
|
468
868
|
});
|
|
469
869
|
}
|
|
870
|
+
function clampUnit(value) {
|
|
871
|
+
return clamp(Number(value) || 0, 0, 1);
|
|
872
|
+
}
|
|
873
|
+
function srgbToLinear(value) {
|
|
874
|
+
const channel = clampUnit(value);
|
|
875
|
+
if (channel <= 0.04045) {
|
|
876
|
+
return channel / 12.92;
|
|
877
|
+
}
|
|
878
|
+
return ((channel + 0.055) / 1.055) ** 2.4;
|
|
879
|
+
}
|
|
880
|
+
function sampleTextureRgba(texture, uv = [0, 0], colorSpace = "linear") {
|
|
881
|
+
if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || !texture.data || texture.width <= 0 || texture.height <= 0) {
|
|
882
|
+
return [1, 1, 1, 1];
|
|
883
|
+
}
|
|
884
|
+
const u = (uv[0] % 1 + 1) % 1;
|
|
885
|
+
const v = (uv[1] % 1 + 1) % 1;
|
|
886
|
+
const x = Math.min(texture.width - 1, Math.max(0, Math.round(u * (texture.width - 1))));
|
|
887
|
+
const y = Math.min(texture.height - 1, Math.max(0, Math.round((1 - v) * (texture.height - 1))));
|
|
888
|
+
const offset = (y * texture.width + x) * 4;
|
|
889
|
+
const data = texture.data;
|
|
890
|
+
const scale2 = resolveTextureSampleScale(data);
|
|
891
|
+
const defaultChannel = scale2 === 1 ? 1 : Math.round(1 / scale2);
|
|
892
|
+
const color = [
|
|
893
|
+
(data[offset] ?? defaultChannel) * scale2,
|
|
894
|
+
(data[offset + 1] ?? defaultChannel) * scale2,
|
|
895
|
+
(data[offset + 2] ?? defaultChannel) * scale2,
|
|
896
|
+
(data[offset + 3] ?? defaultChannel) * scale2
|
|
897
|
+
];
|
|
898
|
+
if (colorSpace === "srgb") {
|
|
899
|
+
return [srgbToLinear(color[0]), srgbToLinear(color[1]), srgbToLinear(color[2]), color[3]];
|
|
900
|
+
}
|
|
901
|
+
return color;
|
|
902
|
+
}
|
|
903
|
+
function resolveTextureSampleScale(data) {
|
|
904
|
+
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
|
|
905
|
+
return 1 / 255;
|
|
906
|
+
}
|
|
907
|
+
if (data instanceof Uint16Array) {
|
|
908
|
+
return 1 / 65535;
|
|
909
|
+
}
|
|
910
|
+
if (Array.isArray(data) && data.some((value) => Number(value) > 1)) {
|
|
911
|
+
return 1 / 255;
|
|
912
|
+
}
|
|
913
|
+
return 1;
|
|
914
|
+
}
|
|
915
|
+
function normalizeVectorOrFallback(vector, fallback) {
|
|
916
|
+
return normalize(Array.isArray(vector) ? vector : fallback, fallback);
|
|
917
|
+
}
|
|
918
|
+
function buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, fallbackNormal) {
|
|
919
|
+
const edge1 = subtract(v1, v0);
|
|
920
|
+
const edge2 = subtract(v2, v0);
|
|
921
|
+
const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
|
|
922
|
+
const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
|
|
923
|
+
const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
|
|
924
|
+
if (Math.abs(determinant) < 1e-6) {
|
|
925
|
+
const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
926
|
+
const tangent2 = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
|
|
927
|
+
const bitangent2 = normalize(cross(fallbackNormal, tangent2), [0, 0, 1]);
|
|
928
|
+
return { tangent: tangent2, bitangent: bitangent2 };
|
|
929
|
+
}
|
|
930
|
+
const inverse = 1 / determinant;
|
|
931
|
+
const tangent = normalize(
|
|
932
|
+
[
|
|
933
|
+
inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
|
|
934
|
+
inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
|
|
935
|
+
inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1])
|
|
936
|
+
],
|
|
937
|
+
[1, 0, 0]
|
|
938
|
+
);
|
|
939
|
+
const bitangent = normalize(
|
|
940
|
+
[
|
|
941
|
+
inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
|
|
942
|
+
inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
|
|
943
|
+
inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0])
|
|
944
|
+
],
|
|
945
|
+
[0, 0, 1]
|
|
946
|
+
);
|
|
947
|
+
return { tangent, bitangent };
|
|
948
|
+
}
|
|
949
|
+
function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
|
|
950
|
+
if (!normalTexture) {
|
|
951
|
+
return normalizeVectorOrFallback(normal, [0, 1, 0]);
|
|
952
|
+
}
|
|
953
|
+
const sample = sampleTextureRgba(normalTexture, uv, "linear");
|
|
954
|
+
const strength = clampUnit(normalTexture.scale ?? 1);
|
|
955
|
+
const tangentNormal = normalize(
|
|
956
|
+
[
|
|
957
|
+
(sample[0] * 2 - 1) * strength,
|
|
958
|
+
(sample[1] * 2 - 1) * strength,
|
|
959
|
+
1 + (sample[2] * 2 - 1 - 1) * strength
|
|
960
|
+
],
|
|
961
|
+
[0, 0, 1]
|
|
962
|
+
);
|
|
963
|
+
return normalize(
|
|
964
|
+
[
|
|
965
|
+
tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
|
|
966
|
+
tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
|
|
967
|
+
tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2]
|
|
968
|
+
],
|
|
969
|
+
normal
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
function sampleBaseColor(mesh, uv) {
|
|
973
|
+
const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
|
|
974
|
+
return [
|
|
975
|
+
clampUnit(mesh.color[0] * sample[0]),
|
|
976
|
+
clampUnit(mesh.color[1] * sample[1]),
|
|
977
|
+
clampUnit(mesh.color[2] * sample[2]),
|
|
978
|
+
clampUnit((mesh.color[3] ?? 1) * sample[3])
|
|
979
|
+
];
|
|
980
|
+
}
|
|
981
|
+
function sampleSurfaceMaterial(mesh, uv) {
|
|
982
|
+
const textureSample = mesh.metallicRoughnessTexture ? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear") : [1, 1, 1, 1];
|
|
983
|
+
return {
|
|
984
|
+
roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
|
|
985
|
+
metallic: clamp(mesh.metallic * textureSample[2], 0, 1)
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
function averageColors(colors) {
|
|
989
|
+
const count = Math.max(colors.length, 1);
|
|
990
|
+
return colors.reduce(
|
|
991
|
+
(accumulator, color) => [
|
|
992
|
+
accumulator[0] + color[0] / count,
|
|
993
|
+
accumulator[1] + color[1] / count,
|
|
994
|
+
accumulator[2] + color[2] / count,
|
|
995
|
+
accumulator[3] + color[3] / count
|
|
996
|
+
],
|
|
997
|
+
[0, 0, 0, 0]
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
function averageNumbers(values, fallback = 0) {
|
|
1001
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
1002
|
+
return fallback;
|
|
1003
|
+
}
|
|
1004
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
1005
|
+
}
|
|
470
1006
|
function createMeshTriangleRecords(meshes) {
|
|
471
1007
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
472
1008
|
let nextTriangleId = 0;
|
|
@@ -487,6 +1023,16 @@ function createMeshTriangleRecords(meshes) {
|
|
|
487
1023
|
const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
|
|
488
1024
|
const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
|
|
489
1025
|
const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
|
|
1026
|
+
const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
|
|
1027
|
+
const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
|
|
1028
|
+
const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
|
|
1029
|
+
const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
|
|
1030
|
+
const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
|
|
1031
|
+
const sampledMaterials = [
|
|
1032
|
+
sampleSurfaceMaterial(mesh, uv0),
|
|
1033
|
+
sampleSurfaceMaterial(mesh, uv1),
|
|
1034
|
+
sampleSurfaceMaterial(mesh, uv2)
|
|
1035
|
+
];
|
|
490
1036
|
const bounds = triangleBounds(v0, v1, v2);
|
|
491
1037
|
triangles.push(
|
|
492
1038
|
Object.freeze({
|
|
@@ -496,18 +1042,42 @@ function createMeshTriangleRecords(meshes) {
|
|
|
496
1042
|
flags: mesh.flags,
|
|
497
1043
|
materialRefId: mesh.materialRefId,
|
|
498
1044
|
mediumRefId: mesh.mediumRefId,
|
|
1045
|
+
materialSlot: meshIndex,
|
|
499
1046
|
v0: Object.freeze(v0),
|
|
500
1047
|
v1: Object.freeze(v1),
|
|
501
1048
|
v2: Object.freeze(v2),
|
|
502
|
-
n0: Object.freeze(
|
|
503
|
-
n1: Object.freeze(
|
|
504
|
-
n2: Object.freeze(
|
|
1049
|
+
n0: Object.freeze(shadedN0),
|
|
1050
|
+
n1: Object.freeze(shadedN1),
|
|
1051
|
+
n2: Object.freeze(shadedN2),
|
|
505
1052
|
uv0: Object.freeze(uv0),
|
|
506
1053
|
uv1: Object.freeze(uv1),
|
|
507
1054
|
uv2: Object.freeze(uv2),
|
|
508
|
-
color:
|
|
1055
|
+
color: Object.freeze(averageColors(sampledColors)),
|
|
509
1056
|
emission: mesh.emission,
|
|
510
|
-
material: Object.freeze([
|
|
1057
|
+
material: Object.freeze([
|
|
1058
|
+
averageNumbers(sampledMaterials.map((sample) => sample.roughness), mesh.roughness),
|
|
1059
|
+
averageNumbers(sampledMaterials.map((sample) => sample.metallic), mesh.metallic),
|
|
1060
|
+
mesh.opacity,
|
|
1061
|
+
mesh.ior
|
|
1062
|
+
]),
|
|
1063
|
+
materialResponse: Object.freeze([
|
|
1064
|
+
mesh.sheenColor[0] ?? 0,
|
|
1065
|
+
mesh.sheenColor[1] ?? 0,
|
|
1066
|
+
mesh.sheenColor[2] ?? 0,
|
|
1067
|
+
mesh.clearcoat
|
|
1068
|
+
]),
|
|
1069
|
+
materialExtension: Object.freeze([
|
|
1070
|
+
mesh.clearcoatRoughness,
|
|
1071
|
+
mesh.specular,
|
|
1072
|
+
mesh.transmission,
|
|
1073
|
+
mesh.thickness
|
|
1074
|
+
]),
|
|
1075
|
+
specularColor: Object.freeze([
|
|
1076
|
+
mesh.specularColor[0] ?? 1,
|
|
1077
|
+
mesh.specularColor[1] ?? 1,
|
|
1078
|
+
mesh.specularColor[2] ?? 1,
|
|
1079
|
+
1
|
|
1080
|
+
]),
|
|
511
1081
|
bounds: Object.freeze({
|
|
512
1082
|
min: Object.freeze(bounds.min),
|
|
513
1083
|
max: Object.freeze(bounds.max)
|
|
@@ -616,50 +1186,264 @@ function nextPowerOfTwo(value) {
|
|
|
616
1186
|
}
|
|
617
1187
|
return 2 ** Math.ceil(Math.log2(value));
|
|
618
1188
|
}
|
|
619
|
-
function
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const itemCount = readNonNegativeInteger("itemCount", itemCountInput, 0);
|
|
624
|
-
const sortCount = estimateBvhLeafSortCapacity(itemCount);
|
|
625
|
-
if (sortCount <= 1) {
|
|
626
|
-
return Object.freeze([]);
|
|
1189
|
+
function textureComponentToByte(value, fallback) {
|
|
1190
|
+
const numeric = Number(value);
|
|
1191
|
+
if (!Number.isFinite(numeric)) {
|
|
1192
|
+
return fallback;
|
|
627
1193
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
for (let compareDistance = sequenceSize / 2; compareDistance >= 1; compareDistance /= 2) {
|
|
631
|
-
stages.push(
|
|
632
|
-
Object.freeze({
|
|
633
|
-
compareDistance,
|
|
634
|
-
sequenceSize
|
|
635
|
-
})
|
|
636
|
-
);
|
|
637
|
-
}
|
|
1194
|
+
if (numeric >= 0 && numeric <= 1) {
|
|
1195
|
+
return Math.max(0, Math.min(255, Math.round(numeric * 255)));
|
|
638
1196
|
}
|
|
639
|
-
return
|
|
1197
|
+
return Math.max(0, Math.min(255, Math.round(numeric)));
|
|
640
1198
|
}
|
|
641
|
-
function
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
1199
|
+
function createSolidTextureSample(width, height, rgba) {
|
|
1200
|
+
const data = new Uint8Array(width * height * 4);
|
|
1201
|
+
for (let offset = 0; offset < data.length; offset += 4) {
|
|
1202
|
+
data[offset] = rgba[0];
|
|
1203
|
+
data[offset + 1] = rgba[1];
|
|
1204
|
+
data[offset + 2] = rgba[2];
|
|
1205
|
+
data[offset + 3] = rgba[3];
|
|
646
1206
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
1207
|
+
return Object.freeze({
|
|
1208
|
+
width,
|
|
1209
|
+
height,
|
|
1210
|
+
data
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
function normalizeTextureSampleInput(texture, fallbackColor) {
|
|
1214
|
+
if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || texture.width <= 0 || texture.height <= 0) {
|
|
1215
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
651
1216
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1217
|
+
const pixelCount = Math.trunc(texture.width) * Math.trunc(texture.height) * 4;
|
|
1218
|
+
const source = ArrayBuffer.isView(texture.data) || Array.isArray(texture.data) ? texture.data : null;
|
|
1219
|
+
if (!source || source.length < pixelCount) {
|
|
1220
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
1221
|
+
}
|
|
1222
|
+
const data = new Uint8Array(pixelCount);
|
|
1223
|
+
for (let index = 0; index < pixelCount; index += 1) {
|
|
1224
|
+
data[index] = textureComponentToByte(source[index], fallbackColor[index % 4]);
|
|
1225
|
+
}
|
|
1226
|
+
return Object.freeze({
|
|
1227
|
+
width: Math.trunc(texture.width),
|
|
1228
|
+
height: Math.trunc(texture.height),
|
|
1229
|
+
data
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
function buildTextureAtlas(textures, fallbackColor) {
|
|
1233
|
+
const padding = 1;
|
|
1234
|
+
const defaultTexture = createSolidTextureSample(1, 1, fallbackColor);
|
|
1235
|
+
const uniqueEntries = [{ source: null, texture: defaultTexture }];
|
|
1236
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
1237
|
+
for (const texture of Array.isArray(textures) ? textures : []) {
|
|
1238
|
+
if (!texture || bySource.has(texture)) {
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
const normalized = normalizeTextureSampleInput(texture, fallbackColor);
|
|
1242
|
+
bySource.set(texture, uniqueEntries.length);
|
|
1243
|
+
uniqueEntries.push({ source: texture, texture: normalized });
|
|
1244
|
+
}
|
|
1245
|
+
const totalArea = uniqueEntries.reduce((sum, entry) => {
|
|
1246
|
+
return sum + (entry.texture.width + padding * 2) * (entry.texture.height + padding * 2);
|
|
1247
|
+
}, 0);
|
|
1248
|
+
const maxTileWidth = uniqueEntries.reduce((maxWidth, entry) => {
|
|
1249
|
+
return Math.max(maxWidth, entry.texture.width + padding * 2);
|
|
1250
|
+
}, 1);
|
|
1251
|
+
const targetWidth = Math.max(
|
|
1252
|
+
maxTileWidth,
|
|
1253
|
+
nextPowerOfTwo(Math.max(maxTileWidth, Math.ceil(Math.sqrt(totalArea))))
|
|
1254
|
+
);
|
|
1255
|
+
let cursorX = 0;
|
|
1256
|
+
let cursorY = 0;
|
|
1257
|
+
let rowHeight = 0;
|
|
1258
|
+
let atlasWidth = 0;
|
|
1259
|
+
const placements = uniqueEntries.map((entry) => {
|
|
1260
|
+
const tileWidth = entry.texture.width + padding * 2;
|
|
1261
|
+
const tileHeight = entry.texture.height + padding * 2;
|
|
1262
|
+
if (cursorX > 0 && cursorX + tileWidth > targetWidth) {
|
|
1263
|
+
cursorX = 0;
|
|
1264
|
+
cursorY += rowHeight;
|
|
1265
|
+
rowHeight = 0;
|
|
1266
|
+
}
|
|
1267
|
+
const placement = Object.freeze({
|
|
1268
|
+
x: cursorX,
|
|
1269
|
+
y: cursorY,
|
|
1270
|
+
tileWidth,
|
|
1271
|
+
tileHeight,
|
|
1272
|
+
width: entry.texture.width,
|
|
1273
|
+
height: entry.texture.height
|
|
1274
|
+
});
|
|
1275
|
+
cursorX += tileWidth;
|
|
1276
|
+
atlasWidth = Math.max(atlasWidth, cursorX);
|
|
1277
|
+
rowHeight = Math.max(rowHeight, tileHeight);
|
|
1278
|
+
return placement;
|
|
1279
|
+
});
|
|
1280
|
+
const atlasHeight = Math.max(1, cursorY + rowHeight);
|
|
1281
|
+
const atlasData = new Uint8Array(Math.max(1, atlasWidth * atlasHeight * 4));
|
|
1282
|
+
const writePixel = (x, y, rgba) => {
|
|
1283
|
+
const offset = (y * atlasWidth + x) * 4;
|
|
1284
|
+
atlasData[offset] = rgba[0];
|
|
1285
|
+
atlasData[offset + 1] = rgba[1];
|
|
1286
|
+
atlasData[offset + 2] = rgba[2];
|
|
1287
|
+
atlasData[offset + 3] = rgba[3];
|
|
1288
|
+
};
|
|
1289
|
+
const rects = placements.map((placement, entryIndex) => {
|
|
1290
|
+
const { texture } = uniqueEntries[entryIndex];
|
|
1291
|
+
for (let y = 0; y < placement.tileHeight; y += 1) {
|
|
1292
|
+
for (let x = 0; x < placement.tileWidth; x += 1) {
|
|
1293
|
+
const sampleX = Math.max(0, Math.min(texture.width - 1, x - padding));
|
|
1294
|
+
const sampleY = Math.max(0, Math.min(texture.height - 1, y - padding));
|
|
1295
|
+
const sourceOffset = (sampleY * texture.width + sampleX) * 4;
|
|
1296
|
+
writePixel(placement.x + x, placement.y + y, texture.data.slice(sourceOffset, sourceOffset + 4));
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
return Object.freeze([
|
|
1300
|
+
(placement.x + padding) / Math.max(1, atlasWidth),
|
|
1301
|
+
(placement.y + padding) / Math.max(1, atlasHeight),
|
|
1302
|
+
placement.width / Math.max(1, atlasWidth),
|
|
1303
|
+
placement.height / Math.max(1, atlasHeight)
|
|
1304
|
+
]);
|
|
1305
|
+
});
|
|
1306
|
+
const rectBySource = /* @__PURE__ */ new Map();
|
|
1307
|
+
uniqueEntries.forEach((entry, index) => {
|
|
1308
|
+
if (entry.source) {
|
|
1309
|
+
rectBySource.set(entry.source, rects[index]);
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
return Object.freeze({
|
|
1313
|
+
width: Math.max(1, atlasWidth),
|
|
1314
|
+
height: Math.max(1, atlasHeight),
|
|
1315
|
+
data: atlasData,
|
|
1316
|
+
defaultRect: rects[0],
|
|
1317
|
+
resolveRect(texture) {
|
|
1318
|
+
return rectBySource.get(texture) ?? rects[0];
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
function createWavefrontGpuMaterialSource(meshes = []) {
|
|
1323
|
+
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
1324
|
+
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1325
|
+
const baseColorAtlas = buildTextureAtlas(
|
|
1326
|
+
normalized.map((mesh) => mesh.baseColorTexture),
|
|
1327
|
+
[255, 255, 255, 255]
|
|
1328
|
+
);
|
|
1329
|
+
const metallicRoughnessAtlas = buildTextureAtlas(
|
|
1330
|
+
normalized.map((mesh) => mesh.metallicRoughnessTexture),
|
|
1331
|
+
[255, 255, 255, 255]
|
|
1332
|
+
);
|
|
1333
|
+
const normalAtlas = buildTextureAtlas(
|
|
1334
|
+
normalized.map((mesh) => mesh.normalTexture),
|
|
1335
|
+
[128, 128, 255, 255]
|
|
1336
|
+
);
|
|
1337
|
+
const occlusionAtlas = buildTextureAtlas(
|
|
1338
|
+
normalized.map((mesh) => mesh.occlusionTexture),
|
|
1339
|
+
[255, 255, 255, 255]
|
|
1340
|
+
);
|
|
1341
|
+
const emissiveAtlas = buildTextureAtlas(
|
|
1342
|
+
normalized.map((mesh) => mesh.emissiveTexture),
|
|
1343
|
+
[255, 255, 255, 255]
|
|
1344
|
+
);
|
|
1345
|
+
const bytes = new ArrayBuffer(Math.max(1, normalized.length) * GPU_MATERIAL_RECORD_BYTES);
|
|
1346
|
+
const floatView = new Float32Array(bytes);
|
|
1347
|
+
normalized.forEach((mesh, meshIndex) => {
|
|
1348
|
+
const byteOffset = meshIndex * GPU_MATERIAL_RECORD_BYTES;
|
|
1349
|
+
writeVec4(floatView, byteOffset, mesh.color);
|
|
1350
|
+
writeVec4(floatView, byteOffset + 16, mesh.emission);
|
|
1351
|
+
writeVec4(floatView, byteOffset + 32, [
|
|
1352
|
+
mesh.roughness,
|
|
1353
|
+
mesh.metallic,
|
|
1354
|
+
mesh.opacity,
|
|
1355
|
+
mesh.ior
|
|
1356
|
+
]);
|
|
1357
|
+
writeVec4(floatView, byteOffset + 48, [
|
|
1358
|
+
mesh.sheenColor[0] ?? 0,
|
|
1359
|
+
mesh.sheenColor[1] ?? 0,
|
|
1360
|
+
mesh.sheenColor[2] ?? 0,
|
|
1361
|
+
mesh.clearcoat
|
|
1362
|
+
]);
|
|
1363
|
+
writeVec4(floatView, byteOffset + 64, [
|
|
1364
|
+
mesh.clearcoatRoughness,
|
|
1365
|
+
mesh.specular,
|
|
1366
|
+
mesh.transmission,
|
|
1367
|
+
mesh.thickness
|
|
1368
|
+
]);
|
|
1369
|
+
writeVec4(floatView, byteOffset + 80, [
|
|
1370
|
+
mesh.specularColor[0] ?? 1,
|
|
1371
|
+
mesh.specularColor[1] ?? 1,
|
|
1372
|
+
mesh.specularColor[2] ?? 1,
|
|
1373
|
+
1
|
|
1374
|
+
]);
|
|
1375
|
+
writeVec4(floatView, byteOffset + 96, baseColorAtlas.resolveRect(mesh.baseColorTexture));
|
|
1376
|
+
writeVec4(
|
|
1377
|
+
floatView,
|
|
1378
|
+
byteOffset + 112,
|
|
1379
|
+
metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1380
|
+
);
|
|
1381
|
+
writeVec4(floatView, byteOffset + 128, normalAtlas.resolveRect(mesh.normalTexture));
|
|
1382
|
+
writeVec4(floatView, byteOffset + 144, occlusionAtlas.resolveRect(mesh.occlusionTexture));
|
|
1383
|
+
writeVec4(floatView, byteOffset + 160, emissiveAtlas.resolveRect(mesh.emissiveTexture));
|
|
1384
|
+
writeVec4(floatView, byteOffset + 176, [
|
|
1385
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1386
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1387
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1388
|
+
0
|
|
1389
|
+
]);
|
|
1390
|
+
});
|
|
1391
|
+
return Object.freeze({
|
|
1392
|
+
buffer: bytes,
|
|
1393
|
+
count: normalized.length,
|
|
1394
|
+
recordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
1395
|
+
records: Object.freeze(normalized),
|
|
1396
|
+
baseColorAtlas,
|
|
1397
|
+
metallicRoughnessAtlas,
|
|
1398
|
+
normalAtlas,
|
|
1399
|
+
occlusionAtlas,
|
|
1400
|
+
emissiveAtlas
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
function estimateBvhLeafSortCapacity(triangleCount) {
|
|
1404
|
+
return triangleCount <= 0 ? 0 : nextPowerOfTwo(triangleCount);
|
|
1405
|
+
}
|
|
1406
|
+
function createWavefrontBvhSortStages(itemCountInput) {
|
|
1407
|
+
const itemCount = readNonNegativeInteger("itemCount", itemCountInput, 0);
|
|
1408
|
+
const sortCount = estimateBvhLeafSortCapacity(itemCount);
|
|
1409
|
+
if (sortCount <= 1) {
|
|
1410
|
+
return Object.freeze([]);
|
|
1411
|
+
}
|
|
1412
|
+
const stages = [];
|
|
1413
|
+
for (let sequenceSize = 2; sequenceSize <= sortCount; sequenceSize *= 2) {
|
|
1414
|
+
for (let compareDistance = sequenceSize / 2; compareDistance >= 1; compareDistance /= 2) {
|
|
1415
|
+
stages.push(
|
|
1416
|
+
Object.freeze({
|
|
1417
|
+
compareDistance,
|
|
1418
|
+
sequenceSize
|
|
1419
|
+
})
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return Object.freeze(stages);
|
|
1424
|
+
}
|
|
1425
|
+
function createWavefrontBvhBuildLevels(triangleCountInput) {
|
|
1426
|
+
const triangleCount = readNonNegativeInteger("triangleCount", triangleCountInput, 0);
|
|
1427
|
+
const internalCount = Math.max(0, triangleCount - 1);
|
|
1428
|
+
if (internalCount === 0) {
|
|
1429
|
+
return Object.freeze([]);
|
|
1430
|
+
}
|
|
1431
|
+
const levels = [];
|
|
1432
|
+
let depth = 0;
|
|
1433
|
+
while (Math.pow(2, depth) - 1 < internalCount) {
|
|
1434
|
+
depth += 1;
|
|
1435
|
+
}
|
|
1436
|
+
for (let level = depth - 1; level >= 0; level -= 1) {
|
|
1437
|
+
const start = Math.pow(2, level) - 1;
|
|
1438
|
+
const end = Math.min(Math.pow(2, level + 1) - 2, internalCount - 1);
|
|
1439
|
+
if (end >= start) {
|
|
1440
|
+
levels.push(
|
|
1441
|
+
Object.freeze({
|
|
1442
|
+
start,
|
|
1443
|
+
count: end - start + 1
|
|
1444
|
+
})
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
663
1447
|
}
|
|
664
1448
|
return Object.freeze(levels);
|
|
665
1449
|
}
|
|
@@ -673,9 +1457,10 @@ function resolveAccelerationBuildMode(options = {}) {
|
|
|
673
1457
|
}
|
|
674
1458
|
return mode;
|
|
675
1459
|
}
|
|
676
|
-
function createWavefrontGpuMeshSource(meshes = []) {
|
|
1460
|
+
function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput = null) {
|
|
677
1461
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
678
1462
|
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1463
|
+
const gpuMaterialSource = gpuMaterialSourceInput ?? createWavefrontGpuMaterialSource(normalized);
|
|
679
1464
|
const vertexCount = normalized.reduce((count, mesh) => count + mesh.positions.length / 3, 0);
|
|
680
1465
|
const indexCount = normalized.reduce((count, mesh) => count + mesh.indices.length, 0);
|
|
681
1466
|
const triangleCount = Math.floor(indexCount / 3);
|
|
@@ -727,7 +1512,7 @@ function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
727
1512
|
meshUints[meshOffset + 8] = mesh.indices.length / 3;
|
|
728
1513
|
meshUints[meshOffset + 9] = meshVertexBase;
|
|
729
1514
|
meshUints[meshOffset + 10] = meshVertexCount;
|
|
730
|
-
meshUints[meshOffset + 11] =
|
|
1515
|
+
meshUints[meshOffset + 11] = meshIndex;
|
|
731
1516
|
const floatOffset = meshOffset;
|
|
732
1517
|
writeVec4(meshFloats, floatOffset * 4 + 48, mesh.color);
|
|
733
1518
|
writeVec4(meshFloats, floatOffset * 4 + 64, mesh.emission);
|
|
@@ -737,6 +1522,55 @@ function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
737
1522
|
mesh.opacity,
|
|
738
1523
|
mesh.ior
|
|
739
1524
|
]);
|
|
1525
|
+
writeVec4(meshFloats, floatOffset * 4 + 96, [
|
|
1526
|
+
mesh.sheenColor[0] ?? 0,
|
|
1527
|
+
mesh.sheenColor[1] ?? 0,
|
|
1528
|
+
mesh.sheenColor[2] ?? 0,
|
|
1529
|
+
mesh.clearcoat
|
|
1530
|
+
]);
|
|
1531
|
+
writeVec4(meshFloats, floatOffset * 4 + 112, [
|
|
1532
|
+
mesh.clearcoatRoughness,
|
|
1533
|
+
mesh.specular,
|
|
1534
|
+
mesh.transmission,
|
|
1535
|
+
mesh.thickness
|
|
1536
|
+
]);
|
|
1537
|
+
writeVec4(meshFloats, floatOffset * 4 + 128, [
|
|
1538
|
+
mesh.specularColor[0] ?? 1,
|
|
1539
|
+
mesh.specularColor[1] ?? 1,
|
|
1540
|
+
mesh.specularColor[2] ?? 1,
|
|
1541
|
+
1
|
|
1542
|
+
]);
|
|
1543
|
+
writeVec4(
|
|
1544
|
+
meshFloats,
|
|
1545
|
+
floatOffset * 4 + 144,
|
|
1546
|
+
gpuMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
|
|
1547
|
+
);
|
|
1548
|
+
writeVec4(
|
|
1549
|
+
meshFloats,
|
|
1550
|
+
floatOffset * 4 + 160,
|
|
1551
|
+
gpuMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1552
|
+
);
|
|
1553
|
+
writeVec4(
|
|
1554
|
+
meshFloats,
|
|
1555
|
+
floatOffset * 4 + 176,
|
|
1556
|
+
gpuMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
|
|
1557
|
+
);
|
|
1558
|
+
writeVec4(
|
|
1559
|
+
meshFloats,
|
|
1560
|
+
floatOffset * 4 + 192,
|
|
1561
|
+
gpuMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
|
|
1562
|
+
);
|
|
1563
|
+
writeVec4(
|
|
1564
|
+
meshFloats,
|
|
1565
|
+
floatOffset * 4 + 208,
|
|
1566
|
+
gpuMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
|
|
1567
|
+
);
|
|
1568
|
+
writeVec4(meshFloats, floatOffset * 4 + 224, [
|
|
1569
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1570
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1571
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1572
|
+
0
|
|
1573
|
+
]);
|
|
740
1574
|
vertexCursor += meshVertexCount;
|
|
741
1575
|
indexCursor += mesh.indices.length;
|
|
742
1576
|
triangleCursor += mesh.indices.length / 3;
|
|
@@ -799,12 +1633,16 @@ function normalizeSceneObjects(sceneObjects, useDefaultScene = true) {
|
|
|
799
1633
|
const source = Array.isArray(sceneObjects) && sceneObjects.length > 0 ? sceneObjects : useDefaultScene ? createDefaultWavefrontSceneObjects() : [];
|
|
800
1634
|
return source.map((object, index) => normalizeWavefrontSceneObject(object, index));
|
|
801
1635
|
}
|
|
1636
|
+
function normalizeWavefrontMeshes(meshes) {
|
|
1637
|
+
const source = Array.isArray(meshes) ? meshes : [];
|
|
1638
|
+
return source.map((mesh, index) => normalizeWavefrontMesh(mesh, index));
|
|
1639
|
+
}
|
|
802
1640
|
function normalizeMeshes(options = {}) {
|
|
803
1641
|
if (Array.isArray(options.meshes)) {
|
|
804
|
-
return options.meshes;
|
|
1642
|
+
return normalizeWavefrontMeshes(options.meshes);
|
|
805
1643
|
}
|
|
806
1644
|
if (options.mesh) {
|
|
807
|
-
return [options.mesh];
|
|
1645
|
+
return normalizeWavefrontMeshes([options.mesh]);
|
|
808
1646
|
}
|
|
809
1647
|
return [];
|
|
810
1648
|
}
|
|
@@ -1039,12 +1877,14 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1039
1877
|
options.environmentPortalCapacity,
|
|
1040
1878
|
0
|
|
1041
1879
|
);
|
|
1880
|
+
const materialCapacity = readNonNegativeInteger("materialCapacity", options.materialCapacity, 0);
|
|
1042
1881
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
1043
1882
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
1044
1883
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
1045
1884
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
1046
1885
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
1047
1886
|
const triangleBytes = triangleCapacity * TRIANGLE_RECORD_BYTES;
|
|
1887
|
+
const materialTableBytes = materialCapacity * GPU_MATERIAL_RECORD_BYTES;
|
|
1048
1888
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
1049
1889
|
const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
|
|
1050
1890
|
const emissiveTriangleMetadataBytes = emissiveTriangleCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -1057,6 +1897,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1057
1897
|
pathVertexBytes,
|
|
1058
1898
|
sceneObjectBytes,
|
|
1059
1899
|
triangleBytes,
|
|
1900
|
+
materialTableBytes,
|
|
1060
1901
|
bvhNodeBytes,
|
|
1061
1902
|
bvhLeafReferenceBytes,
|
|
1062
1903
|
emissiveTriangleMetadataBytes,
|
|
@@ -1064,7 +1905,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1064
1905
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
1065
1906
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
1066
1907
|
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
|
|
1908
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + pathVertexBytes + sceneObjectBytes + triangleBytes + materialTableBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1068
1909
|
});
|
|
1069
1910
|
}
|
|
1070
1911
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -1078,7 +1919,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1078
1919
|
const samplesPerPixel = clamp(
|
|
1079
1920
|
readPositiveInteger("samplesPerPixel", options.samplesPerPixel, DEFAULT_SAMPLES_PER_PIXEL),
|
|
1080
1921
|
1,
|
|
1081
|
-
|
|
1922
|
+
MAX_SAMPLES_PER_PIXEL
|
|
1082
1923
|
);
|
|
1083
1924
|
const maxFramePassesPerSubmission = clamp(
|
|
1084
1925
|
readPositiveInteger(
|
|
@@ -1096,7 +1937,8 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1096
1937
|
);
|
|
1097
1938
|
const meshes = normalizeMeshes(options);
|
|
1098
1939
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
1099
|
-
const
|
|
1940
|
+
const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
|
|
1941
|
+
const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
|
|
1100
1942
|
const meshAcceleration = accelerationBuildMode === "cpu-debug" ? createWavefrontMeshAcceleration(meshes) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
1101
1943
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
1102
1944
|
meshes,
|
|
@@ -1107,6 +1949,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1107
1949
|
const sceneObjects = Object.freeze(
|
|
1108
1950
|
normalizeSceneObjects(options.sceneObjects, meshes.length === 0)
|
|
1109
1951
|
);
|
|
1952
|
+
const mediums = collectWavefrontMediums(options, meshes, sceneObjects);
|
|
1110
1953
|
const sceneObjectCapacity = Math.max(
|
|
1111
1954
|
sceneObjects.length,
|
|
1112
1955
|
readPositiveInteger("sceneObjectCapacity", options.sceneObjectCapacity, DEFAULT_SCENE_OBJECT_CAPACITY)
|
|
@@ -1165,9 +2008,12 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1165
2008
|
sceneObjects,
|
|
1166
2009
|
sceneObjectCount: sceneObjects.length,
|
|
1167
2010
|
sceneObjectCapacity,
|
|
2011
|
+
mediums,
|
|
2012
|
+
mediumCount: mediums.length,
|
|
1168
2013
|
accelerationBuildMode,
|
|
1169
2014
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
1170
2015
|
gpuMeshSource,
|
|
2016
|
+
gpuMaterialSource,
|
|
1171
2017
|
meshAcceleration,
|
|
1172
2018
|
emissiveTriangleIndices,
|
|
1173
2019
|
emissiveTriangleCount: emissiveTriangleIndices.count,
|
|
@@ -1198,6 +2044,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1198
2044
|
maxDepth,
|
|
1199
2045
|
sceneObjectCapacity,
|
|
1200
2046
|
triangleCapacity,
|
|
2047
|
+
materialCapacity: gpuMaterialSource.count,
|
|
1201
2048
|
bvhNodeCapacity,
|
|
1202
2049
|
bvhLeafSortCapacity,
|
|
1203
2050
|
emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
|
|
@@ -1264,16 +2111,35 @@ function packWavefrontSceneObjects(sceneObjects, capacity = sceneObjects.length)
|
|
|
1264
2111
|
uintView[u32 + 1] = object.id;
|
|
1265
2112
|
uintView[u32 + 2] = object.materialKind;
|
|
1266
2113
|
uintView[u32 + 3] = object.flags;
|
|
1267
|
-
|
|
1268
|
-
writeVec4(floatView, byteOffset + 32, [...object.
|
|
1269
|
-
writeVec4(floatView, byteOffset + 48, object.
|
|
1270
|
-
writeVec4(floatView, byteOffset + 64, object.
|
|
1271
|
-
writeVec4(floatView, byteOffset + 80,
|
|
2114
|
+
uintView[u32 + 4] = object.mediumRefId;
|
|
2115
|
+
writeVec4(floatView, byteOffset + 32, [...object.center, 0]);
|
|
2116
|
+
writeVec4(floatView, byteOffset + 48, [...object.halfExtent, 0]);
|
|
2117
|
+
writeVec4(floatView, byteOffset + 64, object.color);
|
|
2118
|
+
writeVec4(floatView, byteOffset + 80, object.emission);
|
|
2119
|
+
writeVec4(floatView, byteOffset + 96, [
|
|
1272
2120
|
object.roughness,
|
|
1273
2121
|
object.metallic,
|
|
1274
2122
|
object.opacity,
|
|
1275
2123
|
object.ior
|
|
1276
2124
|
]);
|
|
2125
|
+
writeVec4(floatView, byteOffset + 112, [
|
|
2126
|
+
object.sheenColor[0] ?? 0,
|
|
2127
|
+
object.sheenColor[1] ?? 0,
|
|
2128
|
+
object.sheenColor[2] ?? 0,
|
|
2129
|
+
object.clearcoat
|
|
2130
|
+
]);
|
|
2131
|
+
writeVec4(floatView, byteOffset + 128, [
|
|
2132
|
+
object.clearcoatRoughness,
|
|
2133
|
+
object.specular,
|
|
2134
|
+
object.transmission,
|
|
2135
|
+
object.thickness
|
|
2136
|
+
]);
|
|
2137
|
+
writeVec4(floatView, byteOffset + 144, [
|
|
2138
|
+
object.specularColor[0] ?? 1,
|
|
2139
|
+
object.specularColor[1] ?? 1,
|
|
2140
|
+
object.specularColor[2] ?? 1,
|
|
2141
|
+
1
|
|
2142
|
+
]);
|
|
1277
2143
|
});
|
|
1278
2144
|
return Object.freeze({
|
|
1279
2145
|
buffer: bytes,
|
|
@@ -1298,7 +2164,7 @@ function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1298
2164
|
uintView[u32 + 3] = triangle.flags;
|
|
1299
2165
|
uintView[u32 + 4] = triangle.materialRefId;
|
|
1300
2166
|
uintView[u32 + 5] = triangle.mediumRefId;
|
|
1301
|
-
uintView[u32 + 6] = 0;
|
|
2167
|
+
uintView[u32 + 6] = triangle.materialSlot ?? 0;
|
|
1302
2168
|
uintView[u32 + 7] = 0;
|
|
1303
2169
|
writeVec4(floatView, byteOffset + 32, [...triangle.v0, 0]);
|
|
1304
2170
|
writeVec4(floatView, byteOffset + 48, [...triangle.v1, 0]);
|
|
@@ -1311,6 +2177,15 @@ function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1311
2177
|
writeVec4(floatView, byteOffset + 160, triangle.color);
|
|
1312
2178
|
writeVec4(floatView, byteOffset + 176, triangle.emission);
|
|
1313
2179
|
writeVec4(floatView, byteOffset + 192, triangle.material);
|
|
2180
|
+
writeVec4(floatView, byteOffset + 208, triangle.materialResponse);
|
|
2181
|
+
writeVec4(floatView, byteOffset + 224, triangle.materialExtension ?? [0.08, 1, 0, 0]);
|
|
2182
|
+
writeVec4(floatView, byteOffset + 240, triangle.specularColor ?? [1, 1, 1, 1]);
|
|
2183
|
+
writeVec4(floatView, byteOffset + 256, triangle.baseColorAtlas ?? [0, 0, 1, 1]);
|
|
2184
|
+
writeVec4(floatView, byteOffset + 272, triangle.metallicRoughnessAtlas ?? [0, 0, 1, 1]);
|
|
2185
|
+
writeVec4(floatView, byteOffset + 288, triangle.normalAtlas ?? [0, 0, 1, 1]);
|
|
2186
|
+
writeVec4(floatView, byteOffset + 304, triangle.occlusionAtlas ?? [0, 0, 1, 1]);
|
|
2187
|
+
writeVec4(floatView, byteOffset + 320, triangle.emissiveAtlas ?? [0, 0, 1, 1]);
|
|
2188
|
+
writeVec4(floatView, byteOffset + 336, triangle.textureSettings ?? [1, 1, 1, 0]);
|
|
1314
2189
|
});
|
|
1315
2190
|
return Object.freeze({
|
|
1316
2191
|
buffer: bytes,
|
|
@@ -1404,6 +2279,12 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1404
2279
|
0,
|
|
1405
2280
|
0
|
|
1406
2281
|
]);
|
|
2282
|
+
writeVec4(floatView, 304, [
|
|
2283
|
+
config.environmentMap.width ?? 1,
|
|
2284
|
+
config.environmentMap.height ?? 1,
|
|
2285
|
+
config.environmentMap.mipLevelCount ?? 1,
|
|
2286
|
+
config.environmentMap.hasImportanceData ? 1 : 0
|
|
2287
|
+
]);
|
|
1407
2288
|
return bytes;
|
|
1408
2289
|
}
|
|
1409
2290
|
function createTiles(width, height, tileSize) {
|
|
@@ -1577,7 +2458,8 @@ function intersectWavefrontReferenceTriangle(ray, triangle, options = {}) {
|
|
|
1577
2458
|
position: Object.freeze(position),
|
|
1578
2459
|
color: triangle.color,
|
|
1579
2460
|
emission: triangle.emission,
|
|
1580
|
-
material: triangle.material
|
|
2461
|
+
material: triangle.material,
|
|
2462
|
+
materialResponse: triangle.materialResponse
|
|
1581
2463
|
});
|
|
1582
2464
|
}
|
|
1583
2465
|
function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
@@ -1603,7 +2485,8 @@ function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
|
1603
2485
|
position: Object.freeze(add(ray.origin, scale(ray.direction, 1e3))),
|
|
1604
2486
|
color: Object.freeze([0, 0, 0, 0]),
|
|
1605
2487
|
emission: radiance,
|
|
1606
|
-
material: Object.freeze([1, 0, 1, 1])
|
|
2488
|
+
material: Object.freeze([1, 0, 1, 1]),
|
|
2489
|
+
materialResponse: Object.freeze([0, 0, 0, 0])
|
|
1607
2490
|
});
|
|
1608
2491
|
}
|
|
1609
2492
|
function traceWavefrontReferenceTriangles(config, ray, triangles, options = {}) {
|
|
@@ -1682,6 +2565,32 @@ function environmentMapIntegerScale(data) {
|
|
|
1682
2565
|
}
|
|
1683
2566
|
return 1;
|
|
1684
2567
|
}
|
|
2568
|
+
function environmentMapHasSamplingData(environmentMap) {
|
|
2569
|
+
if (!environmentMap || !environmentMap.data) {
|
|
2570
|
+
return false;
|
|
2571
|
+
}
|
|
2572
|
+
const width = Math.max(1, environmentMap.width ?? 1);
|
|
2573
|
+
const height = Math.max(1, environmentMap.height ?? 1);
|
|
2574
|
+
return environmentMap.data.length >= width * height * 4;
|
|
2575
|
+
}
|
|
2576
|
+
function createRgba8TextureUpload(source) {
|
|
2577
|
+
const width = Math.max(1, Math.trunc(source.width));
|
|
2578
|
+
const height = Math.max(1, Math.trunc(source.height));
|
|
2579
|
+
const bytesPerRow = alignTo(width * 4, 256);
|
|
2580
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
2581
|
+
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
2582
|
+
for (let y = 0; y < height; y += 1) {
|
|
2583
|
+
const sourceOffset = y * width * 4;
|
|
2584
|
+
const targetOffset = y * bytesPerRow;
|
|
2585
|
+
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
2586
|
+
}
|
|
2587
|
+
return Object.freeze({
|
|
2588
|
+
bytes,
|
|
2589
|
+
bytesPerRow,
|
|
2590
|
+
width,
|
|
2591
|
+
height
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
1685
2594
|
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
1686
2595
|
if (!data || index >= data.length) {
|
|
1687
2596
|
return fallback;
|
|
@@ -1689,39 +2598,311 @@ function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
|
1689
2598
|
const value = Number(data[index]);
|
|
1690
2599
|
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
1691
2600
|
}
|
|
1692
|
-
function
|
|
1693
|
-
const
|
|
1694
|
-
const
|
|
2601
|
+
function buildOrthonormalBasis(normal) {
|
|
2602
|
+
const tangentFallback = Math.abs(normal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
2603
|
+
const tangent = normalize(cross(tangentFallback, normal), [1, 0, 0]);
|
|
2604
|
+
const bitangent = normalize(cross(normal, tangent), [0, 0, 1]);
|
|
2605
|
+
return { tangent, bitangent };
|
|
2606
|
+
}
|
|
2607
|
+
function localToWorld(local, normal) {
|
|
2608
|
+
const basis = buildOrthonormalBasis(normal);
|
|
2609
|
+
return normalize(
|
|
2610
|
+
add(
|
|
2611
|
+
add(scale(basis.tangent, local[0]), scale(basis.bitangent, local[1])),
|
|
2612
|
+
scale(normal, local[2])
|
|
2613
|
+
),
|
|
2614
|
+
normal
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
function radicalInverseVdc(bits) {
|
|
2618
|
+
let value = bits >>> 0;
|
|
2619
|
+
value = (value << 16 | value >>> 16) >>> 0;
|
|
2620
|
+
value = ((value & 1431655765) << 1 | (value & 2863311530) >>> 1) >>> 0;
|
|
2621
|
+
value = ((value & 858993459) << 2 | (value & 3435973836) >>> 2) >>> 0;
|
|
2622
|
+
value = ((value & 252645135) << 4 | (value & 4042322160) >>> 4) >>> 0;
|
|
2623
|
+
value = ((value & 16711935) << 8 | (value & 4278255360) >>> 8) >>> 0;
|
|
2624
|
+
return value * 23283064365386963e-26;
|
|
2625
|
+
}
|
|
2626
|
+
function hammersley(index, count) {
|
|
2627
|
+
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
2628
|
+
}
|
|
2629
|
+
function importanceSampleGgx(sample, roughness, normal) {
|
|
2630
|
+
const alpha = Math.max(roughness * roughness, 1e-4);
|
|
2631
|
+
const phi = 2 * Math.PI * sample[0];
|
|
2632
|
+
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
2633
|
+
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
2634
|
+
const halfVector = localToWorld(
|
|
2635
|
+
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
2636
|
+
normal
|
|
2637
|
+
);
|
|
2638
|
+
return normalize(halfVector, normal);
|
|
2639
|
+
}
|
|
2640
|
+
function geometrySchlickGgx(nDotV, roughness) {
|
|
2641
|
+
const k = (roughness + 1) * (roughness + 1) / 8;
|
|
2642
|
+
return nDotV / Math.max(nDotV * (1 - k) + k, 1e-6);
|
|
2643
|
+
}
|
|
2644
|
+
function geometrySmith(nDotV, nDotL, roughness) {
|
|
2645
|
+
return geometrySchlickGgx(nDotV, roughness) * geometrySchlickGgx(nDotL, roughness);
|
|
2646
|
+
}
|
|
2647
|
+
function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
2648
|
+
const viewDirection = [Math.sqrt(Math.max(0, 1 - nDotV * nDotV)), 0, nDotV];
|
|
2649
|
+
const normal = [0, 0, 1];
|
|
2650
|
+
let scaleTerm = 0;
|
|
2651
|
+
let biasTerm = 0;
|
|
2652
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
2653
|
+
const xi = hammersley(index, sampleCount);
|
|
2654
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2655
|
+
const vDotH = Math.max(dot(viewDirection, halfVector), 0);
|
|
2656
|
+
const lightDirection = normalize(
|
|
2657
|
+
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
2658
|
+
normal
|
|
2659
|
+
);
|
|
2660
|
+
const nDotL = Math.max(lightDirection[2], 0);
|
|
2661
|
+
const nDotH = Math.max(halfVector[2], 0);
|
|
2662
|
+
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
2663
|
+
continue;
|
|
2664
|
+
}
|
|
2665
|
+
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
2666
|
+
const visibility = geometry * vDotH / Math.max(nDotH * nDotV, 1e-6);
|
|
2667
|
+
const fresnel = (1 - vDotH) ** 5;
|
|
2668
|
+
scaleTerm += (1 - fresnel) * visibility;
|
|
2669
|
+
biasTerm += fresnel * visibility;
|
|
2670
|
+
}
|
|
2671
|
+
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
2672
|
+
}
|
|
2673
|
+
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount = DEFAULT_BRDF_LUT_SAMPLE_COUNT) {
|
|
2674
|
+
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
2675
|
+
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
2676
|
+
if (cached) {
|
|
2677
|
+
return cached;
|
|
2678
|
+
}
|
|
2679
|
+
const width = Math.max(1, Math.trunc(size));
|
|
2680
|
+
const height = Math.max(1, Math.trunc(size));
|
|
1695
2681
|
const rowBytes = width * 8;
|
|
1696
2682
|
const bytesPerRow = alignTo(rowBytes, 256);
|
|
1697
2683
|
const bytes = new Uint8Array(bytesPerRow * height);
|
|
2684
|
+
const view = new DataView(bytes.buffer);
|
|
2685
|
+
for (let y = 0; y < height; y += 1) {
|
|
2686
|
+
const roughness = (y + 0.5) / height;
|
|
2687
|
+
for (let x = 0; x < width; x += 1) {
|
|
2688
|
+
const nDotV = Math.max((x + 0.5) / width, 1e-4);
|
|
2689
|
+
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
2690
|
+
const offset = y * bytesPerRow + x * 8;
|
|
2691
|
+
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
2692
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
2693
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
2694
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
2698
|
+
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
2699
|
+
return upload;
|
|
2700
|
+
}
|
|
2701
|
+
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
2702
|
+
const width = Math.max(1, environmentMap.width);
|
|
2703
|
+
const height = Math.max(1, environmentMap.height);
|
|
2704
|
+
const pixels = new Float32Array(width * height * 4);
|
|
1698
2705
|
const data = environmentMap.data;
|
|
1699
2706
|
const integerScale = environmentMapIntegerScale(data);
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
2707
|
+
for (let index = 0; index < width * height; index += 1) {
|
|
2708
|
+
const sourceOffset = index * 4;
|
|
2709
|
+
const targetOffset = index * 4;
|
|
2710
|
+
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
2711
|
+
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
2712
|
+
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
2713
|
+
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
2714
|
+
}
|
|
2715
|
+
return pixels;
|
|
2716
|
+
}
|
|
2717
|
+
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
2718
|
+
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
2719
|
+
const theta = v * Math.PI;
|
|
2720
|
+
const sinTheta = Math.sin(theta);
|
|
2721
|
+
return [
|
|
2722
|
+
Math.cos(angle) * sinTheta,
|
|
2723
|
+
Math.cos(theta),
|
|
2724
|
+
Math.sin(angle) * sinTheta
|
|
2725
|
+
];
|
|
2726
|
+
}
|
|
2727
|
+
function sampleEnvironmentPixelsBilinear(pixels, width, height, u, v) {
|
|
2728
|
+
const wrappedU = (u % 1 + 1) % 1;
|
|
2729
|
+
const clampedV = clamp(v, 0, 1);
|
|
2730
|
+
const x = wrappedU * width - 0.5;
|
|
2731
|
+
const y = clampedV * height - 0.5;
|
|
2732
|
+
const x0 = (Math.floor(x) % width + width) % width;
|
|
2733
|
+
const y0 = clamp(Math.floor(y), 0, height - 1);
|
|
2734
|
+
const x1 = (x0 + 1) % width;
|
|
2735
|
+
const y1 = clamp(y0 + 1, 0, height - 1);
|
|
2736
|
+
const tx = x - Math.floor(x);
|
|
2737
|
+
const ty = y - Math.floor(y);
|
|
2738
|
+
const read = (px, py) => {
|
|
2739
|
+
const offset = (py * width + px) * 4;
|
|
2740
|
+
return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]];
|
|
1709
2741
|
};
|
|
2742
|
+
const a = read(x0, y0);
|
|
2743
|
+
const b = read(x1, y0);
|
|
2744
|
+
const c = read(x0, y1);
|
|
2745
|
+
const d = read(x1, y1);
|
|
2746
|
+
const mixPair = (first, second, factor) => first * (1 - factor) + second * factor;
|
|
2747
|
+
return [
|
|
2748
|
+
mixPair(mixPair(a[0], b[0], tx), mixPair(c[0], d[0], tx), ty),
|
|
2749
|
+
mixPair(mixPair(a[1], b[1], tx), mixPair(c[1], d[1], tx), ty),
|
|
2750
|
+
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
2751
|
+
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty)
|
|
2752
|
+
];
|
|
2753
|
+
}
|
|
2754
|
+
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
2755
|
+
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
2756
|
+
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
2757
|
+
const u = ((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI) + 0.5 + rotationTurns) % 1 + 1) % 1;
|
|
2758
|
+
const v = Math.acos(clamp(unitDirection[1], -1, 1)) / Math.PI;
|
|
2759
|
+
return [u, clamp(v, 0, 1)];
|
|
2760
|
+
}
|
|
2761
|
+
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
2762
|
+
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
2763
|
+
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
2764
|
+
}
|
|
2765
|
+
function createFloat16RgbaUploadFromLevels(levels) {
|
|
2766
|
+
return levels.map((level) => {
|
|
2767
|
+
const rowBytes = level.width * 8;
|
|
2768
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2769
|
+
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
2770
|
+
const view = new DataView(bytes.buffer);
|
|
2771
|
+
for (let y = 0; y < level.height; y += 1) {
|
|
2772
|
+
for (let x = 0; x < level.width; x += 1) {
|
|
2773
|
+
const sourceOffset = (y * level.width + x) * 4;
|
|
2774
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
2775
|
+
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
2776
|
+
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
2777
|
+
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
2778
|
+
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
return Object.freeze({ bytes, bytesPerRow, width: level.width, height: level.height });
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
function createPrefilteredEnvironmentLevels(environmentMap, fallbackColor) {
|
|
2785
|
+
const sourcePixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
2786
|
+
const sourceWidth = Math.max(1, environmentMap.width);
|
|
2787
|
+
const sourceHeight = Math.max(1, environmentMap.height);
|
|
2788
|
+
const mipLevelCount = Math.max(1, Math.floor(Math.log2(Math.max(sourceWidth, sourceHeight))) + 1);
|
|
2789
|
+
const levels = [
|
|
2790
|
+
Object.freeze({
|
|
2791
|
+
width: sourceWidth,
|
|
2792
|
+
height: sourceHeight,
|
|
2793
|
+
data: sourcePixels
|
|
2794
|
+
})
|
|
2795
|
+
];
|
|
2796
|
+
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
2797
|
+
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
2798
|
+
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
2799
|
+
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
2800
|
+
const data = new Float32Array(width * height * 4);
|
|
2801
|
+
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
2802
|
+
for (let y = 0; y < height; y += 1) {
|
|
2803
|
+
for (let x = 0; x < width; x += 1) {
|
|
2804
|
+
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
2805
|
+
const normal = normalize(direction, [0, 1, 0]);
|
|
2806
|
+
const viewDirection = normal;
|
|
2807
|
+
let totalWeight = 0;
|
|
2808
|
+
const accum = [0, 0, 0];
|
|
2809
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
2810
|
+
const xi = hammersley(sampleIndex, sampleCount);
|
|
2811
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2812
|
+
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
2813
|
+
const lightDirection = normalize(
|
|
2814
|
+
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
2815
|
+
normal
|
|
2816
|
+
);
|
|
2817
|
+
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
2818
|
+
if (nDotL <= 1e-6) {
|
|
2819
|
+
continue;
|
|
2820
|
+
}
|
|
2821
|
+
const radiance = sampleEnvironmentRadiance(
|
|
2822
|
+
sourcePixels,
|
|
2823
|
+
sourceWidth,
|
|
2824
|
+
sourceHeight,
|
|
2825
|
+
lightDirection,
|
|
2826
|
+
environmentMap.rotationRadians
|
|
2827
|
+
);
|
|
2828
|
+
accum[0] += radiance[0] * nDotL;
|
|
2829
|
+
accum[1] += radiance[1] * nDotL;
|
|
2830
|
+
accum[2] += radiance[2] * nDotL;
|
|
2831
|
+
totalWeight += nDotL;
|
|
2832
|
+
}
|
|
2833
|
+
const offset = (y * width + x) * 4;
|
|
2834
|
+
data[offset] = accum[0] / Math.max(totalWeight, 1e-6);
|
|
2835
|
+
data[offset + 1] = accum[1] / Math.max(totalWeight, 1e-6);
|
|
2836
|
+
data[offset + 2] = accum[2] / Math.max(totalWeight, 1e-6);
|
|
2837
|
+
data[offset + 3] = 1;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
levels.push(Object.freeze({ width, height, data }));
|
|
2841
|
+
}
|
|
2842
|
+
return Object.freeze({
|
|
2843
|
+
levels,
|
|
2844
|
+
mipLevelCount,
|
|
2845
|
+
width: sourceWidth,
|
|
2846
|
+
height: sourceHeight
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
function createEnvironmentSamplingTables(environmentMap, fallbackColor) {
|
|
2850
|
+
if (!environmentMapHasSamplingData(environmentMap)) {
|
|
2851
|
+
return Object.freeze({
|
|
2852
|
+
width: 1,
|
|
2853
|
+
height: 1,
|
|
2854
|
+
pdf: new Float32Array([1]),
|
|
2855
|
+
marginalCdf: new Float32Array([1]),
|
|
2856
|
+
conditionalCdf: new Float32Array([1]),
|
|
2857
|
+
hasImportanceData: false
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
const pixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
2861
|
+
const width = Math.max(1, environmentMap.width);
|
|
2862
|
+
const height = Math.max(1, environmentMap.height);
|
|
2863
|
+
const pdf = new Float32Array(width * height);
|
|
2864
|
+
const marginalCdf = new Float32Array(height);
|
|
2865
|
+
const conditionalCdf = new Float32Array(width * height);
|
|
2866
|
+
const rowSums = new Float32Array(height);
|
|
2867
|
+
let totalWeight = 0;
|
|
1710
2868
|
for (let y = 0; y < height; y += 1) {
|
|
2869
|
+
const theta = (y + 0.5) / height * Math.PI;
|
|
2870
|
+
const sinTheta = Math.max(Math.sin(theta), 1e-4);
|
|
2871
|
+
let rowWeight = 0;
|
|
1711
2872
|
for (let x = 0; x < width; x += 1) {
|
|
1712
|
-
const
|
|
1713
|
-
const
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
2873
|
+
const offset = (y * width + x) * 4;
|
|
2874
|
+
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
2875
|
+
const weight = Math.max(luminance * sinTheta, 1e-6);
|
|
2876
|
+
pdf[y * width + x] = weight;
|
|
2877
|
+
rowWeight += weight;
|
|
2878
|
+
conditionalCdf[y * width + x] = rowWeight;
|
|
2879
|
+
}
|
|
2880
|
+
rowSums[y] = rowWeight;
|
|
2881
|
+
totalWeight += rowWeight;
|
|
2882
|
+
if (rowWeight > 0) {
|
|
2883
|
+
for (let x = 0; x < width; x += 1) {
|
|
2884
|
+
conditionalCdf[y * width + x] /= rowWeight;
|
|
2885
|
+
}
|
|
2886
|
+
} else {
|
|
2887
|
+
for (let x = 0; x < width; x += 1) {
|
|
2888
|
+
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
2889
|
+
}
|
|
1718
2890
|
}
|
|
2891
|
+
marginalCdf[y] = totalWeight;
|
|
2892
|
+
}
|
|
2893
|
+
for (let y = 0; y < height; y += 1) {
|
|
2894
|
+
marginalCdf[y] /= Math.max(totalWeight, 1e-6);
|
|
2895
|
+
}
|
|
2896
|
+
for (let index = 0; index < pdf.length; index += 1) {
|
|
2897
|
+
pdf[index] /= Math.max(totalWeight, 1e-6);
|
|
1719
2898
|
}
|
|
1720
2899
|
return Object.freeze({
|
|
1721
|
-
bytes,
|
|
1722
|
-
bytesPerRow,
|
|
1723
2900
|
width,
|
|
1724
|
-
height
|
|
2901
|
+
height,
|
|
2902
|
+
pdf,
|
|
2903
|
+
marginalCdf,
|
|
2904
|
+
conditionalCdf,
|
|
2905
|
+
hasImportanceData: true
|
|
1725
2906
|
});
|
|
1726
2907
|
}
|
|
1727
2908
|
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
@@ -1733,10 +2914,14 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1733
2914
|
addressModeU: "repeat",
|
|
1734
2915
|
addressModeV: "clamp-to-edge",
|
|
1735
2916
|
magFilter: "linear",
|
|
1736
|
-
minFilter: "linear"
|
|
2917
|
+
minFilter: "linear",
|
|
2918
|
+
mipmapFilter: "linear"
|
|
1737
2919
|
}),
|
|
1738
2920
|
texture: null,
|
|
1739
|
-
ownsTexture: false
|
|
2921
|
+
ownsTexture: false,
|
|
2922
|
+
width: Math.max(1, environmentMap.width),
|
|
2923
|
+
height: Math.max(1, environmentMap.height),
|
|
2924
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
1740
2925
|
});
|
|
1741
2926
|
}
|
|
1742
2927
|
if (environmentMap.texture && typeof environmentMap.texture.createView === "function") {
|
|
@@ -1747,15 +2932,91 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1747
2932
|
addressModeU: "repeat",
|
|
1748
2933
|
addressModeV: "clamp-to-edge",
|
|
1749
2934
|
magFilter: "linear",
|
|
1750
|
-
minFilter: "linear"
|
|
2935
|
+
minFilter: "linear",
|
|
2936
|
+
mipmapFilter: "linear"
|
|
1751
2937
|
}),
|
|
1752
2938
|
texture: environmentMap.texture,
|
|
1753
|
-
ownsTexture: false
|
|
2939
|
+
ownsTexture: false,
|
|
2940
|
+
width: Math.max(1, environmentMap.width),
|
|
2941
|
+
height: Math.max(1, environmentMap.height),
|
|
2942
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
1754
2943
|
});
|
|
1755
2944
|
}
|
|
1756
|
-
const
|
|
2945
|
+
const prefiltered = createPrefilteredEnvironmentLevels(environmentMap, fallbackColor);
|
|
2946
|
+
const uploads = createFloat16RgbaUploadFromLevels(prefiltered.levels);
|
|
1757
2947
|
const texture = device.createTexture({
|
|
1758
2948
|
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
2949
|
+
size: { width: prefiltered.width, height: prefiltered.height },
|
|
2950
|
+
format: "rgba16float",
|
|
2951
|
+
mipLevelCount: prefiltered.mipLevelCount,
|
|
2952
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
2953
|
+
});
|
|
2954
|
+
uploads.forEach((upload, mipLevel) => {
|
|
2955
|
+
device.queue.writeTexture(
|
|
2956
|
+
{ texture, mipLevel },
|
|
2957
|
+
upload.bytes,
|
|
2958
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
2959
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
2960
|
+
);
|
|
2961
|
+
});
|
|
2962
|
+
return Object.freeze({
|
|
2963
|
+
view: texture.createView(),
|
|
2964
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
2965
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
2966
|
+
addressModeU: "repeat",
|
|
2967
|
+
addressModeV: "clamp-to-edge",
|
|
2968
|
+
magFilter: "linear",
|
|
2969
|
+
minFilter: "linear",
|
|
2970
|
+
mipmapFilter: "linear"
|
|
2971
|
+
}),
|
|
2972
|
+
texture,
|
|
2973
|
+
ownsTexture: true,
|
|
2974
|
+
width: prefiltered.width,
|
|
2975
|
+
height: prefiltered.height,
|
|
2976
|
+
mipLevelCount: prefiltered.mipLevelCount
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
function createEnvironmentSamplingTextureResource(device, constants, environmentMap, fallbackColor) {
|
|
2980
|
+
const tables = createEnvironmentSamplingTables(environmentMap, fallbackColor);
|
|
2981
|
+
const rowBytes = tables.width * 8;
|
|
2982
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2983
|
+
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
2984
|
+
const view = new DataView(bytes.buffer);
|
|
2985
|
+
for (let y = 0; y < tables.height; y += 1) {
|
|
2986
|
+
for (let x = 0; x < tables.width; x += 1) {
|
|
2987
|
+
const probability = tables.pdf[y * tables.width + x];
|
|
2988
|
+
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
2989
|
+
const marginal = tables.marginalCdf[y];
|
|
2990
|
+
const offset = y * bytesPerRow + x * 8;
|
|
2991
|
+
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
2992
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
2993
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
2994
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
const texture = device.createTexture({
|
|
2998
|
+
label: "plasius.wavefront.environmentSampling",
|
|
2999
|
+
size: { width: tables.width, height: tables.height },
|
|
3000
|
+
format: "rgba16float",
|
|
3001
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
3002
|
+
});
|
|
3003
|
+
device.queue.writeTexture(
|
|
3004
|
+
{ texture },
|
|
3005
|
+
bytes,
|
|
3006
|
+
{ bytesPerRow, rowsPerImage: tables.height },
|
|
3007
|
+
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
3008
|
+
);
|
|
3009
|
+
return Object.freeze({
|
|
3010
|
+
view: texture.createView(),
|
|
3011
|
+
texture,
|
|
3012
|
+
ownsTexture: true,
|
|
3013
|
+
hasImportanceData: tables.hasImportanceData
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE) {
|
|
3017
|
+
const upload = createBrdfLutUploadBytes(size);
|
|
3018
|
+
const texture = device.createTexture({
|
|
3019
|
+
label: "plasius.wavefront.brdfLut",
|
|
1759
3020
|
size: { width: upload.width, height: upload.height },
|
|
1760
3021
|
format: "rgba16float",
|
|
1761
3022
|
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
@@ -1768,14 +3029,110 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1768
3029
|
);
|
|
1769
3030
|
return Object.freeze({
|
|
1770
3031
|
view: texture.createView(),
|
|
1771
|
-
sampler:
|
|
1772
|
-
label: "plasius.wavefront.
|
|
1773
|
-
addressModeU: "
|
|
3032
|
+
sampler: device.createSampler({
|
|
3033
|
+
label: "plasius.wavefront.brdfLutSampler",
|
|
3034
|
+
addressModeU: "clamp-to-edge",
|
|
1774
3035
|
addressModeV: "clamp-to-edge",
|
|
1775
3036
|
magFilter: "linear",
|
|
1776
3037
|
minFilter: "linear"
|
|
1777
3038
|
}),
|
|
1778
3039
|
texture,
|
|
3040
|
+
ownsTexture: true,
|
|
3041
|
+
width: upload.width,
|
|
3042
|
+
height: upload.height
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
function createMediumTextureResource(device, constants, mediums) {
|
|
3046
|
+
const normalized = Array.isArray(mediums) && mediums.length > 0 ? mediums : [{ id: 0 }];
|
|
3047
|
+
const width = Math.max(
|
|
3048
|
+
1,
|
|
3049
|
+
normalized.reduce((maximum, medium) => Math.max(maximum, medium.id ?? 0), 0) + 1
|
|
3050
|
+
);
|
|
3051
|
+
const level = {
|
|
3052
|
+
width,
|
|
3053
|
+
height: MEDIUM_TABLE_ROWS,
|
|
3054
|
+
data: new Float32Array(width * MEDIUM_TABLE_ROWS * 4)
|
|
3055
|
+
};
|
|
3056
|
+
for (const medium of normalized) {
|
|
3057
|
+
const mediumId = Math.max(0, Math.trunc(Number(medium.id) || 0));
|
|
3058
|
+
const absorptionOffset = mediumId * 4;
|
|
3059
|
+
level.data[absorptionOffset] = Math.max(0, medium.absorption?.[0] ?? 0);
|
|
3060
|
+
level.data[absorptionOffset + 1] = Math.max(0, medium.absorption?.[1] ?? 0);
|
|
3061
|
+
level.data[absorptionOffset + 2] = Math.max(0, medium.absorption?.[2] ?? 0);
|
|
3062
|
+
level.data[absorptionOffset + 3] = Math.max(0, medium.phaseModel ?? 0);
|
|
3063
|
+
const scatteringOffset = (width + mediumId) * 4;
|
|
3064
|
+
level.data[scatteringOffset] = Math.max(0, medium.scattering?.[0] ?? 0);
|
|
3065
|
+
level.data[scatteringOffset + 1] = Math.max(0, medium.scattering?.[1] ?? 0);
|
|
3066
|
+
level.data[scatteringOffset + 2] = Math.max(0, medium.scattering?.[2] ?? 0);
|
|
3067
|
+
level.data[scatteringOffset + 3] = Math.max(0, medium.density ?? 0);
|
|
3068
|
+
}
|
|
3069
|
+
const upload = createFloat16RgbaUploadFromLevels([level])[0];
|
|
3070
|
+
const texture = device.createTexture({
|
|
3071
|
+
label: "plasius.wavefront.mediumTable",
|
|
3072
|
+
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
3073
|
+
format: "rgba16float",
|
|
3074
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
3075
|
+
});
|
|
3076
|
+
device.queue.writeTexture(
|
|
3077
|
+
{ texture },
|
|
3078
|
+
upload.bytes,
|
|
3079
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3080
|
+
{ width, height: MEDIUM_TABLE_ROWS, depthOrArrayLayers: 1 }
|
|
3081
|
+
);
|
|
3082
|
+
return Object.freeze({
|
|
3083
|
+
texture,
|
|
3084
|
+
view: texture.createView(),
|
|
3085
|
+
ownsTexture: true,
|
|
3086
|
+
count: normalized.length,
|
|
3087
|
+
width
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
function mediumTablesEqual(left, right) {
|
|
3091
|
+
const leftMediums = Array.isArray(left) ? left : [];
|
|
3092
|
+
const rightMediums = Array.isArray(right) ? right : [];
|
|
3093
|
+
if (leftMediums.length !== rightMediums.length) {
|
|
3094
|
+
return false;
|
|
3095
|
+
}
|
|
3096
|
+
for (let index = 0; index < leftMediums.length; index += 1) {
|
|
3097
|
+
const leftMedium = leftMediums[index];
|
|
3098
|
+
const rightMedium = rightMediums[index];
|
|
3099
|
+
if ((leftMedium?.id ?? 0) !== (rightMedium?.id ?? 0)) {
|
|
3100
|
+
return false;
|
|
3101
|
+
}
|
|
3102
|
+
if ((leftMedium?.phaseModel ?? 0) !== (rightMedium?.phaseModel ?? 0)) {
|
|
3103
|
+
return false;
|
|
3104
|
+
}
|
|
3105
|
+
if ((leftMedium?.density ?? 0) !== (rightMedium?.density ?? 0)) {
|
|
3106
|
+
return false;
|
|
3107
|
+
}
|
|
3108
|
+
for (let component = 0; component < 3; component += 1) {
|
|
3109
|
+
if ((leftMedium?.absorption?.[component] ?? 0) !== (rightMedium?.absorption?.[component] ?? 0)) {
|
|
3110
|
+
return false;
|
|
3111
|
+
}
|
|
3112
|
+
if ((leftMedium?.scattering?.[component] ?? 0) !== (rightMedium?.scattering?.[component] ?? 0)) {
|
|
3113
|
+
return false;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
return true;
|
|
3118
|
+
}
|
|
3119
|
+
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
3120
|
+
const upload = createRgba8TextureUpload(atlas);
|
|
3121
|
+
const texture = device.createTexture({
|
|
3122
|
+
label,
|
|
3123
|
+
size: { width: upload.width, height: upload.height },
|
|
3124
|
+
format: "rgba8unorm",
|
|
3125
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
3126
|
+
});
|
|
3127
|
+
device.queue.writeTexture(
|
|
3128
|
+
{ texture },
|
|
3129
|
+
upload.bytes,
|
|
3130
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3131
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
3132
|
+
);
|
|
3133
|
+
return Object.freeze({
|
|
3134
|
+
texture,
|
|
3135
|
+
view: texture.createView(),
|
|
1779
3136
|
ownsTexture: true
|
|
1780
3137
|
});
|
|
1781
3138
|
}
|
|
@@ -1821,6 +3178,24 @@ ${diagnostics}` : "";
|
|
|
1821
3178
|
});
|
|
1822
3179
|
}
|
|
1823
3180
|
}
|
|
3181
|
+
async function assertShaderModuleCompiles(shaderModule, label) {
|
|
3182
|
+
if (typeof shaderModule?.compilationInfo !== "function") {
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3185
|
+
const info = await shaderModule.compilationInfo();
|
|
3186
|
+
const messages = Array.isArray(info?.messages) ? info.messages : [];
|
|
3187
|
+
const errors = messages.filter((message) => message?.type === "error");
|
|
3188
|
+
if (errors.length <= 0) {
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
const diagnostics = errors.map((message) => {
|
|
3192
|
+
const line = Number.isFinite(message.lineNum) ? message.lineNum : "?";
|
|
3193
|
+
const column = Number.isFinite(message.linePos) ? message.linePos : "?";
|
|
3194
|
+
return `line ${line}:${column} ${message.message}`;
|
|
3195
|
+
}).join("\n");
|
|
3196
|
+
throw new Error(`WGSL compilation preflight failed for ${label}:
|
|
3197
|
+
${diagnostics}`);
|
|
3198
|
+
}
|
|
1824
3199
|
async function createRenderPipeline(device, descriptor) {
|
|
1825
3200
|
if (typeof device.createRenderPipelineAsync === "function") {
|
|
1826
3201
|
return device.createRenderPipelineAsync(descriptor);
|
|
@@ -1829,6 +3204,7 @@ async function createRenderPipeline(device, descriptor) {
|
|
|
1829
3204
|
}
|
|
1830
3205
|
var WAVEFRONT_COMPUTE_WGSL = `
|
|
1831
3206
|
const RAY_FLAG_GUIDED_EMISSIVE: u32 = 1u;
|
|
3207
|
+
const RAY_FLAG_DELTA_SAMPLE: u32 = 2u;
|
|
1832
3208
|
|
|
1833
3209
|
struct RayRecord {
|
|
1834
3210
|
rayId: u32,
|
|
@@ -1854,11 +3230,12 @@ struct HitRecord {
|
|
|
1854
3230
|
primitiveId: u32,
|
|
1855
3231
|
materialRefId: u32,
|
|
1856
3232
|
mediumRefId: u32,
|
|
3233
|
+
materialSlot: u32,
|
|
1857
3234
|
pad0: u32,
|
|
1858
3235
|
pad1: u32,
|
|
1859
|
-
pad2: u32,
|
|
1860
3236
|
distance: f32,
|
|
1861
|
-
|
|
3237
|
+
occlusion: f32,
|
|
3238
|
+
pad2: vec2<f32>,
|
|
1862
3239
|
position: vec4<f32>,
|
|
1863
3240
|
geometricNormal: vec4<f32>,
|
|
1864
3241
|
shadingNormal: vec4<f32>,
|
|
@@ -1867,6 +3244,9 @@ struct HitRecord {
|
|
|
1867
3244
|
color: vec4<f32>,
|
|
1868
3245
|
emission: vec4<f32>,
|
|
1869
3246
|
material: vec4<f32>,
|
|
3247
|
+
materialResponse: vec4<f32>,
|
|
3248
|
+
materialExtension: vec4<f32>,
|
|
3249
|
+
specularColor: vec4<f32>,
|
|
1870
3250
|
};
|
|
1871
3251
|
|
|
1872
3252
|
struct SceneObject {
|
|
@@ -1874,11 +3254,18 @@ struct SceneObject {
|
|
|
1874
3254
|
objectId: u32,
|
|
1875
3255
|
materialKind: u32,
|
|
1876
3256
|
flags: u32,
|
|
3257
|
+
mediumRefId: u32,
|
|
3258
|
+
pad0: u32,
|
|
3259
|
+
pad1: u32,
|
|
3260
|
+
pad2: u32,
|
|
1877
3261
|
center: vec4<f32>,
|
|
1878
3262
|
halfExtent: vec4<f32>,
|
|
1879
3263
|
color: vec4<f32>,
|
|
1880
3264
|
emission: vec4<f32>,
|
|
1881
3265
|
material: vec4<f32>,
|
|
3266
|
+
materialResponse: vec4<f32>,
|
|
3267
|
+
materialExtension: vec4<f32>,
|
|
3268
|
+
specularColor: vec4<f32>,
|
|
1882
3269
|
};
|
|
1883
3270
|
|
|
1884
3271
|
struct TriangleRecord {
|
|
@@ -1888,7 +3275,7 @@ struct TriangleRecord {
|
|
|
1888
3275
|
flags: u32,
|
|
1889
3276
|
materialRefId: u32,
|
|
1890
3277
|
mediumRefId: u32,
|
|
1891
|
-
|
|
3278
|
+
materialSlot: u32,
|
|
1892
3279
|
pad1: u32,
|
|
1893
3280
|
v0: vec4<f32>,
|
|
1894
3281
|
v1: vec4<f32>,
|
|
@@ -1901,6 +3288,15 @@ struct TriangleRecord {
|
|
|
1901
3288
|
color: vec4<f32>,
|
|
1902
3289
|
emission: vec4<f32>,
|
|
1903
3290
|
material: vec4<f32>,
|
|
3291
|
+
materialResponse: vec4<f32>,
|
|
3292
|
+
materialExtension: vec4<f32>,
|
|
3293
|
+
specularColor: vec4<f32>,
|
|
3294
|
+
baseColorAtlas: vec4<f32>,
|
|
3295
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3296
|
+
normalAtlas: vec4<f32>,
|
|
3297
|
+
occlusionAtlas: vec4<f32>,
|
|
3298
|
+
emissiveAtlas: vec4<f32>,
|
|
3299
|
+
textureSettings: vec4<f32>,
|
|
1904
3300
|
};
|
|
1905
3301
|
|
|
1906
3302
|
struct BvhNode {
|
|
@@ -1921,10 +3317,10 @@ struct BvhLeafRef {
|
|
|
1921
3317
|
|
|
1922
3318
|
struct ScatterResult {
|
|
1923
3319
|
direction: vec4<f32>,
|
|
3320
|
+
pdf: f32,
|
|
3321
|
+
mediumRefId: u32,
|
|
1924
3322
|
flags: u32,
|
|
1925
3323
|
pad0: u32,
|
|
1926
|
-
pad1: u32,
|
|
1927
|
-
pad2: u32,
|
|
1928
3324
|
};
|
|
1929
3325
|
|
|
1930
3326
|
struct MeshVertex {
|
|
@@ -1945,10 +3341,19 @@ struct MeshRange {
|
|
|
1945
3341
|
triangleCount: u32,
|
|
1946
3342
|
firstVertex: u32,
|
|
1947
3343
|
vertexCount: u32,
|
|
1948
|
-
|
|
3344
|
+
materialSlot: u32,
|
|
1949
3345
|
color: vec4<f32>,
|
|
1950
3346
|
emission: vec4<f32>,
|
|
1951
3347
|
material: vec4<f32>,
|
|
3348
|
+
materialResponse: vec4<f32>,
|
|
3349
|
+
materialExtension: vec4<f32>,
|
|
3350
|
+
specularColor: vec4<f32>,
|
|
3351
|
+
baseColorAtlas: vec4<f32>,
|
|
3352
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3353
|
+
normalAtlas: vec4<f32>,
|
|
3354
|
+
occlusionAtlas: vec4<f32>,
|
|
3355
|
+
emissiveAtlas: vec4<f32>,
|
|
3356
|
+
textureSettings: vec4<f32>,
|
|
1952
3357
|
};
|
|
1953
3358
|
|
|
1954
3359
|
struct FrameConfig {
|
|
@@ -1989,6 +3394,7 @@ struct FrameConfig {
|
|
|
1989
3394
|
_portalPad1: u32,
|
|
1990
3395
|
environmentMapSettings: vec4<f32>,
|
|
1991
3396
|
pathResolveSettings: vec4<f32>,
|
|
3397
|
+
environmentMapMeta: vec4<f32>,
|
|
1992
3398
|
};
|
|
1993
3399
|
|
|
1994
3400
|
struct Counters {
|
|
@@ -2051,6 +3457,16 @@ struct EnvironmentPortal {
|
|
|
2051
3457
|
@group(0) @binding(20) var environmentMapTexture: texture_2d<f32>;
|
|
2052
3458
|
@group(0) @binding(21) var environmentMapSampler: sampler;
|
|
2053
3459
|
@group(0) @binding(22) var<storage, read_write> pathVertices: array<vec4<f32>>;
|
|
3460
|
+
@group(0) @binding(23) var baseColorAtlasTexture: texture_2d<f32>;
|
|
3461
|
+
@group(0) @binding(24) var metallicRoughnessAtlasTexture: texture_2d<f32>;
|
|
3462
|
+
@group(0) @binding(25) var normalAtlasTexture: texture_2d<f32>;
|
|
3463
|
+
@group(0) @binding(26) var occlusionAtlasTexture: texture_2d<f32>;
|
|
3464
|
+
@group(0) @binding(27) var emissiveAtlasTexture: texture_2d<f32>;
|
|
3465
|
+
@group(0) @binding(28) var materialAtlasSampler: sampler;
|
|
3466
|
+
@group(0) @binding(29) var brdfLutTexture: texture_2d<f32>;
|
|
3467
|
+
@group(0) @binding(30) var brdfLutSampler: sampler;
|
|
3468
|
+
@group(0) @binding(31) var environmentSamplingTexture: texture_2d<f32>;
|
|
3469
|
+
@group(0) @binding(32) var mediumTableTexture: texture_2d<f32>;
|
|
2054
3470
|
|
|
2055
3471
|
fn hash_u32(value: u32) -> u32 {
|
|
2056
3472
|
var x = value;
|
|
@@ -2087,6 +3503,146 @@ fn safe_normalize(value: vec3<f32>, fallback: vec3<f32>) -> vec3<f32> {
|
|
|
2087
3503
|
return value / len;
|
|
2088
3504
|
}
|
|
2089
3505
|
|
|
3506
|
+
struct TangentBasis {
|
|
3507
|
+
tangent: vec3<f32>,
|
|
3508
|
+
bitangent: vec3<f32>,
|
|
3509
|
+
};
|
|
3510
|
+
|
|
3511
|
+
struct SurfaceMaterialSample {
|
|
3512
|
+
color: vec4<f32>,
|
|
3513
|
+
emission: vec4<f32>,
|
|
3514
|
+
material: vec4<f32>,
|
|
3515
|
+
materialResponse: vec4<f32>,
|
|
3516
|
+
materialExtension: vec4<f32>,
|
|
3517
|
+
specularColor: vec4<f32>,
|
|
3518
|
+
shadingNormal: vec3<f32>,
|
|
3519
|
+
occlusion: f32,
|
|
3520
|
+
};
|
|
3521
|
+
|
|
3522
|
+
fn srgb_to_linear_channel(value: f32) -> f32 {
|
|
3523
|
+
if (value <= 0.04045) {
|
|
3524
|
+
return value / 12.92;
|
|
3525
|
+
}
|
|
3526
|
+
return pow((value + 0.055) / 1.055, 2.4);
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
fn srgb_to_linear_vec3(value: vec3<f32>) -> vec3<f32> {
|
|
3530
|
+
return vec3<f32>(
|
|
3531
|
+
srgb_to_linear_channel(value.x),
|
|
3532
|
+
srgb_to_linear_channel(value.y),
|
|
3533
|
+
srgb_to_linear_channel(value.z)
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
fn wrap_uv(uv: vec2<f32>) -> vec2<f32> {
|
|
3538
|
+
return fract(fract(uv) + vec2<f32>(1.0));
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
fn atlas_sample_uv(rect: vec4<f32>, uv: vec2<f32>) -> vec2<f32> {
|
|
3542
|
+
let local = wrap_uv(uv);
|
|
3543
|
+
let clamped = clamp(local, vec2<f32>(0.001), vec2<f32>(0.999));
|
|
3544
|
+
return rect.xy + clamped * rect.zw;
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
fn sample_atlas(textureRef: texture_2d<f32>, rect: vec4<f32>, uv: vec2<f32>) -> vec4<f32> {
|
|
3548
|
+
return textureSampleLevel(textureRef, materialAtlasSampler, atlas_sample_uv(rect, uv), 0.0);
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
fn build_triangle_tangent_basis(
|
|
3552
|
+
triangle: TriangleRecord,
|
|
3553
|
+
fallbackNormal: vec3<f32>
|
|
3554
|
+
) -> TangentBasis {
|
|
3555
|
+
let edge1 = triangle.v1.xyz - triangle.v0.xyz;
|
|
3556
|
+
let edge2 = triangle.v2.xyz - triangle.v0.xyz;
|
|
3557
|
+
let uv0 = triangle.uv0uv1.xy;
|
|
3558
|
+
let uv1 = triangle.uv0uv1.zw;
|
|
3559
|
+
let uv2 = triangle.uv2Pad.xy;
|
|
3560
|
+
let deltaUv1 = uv1 - uv0;
|
|
3561
|
+
let deltaUv2 = uv2 - uv0;
|
|
3562
|
+
let determinant = deltaUv1.x * deltaUv2.y - deltaUv1.y * deltaUv2.x;
|
|
3563
|
+
if (abs(determinant) <= 0.000001) {
|
|
3564
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(fallbackNormal.y) >= 0.999);
|
|
3565
|
+
let tangent = safe_normalize(cross(tangentFallback, fallbackNormal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3566
|
+
let bitangent = safe_normalize(cross(fallbackNormal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3567
|
+
return TangentBasis(tangent, bitangent);
|
|
3568
|
+
}
|
|
3569
|
+
let inverse = 1.0 / determinant;
|
|
3570
|
+
let tangent = safe_normalize(
|
|
3571
|
+
inverse * (edge1 * deltaUv2.y - edge2 * deltaUv1.y),
|
|
3572
|
+
vec3<f32>(1.0, 0.0, 0.0)
|
|
3573
|
+
);
|
|
3574
|
+
let bitangent = safe_normalize(
|
|
3575
|
+
inverse * (-edge1 * deltaUv2.x + edge2 * deltaUv1.x),
|
|
3576
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3577
|
+
);
|
|
3578
|
+
return TangentBasis(tangent, bitangent);
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
fn sample_surface_material(
|
|
3582
|
+
triangle: TriangleRecord,
|
|
3583
|
+
uv: vec2<f32>,
|
|
3584
|
+
geometricNormal: vec3<f32>,
|
|
3585
|
+
shadingNormal: vec3<f32>
|
|
3586
|
+
) -> SurfaceMaterialSample {
|
|
3587
|
+
let baseColorTexel = sample_atlas(baseColorAtlasTexture, triangle.baseColorAtlas, uv);
|
|
3588
|
+
let baseColor = vec4<f32>(
|
|
3589
|
+
clamp(triangle.color.rgb * srgb_to_linear_vec3(baseColorTexel.rgb), vec3<f32>(0.0), vec3<f32>(1.0)),
|
|
3590
|
+
clamp(triangle.color.a * baseColorTexel.a, 0.0, 1.0)
|
|
3591
|
+
);
|
|
3592
|
+
let metallicRoughnessTexel = sample_atlas(
|
|
3593
|
+
metallicRoughnessAtlasTexture,
|
|
3594
|
+
triangle.metallicRoughnessAtlas,
|
|
3595
|
+
uv
|
|
3596
|
+
);
|
|
3597
|
+
let normalTexel = sample_atlas(normalAtlasTexture, triangle.normalAtlas, uv);
|
|
3598
|
+
let occlusionTexel = sample_atlas(occlusionAtlasTexture, triangle.occlusionAtlas, uv);
|
|
3599
|
+
let emissiveTexel = sample_atlas(emissiveAtlasTexture, triangle.emissiveAtlas, uv);
|
|
3600
|
+
let normalScale = clamp(triangle.textureSettings.x, 0.0, 1.0);
|
|
3601
|
+
let tangentBasis = build_triangle_tangent_basis(triangle, geometricNormal);
|
|
3602
|
+
let tangentNormal = safe_normalize(
|
|
3603
|
+
vec3<f32>(
|
|
3604
|
+
(normalTexel.x * 2.0 - 1.0) * normalScale,
|
|
3605
|
+
(normalTexel.y * 2.0 - 1.0) * normalScale,
|
|
3606
|
+
1.0 + ((normalTexel.z * 2.0 - 1.0) - 1.0) * normalScale
|
|
3607
|
+
),
|
|
3608
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3609
|
+
);
|
|
3610
|
+
let mappedNormal = safe_normalize(
|
|
3611
|
+
tangentBasis.tangent * tangentNormal.x +
|
|
3612
|
+
tangentBasis.bitangent * tangentNormal.y +
|
|
3613
|
+
shadingNormal * tangentNormal.z,
|
|
3614
|
+
shadingNormal
|
|
3615
|
+
);
|
|
3616
|
+
let emission = vec4<f32>(
|
|
3617
|
+
max(
|
|
3618
|
+
triangle.emission.rgb *
|
|
3619
|
+
srgb_to_linear_vec3(emissiveTexel.rgb) *
|
|
3620
|
+
max(triangle.textureSettings.z, 0.0),
|
|
3621
|
+
vec3<f32>(0.0)
|
|
3622
|
+
),
|
|
3623
|
+
clamp(triangle.emission.a * emissiveTexel.a, 0.0, 1.0)
|
|
3624
|
+
);
|
|
3625
|
+
return SurfaceMaterialSample(
|
|
3626
|
+
baseColor,
|
|
3627
|
+
emission,
|
|
3628
|
+
vec4<f32>(
|
|
3629
|
+
clamp(triangle.material.x * metallicRoughnessTexel.y, 0.0, 1.0),
|
|
3630
|
+
clamp(triangle.material.y * metallicRoughnessTexel.z, 0.0, 1.0),
|
|
3631
|
+
clamp(triangle.material.z * baseColor.a, 0.0, 1.0),
|
|
3632
|
+
clamp(triangle.material.w, 1.0, 3.0)
|
|
3633
|
+
),
|
|
3634
|
+
triangle.materialResponse,
|
|
3635
|
+
triangle.materialExtension,
|
|
3636
|
+
triangle.specularColor,
|
|
3637
|
+
repair_shading_normal(geometricNormal, mappedNormal),
|
|
3638
|
+
clamp(
|
|
3639
|
+
mix(1.0, occlusionTexel.x, clamp(triangle.textureSettings.y, 0.0, 1.0)),
|
|
3640
|
+
0.0,
|
|
3641
|
+
1.0
|
|
3642
|
+
)
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
|
|
2090
3646
|
fn saturate(value: f32) -> f32 {
|
|
2091
3647
|
return clamp(value, 0.0, 1.0);
|
|
2092
3648
|
}
|
|
@@ -2095,6 +3651,10 @@ fn max_component(value: vec3<f32>) -> f32 {
|
|
|
2095
3651
|
return max(max(value.x, value.y), value.z);
|
|
2096
3652
|
}
|
|
2097
3653
|
|
|
3654
|
+
fn radiance_luminance(value: vec3<f32>) -> f32 {
|
|
3655
|
+
return dot(value, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
3656
|
+
}
|
|
3657
|
+
|
|
2098
3658
|
fn environment_map_enabled() -> bool {
|
|
2099
3659
|
return config.environmentMapSettings.x > 0.5;
|
|
2100
3660
|
}
|
|
@@ -2215,12 +3775,349 @@ fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) ->
|
|
|
2215
3775
|
return scale;
|
|
2216
3776
|
}
|
|
2217
3777
|
|
|
2218
|
-
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
2219
|
-
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
2220
|
-
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
2221
|
-
let portalHit = max_component(portalScale) > 0.0001;
|
|
2222
|
-
return base_environment_radiance(rayDirection) *
|
|
2223
|
-
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
3778
|
+
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
3779
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3780
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
3781
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
3782
|
+
return base_environment_radiance(rayDirection) *
|
|
3783
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3786
|
+
fn direct_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
3787
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3788
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
3789
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
3790
|
+
if (
|
|
3791
|
+
config.environmentPortalCount > 0u &&
|
|
3792
|
+
config.environmentPortalMode == 2u &&
|
|
3793
|
+
!portalHit
|
|
3794
|
+
) {
|
|
3795
|
+
return vec3<f32>(0.0);
|
|
3796
|
+
}
|
|
3797
|
+
return base_environment_radiance(rayDirection) *
|
|
3798
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
fn radical_inverse_vdc(bitsValue: u32) -> f32 {
|
|
3802
|
+
var bits = bitsValue;
|
|
3803
|
+
bits = (bits << 16u) | (bits >> 16u);
|
|
3804
|
+
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xaaaaaaaau) >> 1u);
|
|
3805
|
+
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xccccccccu) >> 2u);
|
|
3806
|
+
bits = ((bits & 0x0f0f0f0fu) << 4u) | ((bits & 0xf0f0f0f0u) >> 4u);
|
|
3807
|
+
bits = ((bits & 0x00ff00ffu) << 8u) | ((bits & 0xff00ff00u) >> 8u);
|
|
3808
|
+
return f32(bits) * 2.3283064365386963e-10;
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
fn hammersley_2d(index: u32, count: u32) -> vec2<f32> {
|
|
3812
|
+
return vec2<f32>(f32(index) / max(f32(count), 1.0), radical_inverse_vdc(index));
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
fn build_basis_tangent(normal: vec3<f32>) -> vec3<f32> {
|
|
3816
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(normal.y) >= 0.999);
|
|
3817
|
+
return safe_normalize(cross(tangentFallback, normal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
fn local_to_world(local: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
3821
|
+
let tangent = build_basis_tangent(normal);
|
|
3822
|
+
let bitangent = safe_normalize(cross(normal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3823
|
+
return safe_normalize(tangent * local.x + bitangent * local.y + normal * local.z, normal);
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
fn cosine_sample_hemisphere(sample: vec2<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
3827
|
+
let phi = 6.28318530718 * sample.x;
|
|
3828
|
+
let radius = sqrt(sample.y);
|
|
3829
|
+
let x = cos(phi) * radius;
|
|
3830
|
+
let y = sin(phi) * radius;
|
|
3831
|
+
let z = sqrt(max(0.0, 1.0 - sample.y));
|
|
3832
|
+
return local_to_world(vec3<f32>(x, y, z), normal);
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
fn importance_sample_ggx(sample: vec2<f32>, roughness: f32, normal: vec3<f32>) -> vec3<f32> {
|
|
3836
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
3837
|
+
let phi = 6.28318530718 * sample.x;
|
|
3838
|
+
let cosTheta = sqrt((1.0 - sample.y) / max(1.0 + (alpha * alpha - 1.0) * sample.y, 0.0001));
|
|
3839
|
+
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3840
|
+
let localHalf = vec3<f32>(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
|
3841
|
+
return local_to_world(localHalf, normal);
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
fn distribution_ggx(normal: vec3<f32>, halfVector: vec3<f32>, roughness: f32) -> f32 {
|
|
3845
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
3846
|
+
let alpha2 = alpha * alpha;
|
|
3847
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
3848
|
+
let denominator = nDotH * nDotH * (alpha2 - 1.0) + 1.0;
|
|
3849
|
+
return alpha2 / max(3.14159265359 * denominator * denominator, 0.000001);
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
fn geometry_schlick_ggx(nDotValue: f32, roughness: f32) -> f32 {
|
|
3853
|
+
let k = ((roughness + 1.0) * (roughness + 1.0)) / 8.0;
|
|
3854
|
+
return nDotValue / max(nDotValue * (1.0 - k) + k, 0.000001);
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
fn geometry_smith(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
3858
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
3859
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
3860
|
+
return geometry_schlick_ggx(nDotV, roughness) * geometry_schlick_ggx(nDotL, roughness);
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
fn fresnel_schlick(cosine: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
3864
|
+
return f0 + (vec3<f32>(1.0) - f0) * pow(1.0 - cosine, 5.0);
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
fn sample_brdf_lut(nDotV: f32, roughness: f32) -> vec2<f32> {
|
|
3868
|
+
let uv = vec2<f32>(clamp(nDotV, 0.0, 1.0), clamp(roughness, 0.0, 1.0));
|
|
3869
|
+
return textureSampleLevel(brdfLutTexture, brdfLutSampler, uv, 0.0).xy;
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
fn prefiltered_environment_radiance(direction: vec3<f32>, roughness: f32) -> vec3<f32> {
|
|
3873
|
+
let uv = environment_map_uv(direction);
|
|
3874
|
+
let maxLevel = max(config.environmentMapMeta.z - 1.0, 0.0);
|
|
3875
|
+
let lod = clamp(roughness, 0.0, 1.0) * maxLevel;
|
|
3876
|
+
let texel = max(textureSampleLevel(environmentMapTexture, environmentMapSampler, uv, lod).rgb, vec3<f32>(0.0));
|
|
3877
|
+
return texel * max(config.environmentMapSettings.y, 0.0);
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
fn environment_pdf_dimensions() -> vec2<u32> {
|
|
3881
|
+
return vec2<u32>(
|
|
3882
|
+
max(u32(config.environmentMapMeta.x), 1u),
|
|
3883
|
+
max(u32(config.environmentMapMeta.y), 1u)
|
|
3884
|
+
);
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
fn environment_importance_sampling_enabled() -> bool {
|
|
3888
|
+
return config.environmentMapMeta.w > 0.5;
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
fn uniform_sphere_pdf() -> f32 {
|
|
3892
|
+
return 1.0 / (4.0 * 3.14159265359);
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
fn sample_uniform_sphere_direction(sample: vec2<f32>) -> vec3<f32> {
|
|
3896
|
+
let z = 1.0 - 2.0 * sample.y;
|
|
3897
|
+
let radial = sqrt(max(1.0 - z * z, 0.0));
|
|
3898
|
+
let phi = sample.x * 6.28318530718;
|
|
3899
|
+
return vec3<f32>(cos(phi) * radial, z, sin(phi) * radial);
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
fn environment_sampling_texel(x: u32, y: u32) -> vec4<f32> {
|
|
3903
|
+
return textureLoad(environmentSamplingTexture, vec2<i32>(i32(x), i32(y)), 0);
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
fn environment_pdf_texel(x: u32, y: u32) -> f32 {
|
|
3907
|
+
return environment_sampling_texel(x, y).x;
|
|
3908
|
+
}
|
|
3909
|
+
|
|
3910
|
+
fn environment_row_cdf_texel(y: u32) -> f32 {
|
|
3911
|
+
return environment_sampling_texel(0u, y).z;
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
fn environment_column_cdf_texel(x: u32, y: u32) -> f32 {
|
|
3915
|
+
return environment_sampling_texel(x, y).y;
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
fn environment_direction_pdf(direction: vec3<f32>) -> f32 {
|
|
3919
|
+
if (!environment_importance_sampling_enabled()) {
|
|
3920
|
+
return uniform_sphere_pdf();
|
|
3921
|
+
}
|
|
3922
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3923
|
+
let uv = environment_map_uv(rayDirection);
|
|
3924
|
+
let dimensions = environment_pdf_dimensions();
|
|
3925
|
+
let width = max(f32(dimensions.x), 1.0);
|
|
3926
|
+
let height = max(f32(dimensions.y), 1.0);
|
|
3927
|
+
let x = min(u32(uv.x * width), dimensions.x - 1u);
|
|
3928
|
+
let y = min(u32(uv.y * height), dimensions.y - 1u);
|
|
3929
|
+
let discretePdf = max(environment_pdf_texel(x, y), 0.0);
|
|
3930
|
+
let sinTheta = sqrt(max(1.0 - rayDirection.y * rayDirection.y, 0.0));
|
|
3931
|
+
let solidAngle = max((2.0 * 3.14159265359 * 3.14159265359 * sinTheta) / (width * height), 0.000001);
|
|
3932
|
+
return discretePdf / solidAngle;
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
fn sample_row_cdf(count: u32, sampleValue: f32) -> u32 {
|
|
3936
|
+
if (count == 0u) {
|
|
3937
|
+
return 0u;
|
|
3938
|
+
}
|
|
3939
|
+
var low = 0u;
|
|
3940
|
+
var high = count - 1u;
|
|
3941
|
+
loop {
|
|
3942
|
+
if (low >= high) {
|
|
3943
|
+
break;
|
|
3944
|
+
}
|
|
3945
|
+
let mid = (low + high) / 2u;
|
|
3946
|
+
let cdfValue = environment_row_cdf_texel(mid);
|
|
3947
|
+
if (sampleValue <= cdfValue) {
|
|
3948
|
+
high = mid;
|
|
3949
|
+
} else {
|
|
3950
|
+
low = mid + 1u;
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
return min(low, count - 1u);
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
fn sample_column_cdf(row: u32, count: u32, sampleValue: f32) -> u32 {
|
|
3957
|
+
if (count == 0u) {
|
|
3958
|
+
return 0u;
|
|
3959
|
+
}
|
|
3960
|
+
var low = 0u;
|
|
3961
|
+
var high = count - 1u;
|
|
3962
|
+
loop {
|
|
3963
|
+
if (low >= high) {
|
|
3964
|
+
break;
|
|
3965
|
+
}
|
|
3966
|
+
let mid = (low + high) / 2u;
|
|
3967
|
+
let cdfValue = environment_column_cdf_texel(mid, row);
|
|
3968
|
+
if (sampleValue <= cdfValue) {
|
|
3969
|
+
high = mid;
|
|
3970
|
+
} else {
|
|
3971
|
+
low = mid + 1u;
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
return min(low, count - 1u);
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
struct EnvironmentSample {
|
|
3978
|
+
direction: vec3<f32>,
|
|
3979
|
+
radiance: vec3<f32>,
|
|
3980
|
+
pdf: f32,
|
|
3981
|
+
};
|
|
3982
|
+
|
|
3983
|
+
fn sample_environment_importance(sample: vec2<f32>) -> EnvironmentSample {
|
|
3984
|
+
if (!environment_importance_sampling_enabled()) {
|
|
3985
|
+
let direction = sample_uniform_sphere_direction(sample);
|
|
3986
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), uniform_sphere_pdf());
|
|
3987
|
+
}
|
|
3988
|
+
let dimensions = environment_pdf_dimensions();
|
|
3989
|
+
let row = sample_row_cdf(dimensions.y, sample.y);
|
|
3990
|
+
let column = sample_column_cdf(row, dimensions.x, sample.x);
|
|
3991
|
+
let uv = vec2<f32>(
|
|
3992
|
+
(f32(column) + 0.5) / max(f32(dimensions.x), 1.0),
|
|
3993
|
+
(f32(row) + 0.5) / max(f32(dimensions.y), 1.0)
|
|
3994
|
+
);
|
|
3995
|
+
let theta = uv.y * 3.14159265359;
|
|
3996
|
+
let phi = (uv.x - 0.5 - config.environmentMapSettings.z / 6.28318530718) * 6.28318530718;
|
|
3997
|
+
let sinTheta = sin(theta);
|
|
3998
|
+
let direction = vec3<f32>(cos(phi) * sinTheta, cos(theta), sin(phi) * sinTheta);
|
|
3999
|
+
let pdf = environment_direction_pdf(direction);
|
|
4000
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), pdf);
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
fn power_heuristic(pdfA: f32, pdfB: f32) -> f32 {
|
|
4004
|
+
let a2 = pdfA * pdfA;
|
|
4005
|
+
let b2 = pdfB * pdfB;
|
|
4006
|
+
return a2 / max(a2 + b2, 0.000001);
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
fn visible_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
4010
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4011
|
+
let visible = !scene_visibility_blocked(origin, rayDirection, 1000000.0);
|
|
4012
|
+
return select(vec3<f32>(0.0), direct_environment_radiance(origin, rayDirection), visible);
|
|
4013
|
+
}
|
|
4014
|
+
|
|
4015
|
+
fn glossy_environment_direction(
|
|
4016
|
+
incidentDirection: vec3<f32>,
|
|
4017
|
+
normal: vec3<f32>,
|
|
4018
|
+
roughness: f32,
|
|
4019
|
+
normalBlendScale: f32
|
|
4020
|
+
) -> vec3<f32> {
|
|
4021
|
+
let reflectionDirection = reflect(incidentDirection, normal);
|
|
4022
|
+
let blend = clamp(roughness * roughness * normalBlendScale, 0.0, 0.92);
|
|
4023
|
+
return safe_normalize(mix(reflectionDirection, normal, blend), normal);
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
fn surface_glossiness(hit: HitRecord) -> f32 {
|
|
4027
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4028
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4029
|
+
let sheen = clamp(max_component(hit.materialResponse.xyz), 0.0, 1.0);
|
|
4030
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4031
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4032
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
4033
|
+
let baseGloss =
|
|
4034
|
+
max(
|
|
4035
|
+
clearcoat,
|
|
4036
|
+
max(sheen * 0.72, max(specularWeight * (0.38 + metallic * 0.62), transmission))
|
|
4037
|
+
);
|
|
4038
|
+
return clamp(baseGloss * (1.0 - roughness * 0.72) + metallic * (1.0 - roughness) * 0.35, 0.0, 1.0);
|
|
4039
|
+
}
|
|
4040
|
+
|
|
4041
|
+
fn surface_specular_f0(hit: HitRecord, surfaceColor: vec3<f32>) -> vec3<f32> {
|
|
4042
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4043
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4044
|
+
let specularColor = clamp(hit.specularColor.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4045
|
+
let dielectricF0 = vec3<f32>(0.04) * specularWeight * specularColor;
|
|
4046
|
+
return mix(dielectricF0, surfaceColor, metallic);
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
fn surface_bsdf_sampling_weights(hit: HitRecord) -> vec3<f32> {
|
|
4050
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4051
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4052
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4053
|
+
let diffuseWeight = clamp(
|
|
4054
|
+
(1.0 - metallic) * max(1.0 - specularWeight * 0.5 - clearcoat * 0.25, 0.15),
|
|
4055
|
+
0.0,
|
|
4056
|
+
1.0
|
|
4057
|
+
);
|
|
4058
|
+
let specWeight = clamp(max(metallic, specularWeight * 0.75) * (1.0 - clearcoat * 0.5), 0.0, 1.0);
|
|
4059
|
+
let clearcoatWeight = clamp(clearcoat, 0.0, 1.0);
|
|
4060
|
+
let totalWeight = max(diffuseWeight + specWeight + clearcoatWeight, 0.000001);
|
|
4061
|
+
return vec3<f32>(
|
|
4062
|
+
diffuseWeight / totalWeight,
|
|
4063
|
+
specWeight / totalWeight,
|
|
4064
|
+
clearcoatWeight / totalWeight
|
|
4065
|
+
);
|
|
4066
|
+
}
|
|
4067
|
+
|
|
4068
|
+
fn evaluate_surface_bsdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> vec3<f32> {
|
|
4069
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4070
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4071
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4072
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4073
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4074
|
+
let clearcoatRoughness = clamp(hit.materialExtension.x, 0.0, 1.0);
|
|
4075
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
4076
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
4077
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4078
|
+
if (nDotV <= 0.0 || nDotL <= 0.0) {
|
|
4079
|
+
return vec3<f32>(0.0);
|
|
4080
|
+
}
|
|
4081
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
4082
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
4083
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
4084
|
+
let fresnel = fresnel_schlick(vDotH, f0);
|
|
4085
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
4086
|
+
let geometry = geometry_smith(normal, viewDirection, lightDirection, roughness);
|
|
4087
|
+
let specular = (distribution * geometry * fresnel) / max(4.0 * nDotV * nDotL, 0.000001);
|
|
4088
|
+
let diffuseWeight = (1.0 - metallic) * (1.0 - clearcoat * 0.24) * (1.0 - clamp(max_component(fresnel), 0.0, 0.98));
|
|
4089
|
+
let diffuse = surfaceColor * diffuseWeight / 3.14159265359;
|
|
4090
|
+
let clearcoatHalf = safe_normalize(viewDirection + lightDirection, normal);
|
|
4091
|
+
let clearcoatDistribution = distribution_ggx(normal, clearcoatHalf, max(clearcoatRoughness, 0.02));
|
|
4092
|
+
let clearcoatGeometry = geometry_smith(normal, viewDirection, lightDirection, max(clearcoatRoughness, 0.02));
|
|
4093
|
+
let clearcoatFresnel = fresnel_schlick(saturate(dot(viewDirection, clearcoatHalf)), vec3<f32>(0.04));
|
|
4094
|
+
let clearcoatTerm =
|
|
4095
|
+
(clearcoatDistribution * clearcoatGeometry * clearcoatFresnel) /
|
|
4096
|
+
max(4.0 * nDotV * nDotL, 0.000001) *
|
|
4097
|
+
clearcoat;
|
|
4098
|
+
return (diffuse + specular + clearcoatTerm) * mix(0.42, 1.0, occlusion);
|
|
4099
|
+
}
|
|
4100
|
+
|
|
4101
|
+
fn diffuse_pdf(normal: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
4102
|
+
return saturate(dot(normal, lightDirection)) / 3.14159265359;
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
fn ggx_pdf(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
4106
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
4107
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
4108
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
4109
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
4110
|
+
return (distribution * nDotH) / max(4.0 * vDotH, 0.000001);
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
fn evaluate_surface_bsdf_pdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
4114
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4115
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4116
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
4117
|
+
let diffuseTerm = diffuse_pdf(normal, lightDirection);
|
|
4118
|
+
let specTerm = ggx_pdf(normal, viewDirection, lightDirection, max(roughness, 0.02));
|
|
4119
|
+
let clearcoatTerm = ggx_pdf(normal, viewDirection, lightDirection, max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02));
|
|
4120
|
+
return weights.x * diffuseTerm + weights.y * specTerm + weights.z * clearcoatTerm;
|
|
2224
4121
|
}
|
|
2225
4122
|
|
|
2226
4123
|
fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
@@ -2235,12 +4132,102 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
2235
4132
|
return environment_radiance(origin, direction);
|
|
2236
4133
|
}
|
|
2237
4134
|
|
|
4135
|
+
fn medium_dimensions() -> vec2<u32> {
|
|
4136
|
+
return textureDimensions(mediumTableTexture);
|
|
4137
|
+
}
|
|
4138
|
+
|
|
4139
|
+
fn medium_valid(mediumRefId: u32) -> bool {
|
|
4140
|
+
let dimensions = medium_dimensions();
|
|
4141
|
+
return mediumRefId > 0u && mediumRefId < dimensions.x;
|
|
4142
|
+
}
|
|
4143
|
+
|
|
4144
|
+
fn medium_absorption(mediumRefId: u32) -> vec3<f32> {
|
|
4145
|
+
if (!medium_valid(mediumRefId)) {
|
|
4146
|
+
return vec3<f32>(0.0);
|
|
4147
|
+
}
|
|
4148
|
+
return max(
|
|
4149
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 0), 0).xyz,
|
|
4150
|
+
vec3<f32>(0.0)
|
|
4151
|
+
);
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
fn medium_scattering(mediumRefId: u32) -> vec3<f32> {
|
|
4155
|
+
if (!medium_valid(mediumRefId)) {
|
|
4156
|
+
return vec3<f32>(0.0);
|
|
4157
|
+
}
|
|
4158
|
+
return max(
|
|
4159
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 1), 0).xyz,
|
|
4160
|
+
vec3<f32>(0.0)
|
|
4161
|
+
);
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4164
|
+
fn medium_transmittance(mediumRefId: u32, distance: f32) -> vec3<f32> {
|
|
4165
|
+
if (!medium_valid(mediumRefId) || distance <= 0.000001) {
|
|
4166
|
+
return vec3<f32>(1.0);
|
|
4167
|
+
}
|
|
4168
|
+
let extinction = medium_absorption(mediumRefId) + medium_scattering(mediumRefId);
|
|
4169
|
+
return vec3<f32>(
|
|
4170
|
+
exp(-extinction.x * distance),
|
|
4171
|
+
exp(-extinction.y * distance),
|
|
4172
|
+
exp(-extinction.z * distance)
|
|
4173
|
+
);
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
fn transmitted_medium_ref_id(ray: RayRecord, hit: HitRecord) -> u32 {
|
|
4177
|
+
if (hit.mediumRefId == 0u) {
|
|
4178
|
+
return ray.mediumRefId;
|
|
4179
|
+
}
|
|
4180
|
+
if (hit.frontFace == 1u) {
|
|
4181
|
+
return hit.mediumRefId;
|
|
4182
|
+
}
|
|
4183
|
+
if (ray.mediumRefId == hit.mediumRefId) {
|
|
4184
|
+
return 0u;
|
|
4185
|
+
}
|
|
4186
|
+
return ray.mediumRefId;
|
|
4187
|
+
}
|
|
4188
|
+
|
|
2238
4189
|
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
2239
4190
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
2240
4191
|
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
4192
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
2241
4193
|
let materialEnergy = select(0.68, 0.92, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2242
4194
|
let transparentEnergy = select(materialEnergy, 0.9, hit.hitType == 3u);
|
|
2243
|
-
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy;
|
|
4195
|
+
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy * mix(0.55, 1.0, occlusion);
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
fn bounded_path_response_luminance(ray: RayRecord, hit: HitRecord) -> f32 {
|
|
4199
|
+
let daylightFloor = max(config.pathResolveSettings.y, 0.0) * 0.08;
|
|
4200
|
+
let hdriFloor = max(config.environmentMapSettings.w, 0.0) * 0.02;
|
|
4201
|
+
let sceneFloor = max(daylightFloor, hdriFloor);
|
|
4202
|
+
if (sceneFloor <= 0.000001) {
|
|
4203
|
+
return 0.0;
|
|
4204
|
+
}
|
|
4205
|
+
let bounceRatio = select(
|
|
4206
|
+
0.0,
|
|
4207
|
+
f32(ray.bounce) / max(f32(config.maxDepth - 1u), 1.0),
|
|
4208
|
+
config.maxDepth > 1u
|
|
4209
|
+
);
|
|
4210
|
+
let bounceScale = 1.0 - bounceRatio * 0.55;
|
|
4211
|
+
let materialScale = select(1.0, 0.34, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
4212
|
+
let transparentScale = select(materialScale, 0.58, hit.hitType == 3u);
|
|
4213
|
+
let opacityScale = mix(0.55, 1.0, clamp(hit.material.z, 0.0, 1.0));
|
|
4214
|
+
return sceneFloor * bounceScale * transparentScale * opacityScale;
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
fn stabilize_surface_path_response(ray: RayRecord, hit: HitRecord, response: vec3<f32>) -> vec3<f32> {
|
|
4218
|
+
let minimumLuminance = bounded_path_response_luminance(ray, hit);
|
|
4219
|
+
let responseLuminance = radiance_luminance(response);
|
|
4220
|
+
if (minimumLuminance <= 0.000001 || responseLuminance >= minimumLuminance) {
|
|
4221
|
+
return response;
|
|
4222
|
+
}
|
|
4223
|
+
let tintBase = max(response, max(hit.color.xyz * 0.65, config.ambientColor.xyz * 0.35));
|
|
4224
|
+
let tint = tintBase / max(max_component(tintBase), 0.0001);
|
|
4225
|
+
let lifted = select(
|
|
4226
|
+
tint * minimumLuminance,
|
|
4227
|
+
response * (minimumLuminance / max(responseLuminance, 0.0001)),
|
|
4228
|
+
responseLuminance > 0.0001
|
|
4229
|
+
);
|
|
4230
|
+
return clamp(lifted, vec3<f32>(0.0), vec3<f32>(0.98));
|
|
2244
4231
|
}
|
|
2245
4232
|
|
|
2246
4233
|
fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
@@ -2259,12 +4246,24 @@ fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
|
2259
4246
|
return clamp_sample_radiance(sunTint * baseline * skyFacing * directionalWeight * 0.04);
|
|
2260
4247
|
}
|
|
2261
4248
|
|
|
2262
|
-
fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
4249
|
+
fn terminal_surface_environment_source(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2263
4250
|
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2264
|
-
let
|
|
2265
|
-
|
|
2266
|
-
|
|
4251
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4252
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4253
|
+
let glossiness = surface_glossiness(hit);
|
|
4254
|
+
let normalEnvironment = gated_environment_radiance(origin, normal);
|
|
4255
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4256
|
+
let reflectionDirection = glossy_environment_direction(
|
|
4257
|
+
ray.direction.xyz,
|
|
4258
|
+
normal,
|
|
4259
|
+
roughness,
|
|
4260
|
+
mix(0.88, 0.38, glossiness)
|
|
2267
4261
|
);
|
|
4262
|
+
let reflectionEnvironment = prefiltered_environment_radiance(reflectionDirection, roughness);
|
|
4263
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4264
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
4265
|
+
let brdfTerm = sample_brdf_lut(saturate(dot(normal, viewDirection)), roughness);
|
|
4266
|
+
let specularEnvironment = reflectionEnvironment * (f0 * brdfTerm.x + vec3<f32>(brdfTerm.y));
|
|
2268
4267
|
let sunlitFloor = sunlit_baseline_radiance(normal);
|
|
2269
4268
|
let ambientFloor = select(
|
|
2270
4269
|
max(config.ambientColor.xyz, sunlitFloor * 0.82),
|
|
@@ -2276,17 +4275,27 @@ fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
|
2276
4275
|
max(config.environmentMapSettings.w, max(0.12, config.pathResolveSettings.y * 0.42)),
|
|
2277
4276
|
environment_map_enabled()
|
|
2278
4277
|
);
|
|
2279
|
-
let
|
|
4278
|
+
let glossyEnvironment = max(
|
|
4279
|
+
normalEnvironment,
|
|
4280
|
+
max(reflectionEnvironment * mix(0.24, 0.92, glossiness), specularEnvironment)
|
|
4281
|
+
);
|
|
4282
|
+
let environmentFloor = max(ambientFloor, max(sunlitFloor, glossyEnvironment * environmentInfluence));
|
|
2280
4283
|
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
2281
4284
|
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
2282
4285
|
}
|
|
2283
4286
|
|
|
2284
|
-
fn terminal_surface_environment_contribution(
|
|
4287
|
+
fn terminal_surface_environment_contribution(
|
|
4288
|
+
ray: RayRecord,
|
|
4289
|
+
throughput: vec3<f32>,
|
|
4290
|
+
hit: HitRecord
|
|
4291
|
+
) -> vec3<f32> {
|
|
2285
4292
|
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
4293
|
+
let occlusion = mix(0.75, 1.0, clamp(hit.occlusion, 0.0, 1.0));
|
|
2286
4294
|
return clamp_sample_radiance(
|
|
2287
|
-
|
|
4295
|
+
throughput *
|
|
2288
4296
|
surfaceColor *
|
|
2289
|
-
terminal_surface_environment_source(hit)
|
|
4297
|
+
terminal_surface_environment_source(ray, hit) *
|
|
4298
|
+
occlusion
|
|
2290
4299
|
);
|
|
2291
4300
|
}
|
|
2292
4301
|
|
|
@@ -2319,6 +4328,10 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2319
4328
|
);
|
|
2320
4329
|
let area = max(portal.position.w, 0.0001);
|
|
2321
4330
|
let distanceFalloff = clamp(area / max(distanceSquared, area * 0.25), 0.0, 2.5);
|
|
4331
|
+
let traceDistance = max(sqrt(distanceSquared) - 0.01, 0.01);
|
|
4332
|
+
if (scene_visibility_blocked(origin, direction, traceDistance)) {
|
|
4333
|
+
continue;
|
|
4334
|
+
}
|
|
2322
4335
|
irradiance = irradiance +
|
|
2323
4336
|
portal.color.rgb *
|
|
2324
4337
|
portal.normal.w *
|
|
@@ -2330,48 +4343,79 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2330
4343
|
return irradiance;
|
|
2331
4344
|
}
|
|
2332
4345
|
|
|
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()
|
|
4346
|
+
fn visibility_test_ray(origin: vec3<f32>, direction: vec3<f32>) -> RayRecord {
|
|
4347
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4348
|
+
return RayRecord(
|
|
4349
|
+
0u,
|
|
4350
|
+
0u,
|
|
4351
|
+
0u,
|
|
4352
|
+
0u,
|
|
4353
|
+
0u,
|
|
4354
|
+
0u,
|
|
4355
|
+
0u,
|
|
4356
|
+
0u,
|
|
4357
|
+
vec4<f32>(origin, 1.0),
|
|
4358
|
+
vec4<f32>(rayDirection, 0.0),
|
|
4359
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2352
4360
|
);
|
|
2353
|
-
|
|
4361
|
+
}
|
|
2354
4362
|
|
|
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);
|
|
4363
|
+
fn scene_visibility_blocked(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> bool {
|
|
4364
|
+
let testRay = visibility_test_ray(origin, direction);
|
|
4365
|
+
let nearest = max(maxDistance, 0.001);
|
|
2363
4366
|
|
|
2364
|
-
|
|
2365
|
-
|
|
4367
|
+
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
4368
|
+
let object = sceneObjects[objectIndex];
|
|
4369
|
+
var current = no_candidate();
|
|
4370
|
+
if (object.kind == 1u) {
|
|
4371
|
+
current = intersect_sphere(testRay, object);
|
|
4372
|
+
} else if (object.kind == 2u) {
|
|
4373
|
+
current = intersect_box(testRay, object);
|
|
4374
|
+
}
|
|
4375
|
+
if (current.hit == 1u && current.distance < nearest) {
|
|
4376
|
+
return true;
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
2366
4379
|
|
|
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);
|
|
4380
|
+
let meshCandidate = intersect_bvh(testRay, nearest);
|
|
4381
|
+
return meshCandidate.hit == 1u && meshCandidate.distance < nearest;
|
|
4382
|
+
}
|
|
2372
4383
|
|
|
2373
|
-
|
|
2374
|
-
|
|
4384
|
+
fn surface_direct_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
4385
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4386
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4387
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4388
|
+
let lightSample = sample_environment_importance(vec2<f32>(
|
|
4389
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 41u)),
|
|
4390
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 43u))
|
|
4391
|
+
));
|
|
4392
|
+
if (lightSample.pdf <= 0.000001) {
|
|
4393
|
+
return vec3<f32>(0.0);
|
|
4394
|
+
}
|
|
4395
|
+
let lightDirection = safe_normalize(lightSample.direction, normal);
|
|
4396
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4397
|
+
if (nDotL <= 0.000001) {
|
|
4398
|
+
return vec3<f32>(0.0);
|
|
4399
|
+
}
|
|
4400
|
+
if (scene_visibility_blocked(origin, lightDirection, 1000000.0)) {
|
|
4401
|
+
return vec3<f32>(0.0);
|
|
4402
|
+
}
|
|
4403
|
+
let incidentRadiance = direct_environment_radiance(origin, lightDirection);
|
|
4404
|
+
if (max_component(incidentRadiance) <= 0.000001) {
|
|
4405
|
+
return vec3<f32>(0.0);
|
|
4406
|
+
}
|
|
4407
|
+
let bsdf = evaluate_surface_bsdf(hit, viewDirection, lightDirection);
|
|
4408
|
+
if (max_component(bsdf) <= 0.000001) {
|
|
4409
|
+
return vec3<f32>(0.0);
|
|
4410
|
+
}
|
|
4411
|
+
let bsdfPdf = evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection);
|
|
4412
|
+
let misWeight = power_heuristic(lightSample.pdf, bsdfPdf);
|
|
4413
|
+
let contribution =
|
|
4414
|
+
ray.throughput.xyz *
|
|
4415
|
+
bsdf *
|
|
4416
|
+
incidentRadiance *
|
|
4417
|
+
(nDotL * misWeight / max(lightSample.pdf, 0.000001));
|
|
4418
|
+
return clamp_sample_radiance(contribution);
|
|
2375
4419
|
}
|
|
2376
4420
|
|
|
2377
4421
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -2390,7 +4434,16 @@ fn default_mesh_range() -> MeshRange {
|
|
|
2390
4434
|
0u,
|
|
2391
4435
|
vec4<f32>(0.72, 0.72, 0.68, 1.0),
|
|
2392
4436
|
vec4<f32>(0.0),
|
|
2393
|
-
vec4<f32>(0.72, 0.0, 1.0, 1.45)
|
|
4437
|
+
vec4<f32>(0.72, 0.0, 1.0, 1.45),
|
|
4438
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4439
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
4440
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4441
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4442
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4443
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4444
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4445
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4446
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
2394
4447
|
);
|
|
2395
4448
|
}
|
|
2396
4449
|
|
|
@@ -2486,7 +4539,7 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2486
4539
|
mesh.flags,
|
|
2487
4540
|
mesh.materialRefId,
|
|
2488
4541
|
mesh.mediumRefId,
|
|
2489
|
-
|
|
4542
|
+
mesh.materialSlot,
|
|
2490
4543
|
0u,
|
|
2491
4544
|
vec4<f32>(vertex0.position.xyz, 0.0),
|
|
2492
4545
|
vec4<f32>(vertex1.position.xyz, 0.0),
|
|
@@ -2498,7 +4551,16 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2498
4551
|
vec4<f32>(uv2, 0.0, 0.0),
|
|
2499
4552
|
mesh.color,
|
|
2500
4553
|
mesh.emission,
|
|
2501
|
-
mesh.material
|
|
4554
|
+
mesh.material,
|
|
4555
|
+
mesh.materialResponse,
|
|
4556
|
+
mesh.materialExtension,
|
|
4557
|
+
mesh.specularColor,
|
|
4558
|
+
mesh.baseColorAtlas,
|
|
4559
|
+
mesh.metallicRoughnessAtlas,
|
|
4560
|
+
mesh.normalAtlas,
|
|
4561
|
+
mesh.occlusionAtlas,
|
|
4562
|
+
mesh.emissiveAtlas,
|
|
4563
|
+
mesh.textureSettings
|
|
2502
4564
|
);
|
|
2503
4565
|
|
|
2504
4566
|
let leafBase = config.triangleCount - 1u;
|
|
@@ -2657,7 +4719,8 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2657
4719
|
0u,
|
|
2658
4720
|
0u,
|
|
2659
4721
|
-1.0,
|
|
2660
|
-
|
|
4722
|
+
1.0,
|
|
4723
|
+
vec2<f32>(0.0),
|
|
2661
4724
|
vec4<f32>(ray.origin.xyz + ray.direction.xyz * 1000.0, 1.0),
|
|
2662
4725
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
2663
4726
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
@@ -2665,7 +4728,10 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2665
4728
|
vec4<f32>(0.0),
|
|
2666
4729
|
vec4<f32>(radiance, 1.0),
|
|
2667
4730
|
vec4<f32>(0.0),
|
|
2668
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
4731
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
4732
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4733
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
4734
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2669
4735
|
);
|
|
2670
4736
|
}
|
|
2671
4737
|
|
|
@@ -2700,7 +4766,7 @@ fn intersect_sphere(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
2700
4766
|
0xffffffffu,
|
|
2701
4767
|
object.objectId,
|
|
2702
4768
|
object.objectId,
|
|
2703
|
-
|
|
4769
|
+
object.mediumRefId
|
|
2704
4770
|
);
|
|
2705
4771
|
}
|
|
2706
4772
|
|
|
@@ -2752,7 +4818,7 @@ fn intersect_box(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
2752
4818
|
0xffffffffu,
|
|
2753
4819
|
object.objectId,
|
|
2754
4820
|
object.objectId,
|
|
2755
|
-
|
|
4821
|
+
object.mediumRefId
|
|
2756
4822
|
);
|
|
2757
4823
|
}
|
|
2758
4824
|
|
|
@@ -2960,6 +5026,19 @@ fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
|
|
|
2960
5026
|
return value / (vec3<f32>(1.0) + value);
|
|
2961
5027
|
}
|
|
2962
5028
|
|
|
5029
|
+
fn denoise_sample_count() -> f32 {
|
|
5030
|
+
return clamp(1.0 / max(config.projectionAndSampling.z, 0.000001), 1.0, 256.0);
|
|
5031
|
+
}
|
|
5032
|
+
|
|
5033
|
+
fn denoise_strength() -> f32 {
|
|
5034
|
+
let spp = denoise_sample_count();
|
|
5035
|
+
return clamp(0.44 / sqrt(spp), 0.08, 0.44);
|
|
5036
|
+
}
|
|
5037
|
+
|
|
5038
|
+
fn denoise_kernel_radius() -> i32 {
|
|
5039
|
+
return select(1i, 2i, denoise_sample_count() < 2.5);
|
|
5040
|
+
}
|
|
5041
|
+
|
|
2963
5042
|
@compute @workgroup_size(64)
|
|
2964
5043
|
fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
2965
5044
|
let index = globalId.x;
|
|
@@ -2990,6 +5069,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2990
5069
|
let ray = activeQueue[index];
|
|
2991
5070
|
var nearest = 1000000.0;
|
|
2992
5071
|
var hitObject = SceneObject(
|
|
5072
|
+
0u,
|
|
5073
|
+
0u,
|
|
5074
|
+
0u,
|
|
5075
|
+
0u,
|
|
2993
5076
|
0u,
|
|
2994
5077
|
0u,
|
|
2995
5078
|
0u,
|
|
@@ -2998,7 +5081,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2998
5081
|
vec4<f32>(0.0),
|
|
2999
5082
|
vec4<f32>(0.0),
|
|
3000
5083
|
vec4<f32>(0.0),
|
|
3001
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
5084
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
5085
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
5086
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
5087
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
3002
5088
|
);
|
|
3003
5089
|
var candidate = no_candidate();
|
|
3004
5090
|
var hitTriangle = TriangleRecord(
|
|
@@ -3020,7 +5106,16 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3020
5106
|
vec4<f32>(0.0),
|
|
3021
5107
|
vec4<f32>(0.0),
|
|
3022
5108
|
vec4<f32>(0.0),
|
|
3023
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
5109
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
5110
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
5111
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
5112
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
5113
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5114
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5115
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5116
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5117
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5118
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
3024
5119
|
);
|
|
3025
5120
|
|
|
3026
5121
|
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
@@ -3053,16 +5148,28 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3053
5148
|
let position = ray.origin.xyz + ray.direction.xyz * candidate.distance;
|
|
3054
5149
|
let hitMaterialKind = select(hitObject.materialKind, hitTriangle.materialKind, candidate.triangleIndex != 0xffffffffu);
|
|
3055
5150
|
let hitObjectId = select(hitObject.objectId, hitTriangle.meshId, candidate.triangleIndex != 0xffffffffu);
|
|
3056
|
-
let
|
|
3057
|
-
|
|
3058
|
-
|
|
5151
|
+
let meshSurface = sample_surface_material(
|
|
5152
|
+
hitTriangle,
|
|
5153
|
+
candidate.uv,
|
|
5154
|
+
candidate.geometricNormal,
|
|
5155
|
+
candidate.shadingNormal
|
|
5156
|
+
);
|
|
5157
|
+
let hitColor = select(hitObject.color, meshSurface.color, candidate.triangleIndex != 0xffffffffu);
|
|
5158
|
+
let hitEmission = select(hitObject.emission, meshSurface.emission, candidate.triangleIndex != 0xffffffffu);
|
|
5159
|
+
let hitMaterial = select(hitObject.material, meshSurface.material, candidate.triangleIndex != 0xffffffffu);
|
|
5160
|
+
let hitMaterialResponse = select(hitObject.materialResponse, meshSurface.materialResponse, candidate.triangleIndex != 0xffffffffu);
|
|
5161
|
+
let hitMaterialExtension = select(hitObject.materialExtension, meshSurface.materialExtension, candidate.triangleIndex != 0xffffffffu);
|
|
5162
|
+
let hitSpecularColor = select(hitObject.specularColor, meshSurface.specularColor, candidate.triangleIndex != 0xffffffffu);
|
|
5163
|
+
let hitShadingNormal = select(candidate.shadingNormal, meshSurface.shadingNormal, candidate.triangleIndex != 0xffffffffu);
|
|
3059
5164
|
let hitPrimitiveId = select(candidate.primitiveId, hitTriangle.triangleId, candidate.triangleIndex != 0xffffffffu);
|
|
3060
5165
|
let hitMaterialRefId = select(candidate.materialRefId, hitTriangle.materialRefId, candidate.triangleIndex != 0xffffffffu);
|
|
3061
5166
|
let hitMediumRefId = select(candidate.mediumRefId, hitTriangle.mediumRefId, candidate.triangleIndex != 0xffffffffu);
|
|
5167
|
+
let hitMaterialSlot = select(0u, hitTriangle.materialSlot, candidate.triangleIndex != 0xffffffffu);
|
|
5168
|
+
let hitOcclusion = select(1.0, meshSurface.occlusion, candidate.triangleIndex != 0xffffffffu);
|
|
3062
5169
|
var hitType = 0u;
|
|
3063
5170
|
if (hitMaterialKind == 4u || emission_power(hitEmission) > 0.0001) {
|
|
3064
5171
|
hitType = 1u;
|
|
3065
|
-
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999) {
|
|
5172
|
+
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999 || hitMaterialExtension.z > 0.001) {
|
|
3066
5173
|
hitType = 3u;
|
|
3067
5174
|
}
|
|
3068
5175
|
atomicAdd(&counters.hitCount, 1u);
|
|
@@ -3076,19 +5183,23 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3076
5183
|
hitPrimitiveId,
|
|
3077
5184
|
hitMaterialRefId,
|
|
3078
5185
|
hitMediumRefId,
|
|
3079
|
-
|
|
5186
|
+
hitMaterialSlot,
|
|
3080
5187
|
0u,
|
|
3081
5188
|
0u,
|
|
3082
5189
|
candidate.distance,
|
|
3083
|
-
|
|
5190
|
+
hitOcclusion,
|
|
5191
|
+
vec2<f32>(0.0),
|
|
3084
5192
|
vec4<f32>(position, 1.0),
|
|
3085
5193
|
vec4<f32>(candidate.geometricNormal, 0.0),
|
|
3086
|
-
vec4<f32>(
|
|
5194
|
+
vec4<f32>(hitShadingNormal, 0.0),
|
|
3087
5195
|
vec4<f32>(candidate.barycentric, 0.0),
|
|
3088
5196
|
vec4<f32>(candidate.uv, 0.0, 0.0),
|
|
3089
5197
|
hitColor,
|
|
3090
5198
|
hitEmission,
|
|
3091
|
-
hitMaterial
|
|
5199
|
+
hitMaterial,
|
|
5200
|
+
hitMaterialResponse,
|
|
5201
|
+
hitMaterialExtension,
|
|
5202
|
+
hitSpecularColor
|
|
3092
5203
|
);
|
|
3093
5204
|
}
|
|
3094
5205
|
|
|
@@ -3157,60 +5268,106 @@ fn sample_environment_portal_direction(hit: HitRecord, seed: u32, fallback: vec3
|
|
|
3157
5268
|
}
|
|
3158
5269
|
|
|
3159
5270
|
fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult {
|
|
5271
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
5272
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
3160
5273
|
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3161
|
-
|
|
5274
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
5275
|
+
if (hit.materialKind == 1u && roughness <= 0.02) {
|
|
3162
5276
|
return ScatterResult(
|
|
3163
|
-
vec4<f32>(
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
),
|
|
3168
|
-
0.0
|
|
3169
|
-
),
|
|
3170
|
-
0u,
|
|
3171
|
-
0u,
|
|
5277
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5278
|
+
1.0,
|
|
5279
|
+
ray.mediumRefId,
|
|
5280
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
3172
5281
|
0u,
|
|
3173
|
-
0u
|
|
3174
5282
|
);
|
|
3175
5283
|
}
|
|
3176
5284
|
|
|
3177
|
-
if (hit.materialKind == 2u || hit.materialKind == 3u) {
|
|
5285
|
+
if (hit.materialKind == 2u || hit.materialKind == 3u || transmission > 0.001) {
|
|
3178
5286
|
let ior = max(hit.material.w, 1.01);
|
|
3179
5287
|
let etaRatio = select(ior, 1.0 / ior, hit.frontFace == 1u);
|
|
3180
|
-
let cosTheta = min(dot(-ray.direction.xyz,
|
|
5288
|
+
let cosTheta = min(dot(-ray.direction.xyz, normal), 1.0);
|
|
3181
5289
|
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3182
5290
|
let cannotRefract = etaRatio * sinTheta > 1.0;
|
|
3183
5291
|
let reflectChance = schlick(cosTheta, etaRatio);
|
|
3184
|
-
|
|
3185
|
-
|
|
5292
|
+
let transmissionReflectChance = select(
|
|
5293
|
+
reflectChance,
|
|
5294
|
+
max(reflectChance, 1.0 - transmission),
|
|
5295
|
+
transmission > 0.001
|
|
5296
|
+
);
|
|
5297
|
+
if (cannotRefract || random01(seed + 23u) < transmissionReflectChance) {
|
|
5298
|
+
return ScatterResult(
|
|
5299
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5300
|
+
1.0,
|
|
5301
|
+
ray.mediumRefId,
|
|
5302
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
5303
|
+
0u,
|
|
5304
|
+
);
|
|
3186
5305
|
}
|
|
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
|
-
)
|
|
5306
|
+
return ScatterResult(
|
|
5307
|
+
vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
|
|
5308
|
+
1.0,
|
|
5309
|
+
transmitted_medium_ref_id(ray, hit),
|
|
5310
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
5311
|
+
0u,
|
|
5312
|
+
);
|
|
5313
|
+
}
|
|
5314
|
+
|
|
5315
|
+
let guidedEmissiveAvailable = config.emissiveTriangleCount > 0u;
|
|
5316
|
+
let guidedPortalAvailable =
|
|
5317
|
+
config.environmentPortalCount > 0u && config.environmentPortalMode != 0u;
|
|
5318
|
+
let guidedSelector = random01(seed + 17u);
|
|
5319
|
+
if (guidedEmissiveAvailable && guidedSelector < 0.18) {
|
|
5320
|
+
let guidedDirection = sample_emissive_triangle_direction(hit, seed + 101u, normal);
|
|
5321
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5322
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5323
|
+
return ScatterResult(
|
|
5324
|
+
vec4<f32>(guidedDirection, 0.0),
|
|
5325
|
+
guidedPdf,
|
|
5326
|
+
ray.mediumRefId,
|
|
5327
|
+
RAY_FLAG_GUIDED_EMISSIVE,
|
|
5328
|
+
0u,
|
|
5329
|
+
);
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
if (guidedPortalAvailable && guidedSelector < 0.32) {
|
|
5333
|
+
let guidedDirection = sample_environment_portal_direction(hit, seed + 131u, normal);
|
|
5334
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5335
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5336
|
+
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf, ray.mediumRefId, 0u, 0u);
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
|
|
5340
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
5341
|
+
let selector = random01(seed + 31u);
|
|
5342
|
+
var lightDirection = normal;
|
|
5343
|
+
if (selector < weights.x) {
|
|
5344
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5345
|
+
vec2<f32>(random01(seed + 37u), random01(seed + 41u)),
|
|
5346
|
+
normal
|
|
5347
|
+
);
|
|
5348
|
+
} else if (selector < weights.x + weights.y) {
|
|
5349
|
+
let halfVector = importance_sample_ggx(
|
|
5350
|
+
vec2<f32>(random01(seed + 47u), random01(seed + 53u)),
|
|
5351
|
+
max(roughness, 0.02),
|
|
5352
|
+
normal
|
|
5353
|
+
);
|
|
5354
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5355
|
+
} else {
|
|
5356
|
+
let halfVector = importance_sample_ggx(
|
|
5357
|
+
vec2<f32>(random01(seed + 59u), random01(seed + 61u)),
|
|
5358
|
+
max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02),
|
|
5359
|
+
normal
|
|
5360
|
+
);
|
|
5361
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5362
|
+
}
|
|
5363
|
+
if (dot(normal, lightDirection) <= 0.000001) {
|
|
5364
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5365
|
+
vec2<f32>(random01(seed + 67u), random01(seed + 71u)),
|
|
5366
|
+
normal
|
|
5367
|
+
);
|
|
5368
|
+
}
|
|
5369
|
+
let pdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection), 0.000001);
|
|
5370
|
+
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf, ray.mediumRefId, 0u, 0u);
|
|
3214
5371
|
}
|
|
3215
5372
|
|
|
3216
5373
|
@compute @workgroup_size(64)
|
|
@@ -3223,15 +5380,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3223
5380
|
|
|
3224
5381
|
let ray = activeQueue[index];
|
|
3225
5382
|
let hit = hits[index];
|
|
5383
|
+
let segmentTransmittance = medium_transmittance(ray.mediumRefId, hit.distance);
|
|
5384
|
+
let arrivingThroughput = ray.throughput.xyz * segmentTransmittance;
|
|
3226
5385
|
var contribution = vec3<f32>(0.0);
|
|
3227
5386
|
|
|
3228
5387
|
if (hit.hitType == 1u) {
|
|
3229
5388
|
let guidedLightWeight = select(1.0, 0.24, (ray.flags & RAY_FLAG_GUIDED_EMISSIVE) != 0u);
|
|
3230
5389
|
let sourceRadiance = max(hit.emission.xyz, hit.color.xyz) * guidedLightWeight;
|
|
3231
5390
|
if (deferred_path_resolve_enabled()) {
|
|
3232
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5391
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
3233
5392
|
} else {
|
|
3234
|
-
contribution = clamp_sample_radiance(
|
|
5393
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
3235
5394
|
accumulation[ray.rayId] =
|
|
3236
5395
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3237
5396
|
}
|
|
@@ -3240,10 +5399,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3240
5399
|
}
|
|
3241
5400
|
|
|
3242
5401
|
if (hit.hitType == 2u) {
|
|
5402
|
+
var sourceRadiance = hit.color.xyz;
|
|
5403
|
+
if ((ray.flags & RAY_FLAG_DELTA_SAMPLE) == 0u) {
|
|
5404
|
+
let bsdfPdf = max(ray.throughput.w, 0.000001);
|
|
5405
|
+
let lightPdf = environment_direction_pdf(ray.direction.xyz);
|
|
5406
|
+
let misWeight = power_heuristic(bsdfPdf, lightPdf);
|
|
5407
|
+
sourceRadiance = sourceRadiance * misWeight;
|
|
5408
|
+
}
|
|
3243
5409
|
if (deferred_path_resolve_enabled()) {
|
|
3244
|
-
record_deferred_terminal_source(ray,
|
|
5410
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
3245
5411
|
} else {
|
|
3246
|
-
contribution = clamp_sample_radiance(
|
|
5412
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
3247
5413
|
accumulation[ray.rayId] =
|
|
3248
5414
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3249
5415
|
}
|
|
@@ -3251,24 +5417,47 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3251
5417
|
return;
|
|
3252
5418
|
}
|
|
3253
5419
|
|
|
3254
|
-
let response =
|
|
5420
|
+
let response = stabilize_surface_path_response(
|
|
5421
|
+
ray,
|
|
5422
|
+
hit,
|
|
5423
|
+
surface_path_response(hit) * segmentTransmittance
|
|
5424
|
+
);
|
|
3255
5425
|
record_deferred_path_response(ray, response);
|
|
3256
5426
|
|
|
3257
5427
|
let shouldEstimateDirectEnvironment =
|
|
3258
|
-
!deferred_path_resolve_enabled() &&
|
|
3259
5428
|
(hit.materialKind == 0u || hit.materialKind == 1u) &&
|
|
3260
|
-
hit.material.z >= 0.95
|
|
5429
|
+
hit.material.z >= 0.95 &&
|
|
5430
|
+
ray.bounce < 2u;
|
|
3261
5431
|
if (shouldEstimateDirectEnvironment) {
|
|
3262
|
-
let directEnvironment = surface_direct_environment_contribution(
|
|
5432
|
+
let directEnvironment = surface_direct_environment_contribution(
|
|
5433
|
+
RayRecord(
|
|
5434
|
+
ray.rayId,
|
|
5435
|
+
ray.parentRayId,
|
|
5436
|
+
ray.sourcePixelId,
|
|
5437
|
+
ray.sampleId,
|
|
5438
|
+
ray.bounce,
|
|
5439
|
+
ray.mediumRefId,
|
|
5440
|
+
ray.flags,
|
|
5441
|
+
0u,
|
|
5442
|
+
ray.origin,
|
|
5443
|
+
ray.direction,
|
|
5444
|
+
vec4<f32>(arrivingThroughput, ray.throughput.w)
|
|
5445
|
+
),
|
|
5446
|
+
hit
|
|
5447
|
+
);
|
|
3263
5448
|
accumulation[ray.rayId] =
|
|
3264
5449
|
accumulation[ray.rayId] + vec4<f32>(directEnvironment * sample_weight(), 0.0);
|
|
3265
5450
|
}
|
|
3266
5451
|
|
|
3267
5452
|
if (ray.bounce + 1u >= config.maxDepth) {
|
|
3268
5453
|
if (deferred_path_resolve_enabled()) {
|
|
3269
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5454
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3270
5455
|
} else {
|
|
3271
|
-
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5456
|
+
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5457
|
+
ray,
|
|
5458
|
+
arrivingThroughput,
|
|
5459
|
+
hit
|
|
5460
|
+
);
|
|
3272
5461
|
accumulation[ray.rayId] =
|
|
3273
5462
|
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
3274
5463
|
}
|
|
@@ -3281,9 +5470,13 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3281
5470
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
3282
5471
|
if (nextIndex >= config.tilePixelCount) {
|
|
3283
5472
|
if (deferred_path_resolve_enabled()) {
|
|
3284
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5473
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3285
5474
|
} else {
|
|
3286
|
-
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5475
|
+
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5476
|
+
ray,
|
|
5477
|
+
arrivingThroughput,
|
|
5478
|
+
hit
|
|
5479
|
+
);
|
|
3287
5480
|
accumulation[ray.rayId] =
|
|
3288
5481
|
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
3289
5482
|
}
|
|
@@ -3297,12 +5490,12 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3297
5490
|
ray.sourcePixelId,
|
|
3298
5491
|
ray.sampleId,
|
|
3299
5492
|
ray.bounce + 1u,
|
|
3300
|
-
|
|
5493
|
+
scatter.mediumRefId,
|
|
3301
5494
|
scatter.flags,
|
|
3302
5495
|
0u,
|
|
3303
5496
|
vec4<f32>(offset_origin(hit.position.xyz, hit.shadingNormal.xyz), 1.0),
|
|
3304
5497
|
scatter.direction,
|
|
3305
|
-
vec4<f32>(throughput,
|
|
5498
|
+
vec4<f32>(throughput, scatter.pdf)
|
|
3306
5499
|
);
|
|
3307
5500
|
}
|
|
3308
5501
|
|
|
@@ -3371,8 +5564,11 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3371
5564
|
|
|
3372
5565
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3373
5566
|
let center = textureLoad(denoiseInputRadiance, pixel, 0).xyz;
|
|
3374
|
-
|
|
3375
|
-
|
|
5567
|
+
let strength = denoise_strength();
|
|
5568
|
+
let kernelRadius = denoise_kernel_radius();
|
|
5569
|
+
let centerWeight = 1.7 - strength * 0.35;
|
|
5570
|
+
var sum = center * centerWeight;
|
|
5571
|
+
var totalWeight = centerWeight;
|
|
3376
5572
|
let centerRange = denoise_range_space(center);
|
|
3377
5573
|
|
|
3378
5574
|
for (var oy = -2i; oy <= 2i; oy = oy + 1i) {
|
|
@@ -3380,13 +5576,16 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3380
5576
|
if (ox == 0i && oy == 0i) {
|
|
3381
5577
|
continue;
|
|
3382
5578
|
}
|
|
5579
|
+
if (abs(ox) > kernelRadius || abs(oy) > kernelRadius) {
|
|
5580
|
+
continue;
|
|
5581
|
+
}
|
|
3383
5582
|
let sx = clamp(i32(x) + ox, 0i, i32(config.canvasWidth) - 1i);
|
|
3384
5583
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3385
5584
|
let sampleColor = textureLoad(denoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3386
5585
|
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.
|
|
5586
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (11.0 + strength * 6.0));
|
|
5587
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.62 + strength * 0.24));
|
|
5588
|
+
let diagonalWeight = select(1.0, 0.92, abs(ox) + abs(oy) > 1i);
|
|
3390
5589
|
let weight = rangeWeight * diagonalWeight * distanceWeight;
|
|
3391
5590
|
sum = sum + sampleColor * weight;
|
|
3392
5591
|
totalWeight = totalWeight + weight;
|
|
@@ -3394,8 +5593,9 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3394
5593
|
}
|
|
3395
5594
|
|
|
3396
5595
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3397
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3398
|
-
let
|
|
5596
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.1);
|
|
5597
|
+
let blend = min(0.3, strength * (0.62 + outlier * 0.12));
|
|
5598
|
+
let color = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3399
5599
|
textureStore(denoisedRadianceImage, pixel, vec4<f32>(color, 1.0));
|
|
3400
5600
|
}
|
|
3401
5601
|
|
|
@@ -3409,8 +5609,10 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3409
5609
|
|
|
3410
5610
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3411
5611
|
let center = textureLoad(finalDenoiseInputRadiance, pixel, 0).xyz;
|
|
3412
|
-
|
|
3413
|
-
|
|
5612
|
+
let strength = denoise_strength();
|
|
5613
|
+
let centerWeight = 1.35 - strength * 0.25;
|
|
5614
|
+
var sum = center * centerWeight;
|
|
5615
|
+
var totalWeight = centerWeight;
|
|
3414
5616
|
let centerRange = denoise_range_space(center);
|
|
3415
5617
|
|
|
3416
5618
|
for (var oy = -1i; oy <= 1i; oy = oy + 1i) {
|
|
@@ -3422,8 +5624,8 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3422
5624
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3423
5625
|
let sampleColor = textureLoad(finalDenoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3424
5626
|
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.
|
|
5627
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (12.0 + strength * 8.0));
|
|
5628
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.82 + strength * 0.28));
|
|
3427
5629
|
let weight = rangeWeight * distanceWeight;
|
|
3428
5630
|
sum = sum + sampleColor * weight;
|
|
3429
5631
|
totalWeight = totalWeight + weight;
|
|
@@ -3431,8 +5633,9 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3431
5633
|
}
|
|
3432
5634
|
|
|
3433
5635
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3434
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3435
|
-
let
|
|
5636
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.2);
|
|
5637
|
+
let blend = min(0.18, strength * (0.42 + outlier * 0.08));
|
|
5638
|
+
let radiance = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3436
5639
|
textureStore(denoisedOutputImage, pixel, vec4<f32>(tone_map_radiance(radiance), 1.0));
|
|
3437
5640
|
}
|
|
3438
5641
|
`;
|
|
@@ -3508,95 +5711,25 @@ function createAdapterInfoSnapshot(adapter) {
|
|
|
3508
5711
|
vendor: typeof info.vendor === "string" ? info.vendor : "",
|
|
3509
5712
|
architecture: typeof info.architecture === "string" ? info.architecture : "",
|
|
3510
5713
|
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"
|
|
5714
|
+
description: typeof info.description === "string" ? info.description : ""
|
|
5715
|
+
});
|
|
5716
|
+
}
|
|
5717
|
+
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
5718
|
+
return Object.freeze({
|
|
5719
|
+
physicalCoreCount: null,
|
|
5720
|
+
physicalCoreCountAvailable: false,
|
|
5721
|
+
physicalCoreCountUnavailableReason: "WebGPU does not expose physical GPU core counts.",
|
|
5722
|
+
adapterInfo: createAdapterInfoSnapshot(adapter),
|
|
5723
|
+
adapterLimits: Object.freeze({
|
|
5724
|
+
maxComputeInvocationsPerWorkgroup: readGpuLimit(adapter, device, "maxComputeInvocationsPerWorkgroup"),
|
|
5725
|
+
maxComputeWorkgroupSizeX: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeX"),
|
|
5726
|
+
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
5727
|
+
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
5728
|
+
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
5729
|
+
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
5730
|
+
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize")
|
|
5731
|
+
}),
|
|
5732
|
+
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
3600
5733
|
});
|
|
3601
5734
|
}
|
|
3602
5735
|
function createEnvironmentMapSnapshot(environmentMap) {
|
|
@@ -3604,12 +5737,38 @@ function createEnvironmentMapSnapshot(environmentMap) {
|
|
|
3604
5737
|
enabled: environmentMap.enabled,
|
|
3605
5738
|
width: environmentMap.width,
|
|
3606
5739
|
height: environmentMap.height,
|
|
5740
|
+
mipLevelCount: environmentMap.mipLevelCount ?? 1,
|
|
3607
5741
|
projection: environmentMap.projection,
|
|
3608
5742
|
intensity: environmentMap.intensity,
|
|
3609
5743
|
rotationRadians: environmentMap.rotationRadians,
|
|
3610
|
-
ambientStrength: environmentMap.ambientStrength
|
|
5744
|
+
ambientStrength: environmentMap.ambientStrength,
|
|
5745
|
+
hasImportanceData: environmentMap.hasImportanceData === true
|
|
3611
5746
|
});
|
|
3612
5747
|
}
|
|
5748
|
+
function nowMs() {
|
|
5749
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
5750
|
+
return performance.now();
|
|
5751
|
+
}
|
|
5752
|
+
return Date.now();
|
|
5753
|
+
}
|
|
5754
|
+
function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs = null) {
|
|
5755
|
+
if (Number.isFinite(overrideTimeoutMs)) {
|
|
5756
|
+
return Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
5757
|
+
}
|
|
5758
|
+
const samplesPerPixel = Math.max(
|
|
5759
|
+
1,
|
|
5760
|
+
Number(config?.renderedSamplesPerPixel ?? config?.samplesPerPixel ?? 1)
|
|
5761
|
+
);
|
|
5762
|
+
const maxDepth = Math.max(1, Number(config?.maxDepth ?? 1));
|
|
5763
|
+
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
5764
|
+
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
5765
|
+
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
5766
|
+
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
5767
|
+
return Math.min(
|
|
5768
|
+
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
5769
|
+
GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5
|
|
5770
|
+
);
|
|
5771
|
+
}
|
|
3613
5772
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
3614
5773
|
assertAnalyticDisplayQualityPolicy(options);
|
|
3615
5774
|
const constants = getGpuUsageConstants();
|
|
@@ -3814,6 +5973,65 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3814
5973
|
config.environmentMap,
|
|
3815
5974
|
config.environmentColor
|
|
3816
5975
|
);
|
|
5976
|
+
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
5977
|
+
device,
|
|
5978
|
+
constants,
|
|
5979
|
+
config.environmentMap,
|
|
5980
|
+
config.environmentColor
|
|
5981
|
+
);
|
|
5982
|
+
let mediumTextureResource = createMediumTextureResource(
|
|
5983
|
+
device,
|
|
5984
|
+
constants,
|
|
5985
|
+
config.mediums
|
|
5986
|
+
);
|
|
5987
|
+
config = Object.freeze({
|
|
5988
|
+
...config,
|
|
5989
|
+
environmentMap: Object.freeze({
|
|
5990
|
+
...config.environmentMap,
|
|
5991
|
+
width: environmentMapResource.width,
|
|
5992
|
+
height: environmentMapResource.height,
|
|
5993
|
+
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
5994
|
+
hasImportanceData: environmentSamplingResource.hasImportanceData
|
|
5995
|
+
})
|
|
5996
|
+
});
|
|
5997
|
+
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
5998
|
+
const baseColorAtlasResource = createAtlasTextureResource(
|
|
5999
|
+
device,
|
|
6000
|
+
constants,
|
|
6001
|
+
config.gpuMaterialSource.baseColorAtlas,
|
|
6002
|
+
"plasius.wavefront.materialAtlas.baseColor"
|
|
6003
|
+
);
|
|
6004
|
+
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
6005
|
+
device,
|
|
6006
|
+
constants,
|
|
6007
|
+
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
6008
|
+
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
6009
|
+
);
|
|
6010
|
+
const normalAtlasResource = createAtlasTextureResource(
|
|
6011
|
+
device,
|
|
6012
|
+
constants,
|
|
6013
|
+
config.gpuMaterialSource.normalAtlas,
|
|
6014
|
+
"plasius.wavefront.materialAtlas.normal"
|
|
6015
|
+
);
|
|
6016
|
+
const occlusionAtlasResource = createAtlasTextureResource(
|
|
6017
|
+
device,
|
|
6018
|
+
constants,
|
|
6019
|
+
config.gpuMaterialSource.occlusionAtlas,
|
|
6020
|
+
"plasius.wavefront.materialAtlas.occlusion"
|
|
6021
|
+
);
|
|
6022
|
+
const emissiveAtlasResource = createAtlasTextureResource(
|
|
6023
|
+
device,
|
|
6024
|
+
constants,
|
|
6025
|
+
config.gpuMaterialSource.emissiveAtlas,
|
|
6026
|
+
"plasius.wavefront.materialAtlas.emissive"
|
|
6027
|
+
);
|
|
6028
|
+
const materialAtlasSampler = device.createSampler({
|
|
6029
|
+
label: "plasius.wavefront.materialAtlasSampler",
|
|
6030
|
+
addressModeU: "clamp-to-edge",
|
|
6031
|
+
addressModeV: "clamp-to-edge",
|
|
6032
|
+
magFilter: "linear",
|
|
6033
|
+
minFilter: "linear"
|
|
6034
|
+
});
|
|
3817
6035
|
const traceBindGroupLayout = device.createBindGroupLayout({
|
|
3818
6036
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
3819
6037
|
entries: [
|
|
@@ -3843,7 +6061,17 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3843
6061
|
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
3844
6062
|
{ binding: 20, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
3845
6063
|
{ binding: 21, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
3846
|
-
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } }
|
|
6064
|
+
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } },
|
|
6065
|
+
{ binding: 23, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6066
|
+
{ binding: 24, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6067
|
+
{ binding: 25, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6068
|
+
{ binding: 26, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6069
|
+
{ binding: 27, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6070
|
+
{ binding: 28, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
6071
|
+
{ binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6072
|
+
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
6073
|
+
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6074
|
+
{ binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
|
|
3847
6075
|
]
|
|
3848
6076
|
});
|
|
3849
6077
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -3922,6 +6150,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3922
6150
|
label: "plasius.wavefront.computeShader",
|
|
3923
6151
|
code: WAVEFRONT_COMPUTE_WGSL
|
|
3924
6152
|
});
|
|
6153
|
+
await assertShaderModuleCompiles(computeShader, "plasius.wavefront.computeShader");
|
|
3925
6154
|
const pipelines = {
|
|
3926
6155
|
prepareMeshTrianglesAndLeaves: await createComputePipeline(
|
|
3927
6156
|
device,
|
|
@@ -4020,14 +6249,27 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4020
6249
|
{ binding: 19, resource: { buffer: environmentPortalBuffer } },
|
|
4021
6250
|
{ binding: 20, resource: environmentMapResource.view },
|
|
4022
6251
|
{ binding: 21, resource: environmentMapResource.sampler },
|
|
4023
|
-
{ binding: 22, resource: { buffer: pathVertexBuffer } }
|
|
6252
|
+
{ binding: 22, resource: { buffer: pathVertexBuffer } },
|
|
6253
|
+
{ binding: 23, resource: baseColorAtlasResource.view },
|
|
6254
|
+
{ binding: 24, resource: metallicRoughnessAtlasResource.view },
|
|
6255
|
+
{ binding: 25, resource: normalAtlasResource.view },
|
|
6256
|
+
{ binding: 26, resource: occlusionAtlasResource.view },
|
|
6257
|
+
{ binding: 27, resource: emissiveAtlasResource.view },
|
|
6258
|
+
{ binding: 28, resource: materialAtlasSampler },
|
|
6259
|
+
{ binding: 29, resource: brdfLutResource.view },
|
|
6260
|
+
{ binding: 30, resource: brdfLutResource.sampler },
|
|
6261
|
+
{ binding: 31, resource: environmentSamplingResource.view },
|
|
6262
|
+
{ binding: 32, resource: mediumTextureResource.view }
|
|
4024
6263
|
]
|
|
4025
6264
|
});
|
|
4026
6265
|
}
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
6266
|
+
function createTraceBindGroups() {
|
|
6267
|
+
return [
|
|
6268
|
+
createTraceBindGroup(activeQueue, nextQueue, "plasius.wavefront.bind.activeNext"),
|
|
6269
|
+
createTraceBindGroup(nextQueue, activeQueue, "plasius.wavefront.bind.nextActive")
|
|
6270
|
+
];
|
|
6271
|
+
}
|
|
6272
|
+
let bindGroups = createTraceBindGroups();
|
|
4031
6273
|
const bvhBuildBindGroup = device.createBindGroup({
|
|
4032
6274
|
label: "plasius.wavefront.bind.bvhBuild",
|
|
4033
6275
|
layout: accelerationBindGroupLayout,
|
|
@@ -4073,6 +6315,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4073
6315
|
outputView,
|
|
4074
6316
|
"plasius.wavefront.bind.denoise.scratchToOutput"
|
|
4075
6317
|
);
|
|
6318
|
+
const denoiseDirectResolveBindGroup = createDenoiseResolveBindGroup(
|
|
6319
|
+
radianceView,
|
|
6320
|
+
outputView,
|
|
6321
|
+
"plasius.wavefront.bind.denoise.radianceToOutput"
|
|
6322
|
+
);
|
|
4076
6323
|
const presentBindGroupLayout = device.createBindGroupLayout({
|
|
4077
6324
|
label: "plasius.wavefront.presentBindGroupLayout",
|
|
4078
6325
|
entries: [
|
|
@@ -4110,23 +6357,129 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4110
6357
|
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
4111
6358
|
let accelerationBuildCount = 0;
|
|
4112
6359
|
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA;
|
|
6360
|
+
let lastCompletedFrameTimeMs = null;
|
|
6361
|
+
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
4113
6362
|
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
4114
6363
|
gpuAdapterParallelism,
|
|
4115
6364
|
createGpuParallelismCounters()
|
|
4116
6365
|
);
|
|
6366
|
+
function resolveRenderedSamplesPerPixel(renderOptions = {}, awaitGPUCompletion = true) {
|
|
6367
|
+
const targetSamplesPerPixel = clamp(
|
|
6368
|
+
readPositiveInteger(
|
|
6369
|
+
"samplesPerPixel",
|
|
6370
|
+
renderOptions.samplesPerPixel,
|
|
6371
|
+
config.samplesPerPixel
|
|
6372
|
+
),
|
|
6373
|
+
1,
|
|
6374
|
+
config.samplesPerPixel
|
|
6375
|
+
);
|
|
6376
|
+
const frameTimeBudgetMs = Number.isFinite(renderOptions.frameTimeBudgetMs) ? Math.max(0, Number(renderOptions.frameTimeBudgetMs)) : null;
|
|
6377
|
+
const minimumSamplesPerPixel = clamp(
|
|
6378
|
+
readPositiveInteger(
|
|
6379
|
+
"minimumSamplesPerPixel",
|
|
6380
|
+
renderOptions.minimumSamplesPerPixel,
|
|
6381
|
+
frameTimeBudgetMs !== null && targetSamplesPerPixel > 1 ? 1 : targetSamplesPerPixel
|
|
6382
|
+
),
|
|
6383
|
+
1,
|
|
6384
|
+
targetSamplesPerPixel
|
|
6385
|
+
);
|
|
6386
|
+
if (frameTimeBudgetMs === null || !awaitGPUCompletion || targetSamplesPerPixel <= minimumSamplesPerPixel) {
|
|
6387
|
+
return Object.freeze({
|
|
6388
|
+
renderedSamplesPerPixel: targetSamplesPerPixel,
|
|
6389
|
+
targetSamplesPerPixel,
|
|
6390
|
+
minimumSamplesPerPixel,
|
|
6391
|
+
frameTimeBudgetMs,
|
|
6392
|
+
budgetConstrained: false
|
|
6393
|
+
});
|
|
6394
|
+
}
|
|
6395
|
+
const estimatedSampleTimeMs = Number.isFinite(lastCompletedFrameTimeMs) && lastCompletedFrameTimeMs > 0 ? lastCompletedFrameTimeMs / Math.max(1, lastCompletedSamplesPerPixel) : null;
|
|
6396
|
+
if (!Number.isFinite(estimatedSampleTimeMs) || estimatedSampleTimeMs <= 0) {
|
|
6397
|
+
return Object.freeze({
|
|
6398
|
+
renderedSamplesPerPixel: minimumSamplesPerPixel,
|
|
6399
|
+
targetSamplesPerPixel,
|
|
6400
|
+
minimumSamplesPerPixel,
|
|
6401
|
+
frameTimeBudgetMs,
|
|
6402
|
+
budgetConstrained: minimumSamplesPerPixel < targetSamplesPerPixel
|
|
6403
|
+
});
|
|
6404
|
+
}
|
|
6405
|
+
const budgetLimitedSamples = clamp(
|
|
6406
|
+
Math.floor(frameTimeBudgetMs / estimatedSampleTimeMs),
|
|
6407
|
+
minimumSamplesPerPixel,
|
|
6408
|
+
targetSamplesPerPixel
|
|
6409
|
+
);
|
|
6410
|
+
return Object.freeze({
|
|
6411
|
+
renderedSamplesPerPixel: budgetLimitedSamples,
|
|
6412
|
+
targetSamplesPerPixel,
|
|
6413
|
+
minimumSamplesPerPixel,
|
|
6414
|
+
frameTimeBudgetMs,
|
|
6415
|
+
budgetConstrained: budgetLimitedSamples < targetSamplesPerPixel
|
|
6416
|
+
});
|
|
6417
|
+
}
|
|
6418
|
+
function createFrameStats({
|
|
6419
|
+
frameIndex,
|
|
6420
|
+
accelerationBuildSubmitted,
|
|
6421
|
+
frameSubmissionCount,
|
|
6422
|
+
parallelismCounters,
|
|
6423
|
+
renderedSamplesPerPixel,
|
|
6424
|
+
targetSamplesPerPixel,
|
|
6425
|
+
frameTimeBudgetMs,
|
|
6426
|
+
budgetConstrained
|
|
6427
|
+
}) {
|
|
6428
|
+
lastGpuParallelism = createGpuParallelismDiagnostics(gpuAdapterParallelism, parallelismCounters);
|
|
6429
|
+
const commandSubmissions = frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0);
|
|
6430
|
+
return Object.freeze({
|
|
6431
|
+
frame,
|
|
6432
|
+
frameIndex,
|
|
6433
|
+
width: config.width,
|
|
6434
|
+
height: config.height,
|
|
6435
|
+
maxDepth: config.maxDepth,
|
|
6436
|
+
tiles: tiles.length,
|
|
6437
|
+
tileSize: config.tileSize,
|
|
6438
|
+
samplesPerPixel: targetSamplesPerPixel,
|
|
6439
|
+
renderedSamplesPerPixel,
|
|
6440
|
+
frameTimeBudgetMs,
|
|
6441
|
+
budgetConstrained,
|
|
6442
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6443
|
+
screenRays: config.width * config.height,
|
|
6444
|
+
primaryRays: config.width * config.height * renderedSamplesPerPixel,
|
|
6445
|
+
sceneObjectCount: config.sceneObjectCount,
|
|
6446
|
+
triangleCount: config.triangleCount,
|
|
6447
|
+
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6448
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
6449
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
6450
|
+
mediumCount: config.mediumCount,
|
|
6451
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6452
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
6453
|
+
bvhNodeCount: config.bvhNodeCount,
|
|
6454
|
+
displayQuality: config.displayQuality,
|
|
6455
|
+
accelerationBuildMode: config.accelerationBuildMode,
|
|
6456
|
+
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
6457
|
+
accelerationBuildSubmitted,
|
|
6458
|
+
accelerationBuilt,
|
|
6459
|
+
accelerationBuildCount,
|
|
6460
|
+
commandSubmissions,
|
|
6461
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
6462
|
+
gpuParallelism: lastGpuParallelism,
|
|
6463
|
+
memory: config.memory
|
|
6464
|
+
});
|
|
6465
|
+
}
|
|
6466
|
+
function writeFrameConfigSlot(slot, tile, frameIndex, buildRange = {}) {
|
|
6467
|
+
if (slot >= frameConfigSlotCount) {
|
|
6468
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
6469
|
+
}
|
|
6470
|
+
const offset = slot * configBufferStride;
|
|
6471
|
+
device.queue.writeBuffer(
|
|
6472
|
+
configBuffer,
|
|
6473
|
+
offset,
|
|
6474
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
6475
|
+
);
|
|
6476
|
+
return offset;
|
|
6477
|
+
}
|
|
4117
6478
|
function createFrameConfigWriter(frameIndex) {
|
|
4118
6479
|
let slot = 0;
|
|
4119
6480
|
return (tile, buildRange = {}) => {
|
|
4120
|
-
|
|
4121
|
-
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
4122
|
-
}
|
|
4123
|
-
const offset = slot * configBufferStride;
|
|
6481
|
+
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
4124
6482
|
slot += 1;
|
|
4125
|
-
device.queue.writeBuffer(
|
|
4126
|
-
configBuffer,
|
|
4127
|
-
offset,
|
|
4128
|
-
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
4129
|
-
);
|
|
4130
6483
|
return offset;
|
|
4131
6484
|
};
|
|
4132
6485
|
}
|
|
@@ -4171,7 +6524,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4171
6524
|
passEncoder.setPipeline(pipelines.prepareMeshTrianglesAndLeaves);
|
|
4172
6525
|
const prepareWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4173
6526
|
passEncoder.dispatchWorkgroups(prepareWorkgroups);
|
|
4174
|
-
recordDirectDispatch(parallelism, [prepareWorkgroups]);
|
|
6527
|
+
recordDirectDispatch(parallelism, [prepareWorkgroups], WORKGROUP_SIZE);
|
|
4175
6528
|
passEncoder.setPipeline(pipelines.sortBvhLeafRefs);
|
|
4176
6529
|
for (let stageIndex = 0; stageIndex < config.bvhSortStages.length; stageIndex += 1) {
|
|
4177
6530
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [
|
|
@@ -4179,13 +6532,13 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4179
6532
|
]);
|
|
4180
6533
|
const sortWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4181
6534
|
passEncoder.dispatchWorkgroups(sortWorkgroups);
|
|
4182
|
-
recordDirectDispatch(parallelism, [sortWorkgroups]);
|
|
6535
|
+
recordDirectDispatch(parallelism, [sortWorkgroups], WORKGROUP_SIZE);
|
|
4183
6536
|
}
|
|
4184
6537
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [0]);
|
|
4185
6538
|
passEncoder.setPipeline(pipelines.writeSortedBvhLeaves);
|
|
4186
6539
|
const leafWriteWorkgroups = Math.ceil(config.triangleCount / WORKGROUP_SIZE);
|
|
4187
6540
|
passEncoder.dispatchWorkgroups(leafWriteWorkgroups);
|
|
4188
|
-
recordDirectDispatch(parallelism, [leafWriteWorkgroups]);
|
|
6541
|
+
recordDirectDispatch(parallelism, [leafWriteWorkgroups], WORKGROUP_SIZE);
|
|
4189
6542
|
passEncoder.setPipeline(pipelines.buildBvhInternalLevel);
|
|
4190
6543
|
for (let levelIndex = 0; levelIndex < config.bvhBuildLevels.length; levelIndex += 1) {
|
|
4191
6544
|
const buildLevel = config.bvhBuildLevels[levelIndex];
|
|
@@ -4194,7 +6547,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4194
6547
|
]);
|
|
4195
6548
|
const levelWorkgroups = Math.ceil(buildLevel.count / WORKGROUP_SIZE);
|
|
4196
6549
|
passEncoder.dispatchWorkgroups(levelWorkgroups);
|
|
4197
|
-
recordDirectDispatch(parallelism, [levelWorkgroups]);
|
|
6550
|
+
recordDirectDispatch(parallelism, [levelWorkgroups], WORKGROUP_SIZE);
|
|
4198
6551
|
}
|
|
4199
6552
|
passEncoder.end();
|
|
4200
6553
|
device.queue.submit([encoder.finish()]);
|
|
@@ -4210,7 +6563,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4210
6563
|
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4211
6564
|
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
4212
6565
|
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
4213
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6566
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4214
6567
|
generatePass.end();
|
|
4215
6568
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
4216
6569
|
encoder.copyBufferToBuffer(
|
|
@@ -4226,10 +6579,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4226
6579
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
4227
6580
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
4228
6581
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4229
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6582
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4230
6583
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
4231
6584
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4232
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6585
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4233
6586
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
4234
6587
|
passEncoder.dispatchWorkgroups(1);
|
|
4235
6588
|
recordDirectDispatch(parallelism, [1], 1);
|
|
@@ -4244,30 +6597,45 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4244
6597
|
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4245
6598
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
4246
6599
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
4247
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6600
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4248
6601
|
passEncoder.end();
|
|
4249
6602
|
}
|
|
4250
|
-
function encodeDenoise(encoder, configOffset, parallelism) {
|
|
6603
|
+
function encodeDenoise(encoder, configOffset, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4251
6604
|
if (!config.denoise) {
|
|
4252
6605
|
return;
|
|
4253
6606
|
}
|
|
4254
6607
|
const denoiseWorkgroupsX = Math.ceil(config.width / 8);
|
|
4255
6608
|
const denoiseWorkgroupsY = Math.ceil(config.height / 8);
|
|
4256
|
-
const
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
6609
|
+
const useTwoPassDenoise = renderedSamplesPerPixel < 4;
|
|
6610
|
+
if (useTwoPassDenoise) {
|
|
6611
|
+
const radiancePass = encoder.beginComputePass({
|
|
6612
|
+
label: "plasius.wavefront.denoiseRadiancePass"
|
|
6613
|
+
});
|
|
6614
|
+
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
6615
|
+
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
6616
|
+
radiancePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
6617
|
+
recordDirectDispatch(
|
|
6618
|
+
parallelism,
|
|
6619
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6620
|
+
WORKGROUP_SIZE
|
|
6621
|
+
);
|
|
6622
|
+
radiancePass.end();
|
|
6623
|
+
}
|
|
4264
6624
|
const resolvePass = encoder.beginComputePass({
|
|
4265
6625
|
label: "plasius.wavefront.denoiseResolvePass"
|
|
4266
6626
|
});
|
|
4267
|
-
resolvePass.setBindGroup(
|
|
6627
|
+
resolvePass.setBindGroup(
|
|
6628
|
+
0,
|
|
6629
|
+
useTwoPassDenoise ? denoiseResolveBindGroup : denoiseDirectResolveBindGroup,
|
|
6630
|
+
[configOffset]
|
|
6631
|
+
);
|
|
4268
6632
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
4269
6633
|
resolvePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
4270
|
-
recordDirectDispatch(
|
|
6634
|
+
recordDirectDispatch(
|
|
6635
|
+
parallelism,
|
|
6636
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6637
|
+
WORKGROUP_SIZE
|
|
6638
|
+
);
|
|
4271
6639
|
resolvePass.end();
|
|
4272
6640
|
}
|
|
4273
6641
|
function encodePresent(encoder) {
|
|
@@ -4288,98 +6656,213 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4288
6656
|
passEncoder.draw(3);
|
|
4289
6657
|
passEncoder.end();
|
|
4290
6658
|
}
|
|
4291
|
-
function dispatchFrame(frameIndex, parallelism) {
|
|
6659
|
+
function dispatchFrame(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4292
6660
|
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
6661
|
+
const batch = createGpuSubmissionBatcher({
|
|
6662
|
+
device,
|
|
6663
|
+
frameIndex,
|
|
6664
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission
|
|
4297
6665
|
});
|
|
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
6666
|
for (const tile of tiles) {
|
|
4317
|
-
for (let sampleIndex = 0; sampleIndex <
|
|
6667
|
+
for (let sampleIndex = 0; sampleIndex < renderedSamplesPerPixel; sampleIndex += 1) {
|
|
4318
6668
|
const configOffset = writeFrameConfig(tile, {
|
|
4319
6669
|
sampleIndex,
|
|
4320
|
-
sampleWeight: 1 /
|
|
6670
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
4321
6671
|
});
|
|
4322
|
-
encodeTileSample(
|
|
6672
|
+
encodeTileSample(
|
|
6673
|
+
batch.reserve(config.maxDepth + 1),
|
|
6674
|
+
tile,
|
|
6675
|
+
configOffset,
|
|
6676
|
+
parallelism
|
|
6677
|
+
);
|
|
4323
6678
|
if (config.deferredPathResolve) {
|
|
4324
|
-
encodeTileOutput(
|
|
6679
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
4325
6680
|
}
|
|
4326
6681
|
}
|
|
4327
6682
|
if (!config.deferredPathResolve) {
|
|
4328
6683
|
const outputConfigOffset = writeFrameConfig(tile, {
|
|
4329
6684
|
sampleIndex: 0,
|
|
4330
|
-
sampleWeight: 1 /
|
|
6685
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
4331
6686
|
});
|
|
4332
|
-
encodeTileOutput(
|
|
6687
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
4333
6688
|
}
|
|
4334
6689
|
}
|
|
4335
6690
|
if (config.denoise) {
|
|
4336
6691
|
const denoiseConfigOffset = writeFrameConfig(
|
|
4337
6692
|
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
4338
|
-
{ sampleIndex: 0, sampleWeight: 1 /
|
|
6693
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6694
|
+
);
|
|
6695
|
+
const denoisePassCount = renderedSamplesPerPixel < 4 ? 2 : 1;
|
|
6696
|
+
encodeDenoise(
|
|
6697
|
+
batch.reserve(denoisePassCount),
|
|
6698
|
+
denoiseConfigOffset,
|
|
6699
|
+
parallelism,
|
|
6700
|
+
renderedSamplesPerPixel
|
|
4339
6701
|
);
|
|
4340
|
-
encodeDenoise(reserveEncoder(), denoiseConfigOffset, parallelism);
|
|
4341
6702
|
}
|
|
4342
|
-
encodePresent(
|
|
4343
|
-
|
|
4344
|
-
return submissionCount;
|
|
6703
|
+
encodePresent(batch.reserve(1));
|
|
6704
|
+
return batch.flush();
|
|
4345
6705
|
}
|
|
4346
|
-
function renderOnce() {
|
|
6706
|
+
function renderOnce(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
6707
|
+
const frameStartTimeMs = nowMs();
|
|
4347
6708
|
frame += 1;
|
|
4348
6709
|
const frameIndex = frame + config.frameIndex;
|
|
6710
|
+
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
4349
6711
|
const parallelismCounters = createGpuParallelismCounters();
|
|
4350
6712
|
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
4351
|
-
const frameSubmissionCount = dispatchFrame(
|
|
4352
|
-
|
|
6713
|
+
const frameSubmissionCount = dispatchFrame(
|
|
6714
|
+
frameIndex,
|
|
6715
|
+
parallelismCounters,
|
|
6716
|
+
samplingPlan.renderedSamplesPerPixel
|
|
6717
|
+
);
|
|
6718
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
4353
6719
|
return Object.freeze({
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
6720
|
+
...createFrameStats({
|
|
6721
|
+
frameIndex,
|
|
6722
|
+
accelerationBuildSubmitted,
|
|
6723
|
+
frameSubmissionCount,
|
|
6724
|
+
parallelismCounters,
|
|
6725
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
6726
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
6727
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
6728
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
6729
|
+
}),
|
|
6730
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
6731
|
+
lastGpuParallelism,
|
|
6732
|
+
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
6733
|
+
frameTimeMs,
|
|
6734
|
+
false
|
|
6735
|
+
)
|
|
6736
|
+
});
|
|
6737
|
+
}
|
|
6738
|
+
async function waitForSubmittedGpuWork(options2 = {}) {
|
|
6739
|
+
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
6740
|
+
return true;
|
|
6741
|
+
}
|
|
6742
|
+
const timeoutMs = Math.max(
|
|
6743
|
+
1,
|
|
6744
|
+
Number.isFinite(options2.timeoutMs) ? Number(options2.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
6745
|
+
);
|
|
6746
|
+
const allowTimeout = options2.allowTimeout !== false;
|
|
6747
|
+
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
6748
|
+
() => ({ status: "done" }),
|
|
6749
|
+
(error) => {
|
|
6750
|
+
throw error;
|
|
6751
|
+
}
|
|
6752
|
+
);
|
|
6753
|
+
const lossPromise = typeof device.lost?.then === "function" ? device.lost.then((info) => {
|
|
6754
|
+
throw new Error(
|
|
6755
|
+
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
6756
|
+
);
|
|
6757
|
+
}) : null;
|
|
6758
|
+
let timeoutHandle = null;
|
|
6759
|
+
let resolveTimeoutPromise = null;
|
|
6760
|
+
let timeoutSettled = false;
|
|
6761
|
+
const settleTimeoutPromise = (value) => {
|
|
6762
|
+
if (timeoutSettled) {
|
|
6763
|
+
return;
|
|
6764
|
+
}
|
|
6765
|
+
timeoutSettled = true;
|
|
6766
|
+
resolveTimeoutPromise?.(value);
|
|
6767
|
+
};
|
|
6768
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
6769
|
+
resolveTimeoutPromise = resolve;
|
|
6770
|
+
timeoutHandle = setTimeout(() => settleTimeoutPromise({ status: "timeout" }), timeoutMs);
|
|
6771
|
+
});
|
|
6772
|
+
let result;
|
|
6773
|
+
try {
|
|
6774
|
+
result = await Promise.race(
|
|
6775
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
6776
|
+
);
|
|
6777
|
+
} finally {
|
|
6778
|
+
if (timeoutHandle !== null) {
|
|
6779
|
+
clearTimeout(timeoutHandle);
|
|
6780
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
6781
|
+
}
|
|
6782
|
+
}
|
|
6783
|
+
if (result?.status === "timeout") {
|
|
6784
|
+
if (!allowTimeout) {
|
|
6785
|
+
throw new Error(`Timed out after ${timeoutMs} ms waiting for submitted GPU work.`);
|
|
6786
|
+
}
|
|
6787
|
+
console.warn(
|
|
6788
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
|
|
6789
|
+
);
|
|
6790
|
+
return false;
|
|
6791
|
+
}
|
|
6792
|
+
return true;
|
|
6793
|
+
}
|
|
6794
|
+
function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
6795
|
+
const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
|
|
6796
|
+
const denoisePassCount = config.denoise ? renderedSamplesPerPixel < 4 ? 2 : 1 : 0;
|
|
6797
|
+
const tailPassCount = denoisePassCount + 1;
|
|
6798
|
+
const sampleBatchSize = Math.max(
|
|
6799
|
+
1,
|
|
6800
|
+
Math.floor(
|
|
6801
|
+
Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) / Math.max(samplePassesPerSample, 1)
|
|
6802
|
+
)
|
|
6803
|
+
);
|
|
6804
|
+
let submissionCount = 0;
|
|
6805
|
+
for (const tile of tiles) {
|
|
6806
|
+
for (let sampleStart = 0; sampleStart < renderedSamplesPerPixel; sampleStart += sampleBatchSize) {
|
|
6807
|
+
const sampleEnd = Math.min(renderedSamplesPerPixel, sampleStart + sampleBatchSize);
|
|
6808
|
+
const batch = createGpuSubmissionBatcher({
|
|
6809
|
+
device,
|
|
6810
|
+
frameIndex,
|
|
6811
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6812
|
+
startingSubmissionCount: submissionCount
|
|
6813
|
+
});
|
|
6814
|
+
let slot = 0;
|
|
6815
|
+
for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
|
|
6816
|
+
const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6817
|
+
sampleIndex,
|
|
6818
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
6819
|
+
});
|
|
6820
|
+
slot += 1;
|
|
6821
|
+
encodeTileSample(
|
|
6822
|
+
batch.reserve(config.maxDepth + 1),
|
|
6823
|
+
tile,
|
|
6824
|
+
configOffset,
|
|
6825
|
+
parallelism
|
|
6826
|
+
);
|
|
6827
|
+
if (config.deferredPathResolve) {
|
|
6828
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
6829
|
+
}
|
|
6830
|
+
}
|
|
6831
|
+
if (!config.deferredPathResolve && sampleEnd >= renderedSamplesPerPixel) {
|
|
6832
|
+
const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6833
|
+
sampleIndex: 0,
|
|
6834
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
6835
|
+
});
|
|
6836
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
6837
|
+
}
|
|
6838
|
+
batch.flush();
|
|
6839
|
+
submissionCount += batch.getSubmissionCount();
|
|
6840
|
+
}
|
|
6841
|
+
}
|
|
6842
|
+
const tail = createGpuSubmissionBatcher({
|
|
6843
|
+
device,
|
|
6844
|
+
frameIndex,
|
|
4361
6845
|
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
|
|
6846
|
+
startingSubmissionCount: submissionCount
|
|
4382
6847
|
});
|
|
6848
|
+
if (config.denoise) {
|
|
6849
|
+
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
6850
|
+
0,
|
|
6851
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
6852
|
+
frameIndex,
|
|
6853
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6854
|
+
);
|
|
6855
|
+
encodeDenoise(
|
|
6856
|
+
tail.reserve(denoisePassCount),
|
|
6857
|
+
denoiseConfigOffset,
|
|
6858
|
+
parallelism,
|
|
6859
|
+
renderedSamplesPerPixel
|
|
6860
|
+
);
|
|
6861
|
+
}
|
|
6862
|
+
encodePresent(tail.reserve(1));
|
|
6863
|
+
tail.flush();
|
|
6864
|
+
submissionCount += tail.getSubmissionCount();
|
|
6865
|
+
return submissionCount;
|
|
4383
6866
|
}
|
|
4384
6867
|
async function readOutputProbe(optionsForProbe = {}) {
|
|
4385
6868
|
const mapMode = constants.map;
|
|
@@ -4393,6 +6876,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4393
6876
|
size: 256,
|
|
4394
6877
|
usage: constants.buffer.COPY_DST | constants.buffer.MAP_READ
|
|
4395
6878
|
});
|
|
6879
|
+
await waitForSubmittedGpuWork({
|
|
6880
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
6881
|
+
allowTimeout: false
|
|
6882
|
+
});
|
|
4396
6883
|
const encoder = device.createCommandEncoder({
|
|
4397
6884
|
label: "plasius.wavefront.outputProbe.copy"
|
|
4398
6885
|
});
|
|
@@ -4402,6 +6889,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4402
6889
|
{ width: 1, height: 1, depthOrArrayLayers: 1 }
|
|
4403
6890
|
);
|
|
4404
6891
|
device.queue.submit([encoder.finish()]);
|
|
6892
|
+
await waitForSubmittedGpuWork({
|
|
6893
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
6894
|
+
allowTimeout: false
|
|
6895
|
+
});
|
|
4405
6896
|
await readback.mapAsync(mapMode.READ);
|
|
4406
6897
|
const bytes = new Uint8Array(readback.getMappedRange()).slice(0, 4);
|
|
4407
6898
|
readback.unmap();
|
|
@@ -4414,7 +6905,57 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4414
6905
|
});
|
|
4415
6906
|
}
|
|
4416
6907
|
async function renderFrame(renderOptions = {}) {
|
|
4417
|
-
const
|
|
6908
|
+
const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
|
|
6909
|
+
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
6910
|
+
const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
6911
|
+
const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
|
|
6912
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6913
|
+
tiles.length,
|
|
6914
|
+
renderOptions.submittedWorkTimeoutMs
|
|
6915
|
+
);
|
|
6916
|
+
const frameStartTimeMs = nowMs();
|
|
6917
|
+
const submissionWaitOptions = awaitGPUCompletion ? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false } : { timeoutMs: submittedWorkTimeoutMs };
|
|
6918
|
+
let frameStats;
|
|
6919
|
+
if (useThrottledHighSamplePath) {
|
|
6920
|
+
frame += 1;
|
|
6921
|
+
const frameIndex = frame + config.frameIndex;
|
|
6922
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
6923
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
6924
|
+
const frameSubmissionCount = dispatchFrameAwaitingGpu(
|
|
6925
|
+
frameIndex,
|
|
6926
|
+
parallelismCounters,
|
|
6927
|
+
samplingPlan.renderedSamplesPerPixel
|
|
6928
|
+
);
|
|
6929
|
+
frameStats = createFrameStats({
|
|
6930
|
+
frameIndex,
|
|
6931
|
+
accelerationBuildSubmitted,
|
|
6932
|
+
frameSubmissionCount,
|
|
6933
|
+
parallelismCounters,
|
|
6934
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
6935
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
6936
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
6937
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
6938
|
+
});
|
|
6939
|
+
} else {
|
|
6940
|
+
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
6941
|
+
}
|
|
6942
|
+
if (awaitGPUCompletion) {
|
|
6943
|
+
await waitForSubmittedGpuWork(submissionWaitOptions);
|
|
6944
|
+
}
|
|
6945
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
6946
|
+
if (awaitGPUCompletion) {
|
|
6947
|
+
lastCompletedFrameTimeMs = frameTimeMs;
|
|
6948
|
+
lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
|
|
6949
|
+
}
|
|
6950
|
+
frameStats = Object.freeze({
|
|
6951
|
+
...frameStats,
|
|
6952
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
6953
|
+
frameStats.gpuParallelism,
|
|
6954
|
+
frameStats.commandSubmissions,
|
|
6955
|
+
frameTimeMs,
|
|
6956
|
+
awaitGPUCompletion
|
|
6957
|
+
)
|
|
6958
|
+
});
|
|
4418
6959
|
const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
4419
6960
|
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
4420
6961
|
return Object.freeze({
|
|
@@ -4435,10 +6976,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4435
6976
|
queueOverflow: 0
|
|
4436
6977
|
});
|
|
4437
6978
|
}
|
|
4438
|
-
function
|
|
4439
|
-
|
|
4440
|
-
packedScene = nextPackedScene;
|
|
4441
|
-
config = createWavefrontPathTracingComputeConfig({
|
|
6979
|
+
function rebuildLiveConfig(overrides = {}) {
|
|
6980
|
+
return createWavefrontPathTracingComputeConfig({
|
|
4442
6981
|
...options,
|
|
4443
6982
|
canvas,
|
|
4444
6983
|
width: config.width,
|
|
@@ -4449,26 +6988,35 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4449
6988
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4450
6989
|
sceneObjects: packedScene.objects,
|
|
4451
6990
|
camera: activeCameraOptions,
|
|
4452
|
-
|
|
6991
|
+
environmentMap: {
|
|
6992
|
+
...config.environmentMap
|
|
6993
|
+
},
|
|
6994
|
+
frameIndex: config.frameIndex,
|
|
6995
|
+
...overrides
|
|
4453
6996
|
});
|
|
6997
|
+
}
|
|
6998
|
+
function rebuildMediumResources(nextConfig) {
|
|
6999
|
+
const previousMediumTextureResource = mediumTextureResource;
|
|
7000
|
+
mediumTextureResource = createMediumTextureResource(device, constants, nextConfig.mediums);
|
|
7001
|
+
bindGroups = createTraceBindGroups();
|
|
7002
|
+
if (previousMediumTextureResource?.ownsTexture) {
|
|
7003
|
+
previousMediumTextureResource.texture?.destroy?.();
|
|
7004
|
+
}
|
|
7005
|
+
}
|
|
7006
|
+
function updateSceneObjects(sceneObjects) {
|
|
7007
|
+
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
7008
|
+
packedScene = nextPackedScene;
|
|
7009
|
+
const nextConfig = rebuildLiveConfig();
|
|
7010
|
+
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
7011
|
+
rebuildMediumResources(nextConfig);
|
|
7012
|
+
}
|
|
7013
|
+
config = nextConfig;
|
|
4454
7014
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
4455
7015
|
return config;
|
|
4456
7016
|
}
|
|
4457
7017
|
function updateCamera(cameraOptions = {}) {
|
|
4458
7018
|
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
|
-
});
|
|
7019
|
+
config = rebuildLiveConfig();
|
|
4472
7020
|
return config;
|
|
4473
7021
|
}
|
|
4474
7022
|
function getSnapshot() {
|
|
@@ -4486,6 +7034,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4486
7034
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
4487
7035
|
environmentPortalCount: config.environmentPortalCount,
|
|
4488
7036
|
environmentPortalMode: config.environmentPortalMode,
|
|
7037
|
+
mediumCount: config.mediumCount,
|
|
4489
7038
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4490
7039
|
deferredPathResolve: config.deferredPathResolve,
|
|
4491
7040
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -4523,6 +7072,28 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4523
7072
|
if (environmentMapResource.ownsTexture) {
|
|
4524
7073
|
environmentMapResource.texture?.destroy?.();
|
|
4525
7074
|
}
|
|
7075
|
+
if (environmentSamplingResource.ownsTexture) {
|
|
7076
|
+
environmentSamplingResource.texture?.destroy?.();
|
|
7077
|
+
}
|
|
7078
|
+
if (mediumTextureResource.ownsTexture) {
|
|
7079
|
+
mediumTextureResource.texture?.destroy?.();
|
|
7080
|
+
}
|
|
7081
|
+
brdfLutResource.texture?.destroy?.();
|
|
7082
|
+
if (baseColorAtlasResource.ownsTexture) {
|
|
7083
|
+
baseColorAtlasResource.texture?.destroy?.();
|
|
7084
|
+
}
|
|
7085
|
+
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
7086
|
+
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
7087
|
+
}
|
|
7088
|
+
if (normalAtlasResource.ownsTexture) {
|
|
7089
|
+
normalAtlasResource.texture?.destroy?.();
|
|
7090
|
+
}
|
|
7091
|
+
if (occlusionAtlasResource.ownsTexture) {
|
|
7092
|
+
occlusionAtlasResource.texture?.destroy?.();
|
|
7093
|
+
}
|
|
7094
|
+
if (emissiveAtlasResource.ownsTexture) {
|
|
7095
|
+
emissiveAtlasResource.texture?.destroy?.();
|
|
7096
|
+
}
|
|
4526
7097
|
context.unconfigure?.();
|
|
4527
7098
|
}
|
|
4528
7099
|
return Object.freeze({
|
|
@@ -4675,6 +7246,48 @@ var rendererAccelerationStructurePolicies = Object.freeze(
|
|
|
4675
7246
|
})
|
|
4676
7247
|
)
|
|
4677
7248
|
);
|
|
7249
|
+
function clampWavefrontAdaptiveSamplesPerPixel(value) {
|
|
7250
|
+
if (!Number.isFinite(value)) {
|
|
7251
|
+
return 1;
|
|
7252
|
+
}
|
|
7253
|
+
return Math.max(1, Math.min(256, Math.round(value)));
|
|
7254
|
+
}
|
|
7255
|
+
function createWavefrontAdaptiveSamplingLevels(options = {}) {
|
|
7256
|
+
const requestedSamplesPerPixel = clampWavefrontAdaptiveSamplesPerPixel(
|
|
7257
|
+
options.samplesPerPixel ?? 1
|
|
7258
|
+
);
|
|
7259
|
+
const minimumSamplesPerPixel = Math.min(
|
|
7260
|
+
requestedSamplesPerPixel,
|
|
7261
|
+
clampWavefrontAdaptiveSamplesPerPixel(options.minimumSamplesPerPixel ?? 1)
|
|
7262
|
+
);
|
|
7263
|
+
const frameTimeBudgetMs = Number.isFinite(options.frameTimeBudgetMs) ? Math.max(0, Number(options.frameTimeBudgetMs)) : 0;
|
|
7264
|
+
const levels = /* @__PURE__ */ new Set([minimumSamplesPerPixel, requestedSamplesPerPixel]);
|
|
7265
|
+
let currentSamplesPerPixel = minimumSamplesPerPixel;
|
|
7266
|
+
while (currentSamplesPerPixel < requestedSamplesPerPixel) {
|
|
7267
|
+
levels.add(currentSamplesPerPixel);
|
|
7268
|
+
currentSamplesPerPixel *= 2;
|
|
7269
|
+
}
|
|
7270
|
+
levels.add(Math.min(currentSamplesPerPixel, requestedSamplesPerPixel));
|
|
7271
|
+
return Object.freeze({
|
|
7272
|
+
requestedSamplesPerPixel,
|
|
7273
|
+
minimumSamplesPerPixel,
|
|
7274
|
+
frameTimeBudgetMs,
|
|
7275
|
+
levels: Object.freeze(
|
|
7276
|
+
[...levels].sort((left, right) => left - right).map(
|
|
7277
|
+
(samplesPerPixel) => Object.freeze({
|
|
7278
|
+
id: `${samplesPerPixel}spp`,
|
|
7279
|
+
label: `${samplesPerPixel} spp`,
|
|
7280
|
+
estimatedCostMs: samplesPerPixel,
|
|
7281
|
+
config: Object.freeze({
|
|
7282
|
+
samplesPerPixel,
|
|
7283
|
+
frameTimeBudgetMs,
|
|
7284
|
+
minimumSamplesPerPixel
|
|
7285
|
+
})
|
|
7286
|
+
})
|
|
7287
|
+
)
|
|
7288
|
+
)
|
|
7289
|
+
});
|
|
7290
|
+
}
|
|
4678
7291
|
function createWavefrontField(name, type, description) {
|
|
4679
7292
|
return Object.freeze({
|
|
4680
7293
|
name,
|
|
@@ -5851,9 +8464,11 @@ export {
|
|
|
5851
8464
|
createGpuRenderer,
|
|
5852
8465
|
createRayTracingRenderPlan,
|
|
5853
8466
|
createRendererDebugHooks,
|
|
8467
|
+
createWavefrontAdaptiveSamplingLevels,
|
|
5854
8468
|
createWavefrontBvhBuildLevels,
|
|
5855
8469
|
createWavefrontBvhSortStages,
|
|
5856
8470
|
createWavefrontEmissiveTriangleIndexSource,
|
|
8471
|
+
createWavefrontGpuMaterialSource,
|
|
5857
8472
|
createWavefrontGpuMeshSource,
|
|
5858
8473
|
createWavefrontMeshAcceleration,
|
|
5859
8474
|
createWavefrontPathTracingComputeConfig,
|