@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.cjs
CHANGED
|
@@ -24,9 +24,11 @@ __export(index_exports, {
|
|
|
24
24
|
createGpuRenderer: () => createGpuRenderer,
|
|
25
25
|
createRayTracingRenderPlan: () => createRayTracingRenderPlan,
|
|
26
26
|
createRendererDebugHooks: () => createRendererDebugHooks,
|
|
27
|
+
createWavefrontAdaptiveSamplingLevels: () => createWavefrontAdaptiveSamplingLevels,
|
|
27
28
|
createWavefrontBvhBuildLevels: () => createWavefrontBvhBuildLevels,
|
|
28
29
|
createWavefrontBvhSortStages: () => createWavefrontBvhSortStages,
|
|
29
30
|
createWavefrontEmissiveTriangleIndexSource: () => createWavefrontEmissiveTriangleIndexSource,
|
|
31
|
+
createWavefrontGpuMaterialSource: () => createWavefrontGpuMaterialSource,
|
|
30
32
|
createWavefrontGpuMeshSource: () => createWavefrontGpuMeshSource,
|
|
31
33
|
createWavefrontMeshAcceleration: () => createWavefrontMeshAcceleration,
|
|
32
34
|
createWavefrontPathTracingComputeConfig: () => createWavefrontPathTracingComputeConfig,
|
|
@@ -70,36 +72,179 @@ __export(index_exports, {
|
|
|
70
72
|
});
|
|
71
73
|
module.exports = __toCommonJS(index_exports);
|
|
72
74
|
|
|
75
|
+
// src/wavefront-frame-runtime.js
|
|
76
|
+
function createGpuParallelismCounters() {
|
|
77
|
+
return {
|
|
78
|
+
directDispatches: 0,
|
|
79
|
+
directWorkgroups: 0,
|
|
80
|
+
directShaderInvocations: 0,
|
|
81
|
+
multiWorkgroupDispatches: 0,
|
|
82
|
+
largestDirectWorkgroupsPerDispatch: 0,
|
|
83
|
+
indirectDispatches: 0,
|
|
84
|
+
estimatedIndirectWorkgroupsUpperBound: 0,
|
|
85
|
+
estimatedIndirectShaderInvocationsUpperBound: 0,
|
|
86
|
+
indirectDispatchesWithMultiWorkgroupCapacity: 0,
|
|
87
|
+
largestEstimatedIndirectWorkgroupsPerDispatch: 0
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function countDispatchWorkgroups(groups) {
|
|
91
|
+
return groups.reduce((product, value) => {
|
|
92
|
+
const numeric = Number(value ?? 1);
|
|
93
|
+
const count = Number.isFinite(numeric) ? Math.max(1, Math.trunc(numeric)) : 1;
|
|
94
|
+
return product * count;
|
|
95
|
+
}, 1);
|
|
96
|
+
}
|
|
97
|
+
function recordDirectDispatch(parallelism, groups, invocationsPerWorkgroup = 1) {
|
|
98
|
+
const workgroups = countDispatchWorkgroups(groups);
|
|
99
|
+
parallelism.directDispatches += 1;
|
|
100
|
+
parallelism.directWorkgroups += workgroups;
|
|
101
|
+
parallelism.directShaderInvocations += workgroups * invocationsPerWorkgroup;
|
|
102
|
+
parallelism.largestDirectWorkgroupsPerDispatch = Math.max(
|
|
103
|
+
parallelism.largestDirectWorkgroupsPerDispatch,
|
|
104
|
+
workgroups
|
|
105
|
+
);
|
|
106
|
+
if (workgroups > 1) {
|
|
107
|
+
parallelism.multiWorkgroupDispatches += 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function recordIndirectDispatch(parallelism, estimatedWorkgroupsUpperBound, invocationsPerWorkgroup = 1) {
|
|
111
|
+
const workgroups = Math.max(1, Math.trunc(Number(estimatedWorkgroupsUpperBound) || 1));
|
|
112
|
+
parallelism.indirectDispatches += 1;
|
|
113
|
+
parallelism.estimatedIndirectWorkgroupsUpperBound += workgroups;
|
|
114
|
+
parallelism.estimatedIndirectShaderInvocationsUpperBound += workgroups * invocationsPerWorkgroup;
|
|
115
|
+
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch = Math.max(
|
|
116
|
+
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
117
|
+
workgroups
|
|
118
|
+
);
|
|
119
|
+
if (workgroups > 1) {
|
|
120
|
+
parallelism.indirectDispatchesWithMultiWorkgroupCapacity += 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function createGpuParallelismDiagnostics(adapterDiagnostics, counters) {
|
|
124
|
+
const totalEstimatedWorkgroupsUpperBound = counters.directWorkgroups + counters.estimatedIndirectWorkgroupsUpperBound;
|
|
125
|
+
const totalEstimatedShaderInvocationsUpperBound = counters.directShaderInvocations + counters.estimatedIndirectShaderInvocationsUpperBound;
|
|
126
|
+
const exposesMultiWorkgroupParallelism = counters.multiWorkgroupDispatches > 0 || counters.indirectDispatchesWithMultiWorkgroupCapacity > 0;
|
|
127
|
+
return Object.freeze({
|
|
128
|
+
...adapterDiagnostics,
|
|
129
|
+
directDispatches: counters.directDispatches,
|
|
130
|
+
directWorkgroups: counters.directWorkgroups,
|
|
131
|
+
directShaderInvocations: counters.directShaderInvocations,
|
|
132
|
+
multiWorkgroupDispatches: counters.multiWorkgroupDispatches,
|
|
133
|
+
largestDirectWorkgroupsPerDispatch: counters.largestDirectWorkgroupsPerDispatch,
|
|
134
|
+
indirectDispatches: counters.indirectDispatches,
|
|
135
|
+
estimatedIndirectWorkgroupsUpperBound: counters.estimatedIndirectWorkgroupsUpperBound,
|
|
136
|
+
estimatedIndirectShaderInvocationsUpperBound: counters.estimatedIndirectShaderInvocationsUpperBound,
|
|
137
|
+
indirectDispatchesWithMultiWorkgroupCapacity: counters.indirectDispatchesWithMultiWorkgroupCapacity,
|
|
138
|
+
largestEstimatedIndirectWorkgroupsPerDispatch: counters.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
139
|
+
totalEstimatedWorkgroupsUpperBound,
|
|
140
|
+
totalEstimatedShaderInvocationsUpperBound,
|
|
141
|
+
exposesMultiWorkgroupParallelism,
|
|
142
|
+
likelyUsesMoreThanOnePhysicalGpuCore: null,
|
|
143
|
+
coreUtilizationStatus: "not-exposed-by-webgpu"
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function createGpuWorkerJobDiagnostics(parallelism, commandSubmissions, frameTimeMs, awaitedGpuCompletion) {
|
|
147
|
+
const directDispatchesCompleted = Math.max(0, Number(parallelism?.directDispatches ?? 0));
|
|
148
|
+
const indirectDispatchesCompleted = Math.max(
|
|
149
|
+
0,
|
|
150
|
+
Number(parallelism?.indirectDispatches ?? 0)
|
|
151
|
+
);
|
|
152
|
+
const completedPerFrame = directDispatchesCompleted + indirectDispatchesCompleted;
|
|
153
|
+
const completedPerSubmission = commandSubmissions > 0 ? completedPerFrame / commandSubmissions : completedPerFrame;
|
|
154
|
+
const completedPerSecond = awaitedGpuCompletion && frameTimeMs > 0 ? completedPerFrame * 1e3 / frameTimeMs : null;
|
|
155
|
+
return Object.freeze({
|
|
156
|
+
completedPerFrame,
|
|
157
|
+
completedPerSecond,
|
|
158
|
+
completedPerSubmission,
|
|
159
|
+
directDispatchesCompleted,
|
|
160
|
+
indirectDispatchesCompleted,
|
|
161
|
+
frameTimeMs,
|
|
162
|
+
awaitedGpuCompletion
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function createGpuSubmissionBatcher({
|
|
166
|
+
device,
|
|
167
|
+
frameIndex,
|
|
168
|
+
maxFramePassesPerSubmission,
|
|
169
|
+
startingSubmissionCount = 0,
|
|
170
|
+
labelPrefix = "plasius.wavefront.frame"
|
|
171
|
+
}) {
|
|
172
|
+
let encodedFramePasses = 0;
|
|
173
|
+
let submissionCount = 0;
|
|
174
|
+
let encoder = createCommandEncoder();
|
|
175
|
+
function createCommandEncoder() {
|
|
176
|
+
return device.createCommandEncoder({
|
|
177
|
+
label: `${labelPrefix}.${frameIndex}.batched.${startingSubmissionCount + submissionCount + 1}`
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function submitCurrentEncoder() {
|
|
181
|
+
if (encodedFramePasses <= 0) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
device.queue.submit([encoder.finish()]);
|
|
185
|
+
submissionCount += 1;
|
|
186
|
+
encodedFramePasses = 0;
|
|
187
|
+
encoder = createCommandEncoder();
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
return Object.freeze({
|
|
191
|
+
reserve(passCount = 1) {
|
|
192
|
+
if (encodedFramePasses > 0 && encodedFramePasses + passCount > maxFramePassesPerSubmission) {
|
|
193
|
+
submitCurrentEncoder();
|
|
194
|
+
}
|
|
195
|
+
encodedFramePasses += passCount;
|
|
196
|
+
return encoder;
|
|
197
|
+
},
|
|
198
|
+
flush() {
|
|
199
|
+
submitCurrentEncoder();
|
|
200
|
+
return submissionCount;
|
|
201
|
+
},
|
|
202
|
+
getSubmissionCount() {
|
|
203
|
+
return submissionCount;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
73
208
|
// src/wavefront-compute.js
|
|
74
209
|
var DEFAULT_WIDTH = 1280;
|
|
75
210
|
var DEFAULT_HEIGHT = 720;
|
|
76
211
|
var DEFAULT_MAX_DEPTH = 6;
|
|
77
212
|
var DEFAULT_TILE_SIZE = 128;
|
|
78
213
|
var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
214
|
+
var MAX_SAMPLES_PER_PIXEL = 256;
|
|
215
|
+
var DEFAULT_BRDF_LUT_SIZE = 128;
|
|
216
|
+
var DEFAULT_BRDF_LUT_SAMPLE_COUNT = 256;
|
|
79
217
|
var DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
80
218
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
81
219
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
220
|
+
var DEFAULT_MEDIUM_PHASE_MODEL = 0;
|
|
82
221
|
var WORKGROUP_SIZE = 64;
|
|
83
222
|
var rendererWavefrontComputeMode = "webgpu-compute";
|
|
84
223
|
var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
85
224
|
var rendererWavefrontComputeStatsStride = 8;
|
|
86
225
|
var RAY_RECORD_BYTES = 80;
|
|
87
|
-
var HIT_RECORD_BYTES =
|
|
88
|
-
var SCENE_OBJECT_RECORD_BYTES =
|
|
226
|
+
var HIT_RECORD_BYTES = 256;
|
|
227
|
+
var SCENE_OBJECT_RECORD_BYTES = 160;
|
|
89
228
|
var MESH_VERTEX_RECORD_BYTES = 48;
|
|
90
|
-
var MESH_RANGE_RECORD_BYTES =
|
|
91
|
-
var TRIANGLE_RECORD_BYTES =
|
|
229
|
+
var MESH_RANGE_RECORD_BYTES = 240;
|
|
230
|
+
var TRIANGLE_RECORD_BYTES = 352;
|
|
231
|
+
var GPU_MATERIAL_RECORD_BYTES = 192;
|
|
92
232
|
var BVH_NODE_RECORD_BYTES = 48;
|
|
93
233
|
var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
94
234
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
95
235
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
236
|
+
var MEDIUM_TABLE_ROWS = 2;
|
|
96
237
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
97
238
|
var PATH_VERTEX_RECORD_BYTES = 16;
|
|
98
|
-
var
|
|
239
|
+
var GPU_SUBMITTED_WORK_TIMEOUT_MS = 5e3;
|
|
240
|
+
var GPU_READBACK_COMPLETION_TIMEOUT_MS = 6e4;
|
|
241
|
+
var GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 6e4;
|
|
242
|
+
var CONFIG_BUFFER_BYTES = 320;
|
|
99
243
|
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
100
244
|
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
101
245
|
var COUNTER_BUFFER_BYTES = 32;
|
|
102
246
|
var TRACE_STORAGE_BUFFER_BINDINGS = 10;
|
|
247
|
+
var BRDF_LUT_UPLOAD_CACHE = /* @__PURE__ */ new Map();
|
|
103
248
|
var MATERIAL_DIFFUSE = 0;
|
|
104
249
|
var MATERIAL_METAL = 1;
|
|
105
250
|
var MATERIAL_DIELECTRIC = 2;
|
|
@@ -134,6 +279,7 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
134
279
|
meshVertexRecordBytes: MESH_VERTEX_RECORD_BYTES,
|
|
135
280
|
meshRangeRecordBytes: MESH_RANGE_RECORD_BYTES,
|
|
136
281
|
triangleRecordBytes: TRIANGLE_RECORD_BYTES,
|
|
282
|
+
materialRecordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
137
283
|
bvhNodeRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
138
284
|
bvhLeafReferenceRecordBytes: BVH_LEAF_REF_RECORD_BYTES,
|
|
139
285
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
@@ -217,6 +363,32 @@ function asColor(value, fallback = [1, 1, 1, 1]) {
|
|
|
217
363
|
clamp(readFiniteNumber("color[3]", value[3], fallback[3] ?? 1), 0, 1)
|
|
218
364
|
];
|
|
219
365
|
}
|
|
366
|
+
function deriveLegacySheenColor(baseColor, sheen, sheenTint) {
|
|
367
|
+
const sheenStrength = clamp(Number(sheen) || 0, 0, 1);
|
|
368
|
+
if (sheenStrength <= 0) {
|
|
369
|
+
return [0, 0, 0, 1];
|
|
370
|
+
}
|
|
371
|
+
const tint = clamp(Number(sheenTint) || 0, 0, 1);
|
|
372
|
+
const base = asColor(baseColor, [1, 1, 1, 1]);
|
|
373
|
+
return [
|
|
374
|
+
clamp((1 - tint) * sheenStrength + base[0] * tint * sheenStrength, 0, 1),
|
|
375
|
+
clamp((1 - tint) * sheenStrength + base[1] * tint * sheenStrength, 0, 1),
|
|
376
|
+
clamp((1 - tint) * sheenStrength + base[2] * tint * sheenStrength, 0, 1),
|
|
377
|
+
1
|
|
378
|
+
];
|
|
379
|
+
}
|
|
380
|
+
function resolveSheenColor(input, fallbackBaseColor) {
|
|
381
|
+
if (input?.sheenColor || input?.material?.sheenColor) {
|
|
382
|
+
return asColor(input.sheenColor ?? input.material?.sheenColor, [0, 0, 0, 1]).map(
|
|
383
|
+
(value, index) => index < 3 ? clamp(value, 0, 1) : 1
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return deriveLegacySheenColor(
|
|
387
|
+
fallbackBaseColor,
|
|
388
|
+
input?.sheen ?? input?.material?.sheen,
|
|
389
|
+
input?.sheenTint ?? input?.material?.sheenTint
|
|
390
|
+
);
|
|
391
|
+
}
|
|
220
392
|
function resolveEnvironmentMap(input = null) {
|
|
221
393
|
const source = input && typeof input === "object" ? input : null;
|
|
222
394
|
const hasTexture = Boolean(source?.view || source?.texture || source?.data);
|
|
@@ -226,6 +398,11 @@ function resolveEnvironmentMap(input = null) {
|
|
|
226
398
|
enabled: hasTexture && source?.enabled !== false,
|
|
227
399
|
width,
|
|
228
400
|
height,
|
|
401
|
+
mipLevelCount: readPositiveInteger(
|
|
402
|
+
"environmentMap.mipLevelCount",
|
|
403
|
+
source?.mipLevelCount,
|
|
404
|
+
1
|
|
405
|
+
),
|
|
229
406
|
format: typeof source?.format === "string" ? source.format : "rgba16float",
|
|
230
407
|
projection: typeof source?.projection === "string" ? source.projection : "equirectangular",
|
|
231
408
|
texture: source?.texture ?? null,
|
|
@@ -237,7 +414,8 @@ function resolveEnvironmentMap(input = null) {
|
|
|
237
414
|
ambientStrength: Math.max(
|
|
238
415
|
0,
|
|
239
416
|
readFiniteNumber("environmentMap.ambientStrength", source?.ambientStrength, 0.32)
|
|
240
|
-
)
|
|
417
|
+
),
|
|
418
|
+
hasImportanceData: source?.hasImportanceData === true
|
|
241
419
|
});
|
|
242
420
|
}
|
|
243
421
|
function resolveDeferredPathResolve(options = {}) {
|
|
@@ -402,6 +580,156 @@ function deriveBounds(input) {
|
|
|
402
580
|
}
|
|
403
581
|
return null;
|
|
404
582
|
}
|
|
583
|
+
function deriveBeerLambertAbsorptionFromAttenuationColor(attenuationColor, attenuationDistance, density = 1) {
|
|
584
|
+
const distance = Number(attenuationDistance);
|
|
585
|
+
const densityScale = Math.max(0, Number(density) || 0);
|
|
586
|
+
if (!Number.isFinite(distance) || distance <= 0 || densityScale <= 0) {
|
|
587
|
+
return [0, 0, 0];
|
|
588
|
+
}
|
|
589
|
+
return attenuationColor.slice(0, 3).map((channel) => {
|
|
590
|
+
const clamped = clamp(Number(channel) || 0, 1e-4, 1);
|
|
591
|
+
return Math.max(0, -Math.log(clamped) / distance * densityScale);
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
function readMediumPhaseModel(value) {
|
|
595
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
596
|
+
return Math.max(0, Math.trunc(value));
|
|
597
|
+
}
|
|
598
|
+
switch (String(value ?? "").trim().toLowerCase()) {
|
|
599
|
+
case "isotropic":
|
|
600
|
+
default:
|
|
601
|
+
return DEFAULT_MEDIUM_PHASE_MODEL;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function resolveWavefrontVolumeInput(input) {
|
|
605
|
+
return input?.volume ?? input?.material?.volume ?? null;
|
|
606
|
+
}
|
|
607
|
+
function normalizeWavefrontThickness(input, label) {
|
|
608
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
609
|
+
return Math.max(
|
|
610
|
+
0,
|
|
611
|
+
readFiniteNumber(
|
|
612
|
+
label,
|
|
613
|
+
input?.thickness ?? volume?.thickness ?? input?.material?.thickness,
|
|
614
|
+
0
|
|
615
|
+
)
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
function resolveWavefrontMediumId(input, fallbackId = 1) {
|
|
619
|
+
return input?.mediumRefId ?? input?.mediumId ?? input?.material?.mediumId ?? input?.materialRefId ?? input?.material?.id ?? input?.materialId ?? input?.id ?? fallbackId;
|
|
620
|
+
}
|
|
621
|
+
function deriveWavefrontTransportMedium(input, fallbackId = 1) {
|
|
622
|
+
const resolvedId = resolveWavefrontMediumId(input, fallbackId);
|
|
623
|
+
if (input?.medium) {
|
|
624
|
+
return normalizeWavefrontMedium(
|
|
625
|
+
{
|
|
626
|
+
...input.medium,
|
|
627
|
+
id: input.medium.id ?? input.medium.mediumId ?? resolvedId
|
|
628
|
+
},
|
|
629
|
+
fallbackId
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
633
|
+
if (!volume) {
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
return normalizeWavefrontMedium(
|
|
637
|
+
{
|
|
638
|
+
id: resolvedId,
|
|
639
|
+
phaseModel: volume.phaseModel,
|
|
640
|
+
density: volume.density,
|
|
641
|
+
attenuationColor: volume.attenuationColor,
|
|
642
|
+
attenuationDistance: volume.attenuationDistance,
|
|
643
|
+
absorption: volume.absorption,
|
|
644
|
+
scattering: volume.scattering
|
|
645
|
+
},
|
|
646
|
+
fallbackId
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
function normalizeWavefrontMedium(input = {}, index = 0) {
|
|
650
|
+
const id = readNonNegativeInteger("medium id", input.id ?? input.mediumId, index);
|
|
651
|
+
const density = Math.max(0, readFiniteNumber("medium density", input.density, 1));
|
|
652
|
+
const attenuationColor = asColor(
|
|
653
|
+
input.attenuationColor ?? input.color ?? input.medium?.attenuationColor,
|
|
654
|
+
[1, 1, 1, 1]
|
|
655
|
+
);
|
|
656
|
+
const attenuationDistance = readFiniteNumber(
|
|
657
|
+
"medium attenuationDistance",
|
|
658
|
+
input.attenuationDistance ?? input.distance ?? input.medium?.attenuationDistance,
|
|
659
|
+
0
|
|
660
|
+
);
|
|
661
|
+
const absorption = Array.isArray(input.absorption) || Array.isArray(input.medium?.absorption) ? asVec3(input.absorption ?? input.medium?.absorption, [0, 0, 0]).map(
|
|
662
|
+
(value) => Math.max(0, Number(value) || 0)
|
|
663
|
+
) : deriveBeerLambertAbsorptionFromAttenuationColor(
|
|
664
|
+
attenuationColor,
|
|
665
|
+
attenuationDistance,
|
|
666
|
+
density
|
|
667
|
+
);
|
|
668
|
+
const scattering = asVec3(
|
|
669
|
+
input.scattering ?? input.medium?.scattering,
|
|
670
|
+
[0, 0, 0]
|
|
671
|
+
).map((value) => Math.max(0, Number(value) || 0));
|
|
672
|
+
return Object.freeze({
|
|
673
|
+
id,
|
|
674
|
+
phaseModel: readMediumPhaseModel(input.phaseModel ?? input.medium?.phaseModel),
|
|
675
|
+
density,
|
|
676
|
+
attenuationColor: Object.freeze(attenuationColor),
|
|
677
|
+
attenuationDistance,
|
|
678
|
+
absorption: Object.freeze(absorption),
|
|
679
|
+
scattering: Object.freeze(scattering)
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
function collectWavefrontMediums(options, meshes, sceneObjects = []) {
|
|
683
|
+
const mediumsById = /* @__PURE__ */ new Map();
|
|
684
|
+
mediumsById.set(
|
|
685
|
+
0,
|
|
686
|
+
Object.freeze({
|
|
687
|
+
id: 0,
|
|
688
|
+
phaseModel: DEFAULT_MEDIUM_PHASE_MODEL,
|
|
689
|
+
density: 0,
|
|
690
|
+
attenuationColor: Object.freeze([1, 1, 1, 1]),
|
|
691
|
+
attenuationDistance: 0,
|
|
692
|
+
absorption: Object.freeze([0, 0, 0]),
|
|
693
|
+
scattering: Object.freeze([0, 0, 0])
|
|
694
|
+
})
|
|
695
|
+
);
|
|
696
|
+
const register = (input, fallbackId = mediumsById.size) => {
|
|
697
|
+
if (!input) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const normalized = normalizeWavefrontMedium(
|
|
701
|
+
typeof input === "object" ? { id: fallbackId, ...input } : { id: fallbackId },
|
|
702
|
+
fallbackId
|
|
703
|
+
);
|
|
704
|
+
const existing = mediumsById.get(normalized.id);
|
|
705
|
+
if (existing && JSON.stringify(existing) !== JSON.stringify(normalized)) {
|
|
706
|
+
throw new Error(`Medium id ${normalized.id} is defined more than once with different values.`);
|
|
707
|
+
}
|
|
708
|
+
mediumsById.set(normalized.id, normalized);
|
|
709
|
+
};
|
|
710
|
+
for (const medium of options.mediums ?? []) {
|
|
711
|
+
register(medium);
|
|
712
|
+
}
|
|
713
|
+
for (const mesh of meshes) {
|
|
714
|
+
register(mesh.medium, mesh.mediumRefId ?? mesh.medium?.id ?? 0);
|
|
715
|
+
}
|
|
716
|
+
for (const mesh of meshes) {
|
|
717
|
+
if ((mesh.mediumRefId ?? 0) > 0 && !mediumsById.has(mesh.mediumRefId)) {
|
|
718
|
+
register({ id: mesh.mediumRefId });
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
for (const object of sceneObjects) {
|
|
722
|
+
register(object.medium, object.mediumRefId ?? object.medium?.id ?? 0);
|
|
723
|
+
}
|
|
724
|
+
for (const object of sceneObjects) {
|
|
725
|
+
if ((object.mediumRefId ?? 0) > 0 && !mediumsById.has(object.mediumRefId)) {
|
|
726
|
+
register({ id: object.mediumRefId });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return Object.freeze(
|
|
730
|
+
Array.from(mediumsById.values()).sort((left, right) => left.id - right.id)
|
|
731
|
+
);
|
|
732
|
+
}
|
|
405
733
|
function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
406
734
|
const bounds = deriveBounds(input);
|
|
407
735
|
const kind = readObjectKind(input.kind ?? input.type ?? (bounds ? "box" : "sphere"));
|
|
@@ -411,7 +739,8 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
411
739
|
input.halfExtent ?? input.halfExtents ?? input.extents ?? bounds?.halfExtent,
|
|
412
740
|
[0.5, 0.5, 0.5]
|
|
413
741
|
).map((value) => Math.max(value, 1e-3));
|
|
414
|
-
const
|
|
742
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
743
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
415
744
|
const color = asColor(
|
|
416
745
|
input.color ?? input.baseColor ?? input.albedo ?? input.material?.color ?? input.material?.baseColor,
|
|
417
746
|
[0.72, 0.72, 0.68, 1]
|
|
@@ -420,19 +749,55 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
420
749
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
421
750
|
[0, 0, 0, 1]
|
|
422
751
|
);
|
|
752
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
753
|
+
const transmission = clamp(
|
|
754
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
755
|
+
0,
|
|
756
|
+
1
|
|
757
|
+
);
|
|
758
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
759
|
+
const specularColor = asColor(
|
|
760
|
+
input.specularColor ?? input.material?.specularColor,
|
|
761
|
+
[1, 1, 1, 1]
|
|
762
|
+
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
763
|
+
const medium = deriveWavefrontTransportMedium(input, index + 1);
|
|
764
|
+
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;
|
|
423
765
|
return Object.freeze({
|
|
424
766
|
id: readNonNegativeInteger("id", input.id, index + 1),
|
|
425
767
|
kind,
|
|
426
|
-
materialKind:
|
|
768
|
+
materialKind: resolvedMaterialKind,
|
|
427
769
|
flags: readNonNegativeInteger("flags", input.flags, 0),
|
|
770
|
+
mediumRefId: readNonNegativeInteger(
|
|
771
|
+
"mediumRefId",
|
|
772
|
+
input.mediumRefId ?? medium?.id ?? input.medium?.id ?? input.mediumId,
|
|
773
|
+
0
|
|
774
|
+
),
|
|
775
|
+
medium,
|
|
428
776
|
center: Object.freeze(center),
|
|
429
777
|
halfExtent: Object.freeze(halfExtent),
|
|
430
778
|
color: Object.freeze(color),
|
|
431
779
|
emission: Object.freeze(emission),
|
|
432
780
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
433
781
|
metallic: clamp(readFiniteNumber("metallic", input.metallic ?? input.material?.metallic, 0), 0, 1),
|
|
434
|
-
opacity
|
|
435
|
-
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3)
|
|
782
|
+
opacity,
|
|
783
|
+
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
784
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
785
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
786
|
+
sheenColor: Object.freeze(sheenColor),
|
|
787
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
788
|
+
clearcoatRoughness: clamp(
|
|
789
|
+
readFiniteNumber(
|
|
790
|
+
"clearcoatRoughness",
|
|
791
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
792
|
+
0.08
|
|
793
|
+
),
|
|
794
|
+
0,
|
|
795
|
+
1
|
|
796
|
+
),
|
|
797
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
798
|
+
specularColor: Object.freeze(specularColor),
|
|
799
|
+
thickness: normalizeWavefrontThickness(input, "thickness"),
|
|
800
|
+
transmission
|
|
436
801
|
});
|
|
437
802
|
}
|
|
438
803
|
function createDefaultWavefrontSceneObjects() {
|
|
@@ -504,7 +869,8 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
504
869
|
input.uvs ?? input.texcoords ?? input.uv,
|
|
505
870
|
(value) => readFiniteNumber("mesh uv", value, 0)
|
|
506
871
|
) : null;
|
|
507
|
-
const
|
|
872
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
873
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
508
874
|
const color = asColor(
|
|
509
875
|
input.color ?? input.baseColor ?? input.albedo ?? input.material?.color ?? input.material?.baseColor,
|
|
510
876
|
[0.72, 0.72, 0.68, 1]
|
|
@@ -513,13 +879,26 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
513
879
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
514
880
|
[0, 0, 0, 1]
|
|
515
881
|
);
|
|
882
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
883
|
+
const transmission = clamp(
|
|
884
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
885
|
+
0,
|
|
886
|
+
1
|
|
887
|
+
);
|
|
888
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
889
|
+
const specularColor = asColor(
|
|
890
|
+
input.specularColor ?? input.material?.specularColor,
|
|
891
|
+
[1, 1, 1, 1]
|
|
892
|
+
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
893
|
+
const medium = deriveWavefrontTransportMedium(input, meshIndex + 1);
|
|
894
|
+
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;
|
|
516
895
|
return Object.freeze({
|
|
517
896
|
id: readNonNegativeInteger("mesh id", input.id, meshIndex + 1),
|
|
518
897
|
positions: Object.freeze(Array.from(positions, (value) => readFiniteNumber("mesh position", value, 0))),
|
|
519
898
|
indices: Object.freeze(indices),
|
|
520
899
|
normals: normals ? Object.freeze(normals) : null,
|
|
521
900
|
uvs: uvs ? Object.freeze(uvs) : null,
|
|
522
|
-
materialKind:
|
|
901
|
+
materialKind: resolvedMaterialKind,
|
|
523
902
|
flags: readNonNegativeInteger("mesh flags", input.flags, 0),
|
|
524
903
|
materialRefId: readNonNegativeInteger(
|
|
525
904
|
"mesh materialRefId",
|
|
@@ -528,17 +907,176 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
528
907
|
),
|
|
529
908
|
mediumRefId: readNonNegativeInteger(
|
|
530
909
|
"mesh mediumRefId",
|
|
531
|
-
input.mediumRefId ?? input.medium?.id ?? input.mediumId,
|
|
910
|
+
input.mediumRefId ?? medium?.id ?? input.medium?.id ?? input.mediumId ?? input.material?.mediumId,
|
|
532
911
|
0
|
|
533
912
|
),
|
|
913
|
+
medium,
|
|
534
914
|
color: Object.freeze(color),
|
|
535
915
|
emission: Object.freeze(emission),
|
|
536
916
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
537
917
|
metallic: clamp(readFiniteNumber("metallic", input.metallic ?? input.material?.metallic, 0), 0, 1),
|
|
538
|
-
opacity
|
|
539
|
-
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3)
|
|
918
|
+
opacity,
|
|
919
|
+
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
920
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
921
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
922
|
+
sheenColor: Object.freeze(sheenColor),
|
|
923
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
924
|
+
clearcoatRoughness: clamp(
|
|
925
|
+
readFiniteNumber(
|
|
926
|
+
"clearcoatRoughness",
|
|
927
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
928
|
+
0.08
|
|
929
|
+
),
|
|
930
|
+
0,
|
|
931
|
+
1
|
|
932
|
+
),
|
|
933
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
934
|
+
specularColor: Object.freeze(specularColor),
|
|
935
|
+
thickness: normalizeWavefrontThickness(input, "mesh thickness"),
|
|
936
|
+
transmission,
|
|
937
|
+
baseColorTexture: input.baseColorTexture ?? input.material?.baseColorTexture ?? null,
|
|
938
|
+
metallicRoughnessTexture: input.metallicRoughnessTexture ?? input.material?.metallicRoughnessTexture ?? null,
|
|
939
|
+
normalTexture: input.normalTexture ?? input.material?.normalTexture ?? null,
|
|
940
|
+
occlusionTexture: input.occlusionTexture ?? input.material?.occlusionTexture ?? null,
|
|
941
|
+
emissiveTexture: input.emissiveTexture ?? input.material?.emissiveTexture ?? null
|
|
540
942
|
});
|
|
541
943
|
}
|
|
944
|
+
function clampUnit(value) {
|
|
945
|
+
return clamp(Number(value) || 0, 0, 1);
|
|
946
|
+
}
|
|
947
|
+
function srgbToLinear(value) {
|
|
948
|
+
const channel = clampUnit(value);
|
|
949
|
+
if (channel <= 0.04045) {
|
|
950
|
+
return channel / 12.92;
|
|
951
|
+
}
|
|
952
|
+
return ((channel + 0.055) / 1.055) ** 2.4;
|
|
953
|
+
}
|
|
954
|
+
function sampleTextureRgba(texture, uv = [0, 0], colorSpace = "linear") {
|
|
955
|
+
if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || !texture.data || texture.width <= 0 || texture.height <= 0) {
|
|
956
|
+
return [1, 1, 1, 1];
|
|
957
|
+
}
|
|
958
|
+
const u = (uv[0] % 1 + 1) % 1;
|
|
959
|
+
const v = (uv[1] % 1 + 1) % 1;
|
|
960
|
+
const x = Math.min(texture.width - 1, Math.max(0, Math.round(u * (texture.width - 1))));
|
|
961
|
+
const y = Math.min(texture.height - 1, Math.max(0, Math.round((1 - v) * (texture.height - 1))));
|
|
962
|
+
const offset = (y * texture.width + x) * 4;
|
|
963
|
+
const data = texture.data;
|
|
964
|
+
const scale2 = resolveTextureSampleScale(data);
|
|
965
|
+
const defaultChannel = scale2 === 1 ? 1 : Math.round(1 / scale2);
|
|
966
|
+
const color = [
|
|
967
|
+
(data[offset] ?? defaultChannel) * scale2,
|
|
968
|
+
(data[offset + 1] ?? defaultChannel) * scale2,
|
|
969
|
+
(data[offset + 2] ?? defaultChannel) * scale2,
|
|
970
|
+
(data[offset + 3] ?? defaultChannel) * scale2
|
|
971
|
+
];
|
|
972
|
+
if (colorSpace === "srgb") {
|
|
973
|
+
return [srgbToLinear(color[0]), srgbToLinear(color[1]), srgbToLinear(color[2]), color[3]];
|
|
974
|
+
}
|
|
975
|
+
return color;
|
|
976
|
+
}
|
|
977
|
+
function resolveTextureSampleScale(data) {
|
|
978
|
+
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
|
|
979
|
+
return 1 / 255;
|
|
980
|
+
}
|
|
981
|
+
if (data instanceof Uint16Array) {
|
|
982
|
+
return 1 / 65535;
|
|
983
|
+
}
|
|
984
|
+
if (Array.isArray(data) && data.some((value) => Number(value) > 1)) {
|
|
985
|
+
return 1 / 255;
|
|
986
|
+
}
|
|
987
|
+
return 1;
|
|
988
|
+
}
|
|
989
|
+
function normalizeVectorOrFallback(vector, fallback) {
|
|
990
|
+
return normalize(Array.isArray(vector) ? vector : fallback, fallback);
|
|
991
|
+
}
|
|
992
|
+
function buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, fallbackNormal) {
|
|
993
|
+
const edge1 = subtract(v1, v0);
|
|
994
|
+
const edge2 = subtract(v2, v0);
|
|
995
|
+
const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
|
|
996
|
+
const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
|
|
997
|
+
const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
|
|
998
|
+
if (Math.abs(determinant) < 1e-6) {
|
|
999
|
+
const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
1000
|
+
const tangent2 = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
|
|
1001
|
+
const bitangent2 = normalize(cross(fallbackNormal, tangent2), [0, 0, 1]);
|
|
1002
|
+
return { tangent: tangent2, bitangent: bitangent2 };
|
|
1003
|
+
}
|
|
1004
|
+
const inverse = 1 / determinant;
|
|
1005
|
+
const tangent = normalize(
|
|
1006
|
+
[
|
|
1007
|
+
inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
|
|
1008
|
+
inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
|
|
1009
|
+
inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1])
|
|
1010
|
+
],
|
|
1011
|
+
[1, 0, 0]
|
|
1012
|
+
);
|
|
1013
|
+
const bitangent = normalize(
|
|
1014
|
+
[
|
|
1015
|
+
inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
|
|
1016
|
+
inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
|
|
1017
|
+
inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0])
|
|
1018
|
+
],
|
|
1019
|
+
[0, 0, 1]
|
|
1020
|
+
);
|
|
1021
|
+
return { tangent, bitangent };
|
|
1022
|
+
}
|
|
1023
|
+
function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
|
|
1024
|
+
if (!normalTexture) {
|
|
1025
|
+
return normalizeVectorOrFallback(normal, [0, 1, 0]);
|
|
1026
|
+
}
|
|
1027
|
+
const sample = sampleTextureRgba(normalTexture, uv, "linear");
|
|
1028
|
+
const strength = clampUnit(normalTexture.scale ?? 1);
|
|
1029
|
+
const tangentNormal = normalize(
|
|
1030
|
+
[
|
|
1031
|
+
(sample[0] * 2 - 1) * strength,
|
|
1032
|
+
(sample[1] * 2 - 1) * strength,
|
|
1033
|
+
1 + (sample[2] * 2 - 1 - 1) * strength
|
|
1034
|
+
],
|
|
1035
|
+
[0, 0, 1]
|
|
1036
|
+
);
|
|
1037
|
+
return normalize(
|
|
1038
|
+
[
|
|
1039
|
+
tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
|
|
1040
|
+
tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
|
|
1041
|
+
tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2]
|
|
1042
|
+
],
|
|
1043
|
+
normal
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
function sampleBaseColor(mesh, uv) {
|
|
1047
|
+
const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
|
|
1048
|
+
return [
|
|
1049
|
+
clampUnit(mesh.color[0] * sample[0]),
|
|
1050
|
+
clampUnit(mesh.color[1] * sample[1]),
|
|
1051
|
+
clampUnit(mesh.color[2] * sample[2]),
|
|
1052
|
+
clampUnit((mesh.color[3] ?? 1) * sample[3])
|
|
1053
|
+
];
|
|
1054
|
+
}
|
|
1055
|
+
function sampleSurfaceMaterial(mesh, uv) {
|
|
1056
|
+
const textureSample = mesh.metallicRoughnessTexture ? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear") : [1, 1, 1, 1];
|
|
1057
|
+
return {
|
|
1058
|
+
roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
|
|
1059
|
+
metallic: clamp(mesh.metallic * textureSample[2], 0, 1)
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
function averageColors(colors) {
|
|
1063
|
+
const count = Math.max(colors.length, 1);
|
|
1064
|
+
return colors.reduce(
|
|
1065
|
+
(accumulator, color) => [
|
|
1066
|
+
accumulator[0] + color[0] / count,
|
|
1067
|
+
accumulator[1] + color[1] / count,
|
|
1068
|
+
accumulator[2] + color[2] / count,
|
|
1069
|
+
accumulator[3] + color[3] / count
|
|
1070
|
+
],
|
|
1071
|
+
[0, 0, 0, 0]
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
function averageNumbers(values, fallback = 0) {
|
|
1075
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
1076
|
+
return fallback;
|
|
1077
|
+
}
|
|
1078
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
1079
|
+
}
|
|
542
1080
|
function createMeshTriangleRecords(meshes) {
|
|
543
1081
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
544
1082
|
let nextTriangleId = 0;
|
|
@@ -559,6 +1097,16 @@ function createMeshTriangleRecords(meshes) {
|
|
|
559
1097
|
const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
|
|
560
1098
|
const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
|
|
561
1099
|
const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
|
|
1100
|
+
const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
|
|
1101
|
+
const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
|
|
1102
|
+
const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
|
|
1103
|
+
const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
|
|
1104
|
+
const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
|
|
1105
|
+
const sampledMaterials = [
|
|
1106
|
+
sampleSurfaceMaterial(mesh, uv0),
|
|
1107
|
+
sampleSurfaceMaterial(mesh, uv1),
|
|
1108
|
+
sampleSurfaceMaterial(mesh, uv2)
|
|
1109
|
+
];
|
|
562
1110
|
const bounds = triangleBounds(v0, v1, v2);
|
|
563
1111
|
triangles.push(
|
|
564
1112
|
Object.freeze({
|
|
@@ -568,18 +1116,42 @@ function createMeshTriangleRecords(meshes) {
|
|
|
568
1116
|
flags: mesh.flags,
|
|
569
1117
|
materialRefId: mesh.materialRefId,
|
|
570
1118
|
mediumRefId: mesh.mediumRefId,
|
|
1119
|
+
materialSlot: meshIndex,
|
|
571
1120
|
v0: Object.freeze(v0),
|
|
572
1121
|
v1: Object.freeze(v1),
|
|
573
1122
|
v2: Object.freeze(v2),
|
|
574
|
-
n0: Object.freeze(
|
|
575
|
-
n1: Object.freeze(
|
|
576
|
-
n2: Object.freeze(
|
|
1123
|
+
n0: Object.freeze(shadedN0),
|
|
1124
|
+
n1: Object.freeze(shadedN1),
|
|
1125
|
+
n2: Object.freeze(shadedN2),
|
|
577
1126
|
uv0: Object.freeze(uv0),
|
|
578
1127
|
uv1: Object.freeze(uv1),
|
|
579
1128
|
uv2: Object.freeze(uv2),
|
|
580
|
-
color:
|
|
1129
|
+
color: Object.freeze(averageColors(sampledColors)),
|
|
581
1130
|
emission: mesh.emission,
|
|
582
|
-
material: Object.freeze([
|
|
1131
|
+
material: Object.freeze([
|
|
1132
|
+
averageNumbers(sampledMaterials.map((sample) => sample.roughness), mesh.roughness),
|
|
1133
|
+
averageNumbers(sampledMaterials.map((sample) => sample.metallic), mesh.metallic),
|
|
1134
|
+
mesh.opacity,
|
|
1135
|
+
mesh.ior
|
|
1136
|
+
]),
|
|
1137
|
+
materialResponse: Object.freeze([
|
|
1138
|
+
mesh.sheenColor[0] ?? 0,
|
|
1139
|
+
mesh.sheenColor[1] ?? 0,
|
|
1140
|
+
mesh.sheenColor[2] ?? 0,
|
|
1141
|
+
mesh.clearcoat
|
|
1142
|
+
]),
|
|
1143
|
+
materialExtension: Object.freeze([
|
|
1144
|
+
mesh.clearcoatRoughness,
|
|
1145
|
+
mesh.specular,
|
|
1146
|
+
mesh.transmission,
|
|
1147
|
+
mesh.thickness
|
|
1148
|
+
]),
|
|
1149
|
+
specularColor: Object.freeze([
|
|
1150
|
+
mesh.specularColor[0] ?? 1,
|
|
1151
|
+
mesh.specularColor[1] ?? 1,
|
|
1152
|
+
mesh.specularColor[2] ?? 1,
|
|
1153
|
+
1
|
|
1154
|
+
]),
|
|
583
1155
|
bounds: Object.freeze({
|
|
584
1156
|
min: Object.freeze(bounds.min),
|
|
585
1157
|
max: Object.freeze(bounds.max)
|
|
@@ -688,50 +1260,264 @@ function nextPowerOfTwo(value) {
|
|
|
688
1260
|
}
|
|
689
1261
|
return 2 ** Math.ceil(Math.log2(value));
|
|
690
1262
|
}
|
|
691
|
-
function
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
const itemCount = readNonNegativeInteger("itemCount", itemCountInput, 0);
|
|
696
|
-
const sortCount = estimateBvhLeafSortCapacity(itemCount);
|
|
697
|
-
if (sortCount <= 1) {
|
|
698
|
-
return Object.freeze([]);
|
|
1263
|
+
function textureComponentToByte(value, fallback) {
|
|
1264
|
+
const numeric = Number(value);
|
|
1265
|
+
if (!Number.isFinite(numeric)) {
|
|
1266
|
+
return fallback;
|
|
699
1267
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
for (let compareDistance = sequenceSize / 2; compareDistance >= 1; compareDistance /= 2) {
|
|
703
|
-
stages.push(
|
|
704
|
-
Object.freeze({
|
|
705
|
-
compareDistance,
|
|
706
|
-
sequenceSize
|
|
707
|
-
})
|
|
708
|
-
);
|
|
709
|
-
}
|
|
1268
|
+
if (numeric >= 0 && numeric <= 1) {
|
|
1269
|
+
return Math.max(0, Math.min(255, Math.round(numeric * 255)));
|
|
710
1270
|
}
|
|
711
|
-
return
|
|
1271
|
+
return Math.max(0, Math.min(255, Math.round(numeric)));
|
|
712
1272
|
}
|
|
713
|
-
function
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1273
|
+
function createSolidTextureSample(width, height, rgba) {
|
|
1274
|
+
const data = new Uint8Array(width * height * 4);
|
|
1275
|
+
for (let offset = 0; offset < data.length; offset += 4) {
|
|
1276
|
+
data[offset] = rgba[0];
|
|
1277
|
+
data[offset + 1] = rgba[1];
|
|
1278
|
+
data[offset + 2] = rgba[2];
|
|
1279
|
+
data[offset + 3] = rgba[3];
|
|
718
1280
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1281
|
+
return Object.freeze({
|
|
1282
|
+
width,
|
|
1283
|
+
height,
|
|
1284
|
+
data
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
function normalizeTextureSampleInput(texture, fallbackColor) {
|
|
1288
|
+
if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || texture.width <= 0 || texture.height <= 0) {
|
|
1289
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
723
1290
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1291
|
+
const pixelCount = Math.trunc(texture.width) * Math.trunc(texture.height) * 4;
|
|
1292
|
+
const source = ArrayBuffer.isView(texture.data) || Array.isArray(texture.data) ? texture.data : null;
|
|
1293
|
+
if (!source || source.length < pixelCount) {
|
|
1294
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
1295
|
+
}
|
|
1296
|
+
const data = new Uint8Array(pixelCount);
|
|
1297
|
+
for (let index = 0; index < pixelCount; index += 1) {
|
|
1298
|
+
data[index] = textureComponentToByte(source[index], fallbackColor[index % 4]);
|
|
1299
|
+
}
|
|
1300
|
+
return Object.freeze({
|
|
1301
|
+
width: Math.trunc(texture.width),
|
|
1302
|
+
height: Math.trunc(texture.height),
|
|
1303
|
+
data
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
function buildTextureAtlas(textures, fallbackColor) {
|
|
1307
|
+
const padding = 1;
|
|
1308
|
+
const defaultTexture = createSolidTextureSample(1, 1, fallbackColor);
|
|
1309
|
+
const uniqueEntries = [{ source: null, texture: defaultTexture }];
|
|
1310
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
1311
|
+
for (const texture of Array.isArray(textures) ? textures : []) {
|
|
1312
|
+
if (!texture || bySource.has(texture)) {
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
const normalized = normalizeTextureSampleInput(texture, fallbackColor);
|
|
1316
|
+
bySource.set(texture, uniqueEntries.length);
|
|
1317
|
+
uniqueEntries.push({ source: texture, texture: normalized });
|
|
1318
|
+
}
|
|
1319
|
+
const totalArea = uniqueEntries.reduce((sum, entry) => {
|
|
1320
|
+
return sum + (entry.texture.width + padding * 2) * (entry.texture.height + padding * 2);
|
|
1321
|
+
}, 0);
|
|
1322
|
+
const maxTileWidth = uniqueEntries.reduce((maxWidth, entry) => {
|
|
1323
|
+
return Math.max(maxWidth, entry.texture.width + padding * 2);
|
|
1324
|
+
}, 1);
|
|
1325
|
+
const targetWidth = Math.max(
|
|
1326
|
+
maxTileWidth,
|
|
1327
|
+
nextPowerOfTwo(Math.max(maxTileWidth, Math.ceil(Math.sqrt(totalArea))))
|
|
1328
|
+
);
|
|
1329
|
+
let cursorX = 0;
|
|
1330
|
+
let cursorY = 0;
|
|
1331
|
+
let rowHeight = 0;
|
|
1332
|
+
let atlasWidth = 0;
|
|
1333
|
+
const placements = uniqueEntries.map((entry) => {
|
|
1334
|
+
const tileWidth = entry.texture.width + padding * 2;
|
|
1335
|
+
const tileHeight = entry.texture.height + padding * 2;
|
|
1336
|
+
if (cursorX > 0 && cursorX + tileWidth > targetWidth) {
|
|
1337
|
+
cursorX = 0;
|
|
1338
|
+
cursorY += rowHeight;
|
|
1339
|
+
rowHeight = 0;
|
|
1340
|
+
}
|
|
1341
|
+
const placement = Object.freeze({
|
|
1342
|
+
x: cursorX,
|
|
1343
|
+
y: cursorY,
|
|
1344
|
+
tileWidth,
|
|
1345
|
+
tileHeight,
|
|
1346
|
+
width: entry.texture.width,
|
|
1347
|
+
height: entry.texture.height
|
|
1348
|
+
});
|
|
1349
|
+
cursorX += tileWidth;
|
|
1350
|
+
atlasWidth = Math.max(atlasWidth, cursorX);
|
|
1351
|
+
rowHeight = Math.max(rowHeight, tileHeight);
|
|
1352
|
+
return placement;
|
|
1353
|
+
});
|
|
1354
|
+
const atlasHeight = Math.max(1, cursorY + rowHeight);
|
|
1355
|
+
const atlasData = new Uint8Array(Math.max(1, atlasWidth * atlasHeight * 4));
|
|
1356
|
+
const writePixel = (x, y, rgba) => {
|
|
1357
|
+
const offset = (y * atlasWidth + x) * 4;
|
|
1358
|
+
atlasData[offset] = rgba[0];
|
|
1359
|
+
atlasData[offset + 1] = rgba[1];
|
|
1360
|
+
atlasData[offset + 2] = rgba[2];
|
|
1361
|
+
atlasData[offset + 3] = rgba[3];
|
|
1362
|
+
};
|
|
1363
|
+
const rects = placements.map((placement, entryIndex) => {
|
|
1364
|
+
const { texture } = uniqueEntries[entryIndex];
|
|
1365
|
+
for (let y = 0; y < placement.tileHeight; y += 1) {
|
|
1366
|
+
for (let x = 0; x < placement.tileWidth; x += 1) {
|
|
1367
|
+
const sampleX = Math.max(0, Math.min(texture.width - 1, x - padding));
|
|
1368
|
+
const sampleY = Math.max(0, Math.min(texture.height - 1, y - padding));
|
|
1369
|
+
const sourceOffset = (sampleY * texture.width + sampleX) * 4;
|
|
1370
|
+
writePixel(placement.x + x, placement.y + y, texture.data.slice(sourceOffset, sourceOffset + 4));
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return Object.freeze([
|
|
1374
|
+
(placement.x + padding) / Math.max(1, atlasWidth),
|
|
1375
|
+
(placement.y + padding) / Math.max(1, atlasHeight),
|
|
1376
|
+
placement.width / Math.max(1, atlasWidth),
|
|
1377
|
+
placement.height / Math.max(1, atlasHeight)
|
|
1378
|
+
]);
|
|
1379
|
+
});
|
|
1380
|
+
const rectBySource = /* @__PURE__ */ new Map();
|
|
1381
|
+
uniqueEntries.forEach((entry, index) => {
|
|
1382
|
+
if (entry.source) {
|
|
1383
|
+
rectBySource.set(entry.source, rects[index]);
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
return Object.freeze({
|
|
1387
|
+
width: Math.max(1, atlasWidth),
|
|
1388
|
+
height: Math.max(1, atlasHeight),
|
|
1389
|
+
data: atlasData,
|
|
1390
|
+
defaultRect: rects[0],
|
|
1391
|
+
resolveRect(texture) {
|
|
1392
|
+
return rectBySource.get(texture) ?? rects[0];
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
function createWavefrontGpuMaterialSource(meshes = []) {
|
|
1397
|
+
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
1398
|
+
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1399
|
+
const baseColorAtlas = buildTextureAtlas(
|
|
1400
|
+
normalized.map((mesh) => mesh.baseColorTexture),
|
|
1401
|
+
[255, 255, 255, 255]
|
|
1402
|
+
);
|
|
1403
|
+
const metallicRoughnessAtlas = buildTextureAtlas(
|
|
1404
|
+
normalized.map((mesh) => mesh.metallicRoughnessTexture),
|
|
1405
|
+
[255, 255, 255, 255]
|
|
1406
|
+
);
|
|
1407
|
+
const normalAtlas = buildTextureAtlas(
|
|
1408
|
+
normalized.map((mesh) => mesh.normalTexture),
|
|
1409
|
+
[128, 128, 255, 255]
|
|
1410
|
+
);
|
|
1411
|
+
const occlusionAtlas = buildTextureAtlas(
|
|
1412
|
+
normalized.map((mesh) => mesh.occlusionTexture),
|
|
1413
|
+
[255, 255, 255, 255]
|
|
1414
|
+
);
|
|
1415
|
+
const emissiveAtlas = buildTextureAtlas(
|
|
1416
|
+
normalized.map((mesh) => mesh.emissiveTexture),
|
|
1417
|
+
[255, 255, 255, 255]
|
|
1418
|
+
);
|
|
1419
|
+
const bytes = new ArrayBuffer(Math.max(1, normalized.length) * GPU_MATERIAL_RECORD_BYTES);
|
|
1420
|
+
const floatView = new Float32Array(bytes);
|
|
1421
|
+
normalized.forEach((mesh, meshIndex) => {
|
|
1422
|
+
const byteOffset = meshIndex * GPU_MATERIAL_RECORD_BYTES;
|
|
1423
|
+
writeVec4(floatView, byteOffset, mesh.color);
|
|
1424
|
+
writeVec4(floatView, byteOffset + 16, mesh.emission);
|
|
1425
|
+
writeVec4(floatView, byteOffset + 32, [
|
|
1426
|
+
mesh.roughness,
|
|
1427
|
+
mesh.metallic,
|
|
1428
|
+
mesh.opacity,
|
|
1429
|
+
mesh.ior
|
|
1430
|
+
]);
|
|
1431
|
+
writeVec4(floatView, byteOffset + 48, [
|
|
1432
|
+
mesh.sheenColor[0] ?? 0,
|
|
1433
|
+
mesh.sheenColor[1] ?? 0,
|
|
1434
|
+
mesh.sheenColor[2] ?? 0,
|
|
1435
|
+
mesh.clearcoat
|
|
1436
|
+
]);
|
|
1437
|
+
writeVec4(floatView, byteOffset + 64, [
|
|
1438
|
+
mesh.clearcoatRoughness,
|
|
1439
|
+
mesh.specular,
|
|
1440
|
+
mesh.transmission,
|
|
1441
|
+
mesh.thickness
|
|
1442
|
+
]);
|
|
1443
|
+
writeVec4(floatView, byteOffset + 80, [
|
|
1444
|
+
mesh.specularColor[0] ?? 1,
|
|
1445
|
+
mesh.specularColor[1] ?? 1,
|
|
1446
|
+
mesh.specularColor[2] ?? 1,
|
|
1447
|
+
1
|
|
1448
|
+
]);
|
|
1449
|
+
writeVec4(floatView, byteOffset + 96, baseColorAtlas.resolveRect(mesh.baseColorTexture));
|
|
1450
|
+
writeVec4(
|
|
1451
|
+
floatView,
|
|
1452
|
+
byteOffset + 112,
|
|
1453
|
+
metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1454
|
+
);
|
|
1455
|
+
writeVec4(floatView, byteOffset + 128, normalAtlas.resolveRect(mesh.normalTexture));
|
|
1456
|
+
writeVec4(floatView, byteOffset + 144, occlusionAtlas.resolveRect(mesh.occlusionTexture));
|
|
1457
|
+
writeVec4(floatView, byteOffset + 160, emissiveAtlas.resolveRect(mesh.emissiveTexture));
|
|
1458
|
+
writeVec4(floatView, byteOffset + 176, [
|
|
1459
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1460
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1461
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1462
|
+
0
|
|
1463
|
+
]);
|
|
1464
|
+
});
|
|
1465
|
+
return Object.freeze({
|
|
1466
|
+
buffer: bytes,
|
|
1467
|
+
count: normalized.length,
|
|
1468
|
+
recordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
1469
|
+
records: Object.freeze(normalized),
|
|
1470
|
+
baseColorAtlas,
|
|
1471
|
+
metallicRoughnessAtlas,
|
|
1472
|
+
normalAtlas,
|
|
1473
|
+
occlusionAtlas,
|
|
1474
|
+
emissiveAtlas
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
function estimateBvhLeafSortCapacity(triangleCount) {
|
|
1478
|
+
return triangleCount <= 0 ? 0 : nextPowerOfTwo(triangleCount);
|
|
1479
|
+
}
|
|
1480
|
+
function createWavefrontBvhSortStages(itemCountInput) {
|
|
1481
|
+
const itemCount = readNonNegativeInteger("itemCount", itemCountInput, 0);
|
|
1482
|
+
const sortCount = estimateBvhLeafSortCapacity(itemCount);
|
|
1483
|
+
if (sortCount <= 1) {
|
|
1484
|
+
return Object.freeze([]);
|
|
1485
|
+
}
|
|
1486
|
+
const stages = [];
|
|
1487
|
+
for (let sequenceSize = 2; sequenceSize <= sortCount; sequenceSize *= 2) {
|
|
1488
|
+
for (let compareDistance = sequenceSize / 2; compareDistance >= 1; compareDistance /= 2) {
|
|
1489
|
+
stages.push(
|
|
1490
|
+
Object.freeze({
|
|
1491
|
+
compareDistance,
|
|
1492
|
+
sequenceSize
|
|
1493
|
+
})
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return Object.freeze(stages);
|
|
1498
|
+
}
|
|
1499
|
+
function createWavefrontBvhBuildLevels(triangleCountInput) {
|
|
1500
|
+
const triangleCount = readNonNegativeInteger("triangleCount", triangleCountInput, 0);
|
|
1501
|
+
const internalCount = Math.max(0, triangleCount - 1);
|
|
1502
|
+
if (internalCount === 0) {
|
|
1503
|
+
return Object.freeze([]);
|
|
1504
|
+
}
|
|
1505
|
+
const levels = [];
|
|
1506
|
+
let depth = 0;
|
|
1507
|
+
while (Math.pow(2, depth) - 1 < internalCount) {
|
|
1508
|
+
depth += 1;
|
|
1509
|
+
}
|
|
1510
|
+
for (let level = depth - 1; level >= 0; level -= 1) {
|
|
1511
|
+
const start = Math.pow(2, level) - 1;
|
|
1512
|
+
const end = Math.min(Math.pow(2, level + 1) - 2, internalCount - 1);
|
|
1513
|
+
if (end >= start) {
|
|
1514
|
+
levels.push(
|
|
1515
|
+
Object.freeze({
|
|
1516
|
+
start,
|
|
1517
|
+
count: end - start + 1
|
|
1518
|
+
})
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
735
1521
|
}
|
|
736
1522
|
return Object.freeze(levels);
|
|
737
1523
|
}
|
|
@@ -745,9 +1531,10 @@ function resolveAccelerationBuildMode(options = {}) {
|
|
|
745
1531
|
}
|
|
746
1532
|
return mode;
|
|
747
1533
|
}
|
|
748
|
-
function createWavefrontGpuMeshSource(meshes = []) {
|
|
1534
|
+
function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput = null) {
|
|
749
1535
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
750
1536
|
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1537
|
+
const gpuMaterialSource = gpuMaterialSourceInput ?? createWavefrontGpuMaterialSource(normalized);
|
|
751
1538
|
const vertexCount = normalized.reduce((count, mesh) => count + mesh.positions.length / 3, 0);
|
|
752
1539
|
const indexCount = normalized.reduce((count, mesh) => count + mesh.indices.length, 0);
|
|
753
1540
|
const triangleCount = Math.floor(indexCount / 3);
|
|
@@ -799,7 +1586,7 @@ function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
799
1586
|
meshUints[meshOffset + 8] = mesh.indices.length / 3;
|
|
800
1587
|
meshUints[meshOffset + 9] = meshVertexBase;
|
|
801
1588
|
meshUints[meshOffset + 10] = meshVertexCount;
|
|
802
|
-
meshUints[meshOffset + 11] =
|
|
1589
|
+
meshUints[meshOffset + 11] = meshIndex;
|
|
803
1590
|
const floatOffset = meshOffset;
|
|
804
1591
|
writeVec4(meshFloats, floatOffset * 4 + 48, mesh.color);
|
|
805
1592
|
writeVec4(meshFloats, floatOffset * 4 + 64, mesh.emission);
|
|
@@ -809,6 +1596,55 @@ function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
809
1596
|
mesh.opacity,
|
|
810
1597
|
mesh.ior
|
|
811
1598
|
]);
|
|
1599
|
+
writeVec4(meshFloats, floatOffset * 4 + 96, [
|
|
1600
|
+
mesh.sheenColor[0] ?? 0,
|
|
1601
|
+
mesh.sheenColor[1] ?? 0,
|
|
1602
|
+
mesh.sheenColor[2] ?? 0,
|
|
1603
|
+
mesh.clearcoat
|
|
1604
|
+
]);
|
|
1605
|
+
writeVec4(meshFloats, floatOffset * 4 + 112, [
|
|
1606
|
+
mesh.clearcoatRoughness,
|
|
1607
|
+
mesh.specular,
|
|
1608
|
+
mesh.transmission,
|
|
1609
|
+
mesh.thickness
|
|
1610
|
+
]);
|
|
1611
|
+
writeVec4(meshFloats, floatOffset * 4 + 128, [
|
|
1612
|
+
mesh.specularColor[0] ?? 1,
|
|
1613
|
+
mesh.specularColor[1] ?? 1,
|
|
1614
|
+
mesh.specularColor[2] ?? 1,
|
|
1615
|
+
1
|
|
1616
|
+
]);
|
|
1617
|
+
writeVec4(
|
|
1618
|
+
meshFloats,
|
|
1619
|
+
floatOffset * 4 + 144,
|
|
1620
|
+
gpuMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
|
|
1621
|
+
);
|
|
1622
|
+
writeVec4(
|
|
1623
|
+
meshFloats,
|
|
1624
|
+
floatOffset * 4 + 160,
|
|
1625
|
+
gpuMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1626
|
+
);
|
|
1627
|
+
writeVec4(
|
|
1628
|
+
meshFloats,
|
|
1629
|
+
floatOffset * 4 + 176,
|
|
1630
|
+
gpuMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
|
|
1631
|
+
);
|
|
1632
|
+
writeVec4(
|
|
1633
|
+
meshFloats,
|
|
1634
|
+
floatOffset * 4 + 192,
|
|
1635
|
+
gpuMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
|
|
1636
|
+
);
|
|
1637
|
+
writeVec4(
|
|
1638
|
+
meshFloats,
|
|
1639
|
+
floatOffset * 4 + 208,
|
|
1640
|
+
gpuMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
|
|
1641
|
+
);
|
|
1642
|
+
writeVec4(meshFloats, floatOffset * 4 + 224, [
|
|
1643
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1644
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1645
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1646
|
+
0
|
|
1647
|
+
]);
|
|
812
1648
|
vertexCursor += meshVertexCount;
|
|
813
1649
|
indexCursor += mesh.indices.length;
|
|
814
1650
|
triangleCursor += mesh.indices.length / 3;
|
|
@@ -871,12 +1707,16 @@ function normalizeSceneObjects(sceneObjects, useDefaultScene = true) {
|
|
|
871
1707
|
const source = Array.isArray(sceneObjects) && sceneObjects.length > 0 ? sceneObjects : useDefaultScene ? createDefaultWavefrontSceneObjects() : [];
|
|
872
1708
|
return source.map((object, index) => normalizeWavefrontSceneObject(object, index));
|
|
873
1709
|
}
|
|
1710
|
+
function normalizeWavefrontMeshes(meshes) {
|
|
1711
|
+
const source = Array.isArray(meshes) ? meshes : [];
|
|
1712
|
+
return source.map((mesh, index) => normalizeWavefrontMesh(mesh, index));
|
|
1713
|
+
}
|
|
874
1714
|
function normalizeMeshes(options = {}) {
|
|
875
1715
|
if (Array.isArray(options.meshes)) {
|
|
876
|
-
return options.meshes;
|
|
1716
|
+
return normalizeWavefrontMeshes(options.meshes);
|
|
877
1717
|
}
|
|
878
1718
|
if (options.mesh) {
|
|
879
|
-
return [options.mesh];
|
|
1719
|
+
return normalizeWavefrontMeshes([options.mesh]);
|
|
880
1720
|
}
|
|
881
1721
|
return [];
|
|
882
1722
|
}
|
|
@@ -1111,12 +1951,14 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1111
1951
|
options.environmentPortalCapacity,
|
|
1112
1952
|
0
|
|
1113
1953
|
);
|
|
1954
|
+
const materialCapacity = readNonNegativeInteger("materialCapacity", options.materialCapacity, 0);
|
|
1114
1955
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
1115
1956
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
1116
1957
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
1117
1958
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
1118
1959
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
1119
1960
|
const triangleBytes = triangleCapacity * TRIANGLE_RECORD_BYTES;
|
|
1961
|
+
const materialTableBytes = materialCapacity * GPU_MATERIAL_RECORD_BYTES;
|
|
1120
1962
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
1121
1963
|
const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
|
|
1122
1964
|
const emissiveTriangleMetadataBytes = emissiveTriangleCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -1129,6 +1971,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1129
1971
|
pathVertexBytes,
|
|
1130
1972
|
sceneObjectBytes,
|
|
1131
1973
|
triangleBytes,
|
|
1974
|
+
materialTableBytes,
|
|
1132
1975
|
bvhNodeBytes,
|
|
1133
1976
|
bvhLeafReferenceBytes,
|
|
1134
1977
|
emissiveTriangleMetadataBytes,
|
|
@@ -1136,7 +1979,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1136
1979
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
1137
1980
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
1138
1981
|
indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
|
|
1139
|
-
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + pathVertexBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1982
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + pathVertexBytes + sceneObjectBytes + triangleBytes + materialTableBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1140
1983
|
});
|
|
1141
1984
|
}
|
|
1142
1985
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -1150,7 +1993,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1150
1993
|
const samplesPerPixel = clamp(
|
|
1151
1994
|
readPositiveInteger("samplesPerPixel", options.samplesPerPixel, DEFAULT_SAMPLES_PER_PIXEL),
|
|
1152
1995
|
1,
|
|
1153
|
-
|
|
1996
|
+
MAX_SAMPLES_PER_PIXEL
|
|
1154
1997
|
);
|
|
1155
1998
|
const maxFramePassesPerSubmission = clamp(
|
|
1156
1999
|
readPositiveInteger(
|
|
@@ -1168,7 +2011,8 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1168
2011
|
);
|
|
1169
2012
|
const meshes = normalizeMeshes(options);
|
|
1170
2013
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
1171
|
-
const
|
|
2014
|
+
const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
|
|
2015
|
+
const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
|
|
1172
2016
|
const meshAcceleration = accelerationBuildMode === "cpu-debug" ? createWavefrontMeshAcceleration(meshes) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
1173
2017
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
1174
2018
|
meshes,
|
|
@@ -1179,6 +2023,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1179
2023
|
const sceneObjects = Object.freeze(
|
|
1180
2024
|
normalizeSceneObjects(options.sceneObjects, meshes.length === 0)
|
|
1181
2025
|
);
|
|
2026
|
+
const mediums = collectWavefrontMediums(options, meshes, sceneObjects);
|
|
1182
2027
|
const sceneObjectCapacity = Math.max(
|
|
1183
2028
|
sceneObjects.length,
|
|
1184
2029
|
readPositiveInteger("sceneObjectCapacity", options.sceneObjectCapacity, DEFAULT_SCENE_OBJECT_CAPACITY)
|
|
@@ -1237,9 +2082,12 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1237
2082
|
sceneObjects,
|
|
1238
2083
|
sceneObjectCount: sceneObjects.length,
|
|
1239
2084
|
sceneObjectCapacity,
|
|
2085
|
+
mediums,
|
|
2086
|
+
mediumCount: mediums.length,
|
|
1240
2087
|
accelerationBuildMode,
|
|
1241
2088
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
1242
2089
|
gpuMeshSource,
|
|
2090
|
+
gpuMaterialSource,
|
|
1243
2091
|
meshAcceleration,
|
|
1244
2092
|
emissiveTriangleIndices,
|
|
1245
2093
|
emissiveTriangleCount: emissiveTriangleIndices.count,
|
|
@@ -1270,6 +2118,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1270
2118
|
maxDepth,
|
|
1271
2119
|
sceneObjectCapacity,
|
|
1272
2120
|
triangleCapacity,
|
|
2121
|
+
materialCapacity: gpuMaterialSource.count,
|
|
1273
2122
|
bvhNodeCapacity,
|
|
1274
2123
|
bvhLeafSortCapacity,
|
|
1275
2124
|
emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
|
|
@@ -1336,16 +2185,35 @@ function packWavefrontSceneObjects(sceneObjects, capacity = sceneObjects.length)
|
|
|
1336
2185
|
uintView[u32 + 1] = object.id;
|
|
1337
2186
|
uintView[u32 + 2] = object.materialKind;
|
|
1338
2187
|
uintView[u32 + 3] = object.flags;
|
|
1339
|
-
|
|
1340
|
-
writeVec4(floatView, byteOffset + 32, [...object.
|
|
1341
|
-
writeVec4(floatView, byteOffset + 48, object.
|
|
1342
|
-
writeVec4(floatView, byteOffset + 64, object.
|
|
1343
|
-
writeVec4(floatView, byteOffset + 80,
|
|
2188
|
+
uintView[u32 + 4] = object.mediumRefId;
|
|
2189
|
+
writeVec4(floatView, byteOffset + 32, [...object.center, 0]);
|
|
2190
|
+
writeVec4(floatView, byteOffset + 48, [...object.halfExtent, 0]);
|
|
2191
|
+
writeVec4(floatView, byteOffset + 64, object.color);
|
|
2192
|
+
writeVec4(floatView, byteOffset + 80, object.emission);
|
|
2193
|
+
writeVec4(floatView, byteOffset + 96, [
|
|
1344
2194
|
object.roughness,
|
|
1345
2195
|
object.metallic,
|
|
1346
2196
|
object.opacity,
|
|
1347
2197
|
object.ior
|
|
1348
2198
|
]);
|
|
2199
|
+
writeVec4(floatView, byteOffset + 112, [
|
|
2200
|
+
object.sheenColor[0] ?? 0,
|
|
2201
|
+
object.sheenColor[1] ?? 0,
|
|
2202
|
+
object.sheenColor[2] ?? 0,
|
|
2203
|
+
object.clearcoat
|
|
2204
|
+
]);
|
|
2205
|
+
writeVec4(floatView, byteOffset + 128, [
|
|
2206
|
+
object.clearcoatRoughness,
|
|
2207
|
+
object.specular,
|
|
2208
|
+
object.transmission,
|
|
2209
|
+
object.thickness
|
|
2210
|
+
]);
|
|
2211
|
+
writeVec4(floatView, byteOffset + 144, [
|
|
2212
|
+
object.specularColor[0] ?? 1,
|
|
2213
|
+
object.specularColor[1] ?? 1,
|
|
2214
|
+
object.specularColor[2] ?? 1,
|
|
2215
|
+
1
|
|
2216
|
+
]);
|
|
1349
2217
|
});
|
|
1350
2218
|
return Object.freeze({
|
|
1351
2219
|
buffer: bytes,
|
|
@@ -1370,7 +2238,7 @@ function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1370
2238
|
uintView[u32 + 3] = triangle.flags;
|
|
1371
2239
|
uintView[u32 + 4] = triangle.materialRefId;
|
|
1372
2240
|
uintView[u32 + 5] = triangle.mediumRefId;
|
|
1373
|
-
uintView[u32 + 6] = 0;
|
|
2241
|
+
uintView[u32 + 6] = triangle.materialSlot ?? 0;
|
|
1374
2242
|
uintView[u32 + 7] = 0;
|
|
1375
2243
|
writeVec4(floatView, byteOffset + 32, [...triangle.v0, 0]);
|
|
1376
2244
|
writeVec4(floatView, byteOffset + 48, [...triangle.v1, 0]);
|
|
@@ -1383,6 +2251,15 @@ function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1383
2251
|
writeVec4(floatView, byteOffset + 160, triangle.color);
|
|
1384
2252
|
writeVec4(floatView, byteOffset + 176, triangle.emission);
|
|
1385
2253
|
writeVec4(floatView, byteOffset + 192, triangle.material);
|
|
2254
|
+
writeVec4(floatView, byteOffset + 208, triangle.materialResponse);
|
|
2255
|
+
writeVec4(floatView, byteOffset + 224, triangle.materialExtension ?? [0.08, 1, 0, 0]);
|
|
2256
|
+
writeVec4(floatView, byteOffset + 240, triangle.specularColor ?? [1, 1, 1, 1]);
|
|
2257
|
+
writeVec4(floatView, byteOffset + 256, triangle.baseColorAtlas ?? [0, 0, 1, 1]);
|
|
2258
|
+
writeVec4(floatView, byteOffset + 272, triangle.metallicRoughnessAtlas ?? [0, 0, 1, 1]);
|
|
2259
|
+
writeVec4(floatView, byteOffset + 288, triangle.normalAtlas ?? [0, 0, 1, 1]);
|
|
2260
|
+
writeVec4(floatView, byteOffset + 304, triangle.occlusionAtlas ?? [0, 0, 1, 1]);
|
|
2261
|
+
writeVec4(floatView, byteOffset + 320, triangle.emissiveAtlas ?? [0, 0, 1, 1]);
|
|
2262
|
+
writeVec4(floatView, byteOffset + 336, triangle.textureSettings ?? [1, 1, 1, 0]);
|
|
1386
2263
|
});
|
|
1387
2264
|
return Object.freeze({
|
|
1388
2265
|
buffer: bytes,
|
|
@@ -1476,6 +2353,12 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1476
2353
|
0,
|
|
1477
2354
|
0
|
|
1478
2355
|
]);
|
|
2356
|
+
writeVec4(floatView, 304, [
|
|
2357
|
+
config.environmentMap.width ?? 1,
|
|
2358
|
+
config.environmentMap.height ?? 1,
|
|
2359
|
+
config.environmentMap.mipLevelCount ?? 1,
|
|
2360
|
+
config.environmentMap.hasImportanceData ? 1 : 0
|
|
2361
|
+
]);
|
|
1479
2362
|
return bytes;
|
|
1480
2363
|
}
|
|
1481
2364
|
function createTiles(width, height, tileSize) {
|
|
@@ -1649,7 +2532,8 @@ function intersectWavefrontReferenceTriangle(ray, triangle, options = {}) {
|
|
|
1649
2532
|
position: Object.freeze(position),
|
|
1650
2533
|
color: triangle.color,
|
|
1651
2534
|
emission: triangle.emission,
|
|
1652
|
-
material: triangle.material
|
|
2535
|
+
material: triangle.material,
|
|
2536
|
+
materialResponse: triangle.materialResponse
|
|
1653
2537
|
});
|
|
1654
2538
|
}
|
|
1655
2539
|
function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
@@ -1675,7 +2559,8 @@ function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
|
1675
2559
|
position: Object.freeze(add(ray.origin, scale(ray.direction, 1e3))),
|
|
1676
2560
|
color: Object.freeze([0, 0, 0, 0]),
|
|
1677
2561
|
emission: radiance,
|
|
1678
|
-
material: Object.freeze([1, 0, 1, 1])
|
|
2562
|
+
material: Object.freeze([1, 0, 1, 1]),
|
|
2563
|
+
materialResponse: Object.freeze([0, 0, 0, 0])
|
|
1679
2564
|
});
|
|
1680
2565
|
}
|
|
1681
2566
|
function traceWavefrontReferenceTriangles(config, ray, triangles, options = {}) {
|
|
@@ -1754,6 +2639,32 @@ function environmentMapIntegerScale(data) {
|
|
|
1754
2639
|
}
|
|
1755
2640
|
return 1;
|
|
1756
2641
|
}
|
|
2642
|
+
function environmentMapHasSamplingData(environmentMap) {
|
|
2643
|
+
if (!environmentMap || !environmentMap.data) {
|
|
2644
|
+
return false;
|
|
2645
|
+
}
|
|
2646
|
+
const width = Math.max(1, environmentMap.width ?? 1);
|
|
2647
|
+
const height = Math.max(1, environmentMap.height ?? 1);
|
|
2648
|
+
return environmentMap.data.length >= width * height * 4;
|
|
2649
|
+
}
|
|
2650
|
+
function createRgba8TextureUpload(source) {
|
|
2651
|
+
const width = Math.max(1, Math.trunc(source.width));
|
|
2652
|
+
const height = Math.max(1, Math.trunc(source.height));
|
|
2653
|
+
const bytesPerRow = alignTo(width * 4, 256);
|
|
2654
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
2655
|
+
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
2656
|
+
for (let y = 0; y < height; y += 1) {
|
|
2657
|
+
const sourceOffset = y * width * 4;
|
|
2658
|
+
const targetOffset = y * bytesPerRow;
|
|
2659
|
+
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
2660
|
+
}
|
|
2661
|
+
return Object.freeze({
|
|
2662
|
+
bytes,
|
|
2663
|
+
bytesPerRow,
|
|
2664
|
+
width,
|
|
2665
|
+
height
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
1757
2668
|
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
1758
2669
|
if (!data || index >= data.length) {
|
|
1759
2670
|
return fallback;
|
|
@@ -1761,39 +2672,311 @@ function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
|
1761
2672
|
const value = Number(data[index]);
|
|
1762
2673
|
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
1763
2674
|
}
|
|
1764
|
-
function
|
|
1765
|
-
const
|
|
1766
|
-
const
|
|
2675
|
+
function buildOrthonormalBasis(normal) {
|
|
2676
|
+
const tangentFallback = Math.abs(normal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
2677
|
+
const tangent = normalize(cross(tangentFallback, normal), [1, 0, 0]);
|
|
2678
|
+
const bitangent = normalize(cross(normal, tangent), [0, 0, 1]);
|
|
2679
|
+
return { tangent, bitangent };
|
|
2680
|
+
}
|
|
2681
|
+
function localToWorld(local, normal) {
|
|
2682
|
+
const basis = buildOrthonormalBasis(normal);
|
|
2683
|
+
return normalize(
|
|
2684
|
+
add(
|
|
2685
|
+
add(scale(basis.tangent, local[0]), scale(basis.bitangent, local[1])),
|
|
2686
|
+
scale(normal, local[2])
|
|
2687
|
+
),
|
|
2688
|
+
normal
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2691
|
+
function radicalInverseVdc(bits) {
|
|
2692
|
+
let value = bits >>> 0;
|
|
2693
|
+
value = (value << 16 | value >>> 16) >>> 0;
|
|
2694
|
+
value = ((value & 1431655765) << 1 | (value & 2863311530) >>> 1) >>> 0;
|
|
2695
|
+
value = ((value & 858993459) << 2 | (value & 3435973836) >>> 2) >>> 0;
|
|
2696
|
+
value = ((value & 252645135) << 4 | (value & 4042322160) >>> 4) >>> 0;
|
|
2697
|
+
value = ((value & 16711935) << 8 | (value & 4278255360) >>> 8) >>> 0;
|
|
2698
|
+
return value * 23283064365386963e-26;
|
|
2699
|
+
}
|
|
2700
|
+
function hammersley(index, count) {
|
|
2701
|
+
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
2702
|
+
}
|
|
2703
|
+
function importanceSampleGgx(sample, roughness, normal) {
|
|
2704
|
+
const alpha = Math.max(roughness * roughness, 1e-4);
|
|
2705
|
+
const phi = 2 * Math.PI * sample[0];
|
|
2706
|
+
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
2707
|
+
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
2708
|
+
const halfVector = localToWorld(
|
|
2709
|
+
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
2710
|
+
normal
|
|
2711
|
+
);
|
|
2712
|
+
return normalize(halfVector, normal);
|
|
2713
|
+
}
|
|
2714
|
+
function geometrySchlickGgx(nDotV, roughness) {
|
|
2715
|
+
const k = (roughness + 1) * (roughness + 1) / 8;
|
|
2716
|
+
return nDotV / Math.max(nDotV * (1 - k) + k, 1e-6);
|
|
2717
|
+
}
|
|
2718
|
+
function geometrySmith(nDotV, nDotL, roughness) {
|
|
2719
|
+
return geometrySchlickGgx(nDotV, roughness) * geometrySchlickGgx(nDotL, roughness);
|
|
2720
|
+
}
|
|
2721
|
+
function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
2722
|
+
const viewDirection = [Math.sqrt(Math.max(0, 1 - nDotV * nDotV)), 0, nDotV];
|
|
2723
|
+
const normal = [0, 0, 1];
|
|
2724
|
+
let scaleTerm = 0;
|
|
2725
|
+
let biasTerm = 0;
|
|
2726
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
2727
|
+
const xi = hammersley(index, sampleCount);
|
|
2728
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2729
|
+
const vDotH = Math.max(dot(viewDirection, halfVector), 0);
|
|
2730
|
+
const lightDirection = normalize(
|
|
2731
|
+
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
2732
|
+
normal
|
|
2733
|
+
);
|
|
2734
|
+
const nDotL = Math.max(lightDirection[2], 0);
|
|
2735
|
+
const nDotH = Math.max(halfVector[2], 0);
|
|
2736
|
+
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
2737
|
+
continue;
|
|
2738
|
+
}
|
|
2739
|
+
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
2740
|
+
const visibility = geometry * vDotH / Math.max(nDotH * nDotV, 1e-6);
|
|
2741
|
+
const fresnel = (1 - vDotH) ** 5;
|
|
2742
|
+
scaleTerm += (1 - fresnel) * visibility;
|
|
2743
|
+
biasTerm += fresnel * visibility;
|
|
2744
|
+
}
|
|
2745
|
+
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
2746
|
+
}
|
|
2747
|
+
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount = DEFAULT_BRDF_LUT_SAMPLE_COUNT) {
|
|
2748
|
+
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
2749
|
+
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
2750
|
+
if (cached) {
|
|
2751
|
+
return cached;
|
|
2752
|
+
}
|
|
2753
|
+
const width = Math.max(1, Math.trunc(size));
|
|
2754
|
+
const height = Math.max(1, Math.trunc(size));
|
|
1767
2755
|
const rowBytes = width * 8;
|
|
1768
2756
|
const bytesPerRow = alignTo(rowBytes, 256);
|
|
1769
2757
|
const bytes = new Uint8Array(bytesPerRow * height);
|
|
2758
|
+
const view = new DataView(bytes.buffer);
|
|
2759
|
+
for (let y = 0; y < height; y += 1) {
|
|
2760
|
+
const roughness = (y + 0.5) / height;
|
|
2761
|
+
for (let x = 0; x < width; x += 1) {
|
|
2762
|
+
const nDotV = Math.max((x + 0.5) / width, 1e-4);
|
|
2763
|
+
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
2764
|
+
const offset = y * bytesPerRow + x * 8;
|
|
2765
|
+
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
2766
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
2767
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
2768
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
2772
|
+
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
2773
|
+
return upload;
|
|
2774
|
+
}
|
|
2775
|
+
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
2776
|
+
const width = Math.max(1, environmentMap.width);
|
|
2777
|
+
const height = Math.max(1, environmentMap.height);
|
|
2778
|
+
const pixels = new Float32Array(width * height * 4);
|
|
1770
2779
|
const data = environmentMap.data;
|
|
1771
2780
|
const integerScale = environmentMapIntegerScale(data);
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
2781
|
+
for (let index = 0; index < width * height; index += 1) {
|
|
2782
|
+
const sourceOffset = index * 4;
|
|
2783
|
+
const targetOffset = index * 4;
|
|
2784
|
+
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
2785
|
+
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
2786
|
+
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
2787
|
+
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
2788
|
+
}
|
|
2789
|
+
return pixels;
|
|
2790
|
+
}
|
|
2791
|
+
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
2792
|
+
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
2793
|
+
const theta = v * Math.PI;
|
|
2794
|
+
const sinTheta = Math.sin(theta);
|
|
2795
|
+
return [
|
|
2796
|
+
Math.cos(angle) * sinTheta,
|
|
2797
|
+
Math.cos(theta),
|
|
2798
|
+
Math.sin(angle) * sinTheta
|
|
2799
|
+
];
|
|
2800
|
+
}
|
|
2801
|
+
function sampleEnvironmentPixelsBilinear(pixels, width, height, u, v) {
|
|
2802
|
+
const wrappedU = (u % 1 + 1) % 1;
|
|
2803
|
+
const clampedV = clamp(v, 0, 1);
|
|
2804
|
+
const x = wrappedU * width - 0.5;
|
|
2805
|
+
const y = clampedV * height - 0.5;
|
|
2806
|
+
const x0 = (Math.floor(x) % width + width) % width;
|
|
2807
|
+
const y0 = clamp(Math.floor(y), 0, height - 1);
|
|
2808
|
+
const x1 = (x0 + 1) % width;
|
|
2809
|
+
const y1 = clamp(y0 + 1, 0, height - 1);
|
|
2810
|
+
const tx = x - Math.floor(x);
|
|
2811
|
+
const ty = y - Math.floor(y);
|
|
2812
|
+
const read = (px, py) => {
|
|
2813
|
+
const offset = (py * width + px) * 4;
|
|
2814
|
+
return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]];
|
|
1781
2815
|
};
|
|
2816
|
+
const a = read(x0, y0);
|
|
2817
|
+
const b = read(x1, y0);
|
|
2818
|
+
const c = read(x0, y1);
|
|
2819
|
+
const d = read(x1, y1);
|
|
2820
|
+
const mixPair = (first, second, factor) => first * (1 - factor) + second * factor;
|
|
2821
|
+
return [
|
|
2822
|
+
mixPair(mixPair(a[0], b[0], tx), mixPair(c[0], d[0], tx), ty),
|
|
2823
|
+
mixPair(mixPair(a[1], b[1], tx), mixPair(c[1], d[1], tx), ty),
|
|
2824
|
+
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
2825
|
+
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty)
|
|
2826
|
+
];
|
|
2827
|
+
}
|
|
2828
|
+
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
2829
|
+
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
2830
|
+
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
2831
|
+
const u = ((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI) + 0.5 + rotationTurns) % 1 + 1) % 1;
|
|
2832
|
+
const v = Math.acos(clamp(unitDirection[1], -1, 1)) / Math.PI;
|
|
2833
|
+
return [u, clamp(v, 0, 1)];
|
|
2834
|
+
}
|
|
2835
|
+
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
2836
|
+
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
2837
|
+
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
2838
|
+
}
|
|
2839
|
+
function createFloat16RgbaUploadFromLevels(levels) {
|
|
2840
|
+
return levels.map((level) => {
|
|
2841
|
+
const rowBytes = level.width * 8;
|
|
2842
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2843
|
+
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
2844
|
+
const view = new DataView(bytes.buffer);
|
|
2845
|
+
for (let y = 0; y < level.height; y += 1) {
|
|
2846
|
+
for (let x = 0; x < level.width; x += 1) {
|
|
2847
|
+
const sourceOffset = (y * level.width + x) * 4;
|
|
2848
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
2849
|
+
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
2850
|
+
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
2851
|
+
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
2852
|
+
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
return Object.freeze({ bytes, bytesPerRow, width: level.width, height: level.height });
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
function createPrefilteredEnvironmentLevels(environmentMap, fallbackColor) {
|
|
2859
|
+
const sourcePixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
2860
|
+
const sourceWidth = Math.max(1, environmentMap.width);
|
|
2861
|
+
const sourceHeight = Math.max(1, environmentMap.height);
|
|
2862
|
+
const mipLevelCount = Math.max(1, Math.floor(Math.log2(Math.max(sourceWidth, sourceHeight))) + 1);
|
|
2863
|
+
const levels = [
|
|
2864
|
+
Object.freeze({
|
|
2865
|
+
width: sourceWidth,
|
|
2866
|
+
height: sourceHeight,
|
|
2867
|
+
data: sourcePixels
|
|
2868
|
+
})
|
|
2869
|
+
];
|
|
2870
|
+
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
2871
|
+
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
2872
|
+
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
2873
|
+
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
2874
|
+
const data = new Float32Array(width * height * 4);
|
|
2875
|
+
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
2876
|
+
for (let y = 0; y < height; y += 1) {
|
|
2877
|
+
for (let x = 0; x < width; x += 1) {
|
|
2878
|
+
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
2879
|
+
const normal = normalize(direction, [0, 1, 0]);
|
|
2880
|
+
const viewDirection = normal;
|
|
2881
|
+
let totalWeight = 0;
|
|
2882
|
+
const accum = [0, 0, 0];
|
|
2883
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
2884
|
+
const xi = hammersley(sampleIndex, sampleCount);
|
|
2885
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2886
|
+
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
2887
|
+
const lightDirection = normalize(
|
|
2888
|
+
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
2889
|
+
normal
|
|
2890
|
+
);
|
|
2891
|
+
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
2892
|
+
if (nDotL <= 1e-6) {
|
|
2893
|
+
continue;
|
|
2894
|
+
}
|
|
2895
|
+
const radiance = sampleEnvironmentRadiance(
|
|
2896
|
+
sourcePixels,
|
|
2897
|
+
sourceWidth,
|
|
2898
|
+
sourceHeight,
|
|
2899
|
+
lightDirection,
|
|
2900
|
+
environmentMap.rotationRadians
|
|
2901
|
+
);
|
|
2902
|
+
accum[0] += radiance[0] * nDotL;
|
|
2903
|
+
accum[1] += radiance[1] * nDotL;
|
|
2904
|
+
accum[2] += radiance[2] * nDotL;
|
|
2905
|
+
totalWeight += nDotL;
|
|
2906
|
+
}
|
|
2907
|
+
const offset = (y * width + x) * 4;
|
|
2908
|
+
data[offset] = accum[0] / Math.max(totalWeight, 1e-6);
|
|
2909
|
+
data[offset + 1] = accum[1] / Math.max(totalWeight, 1e-6);
|
|
2910
|
+
data[offset + 2] = accum[2] / Math.max(totalWeight, 1e-6);
|
|
2911
|
+
data[offset + 3] = 1;
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
levels.push(Object.freeze({ width, height, data }));
|
|
2915
|
+
}
|
|
2916
|
+
return Object.freeze({
|
|
2917
|
+
levels,
|
|
2918
|
+
mipLevelCount,
|
|
2919
|
+
width: sourceWidth,
|
|
2920
|
+
height: sourceHeight
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
function createEnvironmentSamplingTables(environmentMap, fallbackColor) {
|
|
2924
|
+
if (!environmentMapHasSamplingData(environmentMap)) {
|
|
2925
|
+
return Object.freeze({
|
|
2926
|
+
width: 1,
|
|
2927
|
+
height: 1,
|
|
2928
|
+
pdf: new Float32Array([1]),
|
|
2929
|
+
marginalCdf: new Float32Array([1]),
|
|
2930
|
+
conditionalCdf: new Float32Array([1]),
|
|
2931
|
+
hasImportanceData: false
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
const pixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
2935
|
+
const width = Math.max(1, environmentMap.width);
|
|
2936
|
+
const height = Math.max(1, environmentMap.height);
|
|
2937
|
+
const pdf = new Float32Array(width * height);
|
|
2938
|
+
const marginalCdf = new Float32Array(height);
|
|
2939
|
+
const conditionalCdf = new Float32Array(width * height);
|
|
2940
|
+
const rowSums = new Float32Array(height);
|
|
2941
|
+
let totalWeight = 0;
|
|
1782
2942
|
for (let y = 0; y < height; y += 1) {
|
|
2943
|
+
const theta = (y + 0.5) / height * Math.PI;
|
|
2944
|
+
const sinTheta = Math.max(Math.sin(theta), 1e-4);
|
|
2945
|
+
let rowWeight = 0;
|
|
1783
2946
|
for (let x = 0; x < width; x += 1) {
|
|
1784
|
-
const
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
2947
|
+
const offset = (y * width + x) * 4;
|
|
2948
|
+
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
2949
|
+
const weight = Math.max(luminance * sinTheta, 1e-6);
|
|
2950
|
+
pdf[y * width + x] = weight;
|
|
2951
|
+
rowWeight += weight;
|
|
2952
|
+
conditionalCdf[y * width + x] = rowWeight;
|
|
2953
|
+
}
|
|
2954
|
+
rowSums[y] = rowWeight;
|
|
2955
|
+
totalWeight += rowWeight;
|
|
2956
|
+
if (rowWeight > 0) {
|
|
2957
|
+
for (let x = 0; x < width; x += 1) {
|
|
2958
|
+
conditionalCdf[y * width + x] /= rowWeight;
|
|
2959
|
+
}
|
|
2960
|
+
} else {
|
|
2961
|
+
for (let x = 0; x < width; x += 1) {
|
|
2962
|
+
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
2963
|
+
}
|
|
1790
2964
|
}
|
|
2965
|
+
marginalCdf[y] = totalWeight;
|
|
2966
|
+
}
|
|
2967
|
+
for (let y = 0; y < height; y += 1) {
|
|
2968
|
+
marginalCdf[y] /= Math.max(totalWeight, 1e-6);
|
|
2969
|
+
}
|
|
2970
|
+
for (let index = 0; index < pdf.length; index += 1) {
|
|
2971
|
+
pdf[index] /= Math.max(totalWeight, 1e-6);
|
|
1791
2972
|
}
|
|
1792
2973
|
return Object.freeze({
|
|
1793
|
-
bytes,
|
|
1794
|
-
bytesPerRow,
|
|
1795
2974
|
width,
|
|
1796
|
-
height
|
|
2975
|
+
height,
|
|
2976
|
+
pdf,
|
|
2977
|
+
marginalCdf,
|
|
2978
|
+
conditionalCdf,
|
|
2979
|
+
hasImportanceData: true
|
|
1797
2980
|
});
|
|
1798
2981
|
}
|
|
1799
2982
|
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
@@ -1805,10 +2988,14 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1805
2988
|
addressModeU: "repeat",
|
|
1806
2989
|
addressModeV: "clamp-to-edge",
|
|
1807
2990
|
magFilter: "linear",
|
|
1808
|
-
minFilter: "linear"
|
|
2991
|
+
minFilter: "linear",
|
|
2992
|
+
mipmapFilter: "linear"
|
|
1809
2993
|
}),
|
|
1810
2994
|
texture: null,
|
|
1811
|
-
ownsTexture: false
|
|
2995
|
+
ownsTexture: false,
|
|
2996
|
+
width: Math.max(1, environmentMap.width),
|
|
2997
|
+
height: Math.max(1, environmentMap.height),
|
|
2998
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
1812
2999
|
});
|
|
1813
3000
|
}
|
|
1814
3001
|
if (environmentMap.texture && typeof environmentMap.texture.createView === "function") {
|
|
@@ -1819,15 +3006,91 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1819
3006
|
addressModeU: "repeat",
|
|
1820
3007
|
addressModeV: "clamp-to-edge",
|
|
1821
3008
|
magFilter: "linear",
|
|
1822
|
-
minFilter: "linear"
|
|
3009
|
+
minFilter: "linear",
|
|
3010
|
+
mipmapFilter: "linear"
|
|
1823
3011
|
}),
|
|
1824
3012
|
texture: environmentMap.texture,
|
|
1825
|
-
ownsTexture: false
|
|
3013
|
+
ownsTexture: false,
|
|
3014
|
+
width: Math.max(1, environmentMap.width),
|
|
3015
|
+
height: Math.max(1, environmentMap.height),
|
|
3016
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
1826
3017
|
});
|
|
1827
3018
|
}
|
|
1828
|
-
const
|
|
3019
|
+
const prefiltered = createPrefilteredEnvironmentLevels(environmentMap, fallbackColor);
|
|
3020
|
+
const uploads = createFloat16RgbaUploadFromLevels(prefiltered.levels);
|
|
1829
3021
|
const texture = device.createTexture({
|
|
1830
3022
|
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
3023
|
+
size: { width: prefiltered.width, height: prefiltered.height },
|
|
3024
|
+
format: "rgba16float",
|
|
3025
|
+
mipLevelCount: prefiltered.mipLevelCount,
|
|
3026
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
3027
|
+
});
|
|
3028
|
+
uploads.forEach((upload, mipLevel) => {
|
|
3029
|
+
device.queue.writeTexture(
|
|
3030
|
+
{ texture, mipLevel },
|
|
3031
|
+
upload.bytes,
|
|
3032
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3033
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
3034
|
+
);
|
|
3035
|
+
});
|
|
3036
|
+
return Object.freeze({
|
|
3037
|
+
view: texture.createView(),
|
|
3038
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
3039
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
3040
|
+
addressModeU: "repeat",
|
|
3041
|
+
addressModeV: "clamp-to-edge",
|
|
3042
|
+
magFilter: "linear",
|
|
3043
|
+
minFilter: "linear",
|
|
3044
|
+
mipmapFilter: "linear"
|
|
3045
|
+
}),
|
|
3046
|
+
texture,
|
|
3047
|
+
ownsTexture: true,
|
|
3048
|
+
width: prefiltered.width,
|
|
3049
|
+
height: prefiltered.height,
|
|
3050
|
+
mipLevelCount: prefiltered.mipLevelCount
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
function createEnvironmentSamplingTextureResource(device, constants, environmentMap, fallbackColor) {
|
|
3054
|
+
const tables = createEnvironmentSamplingTables(environmentMap, fallbackColor);
|
|
3055
|
+
const rowBytes = tables.width * 8;
|
|
3056
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
3057
|
+
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
3058
|
+
const view = new DataView(bytes.buffer);
|
|
3059
|
+
for (let y = 0; y < tables.height; y += 1) {
|
|
3060
|
+
for (let x = 0; x < tables.width; x += 1) {
|
|
3061
|
+
const probability = tables.pdf[y * tables.width + x];
|
|
3062
|
+
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
3063
|
+
const marginal = tables.marginalCdf[y];
|
|
3064
|
+
const offset = y * bytesPerRow + x * 8;
|
|
3065
|
+
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
3066
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
3067
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
3068
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
const texture = device.createTexture({
|
|
3072
|
+
label: "plasius.wavefront.environmentSampling",
|
|
3073
|
+
size: { width: tables.width, height: tables.height },
|
|
3074
|
+
format: "rgba16float",
|
|
3075
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
3076
|
+
});
|
|
3077
|
+
device.queue.writeTexture(
|
|
3078
|
+
{ texture },
|
|
3079
|
+
bytes,
|
|
3080
|
+
{ bytesPerRow, rowsPerImage: tables.height },
|
|
3081
|
+
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
3082
|
+
);
|
|
3083
|
+
return Object.freeze({
|
|
3084
|
+
view: texture.createView(),
|
|
3085
|
+
texture,
|
|
3086
|
+
ownsTexture: true,
|
|
3087
|
+
hasImportanceData: tables.hasImportanceData
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE) {
|
|
3091
|
+
const upload = createBrdfLutUploadBytes(size);
|
|
3092
|
+
const texture = device.createTexture({
|
|
3093
|
+
label: "plasius.wavefront.brdfLut",
|
|
1831
3094
|
size: { width: upload.width, height: upload.height },
|
|
1832
3095
|
format: "rgba16float",
|
|
1833
3096
|
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
@@ -1840,14 +3103,110 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1840
3103
|
);
|
|
1841
3104
|
return Object.freeze({
|
|
1842
3105
|
view: texture.createView(),
|
|
1843
|
-
sampler:
|
|
1844
|
-
label: "plasius.wavefront.
|
|
1845
|
-
addressModeU: "
|
|
3106
|
+
sampler: device.createSampler({
|
|
3107
|
+
label: "plasius.wavefront.brdfLutSampler",
|
|
3108
|
+
addressModeU: "clamp-to-edge",
|
|
1846
3109
|
addressModeV: "clamp-to-edge",
|
|
1847
3110
|
magFilter: "linear",
|
|
1848
3111
|
minFilter: "linear"
|
|
1849
3112
|
}),
|
|
1850
3113
|
texture,
|
|
3114
|
+
ownsTexture: true,
|
|
3115
|
+
width: upload.width,
|
|
3116
|
+
height: upload.height
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
function createMediumTextureResource(device, constants, mediums) {
|
|
3120
|
+
const normalized = Array.isArray(mediums) && mediums.length > 0 ? mediums : [{ id: 0 }];
|
|
3121
|
+
const width = Math.max(
|
|
3122
|
+
1,
|
|
3123
|
+
normalized.reduce((maximum, medium) => Math.max(maximum, medium.id ?? 0), 0) + 1
|
|
3124
|
+
);
|
|
3125
|
+
const level = {
|
|
3126
|
+
width,
|
|
3127
|
+
height: MEDIUM_TABLE_ROWS,
|
|
3128
|
+
data: new Float32Array(width * MEDIUM_TABLE_ROWS * 4)
|
|
3129
|
+
};
|
|
3130
|
+
for (const medium of normalized) {
|
|
3131
|
+
const mediumId = Math.max(0, Math.trunc(Number(medium.id) || 0));
|
|
3132
|
+
const absorptionOffset = mediumId * 4;
|
|
3133
|
+
level.data[absorptionOffset] = Math.max(0, medium.absorption?.[0] ?? 0);
|
|
3134
|
+
level.data[absorptionOffset + 1] = Math.max(0, medium.absorption?.[1] ?? 0);
|
|
3135
|
+
level.data[absorptionOffset + 2] = Math.max(0, medium.absorption?.[2] ?? 0);
|
|
3136
|
+
level.data[absorptionOffset + 3] = Math.max(0, medium.phaseModel ?? 0);
|
|
3137
|
+
const scatteringOffset = (width + mediumId) * 4;
|
|
3138
|
+
level.data[scatteringOffset] = Math.max(0, medium.scattering?.[0] ?? 0);
|
|
3139
|
+
level.data[scatteringOffset + 1] = Math.max(0, medium.scattering?.[1] ?? 0);
|
|
3140
|
+
level.data[scatteringOffset + 2] = Math.max(0, medium.scattering?.[2] ?? 0);
|
|
3141
|
+
level.data[scatteringOffset + 3] = Math.max(0, medium.density ?? 0);
|
|
3142
|
+
}
|
|
3143
|
+
const upload = createFloat16RgbaUploadFromLevels([level])[0];
|
|
3144
|
+
const texture = device.createTexture({
|
|
3145
|
+
label: "plasius.wavefront.mediumTable",
|
|
3146
|
+
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
3147
|
+
format: "rgba16float",
|
|
3148
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
3149
|
+
});
|
|
3150
|
+
device.queue.writeTexture(
|
|
3151
|
+
{ texture },
|
|
3152
|
+
upload.bytes,
|
|
3153
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3154
|
+
{ width, height: MEDIUM_TABLE_ROWS, depthOrArrayLayers: 1 }
|
|
3155
|
+
);
|
|
3156
|
+
return Object.freeze({
|
|
3157
|
+
texture,
|
|
3158
|
+
view: texture.createView(),
|
|
3159
|
+
ownsTexture: true,
|
|
3160
|
+
count: normalized.length,
|
|
3161
|
+
width
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
3164
|
+
function mediumTablesEqual(left, right) {
|
|
3165
|
+
const leftMediums = Array.isArray(left) ? left : [];
|
|
3166
|
+
const rightMediums = Array.isArray(right) ? right : [];
|
|
3167
|
+
if (leftMediums.length !== rightMediums.length) {
|
|
3168
|
+
return false;
|
|
3169
|
+
}
|
|
3170
|
+
for (let index = 0; index < leftMediums.length; index += 1) {
|
|
3171
|
+
const leftMedium = leftMediums[index];
|
|
3172
|
+
const rightMedium = rightMediums[index];
|
|
3173
|
+
if ((leftMedium?.id ?? 0) !== (rightMedium?.id ?? 0)) {
|
|
3174
|
+
return false;
|
|
3175
|
+
}
|
|
3176
|
+
if ((leftMedium?.phaseModel ?? 0) !== (rightMedium?.phaseModel ?? 0)) {
|
|
3177
|
+
return false;
|
|
3178
|
+
}
|
|
3179
|
+
if ((leftMedium?.density ?? 0) !== (rightMedium?.density ?? 0)) {
|
|
3180
|
+
return false;
|
|
3181
|
+
}
|
|
3182
|
+
for (let component = 0; component < 3; component += 1) {
|
|
3183
|
+
if ((leftMedium?.absorption?.[component] ?? 0) !== (rightMedium?.absorption?.[component] ?? 0)) {
|
|
3184
|
+
return false;
|
|
3185
|
+
}
|
|
3186
|
+
if ((leftMedium?.scattering?.[component] ?? 0) !== (rightMedium?.scattering?.[component] ?? 0)) {
|
|
3187
|
+
return false;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
return true;
|
|
3192
|
+
}
|
|
3193
|
+
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
3194
|
+
const upload = createRgba8TextureUpload(atlas);
|
|
3195
|
+
const texture = device.createTexture({
|
|
3196
|
+
label,
|
|
3197
|
+
size: { width: upload.width, height: upload.height },
|
|
3198
|
+
format: "rgba8unorm",
|
|
3199
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
3200
|
+
});
|
|
3201
|
+
device.queue.writeTexture(
|
|
3202
|
+
{ texture },
|
|
3203
|
+
upload.bytes,
|
|
3204
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3205
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
3206
|
+
);
|
|
3207
|
+
return Object.freeze({
|
|
3208
|
+
texture,
|
|
3209
|
+
view: texture.createView(),
|
|
1851
3210
|
ownsTexture: true
|
|
1852
3211
|
});
|
|
1853
3212
|
}
|
|
@@ -1893,6 +3252,24 @@ ${diagnostics}` : "";
|
|
|
1893
3252
|
});
|
|
1894
3253
|
}
|
|
1895
3254
|
}
|
|
3255
|
+
async function assertShaderModuleCompiles(shaderModule, label) {
|
|
3256
|
+
if (typeof shaderModule?.compilationInfo !== "function") {
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
const info = await shaderModule.compilationInfo();
|
|
3260
|
+
const messages = Array.isArray(info?.messages) ? info.messages : [];
|
|
3261
|
+
const errors = messages.filter((message) => message?.type === "error");
|
|
3262
|
+
if (errors.length <= 0) {
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
const diagnostics = errors.map((message) => {
|
|
3266
|
+
const line = Number.isFinite(message.lineNum) ? message.lineNum : "?";
|
|
3267
|
+
const column = Number.isFinite(message.linePos) ? message.linePos : "?";
|
|
3268
|
+
return `line ${line}:${column} ${message.message}`;
|
|
3269
|
+
}).join("\n");
|
|
3270
|
+
throw new Error(`WGSL compilation preflight failed for ${label}:
|
|
3271
|
+
${diagnostics}`);
|
|
3272
|
+
}
|
|
1896
3273
|
async function createRenderPipeline(device, descriptor) {
|
|
1897
3274
|
if (typeof device.createRenderPipelineAsync === "function") {
|
|
1898
3275
|
return device.createRenderPipelineAsync(descriptor);
|
|
@@ -1901,6 +3278,7 @@ async function createRenderPipeline(device, descriptor) {
|
|
|
1901
3278
|
}
|
|
1902
3279
|
var WAVEFRONT_COMPUTE_WGSL = `
|
|
1903
3280
|
const RAY_FLAG_GUIDED_EMISSIVE: u32 = 1u;
|
|
3281
|
+
const RAY_FLAG_DELTA_SAMPLE: u32 = 2u;
|
|
1904
3282
|
|
|
1905
3283
|
struct RayRecord {
|
|
1906
3284
|
rayId: u32,
|
|
@@ -1926,11 +3304,12 @@ struct HitRecord {
|
|
|
1926
3304
|
primitiveId: u32,
|
|
1927
3305
|
materialRefId: u32,
|
|
1928
3306
|
mediumRefId: u32,
|
|
3307
|
+
materialSlot: u32,
|
|
1929
3308
|
pad0: u32,
|
|
1930
3309
|
pad1: u32,
|
|
1931
|
-
pad2: u32,
|
|
1932
3310
|
distance: f32,
|
|
1933
|
-
|
|
3311
|
+
occlusion: f32,
|
|
3312
|
+
pad2: vec2<f32>,
|
|
1934
3313
|
position: vec4<f32>,
|
|
1935
3314
|
geometricNormal: vec4<f32>,
|
|
1936
3315
|
shadingNormal: vec4<f32>,
|
|
@@ -1939,6 +3318,9 @@ struct HitRecord {
|
|
|
1939
3318
|
color: vec4<f32>,
|
|
1940
3319
|
emission: vec4<f32>,
|
|
1941
3320
|
material: vec4<f32>,
|
|
3321
|
+
materialResponse: vec4<f32>,
|
|
3322
|
+
materialExtension: vec4<f32>,
|
|
3323
|
+
specularColor: vec4<f32>,
|
|
1942
3324
|
};
|
|
1943
3325
|
|
|
1944
3326
|
struct SceneObject {
|
|
@@ -1946,11 +3328,18 @@ struct SceneObject {
|
|
|
1946
3328
|
objectId: u32,
|
|
1947
3329
|
materialKind: u32,
|
|
1948
3330
|
flags: u32,
|
|
3331
|
+
mediumRefId: u32,
|
|
3332
|
+
pad0: u32,
|
|
3333
|
+
pad1: u32,
|
|
3334
|
+
pad2: u32,
|
|
1949
3335
|
center: vec4<f32>,
|
|
1950
3336
|
halfExtent: vec4<f32>,
|
|
1951
3337
|
color: vec4<f32>,
|
|
1952
3338
|
emission: vec4<f32>,
|
|
1953
3339
|
material: vec4<f32>,
|
|
3340
|
+
materialResponse: vec4<f32>,
|
|
3341
|
+
materialExtension: vec4<f32>,
|
|
3342
|
+
specularColor: vec4<f32>,
|
|
1954
3343
|
};
|
|
1955
3344
|
|
|
1956
3345
|
struct TriangleRecord {
|
|
@@ -1960,7 +3349,7 @@ struct TriangleRecord {
|
|
|
1960
3349
|
flags: u32,
|
|
1961
3350
|
materialRefId: u32,
|
|
1962
3351
|
mediumRefId: u32,
|
|
1963
|
-
|
|
3352
|
+
materialSlot: u32,
|
|
1964
3353
|
pad1: u32,
|
|
1965
3354
|
v0: vec4<f32>,
|
|
1966
3355
|
v1: vec4<f32>,
|
|
@@ -1973,6 +3362,15 @@ struct TriangleRecord {
|
|
|
1973
3362
|
color: vec4<f32>,
|
|
1974
3363
|
emission: vec4<f32>,
|
|
1975
3364
|
material: vec4<f32>,
|
|
3365
|
+
materialResponse: vec4<f32>,
|
|
3366
|
+
materialExtension: vec4<f32>,
|
|
3367
|
+
specularColor: vec4<f32>,
|
|
3368
|
+
baseColorAtlas: vec4<f32>,
|
|
3369
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3370
|
+
normalAtlas: vec4<f32>,
|
|
3371
|
+
occlusionAtlas: vec4<f32>,
|
|
3372
|
+
emissiveAtlas: vec4<f32>,
|
|
3373
|
+
textureSettings: vec4<f32>,
|
|
1976
3374
|
};
|
|
1977
3375
|
|
|
1978
3376
|
struct BvhNode {
|
|
@@ -1993,10 +3391,10 @@ struct BvhLeafRef {
|
|
|
1993
3391
|
|
|
1994
3392
|
struct ScatterResult {
|
|
1995
3393
|
direction: vec4<f32>,
|
|
3394
|
+
pdf: f32,
|
|
3395
|
+
mediumRefId: u32,
|
|
1996
3396
|
flags: u32,
|
|
1997
3397
|
pad0: u32,
|
|
1998
|
-
pad1: u32,
|
|
1999
|
-
pad2: u32,
|
|
2000
3398
|
};
|
|
2001
3399
|
|
|
2002
3400
|
struct MeshVertex {
|
|
@@ -2017,10 +3415,19 @@ struct MeshRange {
|
|
|
2017
3415
|
triangleCount: u32,
|
|
2018
3416
|
firstVertex: u32,
|
|
2019
3417
|
vertexCount: u32,
|
|
2020
|
-
|
|
3418
|
+
materialSlot: u32,
|
|
2021
3419
|
color: vec4<f32>,
|
|
2022
3420
|
emission: vec4<f32>,
|
|
2023
3421
|
material: vec4<f32>,
|
|
3422
|
+
materialResponse: vec4<f32>,
|
|
3423
|
+
materialExtension: vec4<f32>,
|
|
3424
|
+
specularColor: vec4<f32>,
|
|
3425
|
+
baseColorAtlas: vec4<f32>,
|
|
3426
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3427
|
+
normalAtlas: vec4<f32>,
|
|
3428
|
+
occlusionAtlas: vec4<f32>,
|
|
3429
|
+
emissiveAtlas: vec4<f32>,
|
|
3430
|
+
textureSettings: vec4<f32>,
|
|
2024
3431
|
};
|
|
2025
3432
|
|
|
2026
3433
|
struct FrameConfig {
|
|
@@ -2061,6 +3468,7 @@ struct FrameConfig {
|
|
|
2061
3468
|
_portalPad1: u32,
|
|
2062
3469
|
environmentMapSettings: vec4<f32>,
|
|
2063
3470
|
pathResolveSettings: vec4<f32>,
|
|
3471
|
+
environmentMapMeta: vec4<f32>,
|
|
2064
3472
|
};
|
|
2065
3473
|
|
|
2066
3474
|
struct Counters {
|
|
@@ -2123,6 +3531,16 @@ struct EnvironmentPortal {
|
|
|
2123
3531
|
@group(0) @binding(20) var environmentMapTexture: texture_2d<f32>;
|
|
2124
3532
|
@group(0) @binding(21) var environmentMapSampler: sampler;
|
|
2125
3533
|
@group(0) @binding(22) var<storage, read_write> pathVertices: array<vec4<f32>>;
|
|
3534
|
+
@group(0) @binding(23) var baseColorAtlasTexture: texture_2d<f32>;
|
|
3535
|
+
@group(0) @binding(24) var metallicRoughnessAtlasTexture: texture_2d<f32>;
|
|
3536
|
+
@group(0) @binding(25) var normalAtlasTexture: texture_2d<f32>;
|
|
3537
|
+
@group(0) @binding(26) var occlusionAtlasTexture: texture_2d<f32>;
|
|
3538
|
+
@group(0) @binding(27) var emissiveAtlasTexture: texture_2d<f32>;
|
|
3539
|
+
@group(0) @binding(28) var materialAtlasSampler: sampler;
|
|
3540
|
+
@group(0) @binding(29) var brdfLutTexture: texture_2d<f32>;
|
|
3541
|
+
@group(0) @binding(30) var brdfLutSampler: sampler;
|
|
3542
|
+
@group(0) @binding(31) var environmentSamplingTexture: texture_2d<f32>;
|
|
3543
|
+
@group(0) @binding(32) var mediumTableTexture: texture_2d<f32>;
|
|
2126
3544
|
|
|
2127
3545
|
fn hash_u32(value: u32) -> u32 {
|
|
2128
3546
|
var x = value;
|
|
@@ -2159,6 +3577,146 @@ fn safe_normalize(value: vec3<f32>, fallback: vec3<f32>) -> vec3<f32> {
|
|
|
2159
3577
|
return value / len;
|
|
2160
3578
|
}
|
|
2161
3579
|
|
|
3580
|
+
struct TangentBasis {
|
|
3581
|
+
tangent: vec3<f32>,
|
|
3582
|
+
bitangent: vec3<f32>,
|
|
3583
|
+
};
|
|
3584
|
+
|
|
3585
|
+
struct SurfaceMaterialSample {
|
|
3586
|
+
color: vec4<f32>,
|
|
3587
|
+
emission: vec4<f32>,
|
|
3588
|
+
material: vec4<f32>,
|
|
3589
|
+
materialResponse: vec4<f32>,
|
|
3590
|
+
materialExtension: vec4<f32>,
|
|
3591
|
+
specularColor: vec4<f32>,
|
|
3592
|
+
shadingNormal: vec3<f32>,
|
|
3593
|
+
occlusion: f32,
|
|
3594
|
+
};
|
|
3595
|
+
|
|
3596
|
+
fn srgb_to_linear_channel(value: f32) -> f32 {
|
|
3597
|
+
if (value <= 0.04045) {
|
|
3598
|
+
return value / 12.92;
|
|
3599
|
+
}
|
|
3600
|
+
return pow((value + 0.055) / 1.055, 2.4);
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
fn srgb_to_linear_vec3(value: vec3<f32>) -> vec3<f32> {
|
|
3604
|
+
return vec3<f32>(
|
|
3605
|
+
srgb_to_linear_channel(value.x),
|
|
3606
|
+
srgb_to_linear_channel(value.y),
|
|
3607
|
+
srgb_to_linear_channel(value.z)
|
|
3608
|
+
);
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
fn wrap_uv(uv: vec2<f32>) -> vec2<f32> {
|
|
3612
|
+
return fract(fract(uv) + vec2<f32>(1.0));
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
fn atlas_sample_uv(rect: vec4<f32>, uv: vec2<f32>) -> vec2<f32> {
|
|
3616
|
+
let local = wrap_uv(uv);
|
|
3617
|
+
let clamped = clamp(local, vec2<f32>(0.001), vec2<f32>(0.999));
|
|
3618
|
+
return rect.xy + clamped * rect.zw;
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
fn sample_atlas(textureRef: texture_2d<f32>, rect: vec4<f32>, uv: vec2<f32>) -> vec4<f32> {
|
|
3622
|
+
return textureSampleLevel(textureRef, materialAtlasSampler, atlas_sample_uv(rect, uv), 0.0);
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
fn build_triangle_tangent_basis(
|
|
3626
|
+
triangle: TriangleRecord,
|
|
3627
|
+
fallbackNormal: vec3<f32>
|
|
3628
|
+
) -> TangentBasis {
|
|
3629
|
+
let edge1 = triangle.v1.xyz - triangle.v0.xyz;
|
|
3630
|
+
let edge2 = triangle.v2.xyz - triangle.v0.xyz;
|
|
3631
|
+
let uv0 = triangle.uv0uv1.xy;
|
|
3632
|
+
let uv1 = triangle.uv0uv1.zw;
|
|
3633
|
+
let uv2 = triangle.uv2Pad.xy;
|
|
3634
|
+
let deltaUv1 = uv1 - uv0;
|
|
3635
|
+
let deltaUv2 = uv2 - uv0;
|
|
3636
|
+
let determinant = deltaUv1.x * deltaUv2.y - deltaUv1.y * deltaUv2.x;
|
|
3637
|
+
if (abs(determinant) <= 0.000001) {
|
|
3638
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(fallbackNormal.y) >= 0.999);
|
|
3639
|
+
let tangent = safe_normalize(cross(tangentFallback, fallbackNormal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3640
|
+
let bitangent = safe_normalize(cross(fallbackNormal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3641
|
+
return TangentBasis(tangent, bitangent);
|
|
3642
|
+
}
|
|
3643
|
+
let inverse = 1.0 / determinant;
|
|
3644
|
+
let tangent = safe_normalize(
|
|
3645
|
+
inverse * (edge1 * deltaUv2.y - edge2 * deltaUv1.y),
|
|
3646
|
+
vec3<f32>(1.0, 0.0, 0.0)
|
|
3647
|
+
);
|
|
3648
|
+
let bitangent = safe_normalize(
|
|
3649
|
+
inverse * (-edge1 * deltaUv2.x + edge2 * deltaUv1.x),
|
|
3650
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3651
|
+
);
|
|
3652
|
+
return TangentBasis(tangent, bitangent);
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
fn sample_surface_material(
|
|
3656
|
+
triangle: TriangleRecord,
|
|
3657
|
+
uv: vec2<f32>,
|
|
3658
|
+
geometricNormal: vec3<f32>,
|
|
3659
|
+
shadingNormal: vec3<f32>
|
|
3660
|
+
) -> SurfaceMaterialSample {
|
|
3661
|
+
let baseColorTexel = sample_atlas(baseColorAtlasTexture, triangle.baseColorAtlas, uv);
|
|
3662
|
+
let baseColor = vec4<f32>(
|
|
3663
|
+
clamp(triangle.color.rgb * srgb_to_linear_vec3(baseColorTexel.rgb), vec3<f32>(0.0), vec3<f32>(1.0)),
|
|
3664
|
+
clamp(triangle.color.a * baseColorTexel.a, 0.0, 1.0)
|
|
3665
|
+
);
|
|
3666
|
+
let metallicRoughnessTexel = sample_atlas(
|
|
3667
|
+
metallicRoughnessAtlasTexture,
|
|
3668
|
+
triangle.metallicRoughnessAtlas,
|
|
3669
|
+
uv
|
|
3670
|
+
);
|
|
3671
|
+
let normalTexel = sample_atlas(normalAtlasTexture, triangle.normalAtlas, uv);
|
|
3672
|
+
let occlusionTexel = sample_atlas(occlusionAtlasTexture, triangle.occlusionAtlas, uv);
|
|
3673
|
+
let emissiveTexel = sample_atlas(emissiveAtlasTexture, triangle.emissiveAtlas, uv);
|
|
3674
|
+
let normalScale = clamp(triangle.textureSettings.x, 0.0, 1.0);
|
|
3675
|
+
let tangentBasis = build_triangle_tangent_basis(triangle, geometricNormal);
|
|
3676
|
+
let tangentNormal = safe_normalize(
|
|
3677
|
+
vec3<f32>(
|
|
3678
|
+
(normalTexel.x * 2.0 - 1.0) * normalScale,
|
|
3679
|
+
(normalTexel.y * 2.0 - 1.0) * normalScale,
|
|
3680
|
+
1.0 + ((normalTexel.z * 2.0 - 1.0) - 1.0) * normalScale
|
|
3681
|
+
),
|
|
3682
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3683
|
+
);
|
|
3684
|
+
let mappedNormal = safe_normalize(
|
|
3685
|
+
tangentBasis.tangent * tangentNormal.x +
|
|
3686
|
+
tangentBasis.bitangent * tangentNormal.y +
|
|
3687
|
+
shadingNormal * tangentNormal.z,
|
|
3688
|
+
shadingNormal
|
|
3689
|
+
);
|
|
3690
|
+
let emission = vec4<f32>(
|
|
3691
|
+
max(
|
|
3692
|
+
triangle.emission.rgb *
|
|
3693
|
+
srgb_to_linear_vec3(emissiveTexel.rgb) *
|
|
3694
|
+
max(triangle.textureSettings.z, 0.0),
|
|
3695
|
+
vec3<f32>(0.0)
|
|
3696
|
+
),
|
|
3697
|
+
clamp(triangle.emission.a * emissiveTexel.a, 0.0, 1.0)
|
|
3698
|
+
);
|
|
3699
|
+
return SurfaceMaterialSample(
|
|
3700
|
+
baseColor,
|
|
3701
|
+
emission,
|
|
3702
|
+
vec4<f32>(
|
|
3703
|
+
clamp(triangle.material.x * metallicRoughnessTexel.y, 0.0, 1.0),
|
|
3704
|
+
clamp(triangle.material.y * metallicRoughnessTexel.z, 0.0, 1.0),
|
|
3705
|
+
clamp(triangle.material.z * baseColor.a, 0.0, 1.0),
|
|
3706
|
+
clamp(triangle.material.w, 1.0, 3.0)
|
|
3707
|
+
),
|
|
3708
|
+
triangle.materialResponse,
|
|
3709
|
+
triangle.materialExtension,
|
|
3710
|
+
triangle.specularColor,
|
|
3711
|
+
repair_shading_normal(geometricNormal, mappedNormal),
|
|
3712
|
+
clamp(
|
|
3713
|
+
mix(1.0, occlusionTexel.x, clamp(triangle.textureSettings.y, 0.0, 1.0)),
|
|
3714
|
+
0.0,
|
|
3715
|
+
1.0
|
|
3716
|
+
)
|
|
3717
|
+
);
|
|
3718
|
+
}
|
|
3719
|
+
|
|
2162
3720
|
fn saturate(value: f32) -> f32 {
|
|
2163
3721
|
return clamp(value, 0.0, 1.0);
|
|
2164
3722
|
}
|
|
@@ -2167,6 +3725,10 @@ fn max_component(value: vec3<f32>) -> f32 {
|
|
|
2167
3725
|
return max(max(value.x, value.y), value.z);
|
|
2168
3726
|
}
|
|
2169
3727
|
|
|
3728
|
+
fn radiance_luminance(value: vec3<f32>) -> f32 {
|
|
3729
|
+
return dot(value, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
3730
|
+
}
|
|
3731
|
+
|
|
2170
3732
|
fn environment_map_enabled() -> bool {
|
|
2171
3733
|
return config.environmentMapSettings.x > 0.5;
|
|
2172
3734
|
}
|
|
@@ -2287,12 +3849,349 @@ fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) ->
|
|
|
2287
3849
|
return scale;
|
|
2288
3850
|
}
|
|
2289
3851
|
|
|
2290
|
-
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
2291
|
-
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
2292
|
-
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
2293
|
-
let portalHit = max_component(portalScale) > 0.0001;
|
|
2294
|
-
return base_environment_radiance(rayDirection) *
|
|
2295
|
-
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
3852
|
+
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
3853
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3854
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
3855
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
3856
|
+
return base_environment_radiance(rayDirection) *
|
|
3857
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
fn direct_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
3861
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3862
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
3863
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
3864
|
+
if (
|
|
3865
|
+
config.environmentPortalCount > 0u &&
|
|
3866
|
+
config.environmentPortalMode == 2u &&
|
|
3867
|
+
!portalHit
|
|
3868
|
+
) {
|
|
3869
|
+
return vec3<f32>(0.0);
|
|
3870
|
+
}
|
|
3871
|
+
return base_environment_radiance(rayDirection) *
|
|
3872
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
fn radical_inverse_vdc(bitsValue: u32) -> f32 {
|
|
3876
|
+
var bits = bitsValue;
|
|
3877
|
+
bits = (bits << 16u) | (bits >> 16u);
|
|
3878
|
+
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xaaaaaaaau) >> 1u);
|
|
3879
|
+
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xccccccccu) >> 2u);
|
|
3880
|
+
bits = ((bits & 0x0f0f0f0fu) << 4u) | ((bits & 0xf0f0f0f0u) >> 4u);
|
|
3881
|
+
bits = ((bits & 0x00ff00ffu) << 8u) | ((bits & 0xff00ff00u) >> 8u);
|
|
3882
|
+
return f32(bits) * 2.3283064365386963e-10;
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
fn hammersley_2d(index: u32, count: u32) -> vec2<f32> {
|
|
3886
|
+
return vec2<f32>(f32(index) / max(f32(count), 1.0), radical_inverse_vdc(index));
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
fn build_basis_tangent(normal: vec3<f32>) -> vec3<f32> {
|
|
3890
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(normal.y) >= 0.999);
|
|
3891
|
+
return safe_normalize(cross(tangentFallback, normal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
fn local_to_world(local: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
3895
|
+
let tangent = build_basis_tangent(normal);
|
|
3896
|
+
let bitangent = safe_normalize(cross(normal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3897
|
+
return safe_normalize(tangent * local.x + bitangent * local.y + normal * local.z, normal);
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
fn cosine_sample_hemisphere(sample: vec2<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
3901
|
+
let phi = 6.28318530718 * sample.x;
|
|
3902
|
+
let radius = sqrt(sample.y);
|
|
3903
|
+
let x = cos(phi) * radius;
|
|
3904
|
+
let y = sin(phi) * radius;
|
|
3905
|
+
let z = sqrt(max(0.0, 1.0 - sample.y));
|
|
3906
|
+
return local_to_world(vec3<f32>(x, y, z), normal);
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
fn importance_sample_ggx(sample: vec2<f32>, roughness: f32, normal: vec3<f32>) -> vec3<f32> {
|
|
3910
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
3911
|
+
let phi = 6.28318530718 * sample.x;
|
|
3912
|
+
let cosTheta = sqrt((1.0 - sample.y) / max(1.0 + (alpha * alpha - 1.0) * sample.y, 0.0001));
|
|
3913
|
+
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3914
|
+
let localHalf = vec3<f32>(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
|
3915
|
+
return local_to_world(localHalf, normal);
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
fn distribution_ggx(normal: vec3<f32>, halfVector: vec3<f32>, roughness: f32) -> f32 {
|
|
3919
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
3920
|
+
let alpha2 = alpha * alpha;
|
|
3921
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
3922
|
+
let denominator = nDotH * nDotH * (alpha2 - 1.0) + 1.0;
|
|
3923
|
+
return alpha2 / max(3.14159265359 * denominator * denominator, 0.000001);
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
fn geometry_schlick_ggx(nDotValue: f32, roughness: f32) -> f32 {
|
|
3927
|
+
let k = ((roughness + 1.0) * (roughness + 1.0)) / 8.0;
|
|
3928
|
+
return nDotValue / max(nDotValue * (1.0 - k) + k, 0.000001);
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
fn geometry_smith(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
3932
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
3933
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
3934
|
+
return geometry_schlick_ggx(nDotV, roughness) * geometry_schlick_ggx(nDotL, roughness);
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
fn fresnel_schlick(cosine: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
3938
|
+
return f0 + (vec3<f32>(1.0) - f0) * pow(1.0 - cosine, 5.0);
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
fn sample_brdf_lut(nDotV: f32, roughness: f32) -> vec2<f32> {
|
|
3942
|
+
let uv = vec2<f32>(clamp(nDotV, 0.0, 1.0), clamp(roughness, 0.0, 1.0));
|
|
3943
|
+
return textureSampleLevel(brdfLutTexture, brdfLutSampler, uv, 0.0).xy;
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
fn prefiltered_environment_radiance(direction: vec3<f32>, roughness: f32) -> vec3<f32> {
|
|
3947
|
+
let uv = environment_map_uv(direction);
|
|
3948
|
+
let maxLevel = max(config.environmentMapMeta.z - 1.0, 0.0);
|
|
3949
|
+
let lod = clamp(roughness, 0.0, 1.0) * maxLevel;
|
|
3950
|
+
let texel = max(textureSampleLevel(environmentMapTexture, environmentMapSampler, uv, lod).rgb, vec3<f32>(0.0));
|
|
3951
|
+
return texel * max(config.environmentMapSettings.y, 0.0);
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
fn environment_pdf_dimensions() -> vec2<u32> {
|
|
3955
|
+
return vec2<u32>(
|
|
3956
|
+
max(u32(config.environmentMapMeta.x), 1u),
|
|
3957
|
+
max(u32(config.environmentMapMeta.y), 1u)
|
|
3958
|
+
);
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
fn environment_importance_sampling_enabled() -> bool {
|
|
3962
|
+
return config.environmentMapMeta.w > 0.5;
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
fn uniform_sphere_pdf() -> f32 {
|
|
3966
|
+
return 1.0 / (4.0 * 3.14159265359);
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
fn sample_uniform_sphere_direction(sample: vec2<f32>) -> vec3<f32> {
|
|
3970
|
+
let z = 1.0 - 2.0 * sample.y;
|
|
3971
|
+
let radial = sqrt(max(1.0 - z * z, 0.0));
|
|
3972
|
+
let phi = sample.x * 6.28318530718;
|
|
3973
|
+
return vec3<f32>(cos(phi) * radial, z, sin(phi) * radial);
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3976
|
+
fn environment_sampling_texel(x: u32, y: u32) -> vec4<f32> {
|
|
3977
|
+
return textureLoad(environmentSamplingTexture, vec2<i32>(i32(x), i32(y)), 0);
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
fn environment_pdf_texel(x: u32, y: u32) -> f32 {
|
|
3981
|
+
return environment_sampling_texel(x, y).x;
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
fn environment_row_cdf_texel(y: u32) -> f32 {
|
|
3985
|
+
return environment_sampling_texel(0u, y).z;
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
fn environment_column_cdf_texel(x: u32, y: u32) -> f32 {
|
|
3989
|
+
return environment_sampling_texel(x, y).y;
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
fn environment_direction_pdf(direction: vec3<f32>) -> f32 {
|
|
3993
|
+
if (!environment_importance_sampling_enabled()) {
|
|
3994
|
+
return uniform_sphere_pdf();
|
|
3995
|
+
}
|
|
3996
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3997
|
+
let uv = environment_map_uv(rayDirection);
|
|
3998
|
+
let dimensions = environment_pdf_dimensions();
|
|
3999
|
+
let width = max(f32(dimensions.x), 1.0);
|
|
4000
|
+
let height = max(f32(dimensions.y), 1.0);
|
|
4001
|
+
let x = min(u32(uv.x * width), dimensions.x - 1u);
|
|
4002
|
+
let y = min(u32(uv.y * height), dimensions.y - 1u);
|
|
4003
|
+
let discretePdf = max(environment_pdf_texel(x, y), 0.0);
|
|
4004
|
+
let sinTheta = sqrt(max(1.0 - rayDirection.y * rayDirection.y, 0.0));
|
|
4005
|
+
let solidAngle = max((2.0 * 3.14159265359 * 3.14159265359 * sinTheta) / (width * height), 0.000001);
|
|
4006
|
+
return discretePdf / solidAngle;
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
fn sample_row_cdf(count: u32, sampleValue: f32) -> u32 {
|
|
4010
|
+
if (count == 0u) {
|
|
4011
|
+
return 0u;
|
|
4012
|
+
}
|
|
4013
|
+
var low = 0u;
|
|
4014
|
+
var high = count - 1u;
|
|
4015
|
+
loop {
|
|
4016
|
+
if (low >= high) {
|
|
4017
|
+
break;
|
|
4018
|
+
}
|
|
4019
|
+
let mid = (low + high) / 2u;
|
|
4020
|
+
let cdfValue = environment_row_cdf_texel(mid);
|
|
4021
|
+
if (sampleValue <= cdfValue) {
|
|
4022
|
+
high = mid;
|
|
4023
|
+
} else {
|
|
4024
|
+
low = mid + 1u;
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
return min(low, count - 1u);
|
|
4028
|
+
}
|
|
4029
|
+
|
|
4030
|
+
fn sample_column_cdf(row: u32, count: u32, sampleValue: f32) -> u32 {
|
|
4031
|
+
if (count == 0u) {
|
|
4032
|
+
return 0u;
|
|
4033
|
+
}
|
|
4034
|
+
var low = 0u;
|
|
4035
|
+
var high = count - 1u;
|
|
4036
|
+
loop {
|
|
4037
|
+
if (low >= high) {
|
|
4038
|
+
break;
|
|
4039
|
+
}
|
|
4040
|
+
let mid = (low + high) / 2u;
|
|
4041
|
+
let cdfValue = environment_column_cdf_texel(mid, row);
|
|
4042
|
+
if (sampleValue <= cdfValue) {
|
|
4043
|
+
high = mid;
|
|
4044
|
+
} else {
|
|
4045
|
+
low = mid + 1u;
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
return min(low, count - 1u);
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
struct EnvironmentSample {
|
|
4052
|
+
direction: vec3<f32>,
|
|
4053
|
+
radiance: vec3<f32>,
|
|
4054
|
+
pdf: f32,
|
|
4055
|
+
};
|
|
4056
|
+
|
|
4057
|
+
fn sample_environment_importance(sample: vec2<f32>) -> EnvironmentSample {
|
|
4058
|
+
if (!environment_importance_sampling_enabled()) {
|
|
4059
|
+
let direction = sample_uniform_sphere_direction(sample);
|
|
4060
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), uniform_sphere_pdf());
|
|
4061
|
+
}
|
|
4062
|
+
let dimensions = environment_pdf_dimensions();
|
|
4063
|
+
let row = sample_row_cdf(dimensions.y, sample.y);
|
|
4064
|
+
let column = sample_column_cdf(row, dimensions.x, sample.x);
|
|
4065
|
+
let uv = vec2<f32>(
|
|
4066
|
+
(f32(column) + 0.5) / max(f32(dimensions.x), 1.0),
|
|
4067
|
+
(f32(row) + 0.5) / max(f32(dimensions.y), 1.0)
|
|
4068
|
+
);
|
|
4069
|
+
let theta = uv.y * 3.14159265359;
|
|
4070
|
+
let phi = (uv.x - 0.5 - config.environmentMapSettings.z / 6.28318530718) * 6.28318530718;
|
|
4071
|
+
let sinTheta = sin(theta);
|
|
4072
|
+
let direction = vec3<f32>(cos(phi) * sinTheta, cos(theta), sin(phi) * sinTheta);
|
|
4073
|
+
let pdf = environment_direction_pdf(direction);
|
|
4074
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), pdf);
|
|
4075
|
+
}
|
|
4076
|
+
|
|
4077
|
+
fn power_heuristic(pdfA: f32, pdfB: f32) -> f32 {
|
|
4078
|
+
let a2 = pdfA * pdfA;
|
|
4079
|
+
let b2 = pdfB * pdfB;
|
|
4080
|
+
return a2 / max(a2 + b2, 0.000001);
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
fn visible_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
4084
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4085
|
+
let visible = !scene_visibility_blocked(origin, rayDirection, 1000000.0);
|
|
4086
|
+
return select(vec3<f32>(0.0), direct_environment_radiance(origin, rayDirection), visible);
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
fn glossy_environment_direction(
|
|
4090
|
+
incidentDirection: vec3<f32>,
|
|
4091
|
+
normal: vec3<f32>,
|
|
4092
|
+
roughness: f32,
|
|
4093
|
+
normalBlendScale: f32
|
|
4094
|
+
) -> vec3<f32> {
|
|
4095
|
+
let reflectionDirection = reflect(incidentDirection, normal);
|
|
4096
|
+
let blend = clamp(roughness * roughness * normalBlendScale, 0.0, 0.92);
|
|
4097
|
+
return safe_normalize(mix(reflectionDirection, normal, blend), normal);
|
|
4098
|
+
}
|
|
4099
|
+
|
|
4100
|
+
fn surface_glossiness(hit: HitRecord) -> f32 {
|
|
4101
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4102
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4103
|
+
let sheen = clamp(max_component(hit.materialResponse.xyz), 0.0, 1.0);
|
|
4104
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4105
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4106
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
4107
|
+
let baseGloss =
|
|
4108
|
+
max(
|
|
4109
|
+
clearcoat,
|
|
4110
|
+
max(sheen * 0.72, max(specularWeight * (0.38 + metallic * 0.62), transmission))
|
|
4111
|
+
);
|
|
4112
|
+
return clamp(baseGloss * (1.0 - roughness * 0.72) + metallic * (1.0 - roughness) * 0.35, 0.0, 1.0);
|
|
4113
|
+
}
|
|
4114
|
+
|
|
4115
|
+
fn surface_specular_f0(hit: HitRecord, surfaceColor: vec3<f32>) -> vec3<f32> {
|
|
4116
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4117
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4118
|
+
let specularColor = clamp(hit.specularColor.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4119
|
+
let dielectricF0 = vec3<f32>(0.04) * specularWeight * specularColor;
|
|
4120
|
+
return mix(dielectricF0, surfaceColor, metallic);
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
fn surface_bsdf_sampling_weights(hit: HitRecord) -> vec3<f32> {
|
|
4124
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4125
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4126
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4127
|
+
let diffuseWeight = clamp(
|
|
4128
|
+
(1.0 - metallic) * max(1.0 - specularWeight * 0.5 - clearcoat * 0.25, 0.15),
|
|
4129
|
+
0.0,
|
|
4130
|
+
1.0
|
|
4131
|
+
);
|
|
4132
|
+
let specWeight = clamp(max(metallic, specularWeight * 0.75) * (1.0 - clearcoat * 0.5), 0.0, 1.0);
|
|
4133
|
+
let clearcoatWeight = clamp(clearcoat, 0.0, 1.0);
|
|
4134
|
+
let totalWeight = max(diffuseWeight + specWeight + clearcoatWeight, 0.000001);
|
|
4135
|
+
return vec3<f32>(
|
|
4136
|
+
diffuseWeight / totalWeight,
|
|
4137
|
+
specWeight / totalWeight,
|
|
4138
|
+
clearcoatWeight / totalWeight
|
|
4139
|
+
);
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
fn evaluate_surface_bsdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> vec3<f32> {
|
|
4143
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4144
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4145
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4146
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4147
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4148
|
+
let clearcoatRoughness = clamp(hit.materialExtension.x, 0.0, 1.0);
|
|
4149
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
4150
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
4151
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4152
|
+
if (nDotV <= 0.0 || nDotL <= 0.0) {
|
|
4153
|
+
return vec3<f32>(0.0);
|
|
4154
|
+
}
|
|
4155
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
4156
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
4157
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
4158
|
+
let fresnel = fresnel_schlick(vDotH, f0);
|
|
4159
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
4160
|
+
let geometry = geometry_smith(normal, viewDirection, lightDirection, roughness);
|
|
4161
|
+
let specular = (distribution * geometry * fresnel) / max(4.0 * nDotV * nDotL, 0.000001);
|
|
4162
|
+
let diffuseWeight = (1.0 - metallic) * (1.0 - clearcoat * 0.24) * (1.0 - clamp(max_component(fresnel), 0.0, 0.98));
|
|
4163
|
+
let diffuse = surfaceColor * diffuseWeight / 3.14159265359;
|
|
4164
|
+
let clearcoatHalf = safe_normalize(viewDirection + lightDirection, normal);
|
|
4165
|
+
let clearcoatDistribution = distribution_ggx(normal, clearcoatHalf, max(clearcoatRoughness, 0.02));
|
|
4166
|
+
let clearcoatGeometry = geometry_smith(normal, viewDirection, lightDirection, max(clearcoatRoughness, 0.02));
|
|
4167
|
+
let clearcoatFresnel = fresnel_schlick(saturate(dot(viewDirection, clearcoatHalf)), vec3<f32>(0.04));
|
|
4168
|
+
let clearcoatTerm =
|
|
4169
|
+
(clearcoatDistribution * clearcoatGeometry * clearcoatFresnel) /
|
|
4170
|
+
max(4.0 * nDotV * nDotL, 0.000001) *
|
|
4171
|
+
clearcoat;
|
|
4172
|
+
return (diffuse + specular + clearcoatTerm) * mix(0.42, 1.0, occlusion);
|
|
4173
|
+
}
|
|
4174
|
+
|
|
4175
|
+
fn diffuse_pdf(normal: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
4176
|
+
return saturate(dot(normal, lightDirection)) / 3.14159265359;
|
|
4177
|
+
}
|
|
4178
|
+
|
|
4179
|
+
fn ggx_pdf(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
4180
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
4181
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
4182
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
4183
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
4184
|
+
return (distribution * nDotH) / max(4.0 * vDotH, 0.000001);
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4187
|
+
fn evaluate_surface_bsdf_pdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
4188
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4189
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4190
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
4191
|
+
let diffuseTerm = diffuse_pdf(normal, lightDirection);
|
|
4192
|
+
let specTerm = ggx_pdf(normal, viewDirection, lightDirection, max(roughness, 0.02));
|
|
4193
|
+
let clearcoatTerm = ggx_pdf(normal, viewDirection, lightDirection, max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02));
|
|
4194
|
+
return weights.x * diffuseTerm + weights.y * specTerm + weights.z * clearcoatTerm;
|
|
2296
4195
|
}
|
|
2297
4196
|
|
|
2298
4197
|
fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
@@ -2307,12 +4206,102 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
2307
4206
|
return environment_radiance(origin, direction);
|
|
2308
4207
|
}
|
|
2309
4208
|
|
|
4209
|
+
fn medium_dimensions() -> vec2<u32> {
|
|
4210
|
+
return textureDimensions(mediumTableTexture);
|
|
4211
|
+
}
|
|
4212
|
+
|
|
4213
|
+
fn medium_valid(mediumRefId: u32) -> bool {
|
|
4214
|
+
let dimensions = medium_dimensions();
|
|
4215
|
+
return mediumRefId > 0u && mediumRefId < dimensions.x;
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
fn medium_absorption(mediumRefId: u32) -> vec3<f32> {
|
|
4219
|
+
if (!medium_valid(mediumRefId)) {
|
|
4220
|
+
return vec3<f32>(0.0);
|
|
4221
|
+
}
|
|
4222
|
+
return max(
|
|
4223
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 0), 0).xyz,
|
|
4224
|
+
vec3<f32>(0.0)
|
|
4225
|
+
);
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4228
|
+
fn medium_scattering(mediumRefId: u32) -> vec3<f32> {
|
|
4229
|
+
if (!medium_valid(mediumRefId)) {
|
|
4230
|
+
return vec3<f32>(0.0);
|
|
4231
|
+
}
|
|
4232
|
+
return max(
|
|
4233
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 1), 0).xyz,
|
|
4234
|
+
vec3<f32>(0.0)
|
|
4235
|
+
);
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
fn medium_transmittance(mediumRefId: u32, distance: f32) -> vec3<f32> {
|
|
4239
|
+
if (!medium_valid(mediumRefId) || distance <= 0.000001) {
|
|
4240
|
+
return vec3<f32>(1.0);
|
|
4241
|
+
}
|
|
4242
|
+
let extinction = medium_absorption(mediumRefId) + medium_scattering(mediumRefId);
|
|
4243
|
+
return vec3<f32>(
|
|
4244
|
+
exp(-extinction.x * distance),
|
|
4245
|
+
exp(-extinction.y * distance),
|
|
4246
|
+
exp(-extinction.z * distance)
|
|
4247
|
+
);
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
fn transmitted_medium_ref_id(ray: RayRecord, hit: HitRecord) -> u32 {
|
|
4251
|
+
if (hit.mediumRefId == 0u) {
|
|
4252
|
+
return ray.mediumRefId;
|
|
4253
|
+
}
|
|
4254
|
+
if (hit.frontFace == 1u) {
|
|
4255
|
+
return hit.mediumRefId;
|
|
4256
|
+
}
|
|
4257
|
+
if (ray.mediumRefId == hit.mediumRefId) {
|
|
4258
|
+
return 0u;
|
|
4259
|
+
}
|
|
4260
|
+
return ray.mediumRefId;
|
|
4261
|
+
}
|
|
4262
|
+
|
|
2310
4263
|
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
2311
4264
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
2312
4265
|
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
4266
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
2313
4267
|
let materialEnergy = select(0.68, 0.92, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2314
4268
|
let transparentEnergy = select(materialEnergy, 0.9, hit.hitType == 3u);
|
|
2315
|
-
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy;
|
|
4269
|
+
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy * mix(0.55, 1.0, occlusion);
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
fn bounded_path_response_luminance(ray: RayRecord, hit: HitRecord) -> f32 {
|
|
4273
|
+
let daylightFloor = max(config.pathResolveSettings.y, 0.0) * 0.08;
|
|
4274
|
+
let hdriFloor = max(config.environmentMapSettings.w, 0.0) * 0.02;
|
|
4275
|
+
let sceneFloor = max(daylightFloor, hdriFloor);
|
|
4276
|
+
if (sceneFloor <= 0.000001) {
|
|
4277
|
+
return 0.0;
|
|
4278
|
+
}
|
|
4279
|
+
let bounceRatio = select(
|
|
4280
|
+
0.0,
|
|
4281
|
+
f32(ray.bounce) / max(f32(config.maxDepth - 1u), 1.0),
|
|
4282
|
+
config.maxDepth > 1u
|
|
4283
|
+
);
|
|
4284
|
+
let bounceScale = 1.0 - bounceRatio * 0.55;
|
|
4285
|
+
let materialScale = select(1.0, 0.34, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
4286
|
+
let transparentScale = select(materialScale, 0.58, hit.hitType == 3u);
|
|
4287
|
+
let opacityScale = mix(0.55, 1.0, clamp(hit.material.z, 0.0, 1.0));
|
|
4288
|
+
return sceneFloor * bounceScale * transparentScale * opacityScale;
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
fn stabilize_surface_path_response(ray: RayRecord, hit: HitRecord, response: vec3<f32>) -> vec3<f32> {
|
|
4292
|
+
let minimumLuminance = bounded_path_response_luminance(ray, hit);
|
|
4293
|
+
let responseLuminance = radiance_luminance(response);
|
|
4294
|
+
if (minimumLuminance <= 0.000001 || responseLuminance >= minimumLuminance) {
|
|
4295
|
+
return response;
|
|
4296
|
+
}
|
|
4297
|
+
let tintBase = max(response, max(hit.color.xyz * 0.65, config.ambientColor.xyz * 0.35));
|
|
4298
|
+
let tint = tintBase / max(max_component(tintBase), 0.0001);
|
|
4299
|
+
let lifted = select(
|
|
4300
|
+
tint * minimumLuminance,
|
|
4301
|
+
response * (minimumLuminance / max(responseLuminance, 0.0001)),
|
|
4302
|
+
responseLuminance > 0.0001
|
|
4303
|
+
);
|
|
4304
|
+
return clamp(lifted, vec3<f32>(0.0), vec3<f32>(0.98));
|
|
2316
4305
|
}
|
|
2317
4306
|
|
|
2318
4307
|
fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
@@ -2331,12 +4320,24 @@ fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
|
2331
4320
|
return clamp_sample_radiance(sunTint * baseline * skyFacing * directionalWeight * 0.04);
|
|
2332
4321
|
}
|
|
2333
4322
|
|
|
2334
|
-
fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
4323
|
+
fn terminal_surface_environment_source(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2335
4324
|
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2336
|
-
let
|
|
2337
|
-
|
|
2338
|
-
|
|
4325
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4326
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4327
|
+
let glossiness = surface_glossiness(hit);
|
|
4328
|
+
let normalEnvironment = gated_environment_radiance(origin, normal);
|
|
4329
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4330
|
+
let reflectionDirection = glossy_environment_direction(
|
|
4331
|
+
ray.direction.xyz,
|
|
4332
|
+
normal,
|
|
4333
|
+
roughness,
|
|
4334
|
+
mix(0.88, 0.38, glossiness)
|
|
2339
4335
|
);
|
|
4336
|
+
let reflectionEnvironment = prefiltered_environment_radiance(reflectionDirection, roughness);
|
|
4337
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4338
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
4339
|
+
let brdfTerm = sample_brdf_lut(saturate(dot(normal, viewDirection)), roughness);
|
|
4340
|
+
let specularEnvironment = reflectionEnvironment * (f0 * brdfTerm.x + vec3<f32>(brdfTerm.y));
|
|
2340
4341
|
let sunlitFloor = sunlit_baseline_radiance(normal);
|
|
2341
4342
|
let ambientFloor = select(
|
|
2342
4343
|
max(config.ambientColor.xyz, sunlitFloor * 0.82),
|
|
@@ -2348,17 +4349,27 @@ fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
|
2348
4349
|
max(config.environmentMapSettings.w, max(0.12, config.pathResolveSettings.y * 0.42)),
|
|
2349
4350
|
environment_map_enabled()
|
|
2350
4351
|
);
|
|
2351
|
-
let
|
|
4352
|
+
let glossyEnvironment = max(
|
|
4353
|
+
normalEnvironment,
|
|
4354
|
+
max(reflectionEnvironment * mix(0.24, 0.92, glossiness), specularEnvironment)
|
|
4355
|
+
);
|
|
4356
|
+
let environmentFloor = max(ambientFloor, max(sunlitFloor, glossyEnvironment * environmentInfluence));
|
|
2352
4357
|
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
2353
4358
|
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
2354
4359
|
}
|
|
2355
4360
|
|
|
2356
|
-
fn terminal_surface_environment_contribution(
|
|
4361
|
+
fn terminal_surface_environment_contribution(
|
|
4362
|
+
ray: RayRecord,
|
|
4363
|
+
throughput: vec3<f32>,
|
|
4364
|
+
hit: HitRecord
|
|
4365
|
+
) -> vec3<f32> {
|
|
2357
4366
|
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
4367
|
+
let occlusion = mix(0.75, 1.0, clamp(hit.occlusion, 0.0, 1.0));
|
|
2358
4368
|
return clamp_sample_radiance(
|
|
2359
|
-
|
|
4369
|
+
throughput *
|
|
2360
4370
|
surfaceColor *
|
|
2361
|
-
terminal_surface_environment_source(hit)
|
|
4371
|
+
terminal_surface_environment_source(ray, hit) *
|
|
4372
|
+
occlusion
|
|
2362
4373
|
);
|
|
2363
4374
|
}
|
|
2364
4375
|
|
|
@@ -2391,6 +4402,10 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2391
4402
|
);
|
|
2392
4403
|
let area = max(portal.position.w, 0.0001);
|
|
2393
4404
|
let distanceFalloff = clamp(area / max(distanceSquared, area * 0.25), 0.0, 2.5);
|
|
4405
|
+
let traceDistance = max(sqrt(distanceSquared) - 0.01, 0.01);
|
|
4406
|
+
if (scene_visibility_blocked(origin, direction, traceDistance)) {
|
|
4407
|
+
continue;
|
|
4408
|
+
}
|
|
2394
4409
|
irradiance = irradiance +
|
|
2395
4410
|
portal.color.rgb *
|
|
2396
4411
|
portal.normal.w *
|
|
@@ -2402,48 +4417,79 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2402
4417
|
return irradiance;
|
|
2403
4418
|
}
|
|
2404
4419
|
|
|
2405
|
-
fn
|
|
2406
|
-
let
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
);
|
|
2420
|
-
let environmentIrradianceScale = select(
|
|
2421
|
-
max(0.16, config.pathResolveSettings.y * 0.45),
|
|
2422
|
-
max(config.environmentMapSettings.w, max(0.16, config.pathResolveSettings.y * 0.45)),
|
|
2423
|
-
environment_map_enabled()
|
|
4420
|
+
fn visibility_test_ray(origin: vec3<f32>, direction: vec3<f32>) -> RayRecord {
|
|
4421
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4422
|
+
return RayRecord(
|
|
4423
|
+
0u,
|
|
4424
|
+
0u,
|
|
4425
|
+
0u,
|
|
4426
|
+
0u,
|
|
4427
|
+
0u,
|
|
4428
|
+
0u,
|
|
4429
|
+
0u,
|
|
4430
|
+
0u,
|
|
4431
|
+
vec4<f32>(origin, 1.0),
|
|
4432
|
+
vec4<f32>(rayDirection, 0.0),
|
|
4433
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2424
4434
|
);
|
|
2425
|
-
|
|
4435
|
+
}
|
|
2426
4436
|
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
);
|
|
2431
|
-
let sunFacing = saturate(dot(normal, sunDirection));
|
|
2432
|
-
let sunRadiance = gated_environment_radiance(origin, sunDirection);
|
|
2433
|
-
let sunIrradiance = sunRadiance * sunFacing * 0.2;
|
|
2434
|
-
let portalIrradiance = direct_environment_portal_irradiance(origin, normal);
|
|
4437
|
+
fn scene_visibility_blocked(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> bool {
|
|
4438
|
+
let testRay = visibility_test_ray(origin, direction);
|
|
4439
|
+
let nearest = max(maxDistance, 0.001);
|
|
2435
4440
|
|
|
2436
|
-
|
|
2437
|
-
|
|
4441
|
+
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
4442
|
+
let object = sceneObjects[objectIndex];
|
|
4443
|
+
var current = no_candidate();
|
|
4444
|
+
if (object.kind == 1u) {
|
|
4445
|
+
current = intersect_sphere(testRay, object);
|
|
4446
|
+
} else if (object.kind == 2u) {
|
|
4447
|
+
current = intersect_box(testRay, object);
|
|
4448
|
+
}
|
|
4449
|
+
if (current.hit == 1u && current.distance < nearest) {
|
|
4450
|
+
return true;
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
2438
4453
|
|
|
2439
|
-
let
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
let specularTint = mix(vec3<f32>(0.04), surfaceColor, metallic);
|
|
2443
|
-
let specular = specularTint * sunRadiance * specularFacing * select(0.16, 0.48, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
4454
|
+
let meshCandidate = intersect_bvh(testRay, nearest);
|
|
4455
|
+
return meshCandidate.hit == 1u && meshCandidate.distance < nearest;
|
|
4456
|
+
}
|
|
2444
4457
|
|
|
2445
|
-
|
|
2446
|
-
|
|
4458
|
+
fn surface_direct_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
4459
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4460
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4461
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4462
|
+
let lightSample = sample_environment_importance(vec2<f32>(
|
|
4463
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 41u)),
|
|
4464
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 43u))
|
|
4465
|
+
));
|
|
4466
|
+
if (lightSample.pdf <= 0.000001) {
|
|
4467
|
+
return vec3<f32>(0.0);
|
|
4468
|
+
}
|
|
4469
|
+
let lightDirection = safe_normalize(lightSample.direction, normal);
|
|
4470
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4471
|
+
if (nDotL <= 0.000001) {
|
|
4472
|
+
return vec3<f32>(0.0);
|
|
4473
|
+
}
|
|
4474
|
+
if (scene_visibility_blocked(origin, lightDirection, 1000000.0)) {
|
|
4475
|
+
return vec3<f32>(0.0);
|
|
4476
|
+
}
|
|
4477
|
+
let incidentRadiance = direct_environment_radiance(origin, lightDirection);
|
|
4478
|
+
if (max_component(incidentRadiance) <= 0.000001) {
|
|
4479
|
+
return vec3<f32>(0.0);
|
|
4480
|
+
}
|
|
4481
|
+
let bsdf = evaluate_surface_bsdf(hit, viewDirection, lightDirection);
|
|
4482
|
+
if (max_component(bsdf) <= 0.000001) {
|
|
4483
|
+
return vec3<f32>(0.0);
|
|
4484
|
+
}
|
|
4485
|
+
let bsdfPdf = evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection);
|
|
4486
|
+
let misWeight = power_heuristic(lightSample.pdf, bsdfPdf);
|
|
4487
|
+
let contribution =
|
|
4488
|
+
ray.throughput.xyz *
|
|
4489
|
+
bsdf *
|
|
4490
|
+
incidentRadiance *
|
|
4491
|
+
(nDotL * misWeight / max(lightSample.pdf, 0.000001));
|
|
4492
|
+
return clamp_sample_radiance(contribution);
|
|
2447
4493
|
}
|
|
2448
4494
|
|
|
2449
4495
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -2462,7 +4508,16 @@ fn default_mesh_range() -> MeshRange {
|
|
|
2462
4508
|
0u,
|
|
2463
4509
|
vec4<f32>(0.72, 0.72, 0.68, 1.0),
|
|
2464
4510
|
vec4<f32>(0.0),
|
|
2465
|
-
vec4<f32>(0.72, 0.0, 1.0, 1.45)
|
|
4511
|
+
vec4<f32>(0.72, 0.0, 1.0, 1.45),
|
|
4512
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4513
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
4514
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4515
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4516
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4517
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4518
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4519
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4520
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
2466
4521
|
);
|
|
2467
4522
|
}
|
|
2468
4523
|
|
|
@@ -2558,7 +4613,7 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2558
4613
|
mesh.flags,
|
|
2559
4614
|
mesh.materialRefId,
|
|
2560
4615
|
mesh.mediumRefId,
|
|
2561
|
-
|
|
4616
|
+
mesh.materialSlot,
|
|
2562
4617
|
0u,
|
|
2563
4618
|
vec4<f32>(vertex0.position.xyz, 0.0),
|
|
2564
4619
|
vec4<f32>(vertex1.position.xyz, 0.0),
|
|
@@ -2570,7 +4625,16 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2570
4625
|
vec4<f32>(uv2, 0.0, 0.0),
|
|
2571
4626
|
mesh.color,
|
|
2572
4627
|
mesh.emission,
|
|
2573
|
-
mesh.material
|
|
4628
|
+
mesh.material,
|
|
4629
|
+
mesh.materialResponse,
|
|
4630
|
+
mesh.materialExtension,
|
|
4631
|
+
mesh.specularColor,
|
|
4632
|
+
mesh.baseColorAtlas,
|
|
4633
|
+
mesh.metallicRoughnessAtlas,
|
|
4634
|
+
mesh.normalAtlas,
|
|
4635
|
+
mesh.occlusionAtlas,
|
|
4636
|
+
mesh.emissiveAtlas,
|
|
4637
|
+
mesh.textureSettings
|
|
2574
4638
|
);
|
|
2575
4639
|
|
|
2576
4640
|
let leafBase = config.triangleCount - 1u;
|
|
@@ -2729,7 +4793,8 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2729
4793
|
0u,
|
|
2730
4794
|
0u,
|
|
2731
4795
|
-1.0,
|
|
2732
|
-
|
|
4796
|
+
1.0,
|
|
4797
|
+
vec2<f32>(0.0),
|
|
2733
4798
|
vec4<f32>(ray.origin.xyz + ray.direction.xyz * 1000.0, 1.0),
|
|
2734
4799
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
2735
4800
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
@@ -2737,7 +4802,10 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2737
4802
|
vec4<f32>(0.0),
|
|
2738
4803
|
vec4<f32>(radiance, 1.0),
|
|
2739
4804
|
vec4<f32>(0.0),
|
|
2740
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
4805
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
4806
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4807
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
4808
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2741
4809
|
);
|
|
2742
4810
|
}
|
|
2743
4811
|
|
|
@@ -2772,7 +4840,7 @@ fn intersect_sphere(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
2772
4840
|
0xffffffffu,
|
|
2773
4841
|
object.objectId,
|
|
2774
4842
|
object.objectId,
|
|
2775
|
-
|
|
4843
|
+
object.mediumRefId
|
|
2776
4844
|
);
|
|
2777
4845
|
}
|
|
2778
4846
|
|
|
@@ -2824,7 +4892,7 @@ fn intersect_box(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
2824
4892
|
0xffffffffu,
|
|
2825
4893
|
object.objectId,
|
|
2826
4894
|
object.objectId,
|
|
2827
|
-
|
|
4895
|
+
object.mediumRefId
|
|
2828
4896
|
);
|
|
2829
4897
|
}
|
|
2830
4898
|
|
|
@@ -3032,6 +5100,19 @@ fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
|
|
|
3032
5100
|
return value / (vec3<f32>(1.0) + value);
|
|
3033
5101
|
}
|
|
3034
5102
|
|
|
5103
|
+
fn denoise_sample_count() -> f32 {
|
|
5104
|
+
return clamp(1.0 / max(config.projectionAndSampling.z, 0.000001), 1.0, 256.0);
|
|
5105
|
+
}
|
|
5106
|
+
|
|
5107
|
+
fn denoise_strength() -> f32 {
|
|
5108
|
+
let spp = denoise_sample_count();
|
|
5109
|
+
return clamp(0.44 / sqrt(spp), 0.08, 0.44);
|
|
5110
|
+
}
|
|
5111
|
+
|
|
5112
|
+
fn denoise_kernel_radius() -> i32 {
|
|
5113
|
+
return select(1i, 2i, denoise_sample_count() < 2.5);
|
|
5114
|
+
}
|
|
5115
|
+
|
|
3035
5116
|
@compute @workgroup_size(64)
|
|
3036
5117
|
fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
3037
5118
|
let index = globalId.x;
|
|
@@ -3062,6 +5143,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3062
5143
|
let ray = activeQueue[index];
|
|
3063
5144
|
var nearest = 1000000.0;
|
|
3064
5145
|
var hitObject = SceneObject(
|
|
5146
|
+
0u,
|
|
5147
|
+
0u,
|
|
5148
|
+
0u,
|
|
5149
|
+
0u,
|
|
3065
5150
|
0u,
|
|
3066
5151
|
0u,
|
|
3067
5152
|
0u,
|
|
@@ -3070,7 +5155,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3070
5155
|
vec4<f32>(0.0),
|
|
3071
5156
|
vec4<f32>(0.0),
|
|
3072
5157
|
vec4<f32>(0.0),
|
|
3073
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
5158
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
5159
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
5160
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
5161
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
3074
5162
|
);
|
|
3075
5163
|
var candidate = no_candidate();
|
|
3076
5164
|
var hitTriangle = TriangleRecord(
|
|
@@ -3092,7 +5180,16 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3092
5180
|
vec4<f32>(0.0),
|
|
3093
5181
|
vec4<f32>(0.0),
|
|
3094
5182
|
vec4<f32>(0.0),
|
|
3095
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
5183
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
5184
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
5185
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
5186
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
5187
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5188
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5189
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5190
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5191
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5192
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
3096
5193
|
);
|
|
3097
5194
|
|
|
3098
5195
|
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
@@ -3125,16 +5222,28 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3125
5222
|
let position = ray.origin.xyz + ray.direction.xyz * candidate.distance;
|
|
3126
5223
|
let hitMaterialKind = select(hitObject.materialKind, hitTriangle.materialKind, candidate.triangleIndex != 0xffffffffu);
|
|
3127
5224
|
let hitObjectId = select(hitObject.objectId, hitTriangle.meshId, candidate.triangleIndex != 0xffffffffu);
|
|
3128
|
-
let
|
|
3129
|
-
|
|
3130
|
-
|
|
5225
|
+
let meshSurface = sample_surface_material(
|
|
5226
|
+
hitTriangle,
|
|
5227
|
+
candidate.uv,
|
|
5228
|
+
candidate.geometricNormal,
|
|
5229
|
+
candidate.shadingNormal
|
|
5230
|
+
);
|
|
5231
|
+
let hitColor = select(hitObject.color, meshSurface.color, candidate.triangleIndex != 0xffffffffu);
|
|
5232
|
+
let hitEmission = select(hitObject.emission, meshSurface.emission, candidate.triangleIndex != 0xffffffffu);
|
|
5233
|
+
let hitMaterial = select(hitObject.material, meshSurface.material, candidate.triangleIndex != 0xffffffffu);
|
|
5234
|
+
let hitMaterialResponse = select(hitObject.materialResponse, meshSurface.materialResponse, candidate.triangleIndex != 0xffffffffu);
|
|
5235
|
+
let hitMaterialExtension = select(hitObject.materialExtension, meshSurface.materialExtension, candidate.triangleIndex != 0xffffffffu);
|
|
5236
|
+
let hitSpecularColor = select(hitObject.specularColor, meshSurface.specularColor, candidate.triangleIndex != 0xffffffffu);
|
|
5237
|
+
let hitShadingNormal = select(candidate.shadingNormal, meshSurface.shadingNormal, candidate.triangleIndex != 0xffffffffu);
|
|
3131
5238
|
let hitPrimitiveId = select(candidate.primitiveId, hitTriangle.triangleId, candidate.triangleIndex != 0xffffffffu);
|
|
3132
5239
|
let hitMaterialRefId = select(candidate.materialRefId, hitTriangle.materialRefId, candidate.triangleIndex != 0xffffffffu);
|
|
3133
5240
|
let hitMediumRefId = select(candidate.mediumRefId, hitTriangle.mediumRefId, candidate.triangleIndex != 0xffffffffu);
|
|
5241
|
+
let hitMaterialSlot = select(0u, hitTriangle.materialSlot, candidate.triangleIndex != 0xffffffffu);
|
|
5242
|
+
let hitOcclusion = select(1.0, meshSurface.occlusion, candidate.triangleIndex != 0xffffffffu);
|
|
3134
5243
|
var hitType = 0u;
|
|
3135
5244
|
if (hitMaterialKind == 4u || emission_power(hitEmission) > 0.0001) {
|
|
3136
5245
|
hitType = 1u;
|
|
3137
|
-
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999) {
|
|
5246
|
+
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999 || hitMaterialExtension.z > 0.001) {
|
|
3138
5247
|
hitType = 3u;
|
|
3139
5248
|
}
|
|
3140
5249
|
atomicAdd(&counters.hitCount, 1u);
|
|
@@ -3148,19 +5257,23 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3148
5257
|
hitPrimitiveId,
|
|
3149
5258
|
hitMaterialRefId,
|
|
3150
5259
|
hitMediumRefId,
|
|
3151
|
-
|
|
5260
|
+
hitMaterialSlot,
|
|
3152
5261
|
0u,
|
|
3153
5262
|
0u,
|
|
3154
5263
|
candidate.distance,
|
|
3155
|
-
|
|
5264
|
+
hitOcclusion,
|
|
5265
|
+
vec2<f32>(0.0),
|
|
3156
5266
|
vec4<f32>(position, 1.0),
|
|
3157
5267
|
vec4<f32>(candidate.geometricNormal, 0.0),
|
|
3158
|
-
vec4<f32>(
|
|
5268
|
+
vec4<f32>(hitShadingNormal, 0.0),
|
|
3159
5269
|
vec4<f32>(candidate.barycentric, 0.0),
|
|
3160
5270
|
vec4<f32>(candidate.uv, 0.0, 0.0),
|
|
3161
5271
|
hitColor,
|
|
3162
5272
|
hitEmission,
|
|
3163
|
-
hitMaterial
|
|
5273
|
+
hitMaterial,
|
|
5274
|
+
hitMaterialResponse,
|
|
5275
|
+
hitMaterialExtension,
|
|
5276
|
+
hitSpecularColor
|
|
3164
5277
|
);
|
|
3165
5278
|
}
|
|
3166
5279
|
|
|
@@ -3229,60 +5342,106 @@ fn sample_environment_portal_direction(hit: HitRecord, seed: u32, fallback: vec3
|
|
|
3229
5342
|
}
|
|
3230
5343
|
|
|
3231
5344
|
fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult {
|
|
5345
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
5346
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
3232
5347
|
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3233
|
-
|
|
5348
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
5349
|
+
if (hit.materialKind == 1u && roughness <= 0.02) {
|
|
3234
5350
|
return ScatterResult(
|
|
3235
|
-
vec4<f32>(
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
),
|
|
3240
|
-
0.0
|
|
3241
|
-
),
|
|
3242
|
-
0u,
|
|
3243
|
-
0u,
|
|
5351
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5352
|
+
1.0,
|
|
5353
|
+
ray.mediumRefId,
|
|
5354
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
3244
5355
|
0u,
|
|
3245
|
-
0u
|
|
3246
5356
|
);
|
|
3247
5357
|
}
|
|
3248
5358
|
|
|
3249
|
-
if (hit.materialKind == 2u || hit.materialKind == 3u) {
|
|
5359
|
+
if (hit.materialKind == 2u || hit.materialKind == 3u || transmission > 0.001) {
|
|
3250
5360
|
let ior = max(hit.material.w, 1.01);
|
|
3251
5361
|
let etaRatio = select(ior, 1.0 / ior, hit.frontFace == 1u);
|
|
3252
|
-
let cosTheta = min(dot(-ray.direction.xyz,
|
|
5362
|
+
let cosTheta = min(dot(-ray.direction.xyz, normal), 1.0);
|
|
3253
5363
|
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3254
5364
|
let cannotRefract = etaRatio * sinTheta > 1.0;
|
|
3255
5365
|
let reflectChance = schlick(cosTheta, etaRatio);
|
|
3256
|
-
|
|
3257
|
-
|
|
5366
|
+
let transmissionReflectChance = select(
|
|
5367
|
+
reflectChance,
|
|
5368
|
+
max(reflectChance, 1.0 - transmission),
|
|
5369
|
+
transmission > 0.001
|
|
5370
|
+
);
|
|
5371
|
+
if (cannotRefract || random01(seed + 23u) < transmissionReflectChance) {
|
|
5372
|
+
return ScatterResult(
|
|
5373
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5374
|
+
1.0,
|
|
5375
|
+
ray.mediumRefId,
|
|
5376
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
5377
|
+
0u,
|
|
5378
|
+
);
|
|
3258
5379
|
}
|
|
3259
|
-
return ScatterResult(
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
let
|
|
3269
|
-
let
|
|
3270
|
-
|
|
3271
|
-
let
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
)
|
|
5380
|
+
return ScatterResult(
|
|
5381
|
+
vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
|
|
5382
|
+
1.0,
|
|
5383
|
+
transmitted_medium_ref_id(ray, hit),
|
|
5384
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
5385
|
+
0u,
|
|
5386
|
+
);
|
|
5387
|
+
}
|
|
5388
|
+
|
|
5389
|
+
let guidedEmissiveAvailable = config.emissiveTriangleCount > 0u;
|
|
5390
|
+
let guidedPortalAvailable =
|
|
5391
|
+
config.environmentPortalCount > 0u && config.environmentPortalMode != 0u;
|
|
5392
|
+
let guidedSelector = random01(seed + 17u);
|
|
5393
|
+
if (guidedEmissiveAvailable && guidedSelector < 0.18) {
|
|
5394
|
+
let guidedDirection = sample_emissive_triangle_direction(hit, seed + 101u, normal);
|
|
5395
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5396
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5397
|
+
return ScatterResult(
|
|
5398
|
+
vec4<f32>(guidedDirection, 0.0),
|
|
5399
|
+
guidedPdf,
|
|
5400
|
+
ray.mediumRefId,
|
|
5401
|
+
RAY_FLAG_GUIDED_EMISSIVE,
|
|
5402
|
+
0u,
|
|
5403
|
+
);
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
if (guidedPortalAvailable && guidedSelector < 0.32) {
|
|
5407
|
+
let guidedDirection = sample_environment_portal_direction(hit, seed + 131u, normal);
|
|
5408
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5409
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5410
|
+
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf, ray.mediumRefId, 0u, 0u);
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5414
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
5415
|
+
let selector = random01(seed + 31u);
|
|
5416
|
+
var lightDirection = normal;
|
|
5417
|
+
if (selector < weights.x) {
|
|
5418
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5419
|
+
vec2<f32>(random01(seed + 37u), random01(seed + 41u)),
|
|
5420
|
+
normal
|
|
5421
|
+
);
|
|
5422
|
+
} else if (selector < weights.x + weights.y) {
|
|
5423
|
+
let halfVector = importance_sample_ggx(
|
|
5424
|
+
vec2<f32>(random01(seed + 47u), random01(seed + 53u)),
|
|
5425
|
+
max(roughness, 0.02),
|
|
5426
|
+
normal
|
|
5427
|
+
);
|
|
5428
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5429
|
+
} else {
|
|
5430
|
+
let halfVector = importance_sample_ggx(
|
|
5431
|
+
vec2<f32>(random01(seed + 59u), random01(seed + 61u)),
|
|
5432
|
+
max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02),
|
|
5433
|
+
normal
|
|
5434
|
+
);
|
|
5435
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5436
|
+
}
|
|
5437
|
+
if (dot(normal, lightDirection) <= 0.000001) {
|
|
5438
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5439
|
+
vec2<f32>(random01(seed + 67u), random01(seed + 71u)),
|
|
5440
|
+
normal
|
|
5441
|
+
);
|
|
5442
|
+
}
|
|
5443
|
+
let pdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection), 0.000001);
|
|
5444
|
+
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf, ray.mediumRefId, 0u, 0u);
|
|
3286
5445
|
}
|
|
3287
5446
|
|
|
3288
5447
|
@compute @workgroup_size(64)
|
|
@@ -3295,15 +5454,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3295
5454
|
|
|
3296
5455
|
let ray = activeQueue[index];
|
|
3297
5456
|
let hit = hits[index];
|
|
5457
|
+
let segmentTransmittance = medium_transmittance(ray.mediumRefId, hit.distance);
|
|
5458
|
+
let arrivingThroughput = ray.throughput.xyz * segmentTransmittance;
|
|
3298
5459
|
var contribution = vec3<f32>(0.0);
|
|
3299
5460
|
|
|
3300
5461
|
if (hit.hitType == 1u) {
|
|
3301
5462
|
let guidedLightWeight = select(1.0, 0.24, (ray.flags & RAY_FLAG_GUIDED_EMISSIVE) != 0u);
|
|
3302
5463
|
let sourceRadiance = max(hit.emission.xyz, hit.color.xyz) * guidedLightWeight;
|
|
3303
5464
|
if (deferred_path_resolve_enabled()) {
|
|
3304
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5465
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
3305
5466
|
} else {
|
|
3306
|
-
contribution = clamp_sample_radiance(
|
|
5467
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
3307
5468
|
accumulation[ray.rayId] =
|
|
3308
5469
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3309
5470
|
}
|
|
@@ -3312,10 +5473,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3312
5473
|
}
|
|
3313
5474
|
|
|
3314
5475
|
if (hit.hitType == 2u) {
|
|
5476
|
+
var sourceRadiance = hit.color.xyz;
|
|
5477
|
+
if ((ray.flags & RAY_FLAG_DELTA_SAMPLE) == 0u) {
|
|
5478
|
+
let bsdfPdf = max(ray.throughput.w, 0.000001);
|
|
5479
|
+
let lightPdf = environment_direction_pdf(ray.direction.xyz);
|
|
5480
|
+
let misWeight = power_heuristic(bsdfPdf, lightPdf);
|
|
5481
|
+
sourceRadiance = sourceRadiance * misWeight;
|
|
5482
|
+
}
|
|
3315
5483
|
if (deferred_path_resolve_enabled()) {
|
|
3316
|
-
record_deferred_terminal_source(ray,
|
|
5484
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
3317
5485
|
} else {
|
|
3318
|
-
contribution = clamp_sample_radiance(
|
|
5486
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
3319
5487
|
accumulation[ray.rayId] =
|
|
3320
5488
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3321
5489
|
}
|
|
@@ -3323,24 +5491,47 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3323
5491
|
return;
|
|
3324
5492
|
}
|
|
3325
5493
|
|
|
3326
|
-
let response =
|
|
5494
|
+
let response = stabilize_surface_path_response(
|
|
5495
|
+
ray,
|
|
5496
|
+
hit,
|
|
5497
|
+
surface_path_response(hit) * segmentTransmittance
|
|
5498
|
+
);
|
|
3327
5499
|
record_deferred_path_response(ray, response);
|
|
3328
5500
|
|
|
3329
5501
|
let shouldEstimateDirectEnvironment =
|
|
3330
|
-
!deferred_path_resolve_enabled() &&
|
|
3331
5502
|
(hit.materialKind == 0u || hit.materialKind == 1u) &&
|
|
3332
|
-
hit.material.z >= 0.95
|
|
5503
|
+
hit.material.z >= 0.95 &&
|
|
5504
|
+
ray.bounce < 2u;
|
|
3333
5505
|
if (shouldEstimateDirectEnvironment) {
|
|
3334
|
-
let directEnvironment = surface_direct_environment_contribution(
|
|
5506
|
+
let directEnvironment = surface_direct_environment_contribution(
|
|
5507
|
+
RayRecord(
|
|
5508
|
+
ray.rayId,
|
|
5509
|
+
ray.parentRayId,
|
|
5510
|
+
ray.sourcePixelId,
|
|
5511
|
+
ray.sampleId,
|
|
5512
|
+
ray.bounce,
|
|
5513
|
+
ray.mediumRefId,
|
|
5514
|
+
ray.flags,
|
|
5515
|
+
0u,
|
|
5516
|
+
ray.origin,
|
|
5517
|
+
ray.direction,
|
|
5518
|
+
vec4<f32>(arrivingThroughput, ray.throughput.w)
|
|
5519
|
+
),
|
|
5520
|
+
hit
|
|
5521
|
+
);
|
|
3335
5522
|
accumulation[ray.rayId] =
|
|
3336
5523
|
accumulation[ray.rayId] + vec4<f32>(directEnvironment * sample_weight(), 0.0);
|
|
3337
5524
|
}
|
|
3338
5525
|
|
|
3339
5526
|
if (ray.bounce + 1u >= config.maxDepth) {
|
|
3340
5527
|
if (deferred_path_resolve_enabled()) {
|
|
3341
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5528
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3342
5529
|
} else {
|
|
3343
|
-
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5530
|
+
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5531
|
+
ray,
|
|
5532
|
+
arrivingThroughput,
|
|
5533
|
+
hit
|
|
5534
|
+
);
|
|
3344
5535
|
accumulation[ray.rayId] =
|
|
3345
5536
|
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
3346
5537
|
}
|
|
@@ -3353,9 +5544,13 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3353
5544
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
3354
5545
|
if (nextIndex >= config.tilePixelCount) {
|
|
3355
5546
|
if (deferred_path_resolve_enabled()) {
|
|
3356
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5547
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3357
5548
|
} else {
|
|
3358
|
-
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5549
|
+
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5550
|
+
ray,
|
|
5551
|
+
arrivingThroughput,
|
|
5552
|
+
hit
|
|
5553
|
+
);
|
|
3359
5554
|
accumulation[ray.rayId] =
|
|
3360
5555
|
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
3361
5556
|
}
|
|
@@ -3369,12 +5564,12 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3369
5564
|
ray.sourcePixelId,
|
|
3370
5565
|
ray.sampleId,
|
|
3371
5566
|
ray.bounce + 1u,
|
|
3372
|
-
|
|
5567
|
+
scatter.mediumRefId,
|
|
3373
5568
|
scatter.flags,
|
|
3374
5569
|
0u,
|
|
3375
5570
|
vec4<f32>(offset_origin(hit.position.xyz, hit.shadingNormal.xyz), 1.0),
|
|
3376
5571
|
scatter.direction,
|
|
3377
|
-
vec4<f32>(throughput,
|
|
5572
|
+
vec4<f32>(throughput, scatter.pdf)
|
|
3378
5573
|
);
|
|
3379
5574
|
}
|
|
3380
5575
|
|
|
@@ -3443,8 +5638,11 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3443
5638
|
|
|
3444
5639
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3445
5640
|
let center = textureLoad(denoiseInputRadiance, pixel, 0).xyz;
|
|
3446
|
-
|
|
3447
|
-
|
|
5641
|
+
let strength = denoise_strength();
|
|
5642
|
+
let kernelRadius = denoise_kernel_radius();
|
|
5643
|
+
let centerWeight = 1.7 - strength * 0.35;
|
|
5644
|
+
var sum = center * centerWeight;
|
|
5645
|
+
var totalWeight = centerWeight;
|
|
3448
5646
|
let centerRange = denoise_range_space(center);
|
|
3449
5647
|
|
|
3450
5648
|
for (var oy = -2i; oy <= 2i; oy = oy + 1i) {
|
|
@@ -3452,13 +5650,16 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3452
5650
|
if (ox == 0i && oy == 0i) {
|
|
3453
5651
|
continue;
|
|
3454
5652
|
}
|
|
5653
|
+
if (abs(ox) > kernelRadius || abs(oy) > kernelRadius) {
|
|
5654
|
+
continue;
|
|
5655
|
+
}
|
|
3455
5656
|
let sx = clamp(i32(x) + ox, 0i, i32(config.canvasWidth) - 1i);
|
|
3456
5657
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3457
5658
|
let sampleColor = textureLoad(denoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3458
5659
|
let colorDistance = length(denoise_range_space(sampleColor) - centerRange);
|
|
3459
|
-
let rangeWeight = 1.0 / (1.0 + colorDistance *
|
|
3460
|
-
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * 0.24);
|
|
3461
|
-
let diagonalWeight = select(1.0, 0.
|
|
5660
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (11.0 + strength * 6.0));
|
|
5661
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.62 + strength * 0.24));
|
|
5662
|
+
let diagonalWeight = select(1.0, 0.92, abs(ox) + abs(oy) > 1i);
|
|
3462
5663
|
let weight = rangeWeight * diagonalWeight * distanceWeight;
|
|
3463
5664
|
sum = sum + sampleColor * weight;
|
|
3464
5665
|
totalWeight = totalWeight + weight;
|
|
@@ -3466,8 +5667,9 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3466
5667
|
}
|
|
3467
5668
|
|
|
3468
5669
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3469
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3470
|
-
let
|
|
5670
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.1);
|
|
5671
|
+
let blend = min(0.3, strength * (0.62 + outlier * 0.12));
|
|
5672
|
+
let color = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3471
5673
|
textureStore(denoisedRadianceImage, pixel, vec4<f32>(color, 1.0));
|
|
3472
5674
|
}
|
|
3473
5675
|
|
|
@@ -3481,8 +5683,10 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3481
5683
|
|
|
3482
5684
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3483
5685
|
let center = textureLoad(finalDenoiseInputRadiance, pixel, 0).xyz;
|
|
3484
|
-
|
|
3485
|
-
|
|
5686
|
+
let strength = denoise_strength();
|
|
5687
|
+
let centerWeight = 1.35 - strength * 0.25;
|
|
5688
|
+
var sum = center * centerWeight;
|
|
5689
|
+
var totalWeight = centerWeight;
|
|
3486
5690
|
let centerRange = denoise_range_space(center);
|
|
3487
5691
|
|
|
3488
5692
|
for (var oy = -1i; oy <= 1i; oy = oy + 1i) {
|
|
@@ -3494,8 +5698,8 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3494
5698
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3495
5699
|
let sampleColor = textureLoad(finalDenoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3496
5700
|
let colorDistance = length(denoise_range_space(sampleColor) - centerRange);
|
|
3497
|
-
let rangeWeight = 1.0 / (1.0 + colorDistance *
|
|
3498
|
-
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * 0.
|
|
5701
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (12.0 + strength * 8.0));
|
|
5702
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.82 + strength * 0.28));
|
|
3499
5703
|
let weight = rangeWeight * distanceWeight;
|
|
3500
5704
|
sum = sum + sampleColor * weight;
|
|
3501
5705
|
totalWeight = totalWeight + weight;
|
|
@@ -3503,8 +5707,9 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3503
5707
|
}
|
|
3504
5708
|
|
|
3505
5709
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3506
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3507
|
-
let
|
|
5710
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.2);
|
|
5711
|
+
let blend = min(0.18, strength * (0.42 + outlier * 0.08));
|
|
5712
|
+
let radiance = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3508
5713
|
textureStore(denoisedOutputImage, pixel, vec4<f32>(tone_map_radiance(radiance), 1.0));
|
|
3509
5714
|
}
|
|
3510
5715
|
`;
|
|
@@ -3580,95 +5785,25 @@ function createAdapterInfoSnapshot(adapter) {
|
|
|
3580
5785
|
vendor: typeof info.vendor === "string" ? info.vendor : "",
|
|
3581
5786
|
architecture: typeof info.architecture === "string" ? info.architecture : "",
|
|
3582
5787
|
device: typeof info.device === "string" ? info.device : "",
|
|
3583
|
-
description: typeof info.description === "string" ? info.description : ""
|
|
3584
|
-
});
|
|
3585
|
-
}
|
|
3586
|
-
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
3587
|
-
return Object.freeze({
|
|
3588
|
-
physicalCoreCount: null,
|
|
3589
|
-
physicalCoreCountAvailable: false,
|
|
3590
|
-
physicalCoreCountUnavailableReason: "WebGPU does not expose physical GPU core counts.",
|
|
3591
|
-
adapterInfo: createAdapterInfoSnapshot(adapter),
|
|
3592
|
-
adapterLimits: Object.freeze({
|
|
3593
|
-
maxComputeInvocationsPerWorkgroup: readGpuLimit(adapter, device, "maxComputeInvocationsPerWorkgroup"),
|
|
3594
|
-
maxComputeWorkgroupSizeX: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeX"),
|
|
3595
|
-
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
3596
|
-
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
3597
|
-
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
3598
|
-
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
3599
|
-
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize")
|
|
3600
|
-
}),
|
|
3601
|
-
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
3602
|
-
});
|
|
3603
|
-
}
|
|
3604
|
-
function createGpuParallelismCounters() {
|
|
3605
|
-
return {
|
|
3606
|
-
directDispatches: 0,
|
|
3607
|
-
directWorkgroups: 0,
|
|
3608
|
-
directShaderInvocations: 0,
|
|
3609
|
-
multiWorkgroupDispatches: 0,
|
|
3610
|
-
largestDirectWorkgroupsPerDispatch: 0,
|
|
3611
|
-
indirectDispatches: 0,
|
|
3612
|
-
estimatedIndirectWorkgroupsUpperBound: 0,
|
|
3613
|
-
estimatedIndirectShaderInvocationsUpperBound: 0,
|
|
3614
|
-
indirectDispatchesWithMultiWorkgroupCapacity: 0,
|
|
3615
|
-
largestEstimatedIndirectWorkgroupsPerDispatch: 0
|
|
3616
|
-
};
|
|
3617
|
-
}
|
|
3618
|
-
function countDispatchWorkgroups(groups) {
|
|
3619
|
-
return groups.reduce((product, value) => {
|
|
3620
|
-
const numeric = Number(value ?? 1);
|
|
3621
|
-
const count = Number.isFinite(numeric) ? Math.max(1, Math.trunc(numeric)) : 1;
|
|
3622
|
-
return product * count;
|
|
3623
|
-
}, 1);
|
|
3624
|
-
}
|
|
3625
|
-
function recordDirectDispatch(parallelism, groups, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3626
|
-
const workgroups = countDispatchWorkgroups(groups);
|
|
3627
|
-
parallelism.directDispatches += 1;
|
|
3628
|
-
parallelism.directWorkgroups += workgroups;
|
|
3629
|
-
parallelism.directShaderInvocations += workgroups * invocationsPerWorkgroup;
|
|
3630
|
-
parallelism.largestDirectWorkgroupsPerDispatch = Math.max(
|
|
3631
|
-
parallelism.largestDirectWorkgroupsPerDispatch,
|
|
3632
|
-
workgroups
|
|
3633
|
-
);
|
|
3634
|
-
if (workgroups > 1) {
|
|
3635
|
-
parallelism.multiWorkgroupDispatches += 1;
|
|
3636
|
-
}
|
|
3637
|
-
}
|
|
3638
|
-
function recordIndirectDispatch(parallelism, estimatedWorkgroupsUpperBound, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3639
|
-
const workgroups = Math.max(1, Math.trunc(Number(estimatedWorkgroupsUpperBound) || 1));
|
|
3640
|
-
parallelism.indirectDispatches += 1;
|
|
3641
|
-
parallelism.estimatedIndirectWorkgroupsUpperBound += workgroups;
|
|
3642
|
-
parallelism.estimatedIndirectShaderInvocationsUpperBound += workgroups * invocationsPerWorkgroup;
|
|
3643
|
-
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch = Math.max(
|
|
3644
|
-
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3645
|
-
workgroups
|
|
3646
|
-
);
|
|
3647
|
-
if (workgroups > 1) {
|
|
3648
|
-
parallelism.indirectDispatchesWithMultiWorkgroupCapacity += 1;
|
|
3649
|
-
}
|
|
3650
|
-
}
|
|
3651
|
-
function createGpuParallelismDiagnostics(adapterDiagnostics, counters) {
|
|
3652
|
-
const totalEstimatedWorkgroupsUpperBound = counters.directWorkgroups + counters.estimatedIndirectWorkgroupsUpperBound;
|
|
3653
|
-
const totalEstimatedShaderInvocationsUpperBound = counters.directShaderInvocations + counters.estimatedIndirectShaderInvocationsUpperBound;
|
|
3654
|
-
const exposesMultiWorkgroupParallelism = counters.multiWorkgroupDispatches > 0 || counters.indirectDispatchesWithMultiWorkgroupCapacity > 0;
|
|
3655
|
-
return Object.freeze({
|
|
3656
|
-
...adapterDiagnostics,
|
|
3657
|
-
directDispatches: counters.directDispatches,
|
|
3658
|
-
directWorkgroups: counters.directWorkgroups,
|
|
3659
|
-
directShaderInvocations: counters.directShaderInvocations,
|
|
3660
|
-
multiWorkgroupDispatches: counters.multiWorkgroupDispatches,
|
|
3661
|
-
largestDirectWorkgroupsPerDispatch: counters.largestDirectWorkgroupsPerDispatch,
|
|
3662
|
-
indirectDispatches: counters.indirectDispatches,
|
|
3663
|
-
estimatedIndirectWorkgroupsUpperBound: counters.estimatedIndirectWorkgroupsUpperBound,
|
|
3664
|
-
estimatedIndirectShaderInvocationsUpperBound: counters.estimatedIndirectShaderInvocationsUpperBound,
|
|
3665
|
-
indirectDispatchesWithMultiWorkgroupCapacity: counters.indirectDispatchesWithMultiWorkgroupCapacity,
|
|
3666
|
-
largestEstimatedIndirectWorkgroupsPerDispatch: counters.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3667
|
-
totalEstimatedWorkgroupsUpperBound,
|
|
3668
|
-
totalEstimatedShaderInvocationsUpperBound,
|
|
3669
|
-
exposesMultiWorkgroupParallelism,
|
|
3670
|
-
likelyUsesMoreThanOnePhysicalGpuCore: null,
|
|
3671
|
-
coreUtilizationStatus: "not-exposed-by-webgpu"
|
|
5788
|
+
description: typeof info.description === "string" ? info.description : ""
|
|
5789
|
+
});
|
|
5790
|
+
}
|
|
5791
|
+
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
5792
|
+
return Object.freeze({
|
|
5793
|
+
physicalCoreCount: null,
|
|
5794
|
+
physicalCoreCountAvailable: false,
|
|
5795
|
+
physicalCoreCountUnavailableReason: "WebGPU does not expose physical GPU core counts.",
|
|
5796
|
+
adapterInfo: createAdapterInfoSnapshot(adapter),
|
|
5797
|
+
adapterLimits: Object.freeze({
|
|
5798
|
+
maxComputeInvocationsPerWorkgroup: readGpuLimit(adapter, device, "maxComputeInvocationsPerWorkgroup"),
|
|
5799
|
+
maxComputeWorkgroupSizeX: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeX"),
|
|
5800
|
+
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
5801
|
+
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
5802
|
+
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
5803
|
+
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
5804
|
+
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize")
|
|
5805
|
+
}),
|
|
5806
|
+
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
3672
5807
|
});
|
|
3673
5808
|
}
|
|
3674
5809
|
function createEnvironmentMapSnapshot(environmentMap) {
|
|
@@ -3676,12 +5811,38 @@ function createEnvironmentMapSnapshot(environmentMap) {
|
|
|
3676
5811
|
enabled: environmentMap.enabled,
|
|
3677
5812
|
width: environmentMap.width,
|
|
3678
5813
|
height: environmentMap.height,
|
|
5814
|
+
mipLevelCount: environmentMap.mipLevelCount ?? 1,
|
|
3679
5815
|
projection: environmentMap.projection,
|
|
3680
5816
|
intensity: environmentMap.intensity,
|
|
3681
5817
|
rotationRadians: environmentMap.rotationRadians,
|
|
3682
|
-
ambientStrength: environmentMap.ambientStrength
|
|
5818
|
+
ambientStrength: environmentMap.ambientStrength,
|
|
5819
|
+
hasImportanceData: environmentMap.hasImportanceData === true
|
|
3683
5820
|
});
|
|
3684
5821
|
}
|
|
5822
|
+
function nowMs() {
|
|
5823
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
5824
|
+
return performance.now();
|
|
5825
|
+
}
|
|
5826
|
+
return Date.now();
|
|
5827
|
+
}
|
|
5828
|
+
function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs = null) {
|
|
5829
|
+
if (Number.isFinite(overrideTimeoutMs)) {
|
|
5830
|
+
return Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
5831
|
+
}
|
|
5832
|
+
const samplesPerPixel = Math.max(
|
|
5833
|
+
1,
|
|
5834
|
+
Number(config?.renderedSamplesPerPixel ?? config?.samplesPerPixel ?? 1)
|
|
5835
|
+
);
|
|
5836
|
+
const maxDepth = Math.max(1, Number(config?.maxDepth ?? 1));
|
|
5837
|
+
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
5838
|
+
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
5839
|
+
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
5840
|
+
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
5841
|
+
return Math.min(
|
|
5842
|
+
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
5843
|
+
GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5
|
|
5844
|
+
);
|
|
5845
|
+
}
|
|
3685
5846
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
3686
5847
|
assertAnalyticDisplayQualityPolicy(options);
|
|
3687
5848
|
const constants = getGpuUsageConstants();
|
|
@@ -3886,6 +6047,65 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3886
6047
|
config.environmentMap,
|
|
3887
6048
|
config.environmentColor
|
|
3888
6049
|
);
|
|
6050
|
+
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
6051
|
+
device,
|
|
6052
|
+
constants,
|
|
6053
|
+
config.environmentMap,
|
|
6054
|
+
config.environmentColor
|
|
6055
|
+
);
|
|
6056
|
+
let mediumTextureResource = createMediumTextureResource(
|
|
6057
|
+
device,
|
|
6058
|
+
constants,
|
|
6059
|
+
config.mediums
|
|
6060
|
+
);
|
|
6061
|
+
config = Object.freeze({
|
|
6062
|
+
...config,
|
|
6063
|
+
environmentMap: Object.freeze({
|
|
6064
|
+
...config.environmentMap,
|
|
6065
|
+
width: environmentMapResource.width,
|
|
6066
|
+
height: environmentMapResource.height,
|
|
6067
|
+
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
6068
|
+
hasImportanceData: environmentSamplingResource.hasImportanceData
|
|
6069
|
+
})
|
|
6070
|
+
});
|
|
6071
|
+
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
6072
|
+
const baseColorAtlasResource = createAtlasTextureResource(
|
|
6073
|
+
device,
|
|
6074
|
+
constants,
|
|
6075
|
+
config.gpuMaterialSource.baseColorAtlas,
|
|
6076
|
+
"plasius.wavefront.materialAtlas.baseColor"
|
|
6077
|
+
);
|
|
6078
|
+
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
6079
|
+
device,
|
|
6080
|
+
constants,
|
|
6081
|
+
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
6082
|
+
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
6083
|
+
);
|
|
6084
|
+
const normalAtlasResource = createAtlasTextureResource(
|
|
6085
|
+
device,
|
|
6086
|
+
constants,
|
|
6087
|
+
config.gpuMaterialSource.normalAtlas,
|
|
6088
|
+
"plasius.wavefront.materialAtlas.normal"
|
|
6089
|
+
);
|
|
6090
|
+
const occlusionAtlasResource = createAtlasTextureResource(
|
|
6091
|
+
device,
|
|
6092
|
+
constants,
|
|
6093
|
+
config.gpuMaterialSource.occlusionAtlas,
|
|
6094
|
+
"plasius.wavefront.materialAtlas.occlusion"
|
|
6095
|
+
);
|
|
6096
|
+
const emissiveAtlasResource = createAtlasTextureResource(
|
|
6097
|
+
device,
|
|
6098
|
+
constants,
|
|
6099
|
+
config.gpuMaterialSource.emissiveAtlas,
|
|
6100
|
+
"plasius.wavefront.materialAtlas.emissive"
|
|
6101
|
+
);
|
|
6102
|
+
const materialAtlasSampler = device.createSampler({
|
|
6103
|
+
label: "plasius.wavefront.materialAtlasSampler",
|
|
6104
|
+
addressModeU: "clamp-to-edge",
|
|
6105
|
+
addressModeV: "clamp-to-edge",
|
|
6106
|
+
magFilter: "linear",
|
|
6107
|
+
minFilter: "linear"
|
|
6108
|
+
});
|
|
3889
6109
|
const traceBindGroupLayout = device.createBindGroupLayout({
|
|
3890
6110
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
3891
6111
|
entries: [
|
|
@@ -3915,7 +6135,17 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3915
6135
|
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
3916
6136
|
{ binding: 20, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
3917
6137
|
{ binding: 21, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
3918
|
-
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } }
|
|
6138
|
+
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } },
|
|
6139
|
+
{ binding: 23, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6140
|
+
{ binding: 24, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6141
|
+
{ binding: 25, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6142
|
+
{ binding: 26, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6143
|
+
{ binding: 27, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6144
|
+
{ binding: 28, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
6145
|
+
{ binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6146
|
+
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
6147
|
+
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6148
|
+
{ binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
|
|
3919
6149
|
]
|
|
3920
6150
|
});
|
|
3921
6151
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -3994,6 +6224,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3994
6224
|
label: "plasius.wavefront.computeShader",
|
|
3995
6225
|
code: WAVEFRONT_COMPUTE_WGSL
|
|
3996
6226
|
});
|
|
6227
|
+
await assertShaderModuleCompiles(computeShader, "plasius.wavefront.computeShader");
|
|
3997
6228
|
const pipelines = {
|
|
3998
6229
|
prepareMeshTrianglesAndLeaves: await createComputePipeline(
|
|
3999
6230
|
device,
|
|
@@ -4092,14 +6323,27 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4092
6323
|
{ binding: 19, resource: { buffer: environmentPortalBuffer } },
|
|
4093
6324
|
{ binding: 20, resource: environmentMapResource.view },
|
|
4094
6325
|
{ binding: 21, resource: environmentMapResource.sampler },
|
|
4095
|
-
{ binding: 22, resource: { buffer: pathVertexBuffer } }
|
|
6326
|
+
{ binding: 22, resource: { buffer: pathVertexBuffer } },
|
|
6327
|
+
{ binding: 23, resource: baseColorAtlasResource.view },
|
|
6328
|
+
{ binding: 24, resource: metallicRoughnessAtlasResource.view },
|
|
6329
|
+
{ binding: 25, resource: normalAtlasResource.view },
|
|
6330
|
+
{ binding: 26, resource: occlusionAtlasResource.view },
|
|
6331
|
+
{ binding: 27, resource: emissiveAtlasResource.view },
|
|
6332
|
+
{ binding: 28, resource: materialAtlasSampler },
|
|
6333
|
+
{ binding: 29, resource: brdfLutResource.view },
|
|
6334
|
+
{ binding: 30, resource: brdfLutResource.sampler },
|
|
6335
|
+
{ binding: 31, resource: environmentSamplingResource.view },
|
|
6336
|
+
{ binding: 32, resource: mediumTextureResource.view }
|
|
4096
6337
|
]
|
|
4097
6338
|
});
|
|
4098
6339
|
}
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
6340
|
+
function createTraceBindGroups() {
|
|
6341
|
+
return [
|
|
6342
|
+
createTraceBindGroup(activeQueue, nextQueue, "plasius.wavefront.bind.activeNext"),
|
|
6343
|
+
createTraceBindGroup(nextQueue, activeQueue, "plasius.wavefront.bind.nextActive")
|
|
6344
|
+
];
|
|
6345
|
+
}
|
|
6346
|
+
let bindGroups = createTraceBindGroups();
|
|
4103
6347
|
const bvhBuildBindGroup = device.createBindGroup({
|
|
4104
6348
|
label: "plasius.wavefront.bind.bvhBuild",
|
|
4105
6349
|
layout: accelerationBindGroupLayout,
|
|
@@ -4145,6 +6389,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4145
6389
|
outputView,
|
|
4146
6390
|
"plasius.wavefront.bind.denoise.scratchToOutput"
|
|
4147
6391
|
);
|
|
6392
|
+
const denoiseDirectResolveBindGroup = createDenoiseResolveBindGroup(
|
|
6393
|
+
radianceView,
|
|
6394
|
+
outputView,
|
|
6395
|
+
"plasius.wavefront.bind.denoise.radianceToOutput"
|
|
6396
|
+
);
|
|
4148
6397
|
const presentBindGroupLayout = device.createBindGroupLayout({
|
|
4149
6398
|
label: "plasius.wavefront.presentBindGroupLayout",
|
|
4150
6399
|
entries: [
|
|
@@ -4182,23 +6431,129 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4182
6431
|
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
4183
6432
|
let accelerationBuildCount = 0;
|
|
4184
6433
|
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA;
|
|
6434
|
+
let lastCompletedFrameTimeMs = null;
|
|
6435
|
+
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
4185
6436
|
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
4186
6437
|
gpuAdapterParallelism,
|
|
4187
6438
|
createGpuParallelismCounters()
|
|
4188
6439
|
);
|
|
6440
|
+
function resolveRenderedSamplesPerPixel(renderOptions = {}, awaitGPUCompletion = true) {
|
|
6441
|
+
const targetSamplesPerPixel = clamp(
|
|
6442
|
+
readPositiveInteger(
|
|
6443
|
+
"samplesPerPixel",
|
|
6444
|
+
renderOptions.samplesPerPixel,
|
|
6445
|
+
config.samplesPerPixel
|
|
6446
|
+
),
|
|
6447
|
+
1,
|
|
6448
|
+
config.samplesPerPixel
|
|
6449
|
+
);
|
|
6450
|
+
const frameTimeBudgetMs = Number.isFinite(renderOptions.frameTimeBudgetMs) ? Math.max(0, Number(renderOptions.frameTimeBudgetMs)) : null;
|
|
6451
|
+
const minimumSamplesPerPixel = clamp(
|
|
6452
|
+
readPositiveInteger(
|
|
6453
|
+
"minimumSamplesPerPixel",
|
|
6454
|
+
renderOptions.minimumSamplesPerPixel,
|
|
6455
|
+
frameTimeBudgetMs !== null && targetSamplesPerPixel > 1 ? 1 : targetSamplesPerPixel
|
|
6456
|
+
),
|
|
6457
|
+
1,
|
|
6458
|
+
targetSamplesPerPixel
|
|
6459
|
+
);
|
|
6460
|
+
if (frameTimeBudgetMs === null || !awaitGPUCompletion || targetSamplesPerPixel <= minimumSamplesPerPixel) {
|
|
6461
|
+
return Object.freeze({
|
|
6462
|
+
renderedSamplesPerPixel: targetSamplesPerPixel,
|
|
6463
|
+
targetSamplesPerPixel,
|
|
6464
|
+
minimumSamplesPerPixel,
|
|
6465
|
+
frameTimeBudgetMs,
|
|
6466
|
+
budgetConstrained: false
|
|
6467
|
+
});
|
|
6468
|
+
}
|
|
6469
|
+
const estimatedSampleTimeMs = Number.isFinite(lastCompletedFrameTimeMs) && lastCompletedFrameTimeMs > 0 ? lastCompletedFrameTimeMs / Math.max(1, lastCompletedSamplesPerPixel) : null;
|
|
6470
|
+
if (!Number.isFinite(estimatedSampleTimeMs) || estimatedSampleTimeMs <= 0) {
|
|
6471
|
+
return Object.freeze({
|
|
6472
|
+
renderedSamplesPerPixel: minimumSamplesPerPixel,
|
|
6473
|
+
targetSamplesPerPixel,
|
|
6474
|
+
minimumSamplesPerPixel,
|
|
6475
|
+
frameTimeBudgetMs,
|
|
6476
|
+
budgetConstrained: minimumSamplesPerPixel < targetSamplesPerPixel
|
|
6477
|
+
});
|
|
6478
|
+
}
|
|
6479
|
+
const budgetLimitedSamples = clamp(
|
|
6480
|
+
Math.floor(frameTimeBudgetMs / estimatedSampleTimeMs),
|
|
6481
|
+
minimumSamplesPerPixel,
|
|
6482
|
+
targetSamplesPerPixel
|
|
6483
|
+
);
|
|
6484
|
+
return Object.freeze({
|
|
6485
|
+
renderedSamplesPerPixel: budgetLimitedSamples,
|
|
6486
|
+
targetSamplesPerPixel,
|
|
6487
|
+
minimumSamplesPerPixel,
|
|
6488
|
+
frameTimeBudgetMs,
|
|
6489
|
+
budgetConstrained: budgetLimitedSamples < targetSamplesPerPixel
|
|
6490
|
+
});
|
|
6491
|
+
}
|
|
6492
|
+
function createFrameStats({
|
|
6493
|
+
frameIndex,
|
|
6494
|
+
accelerationBuildSubmitted,
|
|
6495
|
+
frameSubmissionCount,
|
|
6496
|
+
parallelismCounters,
|
|
6497
|
+
renderedSamplesPerPixel,
|
|
6498
|
+
targetSamplesPerPixel,
|
|
6499
|
+
frameTimeBudgetMs,
|
|
6500
|
+
budgetConstrained
|
|
6501
|
+
}) {
|
|
6502
|
+
lastGpuParallelism = createGpuParallelismDiagnostics(gpuAdapterParallelism, parallelismCounters);
|
|
6503
|
+
const commandSubmissions = frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0);
|
|
6504
|
+
return Object.freeze({
|
|
6505
|
+
frame,
|
|
6506
|
+
frameIndex,
|
|
6507
|
+
width: config.width,
|
|
6508
|
+
height: config.height,
|
|
6509
|
+
maxDepth: config.maxDepth,
|
|
6510
|
+
tiles: tiles.length,
|
|
6511
|
+
tileSize: config.tileSize,
|
|
6512
|
+
samplesPerPixel: targetSamplesPerPixel,
|
|
6513
|
+
renderedSamplesPerPixel,
|
|
6514
|
+
frameTimeBudgetMs,
|
|
6515
|
+
budgetConstrained,
|
|
6516
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6517
|
+
screenRays: config.width * config.height,
|
|
6518
|
+
primaryRays: config.width * config.height * renderedSamplesPerPixel,
|
|
6519
|
+
sceneObjectCount: config.sceneObjectCount,
|
|
6520
|
+
triangleCount: config.triangleCount,
|
|
6521
|
+
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6522
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
6523
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
6524
|
+
mediumCount: config.mediumCount,
|
|
6525
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6526
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
6527
|
+
bvhNodeCount: config.bvhNodeCount,
|
|
6528
|
+
displayQuality: config.displayQuality,
|
|
6529
|
+
accelerationBuildMode: config.accelerationBuildMode,
|
|
6530
|
+
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
6531
|
+
accelerationBuildSubmitted,
|
|
6532
|
+
accelerationBuilt,
|
|
6533
|
+
accelerationBuildCount,
|
|
6534
|
+
commandSubmissions,
|
|
6535
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
6536
|
+
gpuParallelism: lastGpuParallelism,
|
|
6537
|
+
memory: config.memory
|
|
6538
|
+
});
|
|
6539
|
+
}
|
|
6540
|
+
function writeFrameConfigSlot(slot, tile, frameIndex, buildRange = {}) {
|
|
6541
|
+
if (slot >= frameConfigSlotCount) {
|
|
6542
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
6543
|
+
}
|
|
6544
|
+
const offset = slot * configBufferStride;
|
|
6545
|
+
device.queue.writeBuffer(
|
|
6546
|
+
configBuffer,
|
|
6547
|
+
offset,
|
|
6548
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
6549
|
+
);
|
|
6550
|
+
return offset;
|
|
6551
|
+
}
|
|
4189
6552
|
function createFrameConfigWriter(frameIndex) {
|
|
4190
6553
|
let slot = 0;
|
|
4191
6554
|
return (tile, buildRange = {}) => {
|
|
4192
|
-
|
|
4193
|
-
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
4194
|
-
}
|
|
4195
|
-
const offset = slot * configBufferStride;
|
|
6555
|
+
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
4196
6556
|
slot += 1;
|
|
4197
|
-
device.queue.writeBuffer(
|
|
4198
|
-
configBuffer,
|
|
4199
|
-
offset,
|
|
4200
|
-
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
4201
|
-
);
|
|
4202
6557
|
return offset;
|
|
4203
6558
|
};
|
|
4204
6559
|
}
|
|
@@ -4243,7 +6598,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4243
6598
|
passEncoder.setPipeline(pipelines.prepareMeshTrianglesAndLeaves);
|
|
4244
6599
|
const prepareWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4245
6600
|
passEncoder.dispatchWorkgroups(prepareWorkgroups);
|
|
4246
|
-
recordDirectDispatch(parallelism, [prepareWorkgroups]);
|
|
6601
|
+
recordDirectDispatch(parallelism, [prepareWorkgroups], WORKGROUP_SIZE);
|
|
4247
6602
|
passEncoder.setPipeline(pipelines.sortBvhLeafRefs);
|
|
4248
6603
|
for (let stageIndex = 0; stageIndex < config.bvhSortStages.length; stageIndex += 1) {
|
|
4249
6604
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [
|
|
@@ -4251,13 +6606,13 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4251
6606
|
]);
|
|
4252
6607
|
const sortWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4253
6608
|
passEncoder.dispatchWorkgroups(sortWorkgroups);
|
|
4254
|
-
recordDirectDispatch(parallelism, [sortWorkgroups]);
|
|
6609
|
+
recordDirectDispatch(parallelism, [sortWorkgroups], WORKGROUP_SIZE);
|
|
4255
6610
|
}
|
|
4256
6611
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [0]);
|
|
4257
6612
|
passEncoder.setPipeline(pipelines.writeSortedBvhLeaves);
|
|
4258
6613
|
const leafWriteWorkgroups = Math.ceil(config.triangleCount / WORKGROUP_SIZE);
|
|
4259
6614
|
passEncoder.dispatchWorkgroups(leafWriteWorkgroups);
|
|
4260
|
-
recordDirectDispatch(parallelism, [leafWriteWorkgroups]);
|
|
6615
|
+
recordDirectDispatch(parallelism, [leafWriteWorkgroups], WORKGROUP_SIZE);
|
|
4261
6616
|
passEncoder.setPipeline(pipelines.buildBvhInternalLevel);
|
|
4262
6617
|
for (let levelIndex = 0; levelIndex < config.bvhBuildLevels.length; levelIndex += 1) {
|
|
4263
6618
|
const buildLevel = config.bvhBuildLevels[levelIndex];
|
|
@@ -4266,7 +6621,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4266
6621
|
]);
|
|
4267
6622
|
const levelWorkgroups = Math.ceil(buildLevel.count / WORKGROUP_SIZE);
|
|
4268
6623
|
passEncoder.dispatchWorkgroups(levelWorkgroups);
|
|
4269
|
-
recordDirectDispatch(parallelism, [levelWorkgroups]);
|
|
6624
|
+
recordDirectDispatch(parallelism, [levelWorkgroups], WORKGROUP_SIZE);
|
|
4270
6625
|
}
|
|
4271
6626
|
passEncoder.end();
|
|
4272
6627
|
device.queue.submit([encoder.finish()]);
|
|
@@ -4282,7 +6637,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4282
6637
|
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4283
6638
|
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
4284
6639
|
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
4285
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6640
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4286
6641
|
generatePass.end();
|
|
4287
6642
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
4288
6643
|
encoder.copyBufferToBuffer(
|
|
@@ -4298,10 +6653,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4298
6653
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
4299
6654
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
4300
6655
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4301
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6656
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4302
6657
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
4303
6658
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4304
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6659
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4305
6660
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
4306
6661
|
passEncoder.dispatchWorkgroups(1);
|
|
4307
6662
|
recordDirectDispatch(parallelism, [1], 1);
|
|
@@ -4316,30 +6671,45 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4316
6671
|
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4317
6672
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
4318
6673
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
4319
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6674
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4320
6675
|
passEncoder.end();
|
|
4321
6676
|
}
|
|
4322
|
-
function encodeDenoise(encoder, configOffset, parallelism) {
|
|
6677
|
+
function encodeDenoise(encoder, configOffset, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4323
6678
|
if (!config.denoise) {
|
|
4324
6679
|
return;
|
|
4325
6680
|
}
|
|
4326
6681
|
const denoiseWorkgroupsX = Math.ceil(config.width / 8);
|
|
4327
6682
|
const denoiseWorkgroupsY = Math.ceil(config.height / 8);
|
|
4328
|
-
const
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
6683
|
+
const useTwoPassDenoise = renderedSamplesPerPixel < 4;
|
|
6684
|
+
if (useTwoPassDenoise) {
|
|
6685
|
+
const radiancePass = encoder.beginComputePass({
|
|
6686
|
+
label: "plasius.wavefront.denoiseRadiancePass"
|
|
6687
|
+
});
|
|
6688
|
+
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
6689
|
+
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
6690
|
+
radiancePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
6691
|
+
recordDirectDispatch(
|
|
6692
|
+
parallelism,
|
|
6693
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6694
|
+
WORKGROUP_SIZE
|
|
6695
|
+
);
|
|
6696
|
+
radiancePass.end();
|
|
6697
|
+
}
|
|
4336
6698
|
const resolvePass = encoder.beginComputePass({
|
|
4337
6699
|
label: "plasius.wavefront.denoiseResolvePass"
|
|
4338
6700
|
});
|
|
4339
|
-
resolvePass.setBindGroup(
|
|
6701
|
+
resolvePass.setBindGroup(
|
|
6702
|
+
0,
|
|
6703
|
+
useTwoPassDenoise ? denoiseResolveBindGroup : denoiseDirectResolveBindGroup,
|
|
6704
|
+
[configOffset]
|
|
6705
|
+
);
|
|
4340
6706
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
4341
6707
|
resolvePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
4342
|
-
recordDirectDispatch(
|
|
6708
|
+
recordDirectDispatch(
|
|
6709
|
+
parallelism,
|
|
6710
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6711
|
+
WORKGROUP_SIZE
|
|
6712
|
+
);
|
|
4343
6713
|
resolvePass.end();
|
|
4344
6714
|
}
|
|
4345
6715
|
function encodePresent(encoder) {
|
|
@@ -4360,98 +6730,213 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4360
6730
|
passEncoder.draw(3);
|
|
4361
6731
|
passEncoder.end();
|
|
4362
6732
|
}
|
|
4363
|
-
function dispatchFrame(frameIndex, parallelism) {
|
|
6733
|
+
function dispatchFrame(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4364
6734
|
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
6735
|
+
const batch = createGpuSubmissionBatcher({
|
|
6736
|
+
device,
|
|
6737
|
+
frameIndex,
|
|
6738
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission
|
|
4369
6739
|
});
|
|
4370
|
-
function submitCurrentEncoder() {
|
|
4371
|
-
if (encodedFramePasses <= 0) {
|
|
4372
|
-
return;
|
|
4373
|
-
}
|
|
4374
|
-
device.queue.submit([encoder.finish()]);
|
|
4375
|
-
submissionCount += 1;
|
|
4376
|
-
encodedFramePasses = 0;
|
|
4377
|
-
encoder = device.createCommandEncoder({
|
|
4378
|
-
label: `plasius.wavefront.frame.${frameIndex}.batched.${submissionCount + 1}`
|
|
4379
|
-
});
|
|
4380
|
-
}
|
|
4381
|
-
function reserveEncoder(passCount = 1) {
|
|
4382
|
-
if (encodedFramePasses > 0 && encodedFramePasses + passCount > config.maxFramePassesPerSubmission) {
|
|
4383
|
-
submitCurrentEncoder();
|
|
4384
|
-
}
|
|
4385
|
-
encodedFramePasses += passCount;
|
|
4386
|
-
return encoder;
|
|
4387
|
-
}
|
|
4388
6740
|
for (const tile of tiles) {
|
|
4389
|
-
for (let sampleIndex = 0; sampleIndex <
|
|
6741
|
+
for (let sampleIndex = 0; sampleIndex < renderedSamplesPerPixel; sampleIndex += 1) {
|
|
4390
6742
|
const configOffset = writeFrameConfig(tile, {
|
|
4391
6743
|
sampleIndex,
|
|
4392
|
-
sampleWeight: 1 /
|
|
6744
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
4393
6745
|
});
|
|
4394
|
-
encodeTileSample(
|
|
6746
|
+
encodeTileSample(
|
|
6747
|
+
batch.reserve(config.maxDepth + 1),
|
|
6748
|
+
tile,
|
|
6749
|
+
configOffset,
|
|
6750
|
+
parallelism
|
|
6751
|
+
);
|
|
4395
6752
|
if (config.deferredPathResolve) {
|
|
4396
|
-
encodeTileOutput(
|
|
6753
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
4397
6754
|
}
|
|
4398
6755
|
}
|
|
4399
6756
|
if (!config.deferredPathResolve) {
|
|
4400
6757
|
const outputConfigOffset = writeFrameConfig(tile, {
|
|
4401
6758
|
sampleIndex: 0,
|
|
4402
|
-
sampleWeight: 1 /
|
|
6759
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
4403
6760
|
});
|
|
4404
|
-
encodeTileOutput(
|
|
6761
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
4405
6762
|
}
|
|
4406
6763
|
}
|
|
4407
6764
|
if (config.denoise) {
|
|
4408
6765
|
const denoiseConfigOffset = writeFrameConfig(
|
|
4409
6766
|
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
4410
|
-
{ sampleIndex: 0, sampleWeight: 1 /
|
|
6767
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6768
|
+
);
|
|
6769
|
+
const denoisePassCount = renderedSamplesPerPixel < 4 ? 2 : 1;
|
|
6770
|
+
encodeDenoise(
|
|
6771
|
+
batch.reserve(denoisePassCount),
|
|
6772
|
+
denoiseConfigOffset,
|
|
6773
|
+
parallelism,
|
|
6774
|
+
renderedSamplesPerPixel
|
|
4411
6775
|
);
|
|
4412
|
-
encodeDenoise(reserveEncoder(), denoiseConfigOffset, parallelism);
|
|
4413
6776
|
}
|
|
4414
|
-
encodePresent(
|
|
4415
|
-
|
|
4416
|
-
return submissionCount;
|
|
6777
|
+
encodePresent(batch.reserve(1));
|
|
6778
|
+
return batch.flush();
|
|
4417
6779
|
}
|
|
4418
|
-
function renderOnce() {
|
|
6780
|
+
function renderOnce(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
6781
|
+
const frameStartTimeMs = nowMs();
|
|
4419
6782
|
frame += 1;
|
|
4420
6783
|
const frameIndex = frame + config.frameIndex;
|
|
6784
|
+
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
4421
6785
|
const parallelismCounters = createGpuParallelismCounters();
|
|
4422
6786
|
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
4423
|
-
const frameSubmissionCount = dispatchFrame(
|
|
4424
|
-
|
|
6787
|
+
const frameSubmissionCount = dispatchFrame(
|
|
6788
|
+
frameIndex,
|
|
6789
|
+
parallelismCounters,
|
|
6790
|
+
samplingPlan.renderedSamplesPerPixel
|
|
6791
|
+
);
|
|
6792
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
4425
6793
|
return Object.freeze({
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
6794
|
+
...createFrameStats({
|
|
6795
|
+
frameIndex,
|
|
6796
|
+
accelerationBuildSubmitted,
|
|
6797
|
+
frameSubmissionCount,
|
|
6798
|
+
parallelismCounters,
|
|
6799
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
6800
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
6801
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
6802
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
6803
|
+
}),
|
|
6804
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
6805
|
+
lastGpuParallelism,
|
|
6806
|
+
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
6807
|
+
frameTimeMs,
|
|
6808
|
+
false
|
|
6809
|
+
)
|
|
6810
|
+
});
|
|
6811
|
+
}
|
|
6812
|
+
async function waitForSubmittedGpuWork(options2 = {}) {
|
|
6813
|
+
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
6814
|
+
return true;
|
|
6815
|
+
}
|
|
6816
|
+
const timeoutMs = Math.max(
|
|
6817
|
+
1,
|
|
6818
|
+
Number.isFinite(options2.timeoutMs) ? Number(options2.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
6819
|
+
);
|
|
6820
|
+
const allowTimeout = options2.allowTimeout !== false;
|
|
6821
|
+
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
6822
|
+
() => ({ status: "done" }),
|
|
6823
|
+
(error) => {
|
|
6824
|
+
throw error;
|
|
6825
|
+
}
|
|
6826
|
+
);
|
|
6827
|
+
const lossPromise = typeof device.lost?.then === "function" ? device.lost.then((info) => {
|
|
6828
|
+
throw new Error(
|
|
6829
|
+
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
6830
|
+
);
|
|
6831
|
+
}) : null;
|
|
6832
|
+
let timeoutHandle = null;
|
|
6833
|
+
let resolveTimeoutPromise = null;
|
|
6834
|
+
let timeoutSettled = false;
|
|
6835
|
+
const settleTimeoutPromise = (value) => {
|
|
6836
|
+
if (timeoutSettled) {
|
|
6837
|
+
return;
|
|
6838
|
+
}
|
|
6839
|
+
timeoutSettled = true;
|
|
6840
|
+
resolveTimeoutPromise?.(value);
|
|
6841
|
+
};
|
|
6842
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
6843
|
+
resolveTimeoutPromise = resolve;
|
|
6844
|
+
timeoutHandle = setTimeout(() => settleTimeoutPromise({ status: "timeout" }), timeoutMs);
|
|
6845
|
+
});
|
|
6846
|
+
let result;
|
|
6847
|
+
try {
|
|
6848
|
+
result = await Promise.race(
|
|
6849
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
6850
|
+
);
|
|
6851
|
+
} finally {
|
|
6852
|
+
if (timeoutHandle !== null) {
|
|
6853
|
+
clearTimeout(timeoutHandle);
|
|
6854
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
6855
|
+
}
|
|
6856
|
+
}
|
|
6857
|
+
if (result?.status === "timeout") {
|
|
6858
|
+
if (!allowTimeout) {
|
|
6859
|
+
throw new Error(`Timed out after ${timeoutMs} ms waiting for submitted GPU work.`);
|
|
6860
|
+
}
|
|
6861
|
+
console.warn(
|
|
6862
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
|
|
6863
|
+
);
|
|
6864
|
+
return false;
|
|
6865
|
+
}
|
|
6866
|
+
return true;
|
|
6867
|
+
}
|
|
6868
|
+
function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
6869
|
+
const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
|
|
6870
|
+
const denoisePassCount = config.denoise ? renderedSamplesPerPixel < 4 ? 2 : 1 : 0;
|
|
6871
|
+
const tailPassCount = denoisePassCount + 1;
|
|
6872
|
+
const sampleBatchSize = Math.max(
|
|
6873
|
+
1,
|
|
6874
|
+
Math.floor(
|
|
6875
|
+
Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) / Math.max(samplePassesPerSample, 1)
|
|
6876
|
+
)
|
|
6877
|
+
);
|
|
6878
|
+
let submissionCount = 0;
|
|
6879
|
+
for (const tile of tiles) {
|
|
6880
|
+
for (let sampleStart = 0; sampleStart < renderedSamplesPerPixel; sampleStart += sampleBatchSize) {
|
|
6881
|
+
const sampleEnd = Math.min(renderedSamplesPerPixel, sampleStart + sampleBatchSize);
|
|
6882
|
+
const batch = createGpuSubmissionBatcher({
|
|
6883
|
+
device,
|
|
6884
|
+
frameIndex,
|
|
6885
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6886
|
+
startingSubmissionCount: submissionCount
|
|
6887
|
+
});
|
|
6888
|
+
let slot = 0;
|
|
6889
|
+
for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
|
|
6890
|
+
const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6891
|
+
sampleIndex,
|
|
6892
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
6893
|
+
});
|
|
6894
|
+
slot += 1;
|
|
6895
|
+
encodeTileSample(
|
|
6896
|
+
batch.reserve(config.maxDepth + 1),
|
|
6897
|
+
tile,
|
|
6898
|
+
configOffset,
|
|
6899
|
+
parallelism
|
|
6900
|
+
);
|
|
6901
|
+
if (config.deferredPathResolve) {
|
|
6902
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
if (!config.deferredPathResolve && sampleEnd >= renderedSamplesPerPixel) {
|
|
6906
|
+
const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6907
|
+
sampleIndex: 0,
|
|
6908
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
6909
|
+
});
|
|
6910
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
6911
|
+
}
|
|
6912
|
+
batch.flush();
|
|
6913
|
+
submissionCount += batch.getSubmissionCount();
|
|
6914
|
+
}
|
|
6915
|
+
}
|
|
6916
|
+
const tail = createGpuSubmissionBatcher({
|
|
6917
|
+
device,
|
|
6918
|
+
frameIndex,
|
|
4433
6919
|
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
4434
|
-
|
|
4435
|
-
primaryRays: config.width * config.height * config.samplesPerPixel,
|
|
4436
|
-
sceneObjectCount: config.sceneObjectCount,
|
|
4437
|
-
triangleCount: config.triangleCount,
|
|
4438
|
-
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
4439
|
-
environmentPortalCount: config.environmentPortalCount,
|
|
4440
|
-
environmentPortalMode: config.environmentPortalMode,
|
|
4441
|
-
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4442
|
-
deferredPathResolve: config.deferredPathResolve,
|
|
4443
|
-
bvhNodeCount: config.bvhNodeCount,
|
|
4444
|
-
displayQuality: config.displayQuality,
|
|
4445
|
-
accelerationBuildMode: config.accelerationBuildMode,
|
|
4446
|
-
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
4447
|
-
accelerationBuildSubmitted,
|
|
4448
|
-
accelerationBuilt,
|
|
4449
|
-
accelerationBuildCount,
|
|
4450
|
-
commandSubmissions: frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
4451
|
-
frameConfigSlots: frameConfigSlotCount,
|
|
4452
|
-
gpuParallelism: lastGpuParallelism,
|
|
4453
|
-
memory: config.memory
|
|
6920
|
+
startingSubmissionCount: submissionCount
|
|
4454
6921
|
});
|
|
6922
|
+
if (config.denoise) {
|
|
6923
|
+
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
6924
|
+
0,
|
|
6925
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
6926
|
+
frameIndex,
|
|
6927
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6928
|
+
);
|
|
6929
|
+
encodeDenoise(
|
|
6930
|
+
tail.reserve(denoisePassCount),
|
|
6931
|
+
denoiseConfigOffset,
|
|
6932
|
+
parallelism,
|
|
6933
|
+
renderedSamplesPerPixel
|
|
6934
|
+
);
|
|
6935
|
+
}
|
|
6936
|
+
encodePresent(tail.reserve(1));
|
|
6937
|
+
tail.flush();
|
|
6938
|
+
submissionCount += tail.getSubmissionCount();
|
|
6939
|
+
return submissionCount;
|
|
4455
6940
|
}
|
|
4456
6941
|
async function readOutputProbe(optionsForProbe = {}) {
|
|
4457
6942
|
const mapMode = constants.map;
|
|
@@ -4465,6 +6950,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4465
6950
|
size: 256,
|
|
4466
6951
|
usage: constants.buffer.COPY_DST | constants.buffer.MAP_READ
|
|
4467
6952
|
});
|
|
6953
|
+
await waitForSubmittedGpuWork({
|
|
6954
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
6955
|
+
allowTimeout: false
|
|
6956
|
+
});
|
|
4468
6957
|
const encoder = device.createCommandEncoder({
|
|
4469
6958
|
label: "plasius.wavefront.outputProbe.copy"
|
|
4470
6959
|
});
|
|
@@ -4474,6 +6963,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4474
6963
|
{ width: 1, height: 1, depthOrArrayLayers: 1 }
|
|
4475
6964
|
);
|
|
4476
6965
|
device.queue.submit([encoder.finish()]);
|
|
6966
|
+
await waitForSubmittedGpuWork({
|
|
6967
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
6968
|
+
allowTimeout: false
|
|
6969
|
+
});
|
|
4477
6970
|
await readback.mapAsync(mapMode.READ);
|
|
4478
6971
|
const bytes = new Uint8Array(readback.getMappedRange()).slice(0, 4);
|
|
4479
6972
|
readback.unmap();
|
|
@@ -4486,7 +6979,57 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4486
6979
|
});
|
|
4487
6980
|
}
|
|
4488
6981
|
async function renderFrame(renderOptions = {}) {
|
|
4489
|
-
const
|
|
6982
|
+
const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
|
|
6983
|
+
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
6984
|
+
const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
6985
|
+
const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
|
|
6986
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6987
|
+
tiles.length,
|
|
6988
|
+
renderOptions.submittedWorkTimeoutMs
|
|
6989
|
+
);
|
|
6990
|
+
const frameStartTimeMs = nowMs();
|
|
6991
|
+
const submissionWaitOptions = awaitGPUCompletion ? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false } : { timeoutMs: submittedWorkTimeoutMs };
|
|
6992
|
+
let frameStats;
|
|
6993
|
+
if (useThrottledHighSamplePath) {
|
|
6994
|
+
frame += 1;
|
|
6995
|
+
const frameIndex = frame + config.frameIndex;
|
|
6996
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
6997
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
6998
|
+
const frameSubmissionCount = dispatchFrameAwaitingGpu(
|
|
6999
|
+
frameIndex,
|
|
7000
|
+
parallelismCounters,
|
|
7001
|
+
samplingPlan.renderedSamplesPerPixel
|
|
7002
|
+
);
|
|
7003
|
+
frameStats = createFrameStats({
|
|
7004
|
+
frameIndex,
|
|
7005
|
+
accelerationBuildSubmitted,
|
|
7006
|
+
frameSubmissionCount,
|
|
7007
|
+
parallelismCounters,
|
|
7008
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
7009
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
7010
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
7011
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
7012
|
+
});
|
|
7013
|
+
} else {
|
|
7014
|
+
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
7015
|
+
}
|
|
7016
|
+
if (awaitGPUCompletion) {
|
|
7017
|
+
await waitForSubmittedGpuWork(submissionWaitOptions);
|
|
7018
|
+
}
|
|
7019
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
7020
|
+
if (awaitGPUCompletion) {
|
|
7021
|
+
lastCompletedFrameTimeMs = frameTimeMs;
|
|
7022
|
+
lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
|
|
7023
|
+
}
|
|
7024
|
+
frameStats = Object.freeze({
|
|
7025
|
+
...frameStats,
|
|
7026
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
7027
|
+
frameStats.gpuParallelism,
|
|
7028
|
+
frameStats.commandSubmissions,
|
|
7029
|
+
frameTimeMs,
|
|
7030
|
+
awaitGPUCompletion
|
|
7031
|
+
)
|
|
7032
|
+
});
|
|
4490
7033
|
const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
4491
7034
|
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
4492
7035
|
return Object.freeze({
|
|
@@ -4507,10 +7050,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4507
7050
|
queueOverflow: 0
|
|
4508
7051
|
});
|
|
4509
7052
|
}
|
|
4510
|
-
function
|
|
4511
|
-
|
|
4512
|
-
packedScene = nextPackedScene;
|
|
4513
|
-
config = createWavefrontPathTracingComputeConfig({
|
|
7053
|
+
function rebuildLiveConfig(overrides = {}) {
|
|
7054
|
+
return createWavefrontPathTracingComputeConfig({
|
|
4514
7055
|
...options,
|
|
4515
7056
|
canvas,
|
|
4516
7057
|
width: config.width,
|
|
@@ -4521,26 +7062,35 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4521
7062
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4522
7063
|
sceneObjects: packedScene.objects,
|
|
4523
7064
|
camera: activeCameraOptions,
|
|
4524
|
-
|
|
7065
|
+
environmentMap: {
|
|
7066
|
+
...config.environmentMap
|
|
7067
|
+
},
|
|
7068
|
+
frameIndex: config.frameIndex,
|
|
7069
|
+
...overrides
|
|
4525
7070
|
});
|
|
7071
|
+
}
|
|
7072
|
+
function rebuildMediumResources(nextConfig) {
|
|
7073
|
+
const previousMediumTextureResource = mediumTextureResource;
|
|
7074
|
+
mediumTextureResource = createMediumTextureResource(device, constants, nextConfig.mediums);
|
|
7075
|
+
bindGroups = createTraceBindGroups();
|
|
7076
|
+
if (previousMediumTextureResource?.ownsTexture) {
|
|
7077
|
+
previousMediumTextureResource.texture?.destroy?.();
|
|
7078
|
+
}
|
|
7079
|
+
}
|
|
7080
|
+
function updateSceneObjects(sceneObjects) {
|
|
7081
|
+
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
7082
|
+
packedScene = nextPackedScene;
|
|
7083
|
+
const nextConfig = rebuildLiveConfig();
|
|
7084
|
+
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
7085
|
+
rebuildMediumResources(nextConfig);
|
|
7086
|
+
}
|
|
7087
|
+
config = nextConfig;
|
|
4526
7088
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
4527
7089
|
return config;
|
|
4528
7090
|
}
|
|
4529
7091
|
function updateCamera(cameraOptions = {}) {
|
|
4530
7092
|
activeCameraOptions = cameraOptions;
|
|
4531
|
-
config =
|
|
4532
|
-
...options,
|
|
4533
|
-
canvas,
|
|
4534
|
-
width: config.width,
|
|
4535
|
-
height: config.height,
|
|
4536
|
-
maxDepth: config.maxDepth,
|
|
4537
|
-
tileSize: config.tileSize,
|
|
4538
|
-
samplesPerPixel: config.samplesPerPixel,
|
|
4539
|
-
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4540
|
-
sceneObjects: packedScene.objects,
|
|
4541
|
-
camera: activeCameraOptions,
|
|
4542
|
-
frameIndex: config.frameIndex
|
|
4543
|
-
});
|
|
7093
|
+
config = rebuildLiveConfig();
|
|
4544
7094
|
return config;
|
|
4545
7095
|
}
|
|
4546
7096
|
function getSnapshot() {
|
|
@@ -4558,6 +7108,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4558
7108
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
4559
7109
|
environmentPortalCount: config.environmentPortalCount,
|
|
4560
7110
|
environmentPortalMode: config.environmentPortalMode,
|
|
7111
|
+
mediumCount: config.mediumCount,
|
|
4561
7112
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4562
7113
|
deferredPathResolve: config.deferredPathResolve,
|
|
4563
7114
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -4595,6 +7146,28 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4595
7146
|
if (environmentMapResource.ownsTexture) {
|
|
4596
7147
|
environmentMapResource.texture?.destroy?.();
|
|
4597
7148
|
}
|
|
7149
|
+
if (environmentSamplingResource.ownsTexture) {
|
|
7150
|
+
environmentSamplingResource.texture?.destroy?.();
|
|
7151
|
+
}
|
|
7152
|
+
if (mediumTextureResource.ownsTexture) {
|
|
7153
|
+
mediumTextureResource.texture?.destroy?.();
|
|
7154
|
+
}
|
|
7155
|
+
brdfLutResource.texture?.destroy?.();
|
|
7156
|
+
if (baseColorAtlasResource.ownsTexture) {
|
|
7157
|
+
baseColorAtlasResource.texture?.destroy?.();
|
|
7158
|
+
}
|
|
7159
|
+
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
7160
|
+
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
7161
|
+
}
|
|
7162
|
+
if (normalAtlasResource.ownsTexture) {
|
|
7163
|
+
normalAtlasResource.texture?.destroy?.();
|
|
7164
|
+
}
|
|
7165
|
+
if (occlusionAtlasResource.ownsTexture) {
|
|
7166
|
+
occlusionAtlasResource.texture?.destroy?.();
|
|
7167
|
+
}
|
|
7168
|
+
if (emissiveAtlasResource.ownsTexture) {
|
|
7169
|
+
emissiveAtlasResource.texture?.destroy?.();
|
|
7170
|
+
}
|
|
4598
7171
|
context.unconfigure?.();
|
|
4599
7172
|
}
|
|
4600
7173
|
return Object.freeze({
|
|
@@ -4747,6 +7320,48 @@ var rendererAccelerationStructurePolicies = Object.freeze(
|
|
|
4747
7320
|
})
|
|
4748
7321
|
)
|
|
4749
7322
|
);
|
|
7323
|
+
function clampWavefrontAdaptiveSamplesPerPixel(value) {
|
|
7324
|
+
if (!Number.isFinite(value)) {
|
|
7325
|
+
return 1;
|
|
7326
|
+
}
|
|
7327
|
+
return Math.max(1, Math.min(256, Math.round(value)));
|
|
7328
|
+
}
|
|
7329
|
+
function createWavefrontAdaptiveSamplingLevels(options = {}) {
|
|
7330
|
+
const requestedSamplesPerPixel = clampWavefrontAdaptiveSamplesPerPixel(
|
|
7331
|
+
options.samplesPerPixel ?? 1
|
|
7332
|
+
);
|
|
7333
|
+
const minimumSamplesPerPixel = Math.min(
|
|
7334
|
+
requestedSamplesPerPixel,
|
|
7335
|
+
clampWavefrontAdaptiveSamplesPerPixel(options.minimumSamplesPerPixel ?? 1)
|
|
7336
|
+
);
|
|
7337
|
+
const frameTimeBudgetMs = Number.isFinite(options.frameTimeBudgetMs) ? Math.max(0, Number(options.frameTimeBudgetMs)) : 0;
|
|
7338
|
+
const levels = /* @__PURE__ */ new Set([minimumSamplesPerPixel, requestedSamplesPerPixel]);
|
|
7339
|
+
let currentSamplesPerPixel = minimumSamplesPerPixel;
|
|
7340
|
+
while (currentSamplesPerPixel < requestedSamplesPerPixel) {
|
|
7341
|
+
levels.add(currentSamplesPerPixel);
|
|
7342
|
+
currentSamplesPerPixel *= 2;
|
|
7343
|
+
}
|
|
7344
|
+
levels.add(Math.min(currentSamplesPerPixel, requestedSamplesPerPixel));
|
|
7345
|
+
return Object.freeze({
|
|
7346
|
+
requestedSamplesPerPixel,
|
|
7347
|
+
minimumSamplesPerPixel,
|
|
7348
|
+
frameTimeBudgetMs,
|
|
7349
|
+
levels: Object.freeze(
|
|
7350
|
+
[...levels].sort((left, right) => left - right).map(
|
|
7351
|
+
(samplesPerPixel) => Object.freeze({
|
|
7352
|
+
id: `${samplesPerPixel}spp`,
|
|
7353
|
+
label: `${samplesPerPixel} spp`,
|
|
7354
|
+
estimatedCostMs: samplesPerPixel,
|
|
7355
|
+
config: Object.freeze({
|
|
7356
|
+
samplesPerPixel,
|
|
7357
|
+
frameTimeBudgetMs,
|
|
7358
|
+
minimumSamplesPerPixel
|
|
7359
|
+
})
|
|
7360
|
+
})
|
|
7361
|
+
)
|
|
7362
|
+
)
|
|
7363
|
+
});
|
|
7364
|
+
}
|
|
4750
7365
|
function createWavefrontField(name, type, description) {
|
|
4751
7366
|
return Object.freeze({
|
|
4752
7367
|
name,
|
|
@@ -5924,9 +8539,11 @@ var defaultRendererClearColor = DEFAULT_CLEAR_COLOR;
|
|
|
5924
8539
|
createGpuRenderer,
|
|
5925
8540
|
createRayTracingRenderPlan,
|
|
5926
8541
|
createRendererDebugHooks,
|
|
8542
|
+
createWavefrontAdaptiveSamplingLevels,
|
|
5927
8543
|
createWavefrontBvhBuildLevels,
|
|
5928
8544
|
createWavefrontBvhSortStages,
|
|
5929
8545
|
createWavefrontEmissiveTriangleIndexSource,
|
|
8546
|
+
createWavefrontGpuMaterialSource,
|
|
5930
8547
|
createWavefrontGpuMeshSource,
|
|
5931
8548
|
createWavefrontMeshAcceleration,
|
|
5932
8549
|
createWavefrontPathTracingComputeConfig,
|