@plasius/gpu-renderer 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -20
- package/README.md +40 -7
- package/dist/index.cjs +2651 -403
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2649 -403
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.d.ts +173 -5
- package/src/index.js +52 -0
- package/src/wavefront-compute.js +2650 -417
- package/src/wavefront-frame-runtime.js +167 -0
package/dist/index.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,12 +72,147 @@ __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 = 256;
|
|
79
216
|
var DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
80
217
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
81
218
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
@@ -84,22 +221,27 @@ var rendererWavefrontComputeMode = "webgpu-compute";
|
|
|
84
221
|
var rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
85
222
|
var rendererWavefrontComputeStatsStride = 8;
|
|
86
223
|
var RAY_RECORD_BYTES = 80;
|
|
87
|
-
var HIT_RECORD_BYTES =
|
|
88
|
-
var SCENE_OBJECT_RECORD_BYTES =
|
|
224
|
+
var HIT_RECORD_BYTES = 256;
|
|
225
|
+
var SCENE_OBJECT_RECORD_BYTES = 144;
|
|
89
226
|
var MESH_VERTEX_RECORD_BYTES = 48;
|
|
90
|
-
var MESH_RANGE_RECORD_BYTES =
|
|
91
|
-
var TRIANGLE_RECORD_BYTES =
|
|
227
|
+
var MESH_RANGE_RECORD_BYTES = 240;
|
|
228
|
+
var TRIANGLE_RECORD_BYTES = 352;
|
|
229
|
+
var GPU_MATERIAL_RECORD_BYTES = 192;
|
|
92
230
|
var BVH_NODE_RECORD_BYTES = 48;
|
|
93
231
|
var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
94
232
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
95
233
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
96
234
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
97
235
|
var PATH_VERTEX_RECORD_BYTES = 16;
|
|
98
|
-
var
|
|
236
|
+
var GPU_SUBMITTED_WORK_TIMEOUT_MS = 5e3;
|
|
237
|
+
var GPU_READBACK_COMPLETION_TIMEOUT_MS = 6e4;
|
|
238
|
+
var GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 6e4;
|
|
239
|
+
var CONFIG_BUFFER_BYTES = 320;
|
|
99
240
|
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
100
241
|
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
101
242
|
var COUNTER_BUFFER_BYTES = 32;
|
|
102
243
|
var TRACE_STORAGE_BUFFER_BINDINGS = 10;
|
|
244
|
+
var BRDF_LUT_UPLOAD_CACHE = /* @__PURE__ */ new Map();
|
|
103
245
|
var MATERIAL_DIFFUSE = 0;
|
|
104
246
|
var MATERIAL_METAL = 1;
|
|
105
247
|
var MATERIAL_DIELECTRIC = 2;
|
|
@@ -134,6 +276,7 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
134
276
|
meshVertexRecordBytes: MESH_VERTEX_RECORD_BYTES,
|
|
135
277
|
meshRangeRecordBytes: MESH_RANGE_RECORD_BYTES,
|
|
136
278
|
triangleRecordBytes: TRIANGLE_RECORD_BYTES,
|
|
279
|
+
materialRecordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
137
280
|
bvhNodeRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
138
281
|
bvhLeafReferenceRecordBytes: BVH_LEAF_REF_RECORD_BYTES,
|
|
139
282
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
@@ -217,6 +360,32 @@ function asColor(value, fallback = [1, 1, 1, 1]) {
|
|
|
217
360
|
clamp(readFiniteNumber("color[3]", value[3], fallback[3] ?? 1), 0, 1)
|
|
218
361
|
];
|
|
219
362
|
}
|
|
363
|
+
function deriveLegacySheenColor(baseColor, sheen, sheenTint) {
|
|
364
|
+
const sheenStrength = clamp(Number(sheen) || 0, 0, 1);
|
|
365
|
+
if (sheenStrength <= 0) {
|
|
366
|
+
return [0, 0, 0, 1];
|
|
367
|
+
}
|
|
368
|
+
const tint = clamp(Number(sheenTint) || 0, 0, 1);
|
|
369
|
+
const base = asColor(baseColor, [1, 1, 1, 1]);
|
|
370
|
+
return [
|
|
371
|
+
clamp((1 - tint) * sheenStrength + base[0] * tint * sheenStrength, 0, 1),
|
|
372
|
+
clamp((1 - tint) * sheenStrength + base[1] * tint * sheenStrength, 0, 1),
|
|
373
|
+
clamp((1 - tint) * sheenStrength + base[2] * tint * sheenStrength, 0, 1),
|
|
374
|
+
1
|
|
375
|
+
];
|
|
376
|
+
}
|
|
377
|
+
function resolveSheenColor(input, fallbackBaseColor) {
|
|
378
|
+
if (input?.sheenColor || input?.material?.sheenColor) {
|
|
379
|
+
return asColor(input.sheenColor ?? input.material?.sheenColor, [0, 0, 0, 1]).map(
|
|
380
|
+
(value, index) => index < 3 ? clamp(value, 0, 1) : 1
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
return deriveLegacySheenColor(
|
|
384
|
+
fallbackBaseColor,
|
|
385
|
+
input?.sheen ?? input?.material?.sheen,
|
|
386
|
+
input?.sheenTint ?? input?.material?.sheenTint
|
|
387
|
+
);
|
|
388
|
+
}
|
|
220
389
|
function resolveEnvironmentMap(input = null) {
|
|
221
390
|
const source = input && typeof input === "object" ? input : null;
|
|
222
391
|
const hasTexture = Boolean(source?.view || source?.texture || source?.data);
|
|
@@ -226,6 +395,11 @@ function resolveEnvironmentMap(input = null) {
|
|
|
226
395
|
enabled: hasTexture && source?.enabled !== false,
|
|
227
396
|
width,
|
|
228
397
|
height,
|
|
398
|
+
mipLevelCount: readPositiveInteger(
|
|
399
|
+
"environmentMap.mipLevelCount",
|
|
400
|
+
source?.mipLevelCount,
|
|
401
|
+
1
|
|
402
|
+
),
|
|
229
403
|
format: typeof source?.format === "string" ? source.format : "rgba16float",
|
|
230
404
|
projection: typeof source?.projection === "string" ? source.projection : "equirectangular",
|
|
231
405
|
texture: source?.texture ?? null,
|
|
@@ -237,7 +411,8 @@ function resolveEnvironmentMap(input = null) {
|
|
|
237
411
|
ambientStrength: Math.max(
|
|
238
412
|
0,
|
|
239
413
|
readFiniteNumber("environmentMap.ambientStrength", source?.ambientStrength, 0.32)
|
|
240
|
-
)
|
|
414
|
+
),
|
|
415
|
+
hasImportanceData: source?.hasImportanceData === true
|
|
241
416
|
});
|
|
242
417
|
}
|
|
243
418
|
function resolveDeferredPathResolve(options = {}) {
|
|
@@ -411,7 +586,8 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
411
586
|
input.halfExtent ?? input.halfExtents ?? input.extents ?? bounds?.halfExtent,
|
|
412
587
|
[0.5, 0.5, 0.5]
|
|
413
588
|
).map((value) => Math.max(value, 1e-3));
|
|
414
|
-
const
|
|
589
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
590
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
415
591
|
const color = asColor(
|
|
416
592
|
input.color ?? input.baseColor ?? input.albedo ?? input.material?.color ?? input.material?.baseColor,
|
|
417
593
|
[0.72, 0.72, 0.68, 1]
|
|
@@ -420,10 +596,22 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
420
596
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
421
597
|
[0, 0, 0, 1]
|
|
422
598
|
);
|
|
599
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
600
|
+
const transmission = clamp(
|
|
601
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
602
|
+
0,
|
|
603
|
+
1
|
|
604
|
+
);
|
|
605
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
606
|
+
const specularColor = asColor(
|
|
607
|
+
input.specularColor ?? input.material?.specularColor,
|
|
608
|
+
[1, 1, 1, 1]
|
|
609
|
+
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
610
|
+
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
611
|
return Object.freeze({
|
|
424
612
|
id: readNonNegativeInteger("id", input.id, index + 1),
|
|
425
613
|
kind,
|
|
426
|
-
materialKind:
|
|
614
|
+
materialKind: resolvedMaterialKind,
|
|
427
615
|
flags: readNonNegativeInteger("flags", input.flags, 0),
|
|
428
616
|
center: Object.freeze(center),
|
|
429
617
|
halfExtent: Object.freeze(halfExtent),
|
|
@@ -431,8 +619,24 @@ function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
431
619
|
emission: Object.freeze(emission),
|
|
432
620
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
433
621
|
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)
|
|
622
|
+
opacity,
|
|
623
|
+
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
624
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
625
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
626
|
+
sheenColor: Object.freeze(sheenColor),
|
|
627
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
628
|
+
clearcoatRoughness: clamp(
|
|
629
|
+
readFiniteNumber(
|
|
630
|
+
"clearcoatRoughness",
|
|
631
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
632
|
+
0.08
|
|
633
|
+
),
|
|
634
|
+
0,
|
|
635
|
+
1
|
|
636
|
+
),
|
|
637
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
638
|
+
specularColor: Object.freeze(specularColor),
|
|
639
|
+
transmission
|
|
436
640
|
});
|
|
437
641
|
}
|
|
438
642
|
function createDefaultWavefrontSceneObjects() {
|
|
@@ -504,7 +708,8 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
504
708
|
input.uvs ?? input.texcoords ?? input.uv,
|
|
505
709
|
(value) => readFiniteNumber("mesh uv", value, 0)
|
|
506
710
|
) : null;
|
|
507
|
-
const
|
|
711
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
712
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
508
713
|
const color = asColor(
|
|
509
714
|
input.color ?? input.baseColor ?? input.albedo ?? input.material?.color ?? input.material?.baseColor,
|
|
510
715
|
[0.72, 0.72, 0.68, 1]
|
|
@@ -513,13 +718,25 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
513
718
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
514
719
|
[0, 0, 0, 1]
|
|
515
720
|
);
|
|
721
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
722
|
+
const transmission = clamp(
|
|
723
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
724
|
+
0,
|
|
725
|
+
1
|
|
726
|
+
);
|
|
727
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
728
|
+
const specularColor = asColor(
|
|
729
|
+
input.specularColor ?? input.material?.specularColor,
|
|
730
|
+
[1, 1, 1, 1]
|
|
731
|
+
).map((value, componentIndex) => componentIndex < 3 ? clamp(value, 0, 1) : 1);
|
|
732
|
+
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
733
|
return Object.freeze({
|
|
517
734
|
id: readNonNegativeInteger("mesh id", input.id, meshIndex + 1),
|
|
518
735
|
positions: Object.freeze(Array.from(positions, (value) => readFiniteNumber("mesh position", value, 0))),
|
|
519
736
|
indices: Object.freeze(indices),
|
|
520
737
|
normals: normals ? Object.freeze(normals) : null,
|
|
521
738
|
uvs: uvs ? Object.freeze(uvs) : null,
|
|
522
|
-
materialKind:
|
|
739
|
+
materialKind: resolvedMaterialKind,
|
|
523
740
|
flags: readNonNegativeInteger("mesh flags", input.flags, 0),
|
|
524
741
|
materialRefId: readNonNegativeInteger(
|
|
525
742
|
"mesh materialRefId",
|
|
@@ -535,10 +752,167 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
535
752
|
emission: Object.freeze(emission),
|
|
536
753
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
537
754
|
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)
|
|
755
|
+
opacity,
|
|
756
|
+
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
757
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
758
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
759
|
+
sheenColor: Object.freeze(sheenColor),
|
|
760
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
761
|
+
clearcoatRoughness: clamp(
|
|
762
|
+
readFiniteNumber(
|
|
763
|
+
"clearcoatRoughness",
|
|
764
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
765
|
+
0.08
|
|
766
|
+
),
|
|
767
|
+
0,
|
|
768
|
+
1
|
|
769
|
+
),
|
|
770
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
771
|
+
specularColor: Object.freeze(specularColor),
|
|
772
|
+
transmission,
|
|
773
|
+
baseColorTexture: input.baseColorTexture ?? input.material?.baseColorTexture ?? null,
|
|
774
|
+
metallicRoughnessTexture: input.metallicRoughnessTexture ?? input.material?.metallicRoughnessTexture ?? null,
|
|
775
|
+
normalTexture: input.normalTexture ?? input.material?.normalTexture ?? null,
|
|
776
|
+
occlusionTexture: input.occlusionTexture ?? input.material?.occlusionTexture ?? null,
|
|
777
|
+
emissiveTexture: input.emissiveTexture ?? input.material?.emissiveTexture ?? null
|
|
540
778
|
});
|
|
541
779
|
}
|
|
780
|
+
function clampUnit(value) {
|
|
781
|
+
return clamp(Number(value) || 0, 0, 1);
|
|
782
|
+
}
|
|
783
|
+
function srgbToLinear(value) {
|
|
784
|
+
const channel = clampUnit(value);
|
|
785
|
+
if (channel <= 0.04045) {
|
|
786
|
+
return channel / 12.92;
|
|
787
|
+
}
|
|
788
|
+
return ((channel + 0.055) / 1.055) ** 2.4;
|
|
789
|
+
}
|
|
790
|
+
function sampleTextureRgba(texture, uv = [0, 0], colorSpace = "linear") {
|
|
791
|
+
if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || !texture.data || texture.width <= 0 || texture.height <= 0) {
|
|
792
|
+
return [1, 1, 1, 1];
|
|
793
|
+
}
|
|
794
|
+
const u = (uv[0] % 1 + 1) % 1;
|
|
795
|
+
const v = (uv[1] % 1 + 1) % 1;
|
|
796
|
+
const x = Math.min(texture.width - 1, Math.max(0, Math.round(u * (texture.width - 1))));
|
|
797
|
+
const y = Math.min(texture.height - 1, Math.max(0, Math.round((1 - v) * (texture.height - 1))));
|
|
798
|
+
const offset = (y * texture.width + x) * 4;
|
|
799
|
+
const data = texture.data;
|
|
800
|
+
const scale2 = resolveTextureSampleScale(data);
|
|
801
|
+
const defaultChannel = scale2 === 1 ? 1 : Math.round(1 / scale2);
|
|
802
|
+
const color = [
|
|
803
|
+
(data[offset] ?? defaultChannel) * scale2,
|
|
804
|
+
(data[offset + 1] ?? defaultChannel) * scale2,
|
|
805
|
+
(data[offset + 2] ?? defaultChannel) * scale2,
|
|
806
|
+
(data[offset + 3] ?? defaultChannel) * scale2
|
|
807
|
+
];
|
|
808
|
+
if (colorSpace === "srgb") {
|
|
809
|
+
return [srgbToLinear(color[0]), srgbToLinear(color[1]), srgbToLinear(color[2]), color[3]];
|
|
810
|
+
}
|
|
811
|
+
return color;
|
|
812
|
+
}
|
|
813
|
+
function resolveTextureSampleScale(data) {
|
|
814
|
+
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
|
|
815
|
+
return 1 / 255;
|
|
816
|
+
}
|
|
817
|
+
if (data instanceof Uint16Array) {
|
|
818
|
+
return 1 / 65535;
|
|
819
|
+
}
|
|
820
|
+
if (Array.isArray(data) && data.some((value) => Number(value) > 1)) {
|
|
821
|
+
return 1 / 255;
|
|
822
|
+
}
|
|
823
|
+
return 1;
|
|
824
|
+
}
|
|
825
|
+
function normalizeVectorOrFallback(vector, fallback) {
|
|
826
|
+
return normalize(Array.isArray(vector) ? vector : fallback, fallback);
|
|
827
|
+
}
|
|
828
|
+
function buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, fallbackNormal) {
|
|
829
|
+
const edge1 = subtract(v1, v0);
|
|
830
|
+
const edge2 = subtract(v2, v0);
|
|
831
|
+
const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
|
|
832
|
+
const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
|
|
833
|
+
const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
|
|
834
|
+
if (Math.abs(determinant) < 1e-6) {
|
|
835
|
+
const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
836
|
+
const tangent2 = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
|
|
837
|
+
const bitangent2 = normalize(cross(fallbackNormal, tangent2), [0, 0, 1]);
|
|
838
|
+
return { tangent: tangent2, bitangent: bitangent2 };
|
|
839
|
+
}
|
|
840
|
+
const inverse = 1 / determinant;
|
|
841
|
+
const tangent = normalize(
|
|
842
|
+
[
|
|
843
|
+
inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
|
|
844
|
+
inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
|
|
845
|
+
inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1])
|
|
846
|
+
],
|
|
847
|
+
[1, 0, 0]
|
|
848
|
+
);
|
|
849
|
+
const bitangent = normalize(
|
|
850
|
+
[
|
|
851
|
+
inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
|
|
852
|
+
inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
|
|
853
|
+
inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0])
|
|
854
|
+
],
|
|
855
|
+
[0, 0, 1]
|
|
856
|
+
);
|
|
857
|
+
return { tangent, bitangent };
|
|
858
|
+
}
|
|
859
|
+
function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
|
|
860
|
+
if (!normalTexture) {
|
|
861
|
+
return normalizeVectorOrFallback(normal, [0, 1, 0]);
|
|
862
|
+
}
|
|
863
|
+
const sample = sampleTextureRgba(normalTexture, uv, "linear");
|
|
864
|
+
const strength = clampUnit(normalTexture.scale ?? 1);
|
|
865
|
+
const tangentNormal = normalize(
|
|
866
|
+
[
|
|
867
|
+
(sample[0] * 2 - 1) * strength,
|
|
868
|
+
(sample[1] * 2 - 1) * strength,
|
|
869
|
+
1 + (sample[2] * 2 - 1 - 1) * strength
|
|
870
|
+
],
|
|
871
|
+
[0, 0, 1]
|
|
872
|
+
);
|
|
873
|
+
return normalize(
|
|
874
|
+
[
|
|
875
|
+
tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
|
|
876
|
+
tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
|
|
877
|
+
tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2]
|
|
878
|
+
],
|
|
879
|
+
normal
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
function sampleBaseColor(mesh, uv) {
|
|
883
|
+
const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
|
|
884
|
+
return [
|
|
885
|
+
clampUnit(mesh.color[0] * sample[0]),
|
|
886
|
+
clampUnit(mesh.color[1] * sample[1]),
|
|
887
|
+
clampUnit(mesh.color[2] * sample[2]),
|
|
888
|
+
clampUnit((mesh.color[3] ?? 1) * sample[3])
|
|
889
|
+
];
|
|
890
|
+
}
|
|
891
|
+
function sampleSurfaceMaterial(mesh, uv) {
|
|
892
|
+
const textureSample = mesh.metallicRoughnessTexture ? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear") : [1, 1, 1, 1];
|
|
893
|
+
return {
|
|
894
|
+
roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
|
|
895
|
+
metallic: clamp(mesh.metallic * textureSample[2], 0, 1)
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
function averageColors(colors) {
|
|
899
|
+
const count = Math.max(colors.length, 1);
|
|
900
|
+
return colors.reduce(
|
|
901
|
+
(accumulator, color) => [
|
|
902
|
+
accumulator[0] + color[0] / count,
|
|
903
|
+
accumulator[1] + color[1] / count,
|
|
904
|
+
accumulator[2] + color[2] / count,
|
|
905
|
+
accumulator[3] + color[3] / count
|
|
906
|
+
],
|
|
907
|
+
[0, 0, 0, 0]
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
function averageNumbers(values, fallback = 0) {
|
|
911
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
912
|
+
return fallback;
|
|
913
|
+
}
|
|
914
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
915
|
+
}
|
|
542
916
|
function createMeshTriangleRecords(meshes) {
|
|
543
917
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
544
918
|
let nextTriangleId = 0;
|
|
@@ -559,6 +933,16 @@ function createMeshTriangleRecords(meshes) {
|
|
|
559
933
|
const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
|
|
560
934
|
const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
|
|
561
935
|
const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
|
|
936
|
+
const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
|
|
937
|
+
const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
|
|
938
|
+
const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
|
|
939
|
+
const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
|
|
940
|
+
const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
|
|
941
|
+
const sampledMaterials = [
|
|
942
|
+
sampleSurfaceMaterial(mesh, uv0),
|
|
943
|
+
sampleSurfaceMaterial(mesh, uv1),
|
|
944
|
+
sampleSurfaceMaterial(mesh, uv2)
|
|
945
|
+
];
|
|
562
946
|
const bounds = triangleBounds(v0, v1, v2);
|
|
563
947
|
triangles.push(
|
|
564
948
|
Object.freeze({
|
|
@@ -568,18 +952,42 @@ function createMeshTriangleRecords(meshes) {
|
|
|
568
952
|
flags: mesh.flags,
|
|
569
953
|
materialRefId: mesh.materialRefId,
|
|
570
954
|
mediumRefId: mesh.mediumRefId,
|
|
955
|
+
materialSlot: meshIndex,
|
|
571
956
|
v0: Object.freeze(v0),
|
|
572
957
|
v1: Object.freeze(v1),
|
|
573
958
|
v2: Object.freeze(v2),
|
|
574
|
-
n0: Object.freeze(
|
|
575
|
-
n1: Object.freeze(
|
|
576
|
-
n2: Object.freeze(
|
|
959
|
+
n0: Object.freeze(shadedN0),
|
|
960
|
+
n1: Object.freeze(shadedN1),
|
|
961
|
+
n2: Object.freeze(shadedN2),
|
|
577
962
|
uv0: Object.freeze(uv0),
|
|
578
963
|
uv1: Object.freeze(uv1),
|
|
579
964
|
uv2: Object.freeze(uv2),
|
|
580
|
-
color:
|
|
965
|
+
color: Object.freeze(averageColors(sampledColors)),
|
|
581
966
|
emission: mesh.emission,
|
|
582
|
-
material: Object.freeze([
|
|
967
|
+
material: Object.freeze([
|
|
968
|
+
averageNumbers(sampledMaterials.map((sample) => sample.roughness), mesh.roughness),
|
|
969
|
+
averageNumbers(sampledMaterials.map((sample) => sample.metallic), mesh.metallic),
|
|
970
|
+
mesh.opacity,
|
|
971
|
+
mesh.ior
|
|
972
|
+
]),
|
|
973
|
+
materialResponse: Object.freeze([
|
|
974
|
+
mesh.sheenColor[0] ?? 0,
|
|
975
|
+
mesh.sheenColor[1] ?? 0,
|
|
976
|
+
mesh.sheenColor[2] ?? 0,
|
|
977
|
+
mesh.clearcoat
|
|
978
|
+
]),
|
|
979
|
+
materialExtension: Object.freeze([
|
|
980
|
+
mesh.clearcoatRoughness,
|
|
981
|
+
mesh.specular,
|
|
982
|
+
mesh.transmission,
|
|
983
|
+
0
|
|
984
|
+
]),
|
|
985
|
+
specularColor: Object.freeze([
|
|
986
|
+
mesh.specularColor[0] ?? 1,
|
|
987
|
+
mesh.specularColor[1] ?? 1,
|
|
988
|
+
mesh.specularColor[2] ?? 1,
|
|
989
|
+
1
|
|
990
|
+
]),
|
|
583
991
|
bounds: Object.freeze({
|
|
584
992
|
min: Object.freeze(bounds.min),
|
|
585
993
|
max: Object.freeze(bounds.max)
|
|
@@ -688,6 +1096,220 @@ function nextPowerOfTwo(value) {
|
|
|
688
1096
|
}
|
|
689
1097
|
return 2 ** Math.ceil(Math.log2(value));
|
|
690
1098
|
}
|
|
1099
|
+
function textureComponentToByte(value, fallback) {
|
|
1100
|
+
const numeric = Number(value);
|
|
1101
|
+
if (!Number.isFinite(numeric)) {
|
|
1102
|
+
return fallback;
|
|
1103
|
+
}
|
|
1104
|
+
if (numeric >= 0 && numeric <= 1) {
|
|
1105
|
+
return Math.max(0, Math.min(255, Math.round(numeric * 255)));
|
|
1106
|
+
}
|
|
1107
|
+
return Math.max(0, Math.min(255, Math.round(numeric)));
|
|
1108
|
+
}
|
|
1109
|
+
function createSolidTextureSample(width, height, rgba) {
|
|
1110
|
+
const data = new Uint8Array(width * height * 4);
|
|
1111
|
+
for (let offset = 0; offset < data.length; offset += 4) {
|
|
1112
|
+
data[offset] = rgba[0];
|
|
1113
|
+
data[offset + 1] = rgba[1];
|
|
1114
|
+
data[offset + 2] = rgba[2];
|
|
1115
|
+
data[offset + 3] = rgba[3];
|
|
1116
|
+
}
|
|
1117
|
+
return Object.freeze({
|
|
1118
|
+
width,
|
|
1119
|
+
height,
|
|
1120
|
+
data
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
function normalizeTextureSampleInput(texture, fallbackColor) {
|
|
1124
|
+
if (!texture || !Number.isFinite(texture.width) || !Number.isFinite(texture.height) || texture.width <= 0 || texture.height <= 0) {
|
|
1125
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
1126
|
+
}
|
|
1127
|
+
const pixelCount = Math.trunc(texture.width) * Math.trunc(texture.height) * 4;
|
|
1128
|
+
const source = ArrayBuffer.isView(texture.data) || Array.isArray(texture.data) ? texture.data : null;
|
|
1129
|
+
if (!source || source.length < pixelCount) {
|
|
1130
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
1131
|
+
}
|
|
1132
|
+
const data = new Uint8Array(pixelCount);
|
|
1133
|
+
for (let index = 0; index < pixelCount; index += 1) {
|
|
1134
|
+
data[index] = textureComponentToByte(source[index], fallbackColor[index % 4]);
|
|
1135
|
+
}
|
|
1136
|
+
return Object.freeze({
|
|
1137
|
+
width: Math.trunc(texture.width),
|
|
1138
|
+
height: Math.trunc(texture.height),
|
|
1139
|
+
data
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
function buildTextureAtlas(textures, fallbackColor) {
|
|
1143
|
+
const padding = 1;
|
|
1144
|
+
const defaultTexture = createSolidTextureSample(1, 1, fallbackColor);
|
|
1145
|
+
const uniqueEntries = [{ source: null, texture: defaultTexture }];
|
|
1146
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
1147
|
+
for (const texture of Array.isArray(textures) ? textures : []) {
|
|
1148
|
+
if (!texture || bySource.has(texture)) {
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
const normalized = normalizeTextureSampleInput(texture, fallbackColor);
|
|
1152
|
+
bySource.set(texture, uniqueEntries.length);
|
|
1153
|
+
uniqueEntries.push({ source: texture, texture: normalized });
|
|
1154
|
+
}
|
|
1155
|
+
const totalArea = uniqueEntries.reduce((sum, entry) => {
|
|
1156
|
+
return sum + (entry.texture.width + padding * 2) * (entry.texture.height + padding * 2);
|
|
1157
|
+
}, 0);
|
|
1158
|
+
const maxTileWidth = uniqueEntries.reduce((maxWidth, entry) => {
|
|
1159
|
+
return Math.max(maxWidth, entry.texture.width + padding * 2);
|
|
1160
|
+
}, 1);
|
|
1161
|
+
const targetWidth = Math.max(
|
|
1162
|
+
maxTileWidth,
|
|
1163
|
+
nextPowerOfTwo(Math.max(maxTileWidth, Math.ceil(Math.sqrt(totalArea))))
|
|
1164
|
+
);
|
|
1165
|
+
let cursorX = 0;
|
|
1166
|
+
let cursorY = 0;
|
|
1167
|
+
let rowHeight = 0;
|
|
1168
|
+
let atlasWidth = 0;
|
|
1169
|
+
const placements = uniqueEntries.map((entry) => {
|
|
1170
|
+
const tileWidth = entry.texture.width + padding * 2;
|
|
1171
|
+
const tileHeight = entry.texture.height + padding * 2;
|
|
1172
|
+
if (cursorX > 0 && cursorX + tileWidth > targetWidth) {
|
|
1173
|
+
cursorX = 0;
|
|
1174
|
+
cursorY += rowHeight;
|
|
1175
|
+
rowHeight = 0;
|
|
1176
|
+
}
|
|
1177
|
+
const placement = Object.freeze({
|
|
1178
|
+
x: cursorX,
|
|
1179
|
+
y: cursorY,
|
|
1180
|
+
tileWidth,
|
|
1181
|
+
tileHeight,
|
|
1182
|
+
width: entry.texture.width,
|
|
1183
|
+
height: entry.texture.height
|
|
1184
|
+
});
|
|
1185
|
+
cursorX += tileWidth;
|
|
1186
|
+
atlasWidth = Math.max(atlasWidth, cursorX);
|
|
1187
|
+
rowHeight = Math.max(rowHeight, tileHeight);
|
|
1188
|
+
return placement;
|
|
1189
|
+
});
|
|
1190
|
+
const atlasHeight = Math.max(1, cursorY + rowHeight);
|
|
1191
|
+
const atlasData = new Uint8Array(Math.max(1, atlasWidth * atlasHeight * 4));
|
|
1192
|
+
const writePixel = (x, y, rgba) => {
|
|
1193
|
+
const offset = (y * atlasWidth + x) * 4;
|
|
1194
|
+
atlasData[offset] = rgba[0];
|
|
1195
|
+
atlasData[offset + 1] = rgba[1];
|
|
1196
|
+
atlasData[offset + 2] = rgba[2];
|
|
1197
|
+
atlasData[offset + 3] = rgba[3];
|
|
1198
|
+
};
|
|
1199
|
+
const rects = placements.map((placement, entryIndex) => {
|
|
1200
|
+
const { texture } = uniqueEntries[entryIndex];
|
|
1201
|
+
for (let y = 0; y < placement.tileHeight; y += 1) {
|
|
1202
|
+
for (let x = 0; x < placement.tileWidth; x += 1) {
|
|
1203
|
+
const sampleX = Math.max(0, Math.min(texture.width - 1, x - padding));
|
|
1204
|
+
const sampleY = Math.max(0, Math.min(texture.height - 1, y - padding));
|
|
1205
|
+
const sourceOffset = (sampleY * texture.width + sampleX) * 4;
|
|
1206
|
+
writePixel(placement.x + x, placement.y + y, texture.data.slice(sourceOffset, sourceOffset + 4));
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return Object.freeze([
|
|
1210
|
+
(placement.x + padding) / Math.max(1, atlasWidth),
|
|
1211
|
+
(placement.y + padding) / Math.max(1, atlasHeight),
|
|
1212
|
+
placement.width / Math.max(1, atlasWidth),
|
|
1213
|
+
placement.height / Math.max(1, atlasHeight)
|
|
1214
|
+
]);
|
|
1215
|
+
});
|
|
1216
|
+
const rectBySource = /* @__PURE__ */ new Map();
|
|
1217
|
+
uniqueEntries.forEach((entry, index) => {
|
|
1218
|
+
if (entry.source) {
|
|
1219
|
+
rectBySource.set(entry.source, rects[index]);
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
return Object.freeze({
|
|
1223
|
+
width: Math.max(1, atlasWidth),
|
|
1224
|
+
height: Math.max(1, atlasHeight),
|
|
1225
|
+
data: atlasData,
|
|
1226
|
+
defaultRect: rects[0],
|
|
1227
|
+
resolveRect(texture) {
|
|
1228
|
+
return rectBySource.get(texture) ?? rects[0];
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
function createWavefrontGpuMaterialSource(meshes = []) {
|
|
1233
|
+
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
1234
|
+
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1235
|
+
const baseColorAtlas = buildTextureAtlas(
|
|
1236
|
+
normalized.map((mesh) => mesh.baseColorTexture),
|
|
1237
|
+
[255, 255, 255, 255]
|
|
1238
|
+
);
|
|
1239
|
+
const metallicRoughnessAtlas = buildTextureAtlas(
|
|
1240
|
+
normalized.map((mesh) => mesh.metallicRoughnessTexture),
|
|
1241
|
+
[255, 255, 255, 255]
|
|
1242
|
+
);
|
|
1243
|
+
const normalAtlas = buildTextureAtlas(
|
|
1244
|
+
normalized.map((mesh) => mesh.normalTexture),
|
|
1245
|
+
[128, 128, 255, 255]
|
|
1246
|
+
);
|
|
1247
|
+
const occlusionAtlas = buildTextureAtlas(
|
|
1248
|
+
normalized.map((mesh) => mesh.occlusionTexture),
|
|
1249
|
+
[255, 255, 255, 255]
|
|
1250
|
+
);
|
|
1251
|
+
const emissiveAtlas = buildTextureAtlas(
|
|
1252
|
+
normalized.map((mesh) => mesh.emissiveTexture),
|
|
1253
|
+
[255, 255, 255, 255]
|
|
1254
|
+
);
|
|
1255
|
+
const bytes = new ArrayBuffer(Math.max(1, normalized.length) * GPU_MATERIAL_RECORD_BYTES);
|
|
1256
|
+
const floatView = new Float32Array(bytes);
|
|
1257
|
+
normalized.forEach((mesh, meshIndex) => {
|
|
1258
|
+
const byteOffset = meshIndex * GPU_MATERIAL_RECORD_BYTES;
|
|
1259
|
+
writeVec4(floatView, byteOffset, mesh.color);
|
|
1260
|
+
writeVec4(floatView, byteOffset + 16, mesh.emission);
|
|
1261
|
+
writeVec4(floatView, byteOffset + 32, [
|
|
1262
|
+
mesh.roughness,
|
|
1263
|
+
mesh.metallic,
|
|
1264
|
+
mesh.opacity,
|
|
1265
|
+
mesh.ior
|
|
1266
|
+
]);
|
|
1267
|
+
writeVec4(floatView, byteOffset + 48, [
|
|
1268
|
+
mesh.sheenColor[0] ?? 0,
|
|
1269
|
+
mesh.sheenColor[1] ?? 0,
|
|
1270
|
+
mesh.sheenColor[2] ?? 0,
|
|
1271
|
+
mesh.clearcoat
|
|
1272
|
+
]);
|
|
1273
|
+
writeVec4(floatView, byteOffset + 64, [
|
|
1274
|
+
mesh.clearcoatRoughness,
|
|
1275
|
+
mesh.specular,
|
|
1276
|
+
mesh.transmission,
|
|
1277
|
+
0
|
|
1278
|
+
]);
|
|
1279
|
+
writeVec4(floatView, byteOffset + 80, [
|
|
1280
|
+
mesh.specularColor[0] ?? 1,
|
|
1281
|
+
mesh.specularColor[1] ?? 1,
|
|
1282
|
+
mesh.specularColor[2] ?? 1,
|
|
1283
|
+
1
|
|
1284
|
+
]);
|
|
1285
|
+
writeVec4(floatView, byteOffset + 96, baseColorAtlas.resolveRect(mesh.baseColorTexture));
|
|
1286
|
+
writeVec4(
|
|
1287
|
+
floatView,
|
|
1288
|
+
byteOffset + 112,
|
|
1289
|
+
metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1290
|
+
);
|
|
1291
|
+
writeVec4(floatView, byteOffset + 128, normalAtlas.resolveRect(mesh.normalTexture));
|
|
1292
|
+
writeVec4(floatView, byteOffset + 144, occlusionAtlas.resolveRect(mesh.occlusionTexture));
|
|
1293
|
+
writeVec4(floatView, byteOffset + 160, emissiveAtlas.resolveRect(mesh.emissiveTexture));
|
|
1294
|
+
writeVec4(floatView, byteOffset + 176, [
|
|
1295
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1296
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1297
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1298
|
+
0
|
|
1299
|
+
]);
|
|
1300
|
+
});
|
|
1301
|
+
return Object.freeze({
|
|
1302
|
+
buffer: bytes,
|
|
1303
|
+
count: normalized.length,
|
|
1304
|
+
recordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
1305
|
+
records: Object.freeze(normalized),
|
|
1306
|
+
baseColorAtlas,
|
|
1307
|
+
metallicRoughnessAtlas,
|
|
1308
|
+
normalAtlas,
|
|
1309
|
+
occlusionAtlas,
|
|
1310
|
+
emissiveAtlas
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
691
1313
|
function estimateBvhLeafSortCapacity(triangleCount) {
|
|
692
1314
|
return triangleCount <= 0 ? 0 : nextPowerOfTwo(triangleCount);
|
|
693
1315
|
}
|
|
@@ -745,9 +1367,10 @@ function resolveAccelerationBuildMode(options = {}) {
|
|
|
745
1367
|
}
|
|
746
1368
|
return mode;
|
|
747
1369
|
}
|
|
748
|
-
function createWavefrontGpuMeshSource(meshes = []) {
|
|
1370
|
+
function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput = null) {
|
|
749
1371
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
750
1372
|
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1373
|
+
const gpuMaterialSource = gpuMaterialSourceInput ?? createWavefrontGpuMaterialSource(normalized);
|
|
751
1374
|
const vertexCount = normalized.reduce((count, mesh) => count + mesh.positions.length / 3, 0);
|
|
752
1375
|
const indexCount = normalized.reduce((count, mesh) => count + mesh.indices.length, 0);
|
|
753
1376
|
const triangleCount = Math.floor(indexCount / 3);
|
|
@@ -799,7 +1422,7 @@ function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
799
1422
|
meshUints[meshOffset + 8] = mesh.indices.length / 3;
|
|
800
1423
|
meshUints[meshOffset + 9] = meshVertexBase;
|
|
801
1424
|
meshUints[meshOffset + 10] = meshVertexCount;
|
|
802
|
-
meshUints[meshOffset + 11] =
|
|
1425
|
+
meshUints[meshOffset + 11] = meshIndex;
|
|
803
1426
|
const floatOffset = meshOffset;
|
|
804
1427
|
writeVec4(meshFloats, floatOffset * 4 + 48, mesh.color);
|
|
805
1428
|
writeVec4(meshFloats, floatOffset * 4 + 64, mesh.emission);
|
|
@@ -809,6 +1432,55 @@ function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
809
1432
|
mesh.opacity,
|
|
810
1433
|
mesh.ior
|
|
811
1434
|
]);
|
|
1435
|
+
writeVec4(meshFloats, floatOffset * 4 + 96, [
|
|
1436
|
+
mesh.sheenColor[0] ?? 0,
|
|
1437
|
+
mesh.sheenColor[1] ?? 0,
|
|
1438
|
+
mesh.sheenColor[2] ?? 0,
|
|
1439
|
+
mesh.clearcoat
|
|
1440
|
+
]);
|
|
1441
|
+
writeVec4(meshFloats, floatOffset * 4 + 112, [
|
|
1442
|
+
mesh.clearcoatRoughness,
|
|
1443
|
+
mesh.specular,
|
|
1444
|
+
mesh.transmission,
|
|
1445
|
+
0
|
|
1446
|
+
]);
|
|
1447
|
+
writeVec4(meshFloats, floatOffset * 4 + 128, [
|
|
1448
|
+
mesh.specularColor[0] ?? 1,
|
|
1449
|
+
mesh.specularColor[1] ?? 1,
|
|
1450
|
+
mesh.specularColor[2] ?? 1,
|
|
1451
|
+
1
|
|
1452
|
+
]);
|
|
1453
|
+
writeVec4(
|
|
1454
|
+
meshFloats,
|
|
1455
|
+
floatOffset * 4 + 144,
|
|
1456
|
+
gpuMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
|
|
1457
|
+
);
|
|
1458
|
+
writeVec4(
|
|
1459
|
+
meshFloats,
|
|
1460
|
+
floatOffset * 4 + 160,
|
|
1461
|
+
gpuMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1462
|
+
);
|
|
1463
|
+
writeVec4(
|
|
1464
|
+
meshFloats,
|
|
1465
|
+
floatOffset * 4 + 176,
|
|
1466
|
+
gpuMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
|
|
1467
|
+
);
|
|
1468
|
+
writeVec4(
|
|
1469
|
+
meshFloats,
|
|
1470
|
+
floatOffset * 4 + 192,
|
|
1471
|
+
gpuMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
|
|
1472
|
+
);
|
|
1473
|
+
writeVec4(
|
|
1474
|
+
meshFloats,
|
|
1475
|
+
floatOffset * 4 + 208,
|
|
1476
|
+
gpuMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
|
|
1477
|
+
);
|
|
1478
|
+
writeVec4(meshFloats, floatOffset * 4 + 224, [
|
|
1479
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1480
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1481
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1482
|
+
0
|
|
1483
|
+
]);
|
|
812
1484
|
vertexCursor += meshVertexCount;
|
|
813
1485
|
indexCursor += mesh.indices.length;
|
|
814
1486
|
triangleCursor += mesh.indices.length / 3;
|
|
@@ -1111,12 +1783,14 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1111
1783
|
options.environmentPortalCapacity,
|
|
1112
1784
|
0
|
|
1113
1785
|
);
|
|
1786
|
+
const materialCapacity = readNonNegativeInteger("materialCapacity", options.materialCapacity, 0);
|
|
1114
1787
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
1115
1788
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
1116
1789
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
1117
1790
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
1118
1791
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
1119
1792
|
const triangleBytes = triangleCapacity * TRIANGLE_RECORD_BYTES;
|
|
1793
|
+
const materialTableBytes = materialCapacity * GPU_MATERIAL_RECORD_BYTES;
|
|
1120
1794
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
1121
1795
|
const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
|
|
1122
1796
|
const emissiveTriangleMetadataBytes = emissiveTriangleCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -1129,6 +1803,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1129
1803
|
pathVertexBytes,
|
|
1130
1804
|
sceneObjectBytes,
|
|
1131
1805
|
triangleBytes,
|
|
1806
|
+
materialTableBytes,
|
|
1132
1807
|
bvhNodeBytes,
|
|
1133
1808
|
bvhLeafReferenceBytes,
|
|
1134
1809
|
emissiveTriangleMetadataBytes,
|
|
@@ -1136,7 +1811,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1136
1811
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
1137
1812
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
1138
1813
|
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
|
|
1814
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + pathVertexBytes + sceneObjectBytes + triangleBytes + materialTableBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1140
1815
|
});
|
|
1141
1816
|
}
|
|
1142
1817
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -1150,7 +1825,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1150
1825
|
const samplesPerPixel = clamp(
|
|
1151
1826
|
readPositiveInteger("samplesPerPixel", options.samplesPerPixel, DEFAULT_SAMPLES_PER_PIXEL),
|
|
1152
1827
|
1,
|
|
1153
|
-
|
|
1828
|
+
MAX_SAMPLES_PER_PIXEL
|
|
1154
1829
|
);
|
|
1155
1830
|
const maxFramePassesPerSubmission = clamp(
|
|
1156
1831
|
readPositiveInteger(
|
|
@@ -1168,7 +1843,8 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1168
1843
|
);
|
|
1169
1844
|
const meshes = normalizeMeshes(options);
|
|
1170
1845
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
1171
|
-
const
|
|
1846
|
+
const gpuMaterialSource = meshes.length > 0 ? createWavefrontGpuMaterialSource(meshes) : createWavefrontGpuMaterialSource([]);
|
|
1847
|
+
const gpuMeshSource = meshes.length > 0 ? createWavefrontGpuMeshSource(meshes, gpuMaterialSource) : createWavefrontGpuMeshSource([]);
|
|
1172
1848
|
const meshAcceleration = accelerationBuildMode === "cpu-debug" ? createWavefrontMeshAcceleration(meshes) : Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
1173
1849
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
1174
1850
|
meshes,
|
|
@@ -1240,6 +1916,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1240
1916
|
accelerationBuildMode,
|
|
1241
1917
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
1242
1918
|
gpuMeshSource,
|
|
1919
|
+
gpuMaterialSource,
|
|
1243
1920
|
meshAcceleration,
|
|
1244
1921
|
emissiveTriangleIndices,
|
|
1245
1922
|
emissiveTriangleCount: emissiveTriangleIndices.count,
|
|
@@ -1270,6 +1947,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1270
1947
|
maxDepth,
|
|
1271
1948
|
sceneObjectCapacity,
|
|
1272
1949
|
triangleCapacity,
|
|
1950
|
+
materialCapacity: gpuMaterialSource.count,
|
|
1273
1951
|
bvhNodeCapacity,
|
|
1274
1952
|
bvhLeafSortCapacity,
|
|
1275
1953
|
emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
|
|
@@ -1346,6 +2024,24 @@ function packWavefrontSceneObjects(sceneObjects, capacity = sceneObjects.length)
|
|
|
1346
2024
|
object.opacity,
|
|
1347
2025
|
object.ior
|
|
1348
2026
|
]);
|
|
2027
|
+
writeVec4(floatView, byteOffset + 96, [
|
|
2028
|
+
object.sheenColor[0] ?? 0,
|
|
2029
|
+
object.sheenColor[1] ?? 0,
|
|
2030
|
+
object.sheenColor[2] ?? 0,
|
|
2031
|
+
object.clearcoat
|
|
2032
|
+
]);
|
|
2033
|
+
writeVec4(floatView, byteOffset + 112, [
|
|
2034
|
+
object.clearcoatRoughness,
|
|
2035
|
+
object.specular,
|
|
2036
|
+
object.transmission,
|
|
2037
|
+
0
|
|
2038
|
+
]);
|
|
2039
|
+
writeVec4(floatView, byteOffset + 128, [
|
|
2040
|
+
object.specularColor[0] ?? 1,
|
|
2041
|
+
object.specularColor[1] ?? 1,
|
|
2042
|
+
object.specularColor[2] ?? 1,
|
|
2043
|
+
1
|
|
2044
|
+
]);
|
|
1349
2045
|
});
|
|
1350
2046
|
return Object.freeze({
|
|
1351
2047
|
buffer: bytes,
|
|
@@ -1370,7 +2066,7 @@ function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1370
2066
|
uintView[u32 + 3] = triangle.flags;
|
|
1371
2067
|
uintView[u32 + 4] = triangle.materialRefId;
|
|
1372
2068
|
uintView[u32 + 5] = triangle.mediumRefId;
|
|
1373
|
-
uintView[u32 + 6] = 0;
|
|
2069
|
+
uintView[u32 + 6] = triangle.materialSlot ?? 0;
|
|
1374
2070
|
uintView[u32 + 7] = 0;
|
|
1375
2071
|
writeVec4(floatView, byteOffset + 32, [...triangle.v0, 0]);
|
|
1376
2072
|
writeVec4(floatView, byteOffset + 48, [...triangle.v1, 0]);
|
|
@@ -1383,6 +2079,15 @@ function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1383
2079
|
writeVec4(floatView, byteOffset + 160, triangle.color);
|
|
1384
2080
|
writeVec4(floatView, byteOffset + 176, triangle.emission);
|
|
1385
2081
|
writeVec4(floatView, byteOffset + 192, triangle.material);
|
|
2082
|
+
writeVec4(floatView, byteOffset + 208, triangle.materialResponse);
|
|
2083
|
+
writeVec4(floatView, byteOffset + 224, triangle.materialExtension ?? [0.08, 1, 0, 0]);
|
|
2084
|
+
writeVec4(floatView, byteOffset + 240, triangle.specularColor ?? [1, 1, 1, 1]);
|
|
2085
|
+
writeVec4(floatView, byteOffset + 256, triangle.baseColorAtlas ?? [0, 0, 1, 1]);
|
|
2086
|
+
writeVec4(floatView, byteOffset + 272, triangle.metallicRoughnessAtlas ?? [0, 0, 1, 1]);
|
|
2087
|
+
writeVec4(floatView, byteOffset + 288, triangle.normalAtlas ?? [0, 0, 1, 1]);
|
|
2088
|
+
writeVec4(floatView, byteOffset + 304, triangle.occlusionAtlas ?? [0, 0, 1, 1]);
|
|
2089
|
+
writeVec4(floatView, byteOffset + 320, triangle.emissiveAtlas ?? [0, 0, 1, 1]);
|
|
2090
|
+
writeVec4(floatView, byteOffset + 336, triangle.textureSettings ?? [1, 1, 1, 0]);
|
|
1386
2091
|
});
|
|
1387
2092
|
return Object.freeze({
|
|
1388
2093
|
buffer: bytes,
|
|
@@ -1476,6 +2181,12 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1476
2181
|
0,
|
|
1477
2182
|
0
|
|
1478
2183
|
]);
|
|
2184
|
+
writeVec4(floatView, 304, [
|
|
2185
|
+
config.environmentMap.width ?? 1,
|
|
2186
|
+
config.environmentMap.height ?? 1,
|
|
2187
|
+
config.environmentMap.mipLevelCount ?? 1,
|
|
2188
|
+
config.environmentMap.hasImportanceData ? 1 : 0
|
|
2189
|
+
]);
|
|
1479
2190
|
return bytes;
|
|
1480
2191
|
}
|
|
1481
2192
|
function createTiles(width, height, tileSize) {
|
|
@@ -1649,7 +2360,8 @@ function intersectWavefrontReferenceTriangle(ray, triangle, options = {}) {
|
|
|
1649
2360
|
position: Object.freeze(position),
|
|
1650
2361
|
color: triangle.color,
|
|
1651
2362
|
emission: triangle.emission,
|
|
1652
|
-
material: triangle.material
|
|
2363
|
+
material: triangle.material,
|
|
2364
|
+
materialResponse: triangle.materialResponse
|
|
1653
2365
|
});
|
|
1654
2366
|
}
|
|
1655
2367
|
function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
@@ -1675,7 +2387,8 @@ function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
|
1675
2387
|
position: Object.freeze(add(ray.origin, scale(ray.direction, 1e3))),
|
|
1676
2388
|
color: Object.freeze([0, 0, 0, 0]),
|
|
1677
2389
|
emission: radiance,
|
|
1678
|
-
material: Object.freeze([1, 0, 1, 1])
|
|
2390
|
+
material: Object.freeze([1, 0, 1, 1]),
|
|
2391
|
+
materialResponse: Object.freeze([0, 0, 0, 0])
|
|
1679
2392
|
});
|
|
1680
2393
|
}
|
|
1681
2394
|
function traceWavefrontReferenceTriangles(config, ray, triangles, options = {}) {
|
|
@@ -1754,40 +2467,24 @@ function environmentMapIntegerScale(data) {
|
|
|
1754
2467
|
}
|
|
1755
2468
|
return 1;
|
|
1756
2469
|
}
|
|
1757
|
-
function
|
|
1758
|
-
if (!
|
|
1759
|
-
return
|
|
2470
|
+
function environmentMapHasSamplingData(environmentMap) {
|
|
2471
|
+
if (!environmentMap || !environmentMap.data) {
|
|
2472
|
+
return false;
|
|
1760
2473
|
}
|
|
1761
|
-
const
|
|
1762
|
-
|
|
2474
|
+
const width = Math.max(1, environmentMap.width ?? 1);
|
|
2475
|
+
const height = Math.max(1, environmentMap.height ?? 1);
|
|
2476
|
+
return environmentMap.data.length >= width * height * 4;
|
|
1763
2477
|
}
|
|
1764
|
-
function
|
|
1765
|
-
const width = Math.max(1,
|
|
1766
|
-
const height = Math.max(1,
|
|
1767
|
-
const
|
|
1768
|
-
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2478
|
+
function createRgba8TextureUpload(source) {
|
|
2479
|
+
const width = Math.max(1, Math.trunc(source.width));
|
|
2480
|
+
const height = Math.max(1, Math.trunc(source.height));
|
|
2481
|
+
const bytesPerRow = alignTo(width * 4, 256);
|
|
1769
2482
|
const bytes = new Uint8Array(bytesPerRow * height);
|
|
1770
|
-
const data =
|
|
1771
|
-
const integerScale = environmentMapIntegerScale(data);
|
|
1772
|
-
const view = new DataView(bytes.buffer);
|
|
1773
|
-
const writeComponent = (targetOffset, sourceOffset, fallback) => {
|
|
1774
|
-
view.setUint16(
|
|
1775
|
-
targetOffset,
|
|
1776
|
-
float32ToFloat16Bits(
|
|
1777
|
-
readEnvironmentMapComponent(data, sourceOffset, fallback, integerScale)
|
|
1778
|
-
),
|
|
1779
|
-
true
|
|
1780
|
-
);
|
|
1781
|
-
};
|
|
2483
|
+
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
1782
2484
|
for (let y = 0; y < height; y += 1) {
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
writeComponent(targetOffset, sourceOffset, fallbackColor[0]);
|
|
1787
|
-
writeComponent(targetOffset + 2, sourceOffset + 1, fallbackColor[1]);
|
|
1788
|
-
writeComponent(targetOffset + 4, sourceOffset + 2, fallbackColor[2]);
|
|
1789
|
-
writeComponent(targetOffset + 6, sourceOffset + 3, fallbackColor[3] ?? 1);
|
|
1790
|
-
}
|
|
2485
|
+
const sourceOffset = y * width * 4;
|
|
2486
|
+
const targetOffset = y * bytesPerRow;
|
|
2487
|
+
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
1791
2488
|
}
|
|
1792
2489
|
return Object.freeze({
|
|
1793
2490
|
bytes,
|
|
@@ -1796,6 +2493,320 @@ function createEnvironmentMapUploadBytes(environmentMap, fallbackColor) {
|
|
|
1796
2493
|
height
|
|
1797
2494
|
});
|
|
1798
2495
|
}
|
|
2496
|
+
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
2497
|
+
if (!data || index >= data.length) {
|
|
2498
|
+
return fallback;
|
|
2499
|
+
}
|
|
2500
|
+
const value = Number(data[index]);
|
|
2501
|
+
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
2502
|
+
}
|
|
2503
|
+
function buildOrthonormalBasis(normal) {
|
|
2504
|
+
const tangentFallback = Math.abs(normal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
2505
|
+
const tangent = normalize(cross(tangentFallback, normal), [1, 0, 0]);
|
|
2506
|
+
const bitangent = normalize(cross(normal, tangent), [0, 0, 1]);
|
|
2507
|
+
return { tangent, bitangent };
|
|
2508
|
+
}
|
|
2509
|
+
function localToWorld(local, normal) {
|
|
2510
|
+
const basis = buildOrthonormalBasis(normal);
|
|
2511
|
+
return normalize(
|
|
2512
|
+
add(
|
|
2513
|
+
add(scale(basis.tangent, local[0]), scale(basis.bitangent, local[1])),
|
|
2514
|
+
scale(normal, local[2])
|
|
2515
|
+
),
|
|
2516
|
+
normal
|
|
2517
|
+
);
|
|
2518
|
+
}
|
|
2519
|
+
function radicalInverseVdc(bits) {
|
|
2520
|
+
let value = bits >>> 0;
|
|
2521
|
+
value = (value << 16 | value >>> 16) >>> 0;
|
|
2522
|
+
value = ((value & 1431655765) << 1 | (value & 2863311530) >>> 1) >>> 0;
|
|
2523
|
+
value = ((value & 858993459) << 2 | (value & 3435973836) >>> 2) >>> 0;
|
|
2524
|
+
value = ((value & 252645135) << 4 | (value & 4042322160) >>> 4) >>> 0;
|
|
2525
|
+
value = ((value & 16711935) << 8 | (value & 4278255360) >>> 8) >>> 0;
|
|
2526
|
+
return value * 23283064365386963e-26;
|
|
2527
|
+
}
|
|
2528
|
+
function hammersley(index, count) {
|
|
2529
|
+
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
2530
|
+
}
|
|
2531
|
+
function importanceSampleGgx(sample, roughness, normal) {
|
|
2532
|
+
const alpha = Math.max(roughness * roughness, 1e-4);
|
|
2533
|
+
const phi = 2 * Math.PI * sample[0];
|
|
2534
|
+
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
2535
|
+
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
2536
|
+
const halfVector = localToWorld(
|
|
2537
|
+
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
2538
|
+
normal
|
|
2539
|
+
);
|
|
2540
|
+
return normalize(halfVector, normal);
|
|
2541
|
+
}
|
|
2542
|
+
function geometrySchlickGgx(nDotV, roughness) {
|
|
2543
|
+
const k = (roughness + 1) * (roughness + 1) / 8;
|
|
2544
|
+
return nDotV / Math.max(nDotV * (1 - k) + k, 1e-6);
|
|
2545
|
+
}
|
|
2546
|
+
function geometrySmith(nDotV, nDotL, roughness) {
|
|
2547
|
+
return geometrySchlickGgx(nDotV, roughness) * geometrySchlickGgx(nDotL, roughness);
|
|
2548
|
+
}
|
|
2549
|
+
function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
2550
|
+
const viewDirection = [Math.sqrt(Math.max(0, 1 - nDotV * nDotV)), 0, nDotV];
|
|
2551
|
+
const normal = [0, 0, 1];
|
|
2552
|
+
let scaleTerm = 0;
|
|
2553
|
+
let biasTerm = 0;
|
|
2554
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
2555
|
+
const xi = hammersley(index, sampleCount);
|
|
2556
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2557
|
+
const vDotH = Math.max(dot(viewDirection, halfVector), 0);
|
|
2558
|
+
const lightDirection = normalize(
|
|
2559
|
+
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
2560
|
+
normal
|
|
2561
|
+
);
|
|
2562
|
+
const nDotL = Math.max(lightDirection[2], 0);
|
|
2563
|
+
const nDotH = Math.max(halfVector[2], 0);
|
|
2564
|
+
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
2565
|
+
continue;
|
|
2566
|
+
}
|
|
2567
|
+
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
2568
|
+
const visibility = geometry * vDotH / Math.max(nDotH * nDotV, 1e-6);
|
|
2569
|
+
const fresnel = (1 - vDotH) ** 5;
|
|
2570
|
+
scaleTerm += (1 - fresnel) * visibility;
|
|
2571
|
+
biasTerm += fresnel * visibility;
|
|
2572
|
+
}
|
|
2573
|
+
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
2574
|
+
}
|
|
2575
|
+
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount = 1024) {
|
|
2576
|
+
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
2577
|
+
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
2578
|
+
if (cached) {
|
|
2579
|
+
return cached;
|
|
2580
|
+
}
|
|
2581
|
+
const width = Math.max(1, Math.trunc(size));
|
|
2582
|
+
const height = Math.max(1, Math.trunc(size));
|
|
2583
|
+
const rowBytes = width * 8;
|
|
2584
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2585
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
2586
|
+
const view = new DataView(bytes.buffer);
|
|
2587
|
+
for (let y = 0; y < height; y += 1) {
|
|
2588
|
+
const roughness = (y + 0.5) / height;
|
|
2589
|
+
for (let x = 0; x < width; x += 1) {
|
|
2590
|
+
const nDotV = Math.max((x + 0.5) / width, 1e-4);
|
|
2591
|
+
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
2592
|
+
const offset = y * bytesPerRow + x * 8;
|
|
2593
|
+
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
2594
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
2595
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
2596
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
2600
|
+
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
2601
|
+
return upload;
|
|
2602
|
+
}
|
|
2603
|
+
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
2604
|
+
const width = Math.max(1, environmentMap.width);
|
|
2605
|
+
const height = Math.max(1, environmentMap.height);
|
|
2606
|
+
const pixels = new Float32Array(width * height * 4);
|
|
2607
|
+
const data = environmentMap.data;
|
|
2608
|
+
const integerScale = environmentMapIntegerScale(data);
|
|
2609
|
+
for (let index = 0; index < width * height; index += 1) {
|
|
2610
|
+
const sourceOffset = index * 4;
|
|
2611
|
+
const targetOffset = index * 4;
|
|
2612
|
+
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
2613
|
+
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
2614
|
+
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
2615
|
+
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
2616
|
+
}
|
|
2617
|
+
return pixels;
|
|
2618
|
+
}
|
|
2619
|
+
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
2620
|
+
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
2621
|
+
const theta = v * Math.PI;
|
|
2622
|
+
const sinTheta = Math.sin(theta);
|
|
2623
|
+
return [
|
|
2624
|
+
Math.cos(angle) * sinTheta,
|
|
2625
|
+
Math.cos(theta),
|
|
2626
|
+
Math.sin(angle) * sinTheta
|
|
2627
|
+
];
|
|
2628
|
+
}
|
|
2629
|
+
function sampleEnvironmentPixelsBilinear(pixels, width, height, u, v) {
|
|
2630
|
+
const wrappedU = (u % 1 + 1) % 1;
|
|
2631
|
+
const clampedV = clamp(v, 0, 1);
|
|
2632
|
+
const x = wrappedU * width - 0.5;
|
|
2633
|
+
const y = clampedV * height - 0.5;
|
|
2634
|
+
const x0 = (Math.floor(x) % width + width) % width;
|
|
2635
|
+
const y0 = clamp(Math.floor(y), 0, height - 1);
|
|
2636
|
+
const x1 = (x0 + 1) % width;
|
|
2637
|
+
const y1 = clamp(y0 + 1, 0, height - 1);
|
|
2638
|
+
const tx = x - Math.floor(x);
|
|
2639
|
+
const ty = y - Math.floor(y);
|
|
2640
|
+
const read = (px, py) => {
|
|
2641
|
+
const offset = (py * width + px) * 4;
|
|
2642
|
+
return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]];
|
|
2643
|
+
};
|
|
2644
|
+
const a = read(x0, y0);
|
|
2645
|
+
const b = read(x1, y0);
|
|
2646
|
+
const c = read(x0, y1);
|
|
2647
|
+
const d = read(x1, y1);
|
|
2648
|
+
const mixPair = (first, second, factor) => first * (1 - factor) + second * factor;
|
|
2649
|
+
return [
|
|
2650
|
+
mixPair(mixPair(a[0], b[0], tx), mixPair(c[0], d[0], tx), ty),
|
|
2651
|
+
mixPair(mixPair(a[1], b[1], tx), mixPair(c[1], d[1], tx), ty),
|
|
2652
|
+
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
2653
|
+
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty)
|
|
2654
|
+
];
|
|
2655
|
+
}
|
|
2656
|
+
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
2657
|
+
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
2658
|
+
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
2659
|
+
const u = ((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI) + 0.5 + rotationTurns) % 1 + 1) % 1;
|
|
2660
|
+
const v = Math.acos(clamp(unitDirection[1], -1, 1)) / Math.PI;
|
|
2661
|
+
return [u, clamp(v, 0, 1)];
|
|
2662
|
+
}
|
|
2663
|
+
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
2664
|
+
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
2665
|
+
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
2666
|
+
}
|
|
2667
|
+
function createFloat16RgbaUploadFromLevels(levels) {
|
|
2668
|
+
return levels.map((level) => {
|
|
2669
|
+
const rowBytes = level.width * 8;
|
|
2670
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2671
|
+
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
2672
|
+
const view = new DataView(bytes.buffer);
|
|
2673
|
+
for (let y = 0; y < level.height; y += 1) {
|
|
2674
|
+
for (let x = 0; x < level.width; x += 1) {
|
|
2675
|
+
const sourceOffset = (y * level.width + x) * 4;
|
|
2676
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
2677
|
+
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
2678
|
+
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
2679
|
+
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
2680
|
+
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
return Object.freeze({ bytes, bytesPerRow, width: level.width, height: level.height });
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
function createPrefilteredEnvironmentLevels(environmentMap, fallbackColor) {
|
|
2687
|
+
const sourcePixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
2688
|
+
const sourceWidth = Math.max(1, environmentMap.width);
|
|
2689
|
+
const sourceHeight = Math.max(1, environmentMap.height);
|
|
2690
|
+
const mipLevelCount = Math.max(1, Math.floor(Math.log2(Math.max(sourceWidth, sourceHeight))) + 1);
|
|
2691
|
+
const levels = [
|
|
2692
|
+
Object.freeze({
|
|
2693
|
+
width: sourceWidth,
|
|
2694
|
+
height: sourceHeight,
|
|
2695
|
+
data: sourcePixels
|
|
2696
|
+
})
|
|
2697
|
+
];
|
|
2698
|
+
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
2699
|
+
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
2700
|
+
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
2701
|
+
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
2702
|
+
const data = new Float32Array(width * height * 4);
|
|
2703
|
+
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
2704
|
+
for (let y = 0; y < height; y += 1) {
|
|
2705
|
+
for (let x = 0; x < width; x += 1) {
|
|
2706
|
+
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
2707
|
+
const normal = normalize(direction, [0, 1, 0]);
|
|
2708
|
+
const viewDirection = normal;
|
|
2709
|
+
let totalWeight = 0;
|
|
2710
|
+
const accum = [0, 0, 0];
|
|
2711
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
2712
|
+
const xi = hammersley(sampleIndex, sampleCount);
|
|
2713
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2714
|
+
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
2715
|
+
const lightDirection = normalize(
|
|
2716
|
+
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
2717
|
+
normal
|
|
2718
|
+
);
|
|
2719
|
+
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
2720
|
+
if (nDotL <= 1e-6) {
|
|
2721
|
+
continue;
|
|
2722
|
+
}
|
|
2723
|
+
const radiance = sampleEnvironmentRadiance(
|
|
2724
|
+
sourcePixels,
|
|
2725
|
+
sourceWidth,
|
|
2726
|
+
sourceHeight,
|
|
2727
|
+
lightDirection,
|
|
2728
|
+
environmentMap.rotationRadians
|
|
2729
|
+
);
|
|
2730
|
+
accum[0] += radiance[0] * nDotL;
|
|
2731
|
+
accum[1] += radiance[1] * nDotL;
|
|
2732
|
+
accum[2] += radiance[2] * nDotL;
|
|
2733
|
+
totalWeight += nDotL;
|
|
2734
|
+
}
|
|
2735
|
+
const offset = (y * width + x) * 4;
|
|
2736
|
+
data[offset] = accum[0] / Math.max(totalWeight, 1e-6);
|
|
2737
|
+
data[offset + 1] = accum[1] / Math.max(totalWeight, 1e-6);
|
|
2738
|
+
data[offset + 2] = accum[2] / Math.max(totalWeight, 1e-6);
|
|
2739
|
+
data[offset + 3] = 1;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
levels.push(Object.freeze({ width, height, data }));
|
|
2743
|
+
}
|
|
2744
|
+
return Object.freeze({
|
|
2745
|
+
levels,
|
|
2746
|
+
mipLevelCount,
|
|
2747
|
+
width: sourceWidth,
|
|
2748
|
+
height: sourceHeight
|
|
2749
|
+
});
|
|
2750
|
+
}
|
|
2751
|
+
function createEnvironmentSamplingTables(environmentMap, fallbackColor) {
|
|
2752
|
+
if (!environmentMapHasSamplingData(environmentMap)) {
|
|
2753
|
+
return Object.freeze({
|
|
2754
|
+
width: 1,
|
|
2755
|
+
height: 1,
|
|
2756
|
+
pdf: new Float32Array([1]),
|
|
2757
|
+
marginalCdf: new Float32Array([1]),
|
|
2758
|
+
conditionalCdf: new Float32Array([1]),
|
|
2759
|
+
hasImportanceData: false
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2762
|
+
const pixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
2763
|
+
const width = Math.max(1, environmentMap.width);
|
|
2764
|
+
const height = Math.max(1, environmentMap.height);
|
|
2765
|
+
const pdf = new Float32Array(width * height);
|
|
2766
|
+
const marginalCdf = new Float32Array(height);
|
|
2767
|
+
const conditionalCdf = new Float32Array(width * height);
|
|
2768
|
+
const rowSums = new Float32Array(height);
|
|
2769
|
+
let totalWeight = 0;
|
|
2770
|
+
for (let y = 0; y < height; y += 1) {
|
|
2771
|
+
const theta = (y + 0.5) / height * Math.PI;
|
|
2772
|
+
const sinTheta = Math.max(Math.sin(theta), 1e-4);
|
|
2773
|
+
let rowWeight = 0;
|
|
2774
|
+
for (let x = 0; x < width; x += 1) {
|
|
2775
|
+
const offset = (y * width + x) * 4;
|
|
2776
|
+
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
2777
|
+
const weight = Math.max(luminance * sinTheta, 1e-6);
|
|
2778
|
+
pdf[y * width + x] = weight;
|
|
2779
|
+
rowWeight += weight;
|
|
2780
|
+
conditionalCdf[y * width + x] = rowWeight;
|
|
2781
|
+
}
|
|
2782
|
+
rowSums[y] = rowWeight;
|
|
2783
|
+
totalWeight += rowWeight;
|
|
2784
|
+
if (rowWeight > 0) {
|
|
2785
|
+
for (let x = 0; x < width; x += 1) {
|
|
2786
|
+
conditionalCdf[y * width + x] /= rowWeight;
|
|
2787
|
+
}
|
|
2788
|
+
} else {
|
|
2789
|
+
for (let x = 0; x < width; x += 1) {
|
|
2790
|
+
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
marginalCdf[y] = totalWeight;
|
|
2794
|
+
}
|
|
2795
|
+
for (let y = 0; y < height; y += 1) {
|
|
2796
|
+
marginalCdf[y] /= Math.max(totalWeight, 1e-6);
|
|
2797
|
+
}
|
|
2798
|
+
for (let index = 0; index < pdf.length; index += 1) {
|
|
2799
|
+
pdf[index] /= Math.max(totalWeight, 1e-6);
|
|
2800
|
+
}
|
|
2801
|
+
return Object.freeze({
|
|
2802
|
+
width,
|
|
2803
|
+
height,
|
|
2804
|
+
pdf,
|
|
2805
|
+
marginalCdf,
|
|
2806
|
+
conditionalCdf,
|
|
2807
|
+
hasImportanceData: true
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
1799
2810
|
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
1800
2811
|
if (environmentMap.view) {
|
|
1801
2812
|
return Object.freeze({
|
|
@@ -1805,10 +2816,14 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1805
2816
|
addressModeU: "repeat",
|
|
1806
2817
|
addressModeV: "clamp-to-edge",
|
|
1807
2818
|
magFilter: "linear",
|
|
1808
|
-
minFilter: "linear"
|
|
2819
|
+
minFilter: "linear",
|
|
2820
|
+
mipmapFilter: "linear"
|
|
1809
2821
|
}),
|
|
1810
2822
|
texture: null,
|
|
1811
|
-
ownsTexture: false
|
|
2823
|
+
ownsTexture: false,
|
|
2824
|
+
width: Math.max(1, environmentMap.width),
|
|
2825
|
+
height: Math.max(1, environmentMap.height),
|
|
2826
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
1812
2827
|
});
|
|
1813
2828
|
}
|
|
1814
2829
|
if (environmentMap.texture && typeof environmentMap.texture.createView === "function") {
|
|
@@ -1819,15 +2834,91 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1819
2834
|
addressModeU: "repeat",
|
|
1820
2835
|
addressModeV: "clamp-to-edge",
|
|
1821
2836
|
magFilter: "linear",
|
|
1822
|
-
minFilter: "linear"
|
|
2837
|
+
minFilter: "linear",
|
|
2838
|
+
mipmapFilter: "linear"
|
|
1823
2839
|
}),
|
|
1824
2840
|
texture: environmentMap.texture,
|
|
1825
|
-
ownsTexture: false
|
|
2841
|
+
ownsTexture: false,
|
|
2842
|
+
width: Math.max(1, environmentMap.width),
|
|
2843
|
+
height: Math.max(1, environmentMap.height),
|
|
2844
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1)
|
|
1826
2845
|
});
|
|
1827
2846
|
}
|
|
1828
|
-
const
|
|
2847
|
+
const prefiltered = createPrefilteredEnvironmentLevels(environmentMap, fallbackColor);
|
|
2848
|
+
const uploads = createFloat16RgbaUploadFromLevels(prefiltered.levels);
|
|
1829
2849
|
const texture = device.createTexture({
|
|
1830
2850
|
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
2851
|
+
size: { width: prefiltered.width, height: prefiltered.height },
|
|
2852
|
+
format: "rgba16float",
|
|
2853
|
+
mipLevelCount: prefiltered.mipLevelCount,
|
|
2854
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
2855
|
+
});
|
|
2856
|
+
uploads.forEach((upload, mipLevel) => {
|
|
2857
|
+
device.queue.writeTexture(
|
|
2858
|
+
{ texture, mipLevel },
|
|
2859
|
+
upload.bytes,
|
|
2860
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
2861
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
2862
|
+
);
|
|
2863
|
+
});
|
|
2864
|
+
return Object.freeze({
|
|
2865
|
+
view: texture.createView(),
|
|
2866
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
2867
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
2868
|
+
addressModeU: "repeat",
|
|
2869
|
+
addressModeV: "clamp-to-edge",
|
|
2870
|
+
magFilter: "linear",
|
|
2871
|
+
minFilter: "linear",
|
|
2872
|
+
mipmapFilter: "linear"
|
|
2873
|
+
}),
|
|
2874
|
+
texture,
|
|
2875
|
+
ownsTexture: true,
|
|
2876
|
+
width: prefiltered.width,
|
|
2877
|
+
height: prefiltered.height,
|
|
2878
|
+
mipLevelCount: prefiltered.mipLevelCount
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
function createEnvironmentSamplingTextureResource(device, constants, environmentMap, fallbackColor) {
|
|
2882
|
+
const tables = createEnvironmentSamplingTables(environmentMap, fallbackColor);
|
|
2883
|
+
const rowBytes = tables.width * 8;
|
|
2884
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2885
|
+
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
2886
|
+
const view = new DataView(bytes.buffer);
|
|
2887
|
+
for (let y = 0; y < tables.height; y += 1) {
|
|
2888
|
+
for (let x = 0; x < tables.width; x += 1) {
|
|
2889
|
+
const probability = tables.pdf[y * tables.width + x];
|
|
2890
|
+
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
2891
|
+
const marginal = tables.marginalCdf[y];
|
|
2892
|
+
const offset = y * bytesPerRow + x * 8;
|
|
2893
|
+
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
2894
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
2895
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
2896
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
const texture = device.createTexture({
|
|
2900
|
+
label: "plasius.wavefront.environmentSampling",
|
|
2901
|
+
size: { width: tables.width, height: tables.height },
|
|
2902
|
+
format: "rgba16float",
|
|
2903
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
2904
|
+
});
|
|
2905
|
+
device.queue.writeTexture(
|
|
2906
|
+
{ texture },
|
|
2907
|
+
bytes,
|
|
2908
|
+
{ bytesPerRow, rowsPerImage: tables.height },
|
|
2909
|
+
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
2910
|
+
);
|
|
2911
|
+
return Object.freeze({
|
|
2912
|
+
view: texture.createView(),
|
|
2913
|
+
texture,
|
|
2914
|
+
ownsTexture: true,
|
|
2915
|
+
hasImportanceData: tables.hasImportanceData
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE) {
|
|
2919
|
+
const upload = createBrdfLutUploadBytes(size);
|
|
2920
|
+
const texture = device.createTexture({
|
|
2921
|
+
label: "plasius.wavefront.brdfLut",
|
|
1831
2922
|
size: { width: upload.width, height: upload.height },
|
|
1832
2923
|
format: "rgba16float",
|
|
1833
2924
|
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
@@ -1840,14 +2931,36 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
1840
2931
|
);
|
|
1841
2932
|
return Object.freeze({
|
|
1842
2933
|
view: texture.createView(),
|
|
1843
|
-
sampler:
|
|
1844
|
-
label: "plasius.wavefront.
|
|
1845
|
-
addressModeU: "
|
|
2934
|
+
sampler: device.createSampler({
|
|
2935
|
+
label: "plasius.wavefront.brdfLutSampler",
|
|
2936
|
+
addressModeU: "clamp-to-edge",
|
|
1846
2937
|
addressModeV: "clamp-to-edge",
|
|
1847
2938
|
magFilter: "linear",
|
|
1848
2939
|
minFilter: "linear"
|
|
1849
2940
|
}),
|
|
1850
2941
|
texture,
|
|
2942
|
+
ownsTexture: true,
|
|
2943
|
+
width: upload.width,
|
|
2944
|
+
height: upload.height
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
2948
|
+
const upload = createRgba8TextureUpload(atlas);
|
|
2949
|
+
const texture = device.createTexture({
|
|
2950
|
+
label,
|
|
2951
|
+
size: { width: upload.width, height: upload.height },
|
|
2952
|
+
format: "rgba8unorm",
|
|
2953
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
2954
|
+
});
|
|
2955
|
+
device.queue.writeTexture(
|
|
2956
|
+
{ texture },
|
|
2957
|
+
upload.bytes,
|
|
2958
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
2959
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
2960
|
+
);
|
|
2961
|
+
return Object.freeze({
|
|
2962
|
+
texture,
|
|
2963
|
+
view: texture.createView(),
|
|
1851
2964
|
ownsTexture: true
|
|
1852
2965
|
});
|
|
1853
2966
|
}
|
|
@@ -1893,6 +3006,24 @@ ${diagnostics}` : "";
|
|
|
1893
3006
|
});
|
|
1894
3007
|
}
|
|
1895
3008
|
}
|
|
3009
|
+
async function assertShaderModuleCompiles(shaderModule, label) {
|
|
3010
|
+
if (typeof shaderModule?.compilationInfo !== "function") {
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
const info = await shaderModule.compilationInfo();
|
|
3014
|
+
const messages = Array.isArray(info?.messages) ? info.messages : [];
|
|
3015
|
+
const errors = messages.filter((message) => message?.type === "error");
|
|
3016
|
+
if (errors.length <= 0) {
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
const diagnostics = errors.map((message) => {
|
|
3020
|
+
const line = Number.isFinite(message.lineNum) ? message.lineNum : "?";
|
|
3021
|
+
const column = Number.isFinite(message.linePos) ? message.linePos : "?";
|
|
3022
|
+
return `line ${line}:${column} ${message.message}`;
|
|
3023
|
+
}).join("\n");
|
|
3024
|
+
throw new Error(`WGSL compilation preflight failed for ${label}:
|
|
3025
|
+
${diagnostics}`);
|
|
3026
|
+
}
|
|
1896
3027
|
async function createRenderPipeline(device, descriptor) {
|
|
1897
3028
|
if (typeof device.createRenderPipelineAsync === "function") {
|
|
1898
3029
|
return device.createRenderPipelineAsync(descriptor);
|
|
@@ -1901,6 +3032,7 @@ async function createRenderPipeline(device, descriptor) {
|
|
|
1901
3032
|
}
|
|
1902
3033
|
var WAVEFRONT_COMPUTE_WGSL = `
|
|
1903
3034
|
const RAY_FLAG_GUIDED_EMISSIVE: u32 = 1u;
|
|
3035
|
+
const RAY_FLAG_DELTA_SAMPLE: u32 = 2u;
|
|
1904
3036
|
|
|
1905
3037
|
struct RayRecord {
|
|
1906
3038
|
rayId: u32,
|
|
@@ -1926,11 +3058,12 @@ struct HitRecord {
|
|
|
1926
3058
|
primitiveId: u32,
|
|
1927
3059
|
materialRefId: u32,
|
|
1928
3060
|
mediumRefId: u32,
|
|
3061
|
+
materialSlot: u32,
|
|
1929
3062
|
pad0: u32,
|
|
1930
3063
|
pad1: u32,
|
|
1931
|
-
pad2: u32,
|
|
1932
3064
|
distance: f32,
|
|
1933
|
-
|
|
3065
|
+
occlusion: f32,
|
|
3066
|
+
pad2: vec2<f32>,
|
|
1934
3067
|
position: vec4<f32>,
|
|
1935
3068
|
geometricNormal: vec4<f32>,
|
|
1936
3069
|
shadingNormal: vec4<f32>,
|
|
@@ -1939,6 +3072,9 @@ struct HitRecord {
|
|
|
1939
3072
|
color: vec4<f32>,
|
|
1940
3073
|
emission: vec4<f32>,
|
|
1941
3074
|
material: vec4<f32>,
|
|
3075
|
+
materialResponse: vec4<f32>,
|
|
3076
|
+
materialExtension: vec4<f32>,
|
|
3077
|
+
specularColor: vec4<f32>,
|
|
1942
3078
|
};
|
|
1943
3079
|
|
|
1944
3080
|
struct SceneObject {
|
|
@@ -1951,6 +3087,9 @@ struct SceneObject {
|
|
|
1951
3087
|
color: vec4<f32>,
|
|
1952
3088
|
emission: vec4<f32>,
|
|
1953
3089
|
material: vec4<f32>,
|
|
3090
|
+
materialResponse: vec4<f32>,
|
|
3091
|
+
materialExtension: vec4<f32>,
|
|
3092
|
+
specularColor: vec4<f32>,
|
|
1954
3093
|
};
|
|
1955
3094
|
|
|
1956
3095
|
struct TriangleRecord {
|
|
@@ -1960,7 +3099,7 @@ struct TriangleRecord {
|
|
|
1960
3099
|
flags: u32,
|
|
1961
3100
|
materialRefId: u32,
|
|
1962
3101
|
mediumRefId: u32,
|
|
1963
|
-
|
|
3102
|
+
materialSlot: u32,
|
|
1964
3103
|
pad1: u32,
|
|
1965
3104
|
v0: vec4<f32>,
|
|
1966
3105
|
v1: vec4<f32>,
|
|
@@ -1973,6 +3112,15 @@ struct TriangleRecord {
|
|
|
1973
3112
|
color: vec4<f32>,
|
|
1974
3113
|
emission: vec4<f32>,
|
|
1975
3114
|
material: vec4<f32>,
|
|
3115
|
+
materialResponse: vec4<f32>,
|
|
3116
|
+
materialExtension: vec4<f32>,
|
|
3117
|
+
specularColor: vec4<f32>,
|
|
3118
|
+
baseColorAtlas: vec4<f32>,
|
|
3119
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3120
|
+
normalAtlas: vec4<f32>,
|
|
3121
|
+
occlusionAtlas: vec4<f32>,
|
|
3122
|
+
emissiveAtlas: vec4<f32>,
|
|
3123
|
+
textureSettings: vec4<f32>,
|
|
1976
3124
|
};
|
|
1977
3125
|
|
|
1978
3126
|
struct BvhNode {
|
|
@@ -1993,10 +3141,10 @@ struct BvhLeafRef {
|
|
|
1993
3141
|
|
|
1994
3142
|
struct ScatterResult {
|
|
1995
3143
|
direction: vec4<f32>,
|
|
3144
|
+
pdf: f32,
|
|
1996
3145
|
flags: u32,
|
|
1997
3146
|
pad0: u32,
|
|
1998
3147
|
pad1: u32,
|
|
1999
|
-
pad2: u32,
|
|
2000
3148
|
};
|
|
2001
3149
|
|
|
2002
3150
|
struct MeshVertex {
|
|
@@ -2017,10 +3165,19 @@ struct MeshRange {
|
|
|
2017
3165
|
triangleCount: u32,
|
|
2018
3166
|
firstVertex: u32,
|
|
2019
3167
|
vertexCount: u32,
|
|
2020
|
-
|
|
3168
|
+
materialSlot: u32,
|
|
2021
3169
|
color: vec4<f32>,
|
|
2022
3170
|
emission: vec4<f32>,
|
|
2023
3171
|
material: vec4<f32>,
|
|
3172
|
+
materialResponse: vec4<f32>,
|
|
3173
|
+
materialExtension: vec4<f32>,
|
|
3174
|
+
specularColor: vec4<f32>,
|
|
3175
|
+
baseColorAtlas: vec4<f32>,
|
|
3176
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3177
|
+
normalAtlas: vec4<f32>,
|
|
3178
|
+
occlusionAtlas: vec4<f32>,
|
|
3179
|
+
emissiveAtlas: vec4<f32>,
|
|
3180
|
+
textureSettings: vec4<f32>,
|
|
2024
3181
|
};
|
|
2025
3182
|
|
|
2026
3183
|
struct FrameConfig {
|
|
@@ -2061,6 +3218,7 @@ struct FrameConfig {
|
|
|
2061
3218
|
_portalPad1: u32,
|
|
2062
3219
|
environmentMapSettings: vec4<f32>,
|
|
2063
3220
|
pathResolveSettings: vec4<f32>,
|
|
3221
|
+
environmentMapMeta: vec4<f32>,
|
|
2064
3222
|
};
|
|
2065
3223
|
|
|
2066
3224
|
struct Counters {
|
|
@@ -2123,6 +3281,15 @@ struct EnvironmentPortal {
|
|
|
2123
3281
|
@group(0) @binding(20) var environmentMapTexture: texture_2d<f32>;
|
|
2124
3282
|
@group(0) @binding(21) var environmentMapSampler: sampler;
|
|
2125
3283
|
@group(0) @binding(22) var<storage, read_write> pathVertices: array<vec4<f32>>;
|
|
3284
|
+
@group(0) @binding(23) var baseColorAtlasTexture: texture_2d<f32>;
|
|
3285
|
+
@group(0) @binding(24) var metallicRoughnessAtlasTexture: texture_2d<f32>;
|
|
3286
|
+
@group(0) @binding(25) var normalAtlasTexture: texture_2d<f32>;
|
|
3287
|
+
@group(0) @binding(26) var occlusionAtlasTexture: texture_2d<f32>;
|
|
3288
|
+
@group(0) @binding(27) var emissiveAtlasTexture: texture_2d<f32>;
|
|
3289
|
+
@group(0) @binding(28) var materialAtlasSampler: sampler;
|
|
3290
|
+
@group(0) @binding(29) var brdfLutTexture: texture_2d<f32>;
|
|
3291
|
+
@group(0) @binding(30) var brdfLutSampler: sampler;
|
|
3292
|
+
@group(0) @binding(31) var environmentSamplingTexture: texture_2d<f32>;
|
|
2126
3293
|
|
|
2127
3294
|
fn hash_u32(value: u32) -> u32 {
|
|
2128
3295
|
var x = value;
|
|
@@ -2159,6 +3326,146 @@ fn safe_normalize(value: vec3<f32>, fallback: vec3<f32>) -> vec3<f32> {
|
|
|
2159
3326
|
return value / len;
|
|
2160
3327
|
}
|
|
2161
3328
|
|
|
3329
|
+
struct TangentBasis {
|
|
3330
|
+
tangent: vec3<f32>,
|
|
3331
|
+
bitangent: vec3<f32>,
|
|
3332
|
+
};
|
|
3333
|
+
|
|
3334
|
+
struct SurfaceMaterialSample {
|
|
3335
|
+
color: vec4<f32>,
|
|
3336
|
+
emission: vec4<f32>,
|
|
3337
|
+
material: vec4<f32>,
|
|
3338
|
+
materialResponse: vec4<f32>,
|
|
3339
|
+
materialExtension: vec4<f32>,
|
|
3340
|
+
specularColor: vec4<f32>,
|
|
3341
|
+
shadingNormal: vec3<f32>,
|
|
3342
|
+
occlusion: f32,
|
|
3343
|
+
};
|
|
3344
|
+
|
|
3345
|
+
fn srgb_to_linear_channel(value: f32) -> f32 {
|
|
3346
|
+
if (value <= 0.04045) {
|
|
3347
|
+
return value / 12.92;
|
|
3348
|
+
}
|
|
3349
|
+
return pow((value + 0.055) / 1.055, 2.4);
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
fn srgb_to_linear_vec3(value: vec3<f32>) -> vec3<f32> {
|
|
3353
|
+
return vec3<f32>(
|
|
3354
|
+
srgb_to_linear_channel(value.x),
|
|
3355
|
+
srgb_to_linear_channel(value.y),
|
|
3356
|
+
srgb_to_linear_channel(value.z)
|
|
3357
|
+
);
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
fn wrap_uv(uv: vec2<f32>) -> vec2<f32> {
|
|
3361
|
+
return fract(fract(uv) + vec2<f32>(1.0));
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
fn atlas_sample_uv(rect: vec4<f32>, uv: vec2<f32>) -> vec2<f32> {
|
|
3365
|
+
let local = wrap_uv(uv);
|
|
3366
|
+
let clamped = clamp(local, vec2<f32>(0.001), vec2<f32>(0.999));
|
|
3367
|
+
return rect.xy + clamped * rect.zw;
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
fn sample_atlas(textureRef: texture_2d<f32>, rect: vec4<f32>, uv: vec2<f32>) -> vec4<f32> {
|
|
3371
|
+
return textureSampleLevel(textureRef, materialAtlasSampler, atlas_sample_uv(rect, uv), 0.0);
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
fn build_triangle_tangent_basis(
|
|
3375
|
+
triangle: TriangleRecord,
|
|
3376
|
+
fallbackNormal: vec3<f32>
|
|
3377
|
+
) -> TangentBasis {
|
|
3378
|
+
let edge1 = triangle.v1.xyz - triangle.v0.xyz;
|
|
3379
|
+
let edge2 = triangle.v2.xyz - triangle.v0.xyz;
|
|
3380
|
+
let uv0 = triangle.uv0uv1.xy;
|
|
3381
|
+
let uv1 = triangle.uv0uv1.zw;
|
|
3382
|
+
let uv2 = triangle.uv2Pad.xy;
|
|
3383
|
+
let deltaUv1 = uv1 - uv0;
|
|
3384
|
+
let deltaUv2 = uv2 - uv0;
|
|
3385
|
+
let determinant = deltaUv1.x * deltaUv2.y - deltaUv1.y * deltaUv2.x;
|
|
3386
|
+
if (abs(determinant) <= 0.000001) {
|
|
3387
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(fallbackNormal.y) >= 0.999);
|
|
3388
|
+
let tangent = safe_normalize(cross(tangentFallback, fallbackNormal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3389
|
+
let bitangent = safe_normalize(cross(fallbackNormal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3390
|
+
return TangentBasis(tangent, bitangent);
|
|
3391
|
+
}
|
|
3392
|
+
let inverse = 1.0 / determinant;
|
|
3393
|
+
let tangent = safe_normalize(
|
|
3394
|
+
inverse * (edge1 * deltaUv2.y - edge2 * deltaUv1.y),
|
|
3395
|
+
vec3<f32>(1.0, 0.0, 0.0)
|
|
3396
|
+
);
|
|
3397
|
+
let bitangent = safe_normalize(
|
|
3398
|
+
inverse * (-edge1 * deltaUv2.x + edge2 * deltaUv1.x),
|
|
3399
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3400
|
+
);
|
|
3401
|
+
return TangentBasis(tangent, bitangent);
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
fn sample_surface_material(
|
|
3405
|
+
triangle: TriangleRecord,
|
|
3406
|
+
uv: vec2<f32>,
|
|
3407
|
+
geometricNormal: vec3<f32>,
|
|
3408
|
+
shadingNormal: vec3<f32>
|
|
3409
|
+
) -> SurfaceMaterialSample {
|
|
3410
|
+
let baseColorTexel = sample_atlas(baseColorAtlasTexture, triangle.baseColorAtlas, uv);
|
|
3411
|
+
let baseColor = vec4<f32>(
|
|
3412
|
+
clamp(triangle.color.rgb * srgb_to_linear_vec3(baseColorTexel.rgb), vec3<f32>(0.0), vec3<f32>(1.0)),
|
|
3413
|
+
clamp(triangle.color.a * baseColorTexel.a, 0.0, 1.0)
|
|
3414
|
+
);
|
|
3415
|
+
let metallicRoughnessTexel = sample_atlas(
|
|
3416
|
+
metallicRoughnessAtlasTexture,
|
|
3417
|
+
triangle.metallicRoughnessAtlas,
|
|
3418
|
+
uv
|
|
3419
|
+
);
|
|
3420
|
+
let normalTexel = sample_atlas(normalAtlasTexture, triangle.normalAtlas, uv);
|
|
3421
|
+
let occlusionTexel = sample_atlas(occlusionAtlasTexture, triangle.occlusionAtlas, uv);
|
|
3422
|
+
let emissiveTexel = sample_atlas(emissiveAtlasTexture, triangle.emissiveAtlas, uv);
|
|
3423
|
+
let normalScale = clamp(triangle.textureSettings.x, 0.0, 1.0);
|
|
3424
|
+
let tangentBasis = build_triangle_tangent_basis(triangle, geometricNormal);
|
|
3425
|
+
let tangentNormal = safe_normalize(
|
|
3426
|
+
vec3<f32>(
|
|
3427
|
+
(normalTexel.x * 2.0 - 1.0) * normalScale,
|
|
3428
|
+
(normalTexel.y * 2.0 - 1.0) * normalScale,
|
|
3429
|
+
1.0 + ((normalTexel.z * 2.0 - 1.0) - 1.0) * normalScale
|
|
3430
|
+
),
|
|
3431
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3432
|
+
);
|
|
3433
|
+
let mappedNormal = safe_normalize(
|
|
3434
|
+
tangentBasis.tangent * tangentNormal.x +
|
|
3435
|
+
tangentBasis.bitangent * tangentNormal.y +
|
|
3436
|
+
shadingNormal * tangentNormal.z,
|
|
3437
|
+
shadingNormal
|
|
3438
|
+
);
|
|
3439
|
+
let emission = vec4<f32>(
|
|
3440
|
+
max(
|
|
3441
|
+
triangle.emission.rgb *
|
|
3442
|
+
srgb_to_linear_vec3(emissiveTexel.rgb) *
|
|
3443
|
+
max(triangle.textureSettings.z, 0.0),
|
|
3444
|
+
vec3<f32>(0.0)
|
|
3445
|
+
),
|
|
3446
|
+
clamp(triangle.emission.a * emissiveTexel.a, 0.0, 1.0)
|
|
3447
|
+
);
|
|
3448
|
+
return SurfaceMaterialSample(
|
|
3449
|
+
baseColor,
|
|
3450
|
+
emission,
|
|
3451
|
+
vec4<f32>(
|
|
3452
|
+
clamp(triangle.material.x * metallicRoughnessTexel.y, 0.0, 1.0),
|
|
3453
|
+
clamp(triangle.material.y * metallicRoughnessTexel.z, 0.0, 1.0),
|
|
3454
|
+
clamp(triangle.material.z * baseColor.a, 0.0, 1.0),
|
|
3455
|
+
clamp(triangle.material.w, 1.0, 3.0)
|
|
3456
|
+
),
|
|
3457
|
+
triangle.materialResponse,
|
|
3458
|
+
triangle.materialExtension,
|
|
3459
|
+
triangle.specularColor,
|
|
3460
|
+
repair_shading_normal(geometricNormal, mappedNormal),
|
|
3461
|
+
clamp(
|
|
3462
|
+
mix(1.0, occlusionTexel.x, clamp(triangle.textureSettings.y, 0.0, 1.0)),
|
|
3463
|
+
0.0,
|
|
3464
|
+
1.0
|
|
3465
|
+
)
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
|
|
2162
3469
|
fn saturate(value: f32) -> f32 {
|
|
2163
3470
|
return clamp(value, 0.0, 1.0);
|
|
2164
3471
|
}
|
|
@@ -2167,6 +3474,10 @@ fn max_component(value: vec3<f32>) -> f32 {
|
|
|
2167
3474
|
return max(max(value.x, value.y), value.z);
|
|
2168
3475
|
}
|
|
2169
3476
|
|
|
3477
|
+
fn radiance_luminance(value: vec3<f32>) -> f32 {
|
|
3478
|
+
return dot(value, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
3479
|
+
}
|
|
3480
|
+
|
|
2170
3481
|
fn environment_map_enabled() -> bool {
|
|
2171
3482
|
return config.environmentMapSettings.x > 0.5;
|
|
2172
3483
|
}
|
|
@@ -2295,6 +3606,343 @@ fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
|
2295
3606
|
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
2296
3607
|
}
|
|
2297
3608
|
|
|
3609
|
+
fn direct_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
3610
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3611
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
3612
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
3613
|
+
if (
|
|
3614
|
+
config.environmentPortalCount > 0u &&
|
|
3615
|
+
config.environmentPortalMode == 2u &&
|
|
3616
|
+
!portalHit
|
|
3617
|
+
) {
|
|
3618
|
+
return vec3<f32>(0.0);
|
|
3619
|
+
}
|
|
3620
|
+
return base_environment_radiance(rayDirection) *
|
|
3621
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
3622
|
+
}
|
|
3623
|
+
|
|
3624
|
+
fn radical_inverse_vdc(bitsValue: u32) -> f32 {
|
|
3625
|
+
var bits = bitsValue;
|
|
3626
|
+
bits = (bits << 16u) | (bits >> 16u);
|
|
3627
|
+
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xaaaaaaaau) >> 1u);
|
|
3628
|
+
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xccccccccu) >> 2u);
|
|
3629
|
+
bits = ((bits & 0x0f0f0f0fu) << 4u) | ((bits & 0xf0f0f0f0u) >> 4u);
|
|
3630
|
+
bits = ((bits & 0x00ff00ffu) << 8u) | ((bits & 0xff00ff00u) >> 8u);
|
|
3631
|
+
return f32(bits) * 2.3283064365386963e-10;
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
fn hammersley_2d(index: u32, count: u32) -> vec2<f32> {
|
|
3635
|
+
return vec2<f32>(f32(index) / max(f32(count), 1.0), radical_inverse_vdc(index));
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
fn build_basis_tangent(normal: vec3<f32>) -> vec3<f32> {
|
|
3639
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(normal.y) >= 0.999);
|
|
3640
|
+
return safe_normalize(cross(tangentFallback, normal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3641
|
+
}
|
|
3642
|
+
|
|
3643
|
+
fn local_to_world(local: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
3644
|
+
let tangent = build_basis_tangent(normal);
|
|
3645
|
+
let bitangent = safe_normalize(cross(normal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3646
|
+
return safe_normalize(tangent * local.x + bitangent * local.y + normal * local.z, normal);
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
fn cosine_sample_hemisphere(sample: vec2<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
3650
|
+
let phi = 6.28318530718 * sample.x;
|
|
3651
|
+
let radius = sqrt(sample.y);
|
|
3652
|
+
let x = cos(phi) * radius;
|
|
3653
|
+
let y = sin(phi) * radius;
|
|
3654
|
+
let z = sqrt(max(0.0, 1.0 - sample.y));
|
|
3655
|
+
return local_to_world(vec3<f32>(x, y, z), normal);
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
fn importance_sample_ggx(sample: vec2<f32>, roughness: f32, normal: vec3<f32>) -> vec3<f32> {
|
|
3659
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
3660
|
+
let phi = 6.28318530718 * sample.x;
|
|
3661
|
+
let cosTheta = sqrt((1.0 - sample.y) / max(1.0 + (alpha * alpha - 1.0) * sample.y, 0.0001));
|
|
3662
|
+
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3663
|
+
let localHalf = vec3<f32>(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
|
3664
|
+
return local_to_world(localHalf, normal);
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
fn distribution_ggx(normal: vec3<f32>, halfVector: vec3<f32>, roughness: f32) -> f32 {
|
|
3668
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
3669
|
+
let alpha2 = alpha * alpha;
|
|
3670
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
3671
|
+
let denominator = nDotH * nDotH * (alpha2 - 1.0) + 1.0;
|
|
3672
|
+
return alpha2 / max(3.14159265359 * denominator * denominator, 0.000001);
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
fn geometry_schlick_ggx(nDotValue: f32, roughness: f32) -> f32 {
|
|
3676
|
+
let k = ((roughness + 1.0) * (roughness + 1.0)) / 8.0;
|
|
3677
|
+
return nDotValue / max(nDotValue * (1.0 - k) + k, 0.000001);
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
fn geometry_smith(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
3681
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
3682
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
3683
|
+
return geometry_schlick_ggx(nDotV, roughness) * geometry_schlick_ggx(nDotL, roughness);
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3686
|
+
fn fresnel_schlick(cosine: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
3687
|
+
return f0 + (vec3<f32>(1.0) - f0) * pow(1.0 - cosine, 5.0);
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
fn sample_brdf_lut(nDotV: f32, roughness: f32) -> vec2<f32> {
|
|
3691
|
+
let uv = vec2<f32>(clamp(nDotV, 0.0, 1.0), clamp(roughness, 0.0, 1.0));
|
|
3692
|
+
return textureSampleLevel(brdfLutTexture, brdfLutSampler, uv, 0.0).xy;
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
fn prefiltered_environment_radiance(direction: vec3<f32>, roughness: f32) -> vec3<f32> {
|
|
3696
|
+
let uv = environment_map_uv(direction);
|
|
3697
|
+
let maxLevel = max(config.environmentMapMeta.z - 1.0, 0.0);
|
|
3698
|
+
let lod = clamp(roughness, 0.0, 1.0) * maxLevel;
|
|
3699
|
+
let texel = max(textureSampleLevel(environmentMapTexture, environmentMapSampler, uv, lod).rgb, vec3<f32>(0.0));
|
|
3700
|
+
return texel * max(config.environmentMapSettings.y, 0.0);
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
fn environment_pdf_dimensions() -> vec2<u32> {
|
|
3704
|
+
return vec2<u32>(
|
|
3705
|
+
max(u32(config.environmentMapMeta.x), 1u),
|
|
3706
|
+
max(u32(config.environmentMapMeta.y), 1u)
|
|
3707
|
+
);
|
|
3708
|
+
}
|
|
3709
|
+
|
|
3710
|
+
fn environment_importance_sampling_enabled() -> bool {
|
|
3711
|
+
return config.environmentMapMeta.w > 0.5;
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3714
|
+
fn uniform_sphere_pdf() -> f32 {
|
|
3715
|
+
return 1.0 / (4.0 * 3.14159265359);
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
fn sample_uniform_sphere_direction(sample: vec2<f32>) -> vec3<f32> {
|
|
3719
|
+
let z = 1.0 - 2.0 * sample.y;
|
|
3720
|
+
let radial = sqrt(max(1.0 - z * z, 0.0));
|
|
3721
|
+
let phi = sample.x * 6.28318530718;
|
|
3722
|
+
return vec3<f32>(cos(phi) * radial, z, sin(phi) * radial);
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
fn environment_sampling_texel(x: u32, y: u32) -> vec4<f32> {
|
|
3726
|
+
return textureLoad(environmentSamplingTexture, vec2<i32>(i32(x), i32(y)), 0);
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
fn environment_pdf_texel(x: u32, y: u32) -> f32 {
|
|
3730
|
+
return environment_sampling_texel(x, y).x;
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
fn environment_row_cdf_texel(y: u32) -> f32 {
|
|
3734
|
+
return environment_sampling_texel(0u, y).z;
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
fn environment_column_cdf_texel(x: u32, y: u32) -> f32 {
|
|
3738
|
+
return environment_sampling_texel(x, y).y;
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
fn environment_direction_pdf(direction: vec3<f32>) -> f32 {
|
|
3742
|
+
if (!environment_importance_sampling_enabled()) {
|
|
3743
|
+
return uniform_sphere_pdf();
|
|
3744
|
+
}
|
|
3745
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3746
|
+
let uv = environment_map_uv(rayDirection);
|
|
3747
|
+
let dimensions = environment_pdf_dimensions();
|
|
3748
|
+
let width = max(f32(dimensions.x), 1.0);
|
|
3749
|
+
let height = max(f32(dimensions.y), 1.0);
|
|
3750
|
+
let x = min(u32(uv.x * width), dimensions.x - 1u);
|
|
3751
|
+
let y = min(u32(uv.y * height), dimensions.y - 1u);
|
|
3752
|
+
let discretePdf = max(environment_pdf_texel(x, y), 0.0);
|
|
3753
|
+
let sinTheta = sqrt(max(1.0 - rayDirection.y * rayDirection.y, 0.0));
|
|
3754
|
+
let solidAngle = max((2.0 * 3.14159265359 * 3.14159265359 * sinTheta) / (width * height), 0.000001);
|
|
3755
|
+
return discretePdf / solidAngle;
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
fn sample_row_cdf(count: u32, sampleValue: f32) -> u32 {
|
|
3759
|
+
if (count == 0u) {
|
|
3760
|
+
return 0u;
|
|
3761
|
+
}
|
|
3762
|
+
var low = 0u;
|
|
3763
|
+
var high = count - 1u;
|
|
3764
|
+
loop {
|
|
3765
|
+
if (low >= high) {
|
|
3766
|
+
break;
|
|
3767
|
+
}
|
|
3768
|
+
let mid = (low + high) / 2u;
|
|
3769
|
+
let cdfValue = environment_row_cdf_texel(mid);
|
|
3770
|
+
if (sampleValue <= cdfValue) {
|
|
3771
|
+
high = mid;
|
|
3772
|
+
} else {
|
|
3773
|
+
low = mid + 1u;
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
return min(low, count - 1u);
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3779
|
+
fn sample_column_cdf(row: u32, count: u32, sampleValue: f32) -> u32 {
|
|
3780
|
+
if (count == 0u) {
|
|
3781
|
+
return 0u;
|
|
3782
|
+
}
|
|
3783
|
+
var low = 0u;
|
|
3784
|
+
var high = count - 1u;
|
|
3785
|
+
loop {
|
|
3786
|
+
if (low >= high) {
|
|
3787
|
+
break;
|
|
3788
|
+
}
|
|
3789
|
+
let mid = (low + high) / 2u;
|
|
3790
|
+
let cdfValue = environment_column_cdf_texel(mid, row);
|
|
3791
|
+
if (sampleValue <= cdfValue) {
|
|
3792
|
+
high = mid;
|
|
3793
|
+
} else {
|
|
3794
|
+
low = mid + 1u;
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
return min(low, count - 1u);
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
struct EnvironmentSample {
|
|
3801
|
+
direction: vec3<f32>,
|
|
3802
|
+
radiance: vec3<f32>,
|
|
3803
|
+
pdf: f32,
|
|
3804
|
+
};
|
|
3805
|
+
|
|
3806
|
+
fn sample_environment_importance(sample: vec2<f32>) -> EnvironmentSample {
|
|
3807
|
+
if (!environment_importance_sampling_enabled()) {
|
|
3808
|
+
let direction = sample_uniform_sphere_direction(sample);
|
|
3809
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), uniform_sphere_pdf());
|
|
3810
|
+
}
|
|
3811
|
+
let dimensions = environment_pdf_dimensions();
|
|
3812
|
+
let row = sample_row_cdf(dimensions.y, sample.y);
|
|
3813
|
+
let column = sample_column_cdf(row, dimensions.x, sample.x);
|
|
3814
|
+
let uv = vec2<f32>(
|
|
3815
|
+
(f32(column) + 0.5) / max(f32(dimensions.x), 1.0),
|
|
3816
|
+
(f32(row) + 0.5) / max(f32(dimensions.y), 1.0)
|
|
3817
|
+
);
|
|
3818
|
+
let theta = uv.y * 3.14159265359;
|
|
3819
|
+
let phi = (uv.x - 0.5 - config.environmentMapSettings.z / 6.28318530718) * 6.28318530718;
|
|
3820
|
+
let sinTheta = sin(theta);
|
|
3821
|
+
let direction = vec3<f32>(cos(phi) * sinTheta, cos(theta), sin(phi) * sinTheta);
|
|
3822
|
+
let pdf = environment_direction_pdf(direction);
|
|
3823
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), pdf);
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3826
|
+
fn power_heuristic(pdfA: f32, pdfB: f32) -> f32 {
|
|
3827
|
+
let a2 = pdfA * pdfA;
|
|
3828
|
+
let b2 = pdfB * pdfB;
|
|
3829
|
+
return a2 / max(a2 + b2, 0.000001);
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
fn visible_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
3833
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3834
|
+
let visible = !scene_visibility_blocked(origin, rayDirection, 1000000.0);
|
|
3835
|
+
return select(vec3<f32>(0.0), direct_environment_radiance(origin, rayDirection), visible);
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
fn glossy_environment_direction(
|
|
3839
|
+
incidentDirection: vec3<f32>,
|
|
3840
|
+
normal: vec3<f32>,
|
|
3841
|
+
roughness: f32,
|
|
3842
|
+
normalBlendScale: f32
|
|
3843
|
+
) -> vec3<f32> {
|
|
3844
|
+
let reflectionDirection = reflect(incidentDirection, normal);
|
|
3845
|
+
let blend = clamp(roughness * roughness * normalBlendScale, 0.0, 0.92);
|
|
3846
|
+
return safe_normalize(mix(reflectionDirection, normal, blend), normal);
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
fn surface_glossiness(hit: HitRecord) -> f32 {
|
|
3850
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3851
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
3852
|
+
let sheen = clamp(max_component(hit.materialResponse.xyz), 0.0, 1.0);
|
|
3853
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
3854
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
3855
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
3856
|
+
let baseGloss =
|
|
3857
|
+
max(
|
|
3858
|
+
clearcoat,
|
|
3859
|
+
max(sheen * 0.72, max(specularWeight * (0.38 + metallic * 0.62), transmission))
|
|
3860
|
+
);
|
|
3861
|
+
return clamp(baseGloss * (1.0 - roughness * 0.72) + metallic * (1.0 - roughness) * 0.35, 0.0, 1.0);
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
fn surface_specular_f0(hit: HitRecord, surfaceColor: vec3<f32>) -> vec3<f32> {
|
|
3865
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
3866
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
3867
|
+
let specularColor = clamp(hit.specularColor.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
3868
|
+
let dielectricF0 = vec3<f32>(0.04) * specularWeight * specularColor;
|
|
3869
|
+
return mix(dielectricF0, surfaceColor, metallic);
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
fn surface_bsdf_sampling_weights(hit: HitRecord) -> vec3<f32> {
|
|
3873
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
3874
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
3875
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
3876
|
+
let diffuseWeight = clamp(
|
|
3877
|
+
(1.0 - metallic) * max(1.0 - specularWeight * 0.5 - clearcoat * 0.25, 0.15),
|
|
3878
|
+
0.0,
|
|
3879
|
+
1.0
|
|
3880
|
+
);
|
|
3881
|
+
let specWeight = clamp(max(metallic, specularWeight * 0.75) * (1.0 - clearcoat * 0.5), 0.0, 1.0);
|
|
3882
|
+
let clearcoatWeight = clamp(clearcoat, 0.0, 1.0);
|
|
3883
|
+
let totalWeight = max(diffuseWeight + specWeight + clearcoatWeight, 0.000001);
|
|
3884
|
+
return vec3<f32>(
|
|
3885
|
+
diffuseWeight / totalWeight,
|
|
3886
|
+
specWeight / totalWeight,
|
|
3887
|
+
clearcoatWeight / totalWeight
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
fn evaluate_surface_bsdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> vec3<f32> {
|
|
3892
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
3893
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
3894
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3895
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
3896
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
3897
|
+
let clearcoatRoughness = clamp(hit.materialExtension.x, 0.0, 1.0);
|
|
3898
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
3899
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
3900
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
3901
|
+
if (nDotV <= 0.0 || nDotL <= 0.0) {
|
|
3902
|
+
return vec3<f32>(0.0);
|
|
3903
|
+
}
|
|
3904
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
3905
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
3906
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
3907
|
+
let fresnel = fresnel_schlick(vDotH, f0);
|
|
3908
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
3909
|
+
let geometry = geometry_smith(normal, viewDirection, lightDirection, roughness);
|
|
3910
|
+
let specular = (distribution * geometry * fresnel) / max(4.0 * nDotV * nDotL, 0.000001);
|
|
3911
|
+
let diffuseWeight = (1.0 - metallic) * (1.0 - clearcoat * 0.24) * (1.0 - clamp(max_component(fresnel), 0.0, 0.98));
|
|
3912
|
+
let diffuse = surfaceColor * diffuseWeight / 3.14159265359;
|
|
3913
|
+
let clearcoatHalf = safe_normalize(viewDirection + lightDirection, normal);
|
|
3914
|
+
let clearcoatDistribution = distribution_ggx(normal, clearcoatHalf, max(clearcoatRoughness, 0.02));
|
|
3915
|
+
let clearcoatGeometry = geometry_smith(normal, viewDirection, lightDirection, max(clearcoatRoughness, 0.02));
|
|
3916
|
+
let clearcoatFresnel = fresnel_schlick(saturate(dot(viewDirection, clearcoatHalf)), vec3<f32>(0.04));
|
|
3917
|
+
let clearcoatTerm =
|
|
3918
|
+
(clearcoatDistribution * clearcoatGeometry * clearcoatFresnel) /
|
|
3919
|
+
max(4.0 * nDotV * nDotL, 0.000001) *
|
|
3920
|
+
clearcoat;
|
|
3921
|
+
return (diffuse + specular + clearcoatTerm) * mix(0.42, 1.0, occlusion);
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
fn diffuse_pdf(normal: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
3925
|
+
return saturate(dot(normal, lightDirection)) / 3.14159265359;
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
fn ggx_pdf(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
3929
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
3930
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
3931
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
3932
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
3933
|
+
return (distribution * nDotH) / max(4.0 * vDotH, 0.000001);
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3936
|
+
fn evaluate_surface_bsdf_pdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
3937
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
3938
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3939
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
3940
|
+
let diffuseTerm = diffuse_pdf(normal, lightDirection);
|
|
3941
|
+
let specTerm = ggx_pdf(normal, viewDirection, lightDirection, max(roughness, 0.02));
|
|
3942
|
+
let clearcoatTerm = ggx_pdf(normal, viewDirection, lightDirection, max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02));
|
|
3943
|
+
return weights.x * diffuseTerm + weights.y * specTerm + weights.z * clearcoatTerm;
|
|
3944
|
+
}
|
|
3945
|
+
|
|
2298
3946
|
fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
2299
3947
|
let portalScale = environment_portal_radiance_scale(origin, safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0)));
|
|
2300
3948
|
if (
|
|
@@ -2310,9 +3958,45 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
2310
3958
|
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
2311
3959
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
2312
3960
|
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
3961
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
2313
3962
|
let materialEnergy = select(0.68, 0.92, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2314
3963
|
let transparentEnergy = select(materialEnergy, 0.9, hit.hitType == 3u);
|
|
2315
|
-
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy;
|
|
3964
|
+
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy * mix(0.55, 1.0, occlusion);
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
fn bounded_path_response_luminance(ray: RayRecord, hit: HitRecord) -> f32 {
|
|
3968
|
+
let daylightFloor = max(config.pathResolveSettings.y, 0.0) * 0.08;
|
|
3969
|
+
let hdriFloor = max(config.environmentMapSettings.w, 0.0) * 0.02;
|
|
3970
|
+
let sceneFloor = max(daylightFloor, hdriFloor);
|
|
3971
|
+
if (sceneFloor <= 0.000001) {
|
|
3972
|
+
return 0.0;
|
|
3973
|
+
}
|
|
3974
|
+
let bounceRatio = select(
|
|
3975
|
+
0.0,
|
|
3976
|
+
f32(ray.bounce) / max(f32(config.maxDepth - 1u), 1.0),
|
|
3977
|
+
config.maxDepth > 1u
|
|
3978
|
+
);
|
|
3979
|
+
let bounceScale = 1.0 - bounceRatio * 0.55;
|
|
3980
|
+
let materialScale = select(1.0, 0.34, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
3981
|
+
let transparentScale = select(materialScale, 0.58, hit.hitType == 3u);
|
|
3982
|
+
let opacityScale = mix(0.55, 1.0, clamp(hit.material.z, 0.0, 1.0));
|
|
3983
|
+
return sceneFloor * bounceScale * transparentScale * opacityScale;
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3986
|
+
fn stabilize_surface_path_response(ray: RayRecord, hit: HitRecord, response: vec3<f32>) -> vec3<f32> {
|
|
3987
|
+
let minimumLuminance = bounded_path_response_luminance(ray, hit);
|
|
3988
|
+
let responseLuminance = radiance_luminance(response);
|
|
3989
|
+
if (minimumLuminance <= 0.000001 || responseLuminance >= minimumLuminance) {
|
|
3990
|
+
return response;
|
|
3991
|
+
}
|
|
3992
|
+
let tintBase = max(response, max(hit.color.xyz * 0.65, config.ambientColor.xyz * 0.35));
|
|
3993
|
+
let tint = tintBase / max(max_component(tintBase), 0.0001);
|
|
3994
|
+
let lifted = select(
|
|
3995
|
+
tint * minimumLuminance,
|
|
3996
|
+
response * (minimumLuminance / max(responseLuminance, 0.0001)),
|
|
3997
|
+
responseLuminance > 0.0001
|
|
3998
|
+
);
|
|
3999
|
+
return clamp(lifted, vec3<f32>(0.0), vec3<f32>(0.98));
|
|
2316
4000
|
}
|
|
2317
4001
|
|
|
2318
4002
|
fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
@@ -2331,12 +4015,24 @@ fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
|
2331
4015
|
return clamp_sample_radiance(sunTint * baseline * skyFacing * directionalWeight * 0.04);
|
|
2332
4016
|
}
|
|
2333
4017
|
|
|
2334
|
-
fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
4018
|
+
fn terminal_surface_environment_source(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2335
4019
|
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2336
|
-
let
|
|
2337
|
-
|
|
2338
|
-
|
|
4020
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4021
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4022
|
+
let glossiness = surface_glossiness(hit);
|
|
4023
|
+
let normalEnvironment = gated_environment_radiance(origin, normal);
|
|
4024
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4025
|
+
let reflectionDirection = glossy_environment_direction(
|
|
4026
|
+
ray.direction.xyz,
|
|
4027
|
+
normal,
|
|
4028
|
+
roughness,
|
|
4029
|
+
mix(0.88, 0.38, glossiness)
|
|
2339
4030
|
);
|
|
4031
|
+
let reflectionEnvironment = prefiltered_environment_radiance(reflectionDirection, roughness);
|
|
4032
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4033
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
4034
|
+
let brdfTerm = sample_brdf_lut(saturate(dot(normal, viewDirection)), roughness);
|
|
4035
|
+
let specularEnvironment = reflectionEnvironment * (f0 * brdfTerm.x + vec3<f32>(brdfTerm.y));
|
|
2340
4036
|
let sunlitFloor = sunlit_baseline_radiance(normal);
|
|
2341
4037
|
let ambientFloor = select(
|
|
2342
4038
|
max(config.ambientColor.xyz, sunlitFloor * 0.82),
|
|
@@ -2348,17 +4044,23 @@ fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
|
2348
4044
|
max(config.environmentMapSettings.w, max(0.12, config.pathResolveSettings.y * 0.42)),
|
|
2349
4045
|
environment_map_enabled()
|
|
2350
4046
|
);
|
|
2351
|
-
let
|
|
4047
|
+
let glossyEnvironment = max(
|
|
4048
|
+
normalEnvironment,
|
|
4049
|
+
max(reflectionEnvironment * mix(0.24, 0.92, glossiness), specularEnvironment)
|
|
4050
|
+
);
|
|
4051
|
+
let environmentFloor = max(ambientFloor, max(sunlitFloor, glossyEnvironment * environmentInfluence));
|
|
2352
4052
|
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
2353
4053
|
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
2354
4054
|
}
|
|
2355
4055
|
|
|
2356
4056
|
fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2357
4057
|
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
4058
|
+
let occlusion = mix(0.75, 1.0, clamp(hit.occlusion, 0.0, 1.0));
|
|
2358
4059
|
return clamp_sample_radiance(
|
|
2359
4060
|
ray.throughput.xyz *
|
|
2360
4061
|
surfaceColor *
|
|
2361
|
-
terminal_surface_environment_source(hit)
|
|
4062
|
+
terminal_surface_environment_source(ray, hit) *
|
|
4063
|
+
occlusion
|
|
2362
4064
|
);
|
|
2363
4065
|
}
|
|
2364
4066
|
|
|
@@ -2391,6 +4093,10 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2391
4093
|
);
|
|
2392
4094
|
let area = max(portal.position.w, 0.0001);
|
|
2393
4095
|
let distanceFalloff = clamp(area / max(distanceSquared, area * 0.25), 0.0, 2.5);
|
|
4096
|
+
let traceDistance = max(sqrt(distanceSquared) - 0.01, 0.01);
|
|
4097
|
+
if (scene_visibility_blocked(origin, direction, traceDistance)) {
|
|
4098
|
+
continue;
|
|
4099
|
+
}
|
|
2394
4100
|
irradiance = irradiance +
|
|
2395
4101
|
portal.color.rgb *
|
|
2396
4102
|
portal.normal.w *
|
|
@@ -2402,48 +4108,79 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2402
4108
|
return irradiance;
|
|
2403
4109
|
}
|
|
2404
4110
|
|
|
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()
|
|
4111
|
+
fn visibility_test_ray(origin: vec3<f32>, direction: vec3<f32>) -> RayRecord {
|
|
4112
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4113
|
+
return RayRecord(
|
|
4114
|
+
0u,
|
|
4115
|
+
0u,
|
|
4116
|
+
0u,
|
|
4117
|
+
0u,
|
|
4118
|
+
0u,
|
|
4119
|
+
0u,
|
|
4120
|
+
0u,
|
|
4121
|
+
0u,
|
|
4122
|
+
vec4<f32>(origin, 1.0),
|
|
4123
|
+
vec4<f32>(rayDirection, 0.0),
|
|
4124
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2424
4125
|
);
|
|
2425
|
-
|
|
4126
|
+
}
|
|
2426
4127
|
|
|
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);
|
|
4128
|
+
fn scene_visibility_blocked(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> bool {
|
|
4129
|
+
let testRay = visibility_test_ray(origin, direction);
|
|
4130
|
+
let nearest = max(maxDistance, 0.001);
|
|
2435
4131
|
|
|
2436
|
-
|
|
2437
|
-
|
|
4132
|
+
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
4133
|
+
let object = sceneObjects[objectIndex];
|
|
4134
|
+
var current = no_candidate();
|
|
4135
|
+
if (object.kind == 1u) {
|
|
4136
|
+
current = intersect_sphere(testRay, object);
|
|
4137
|
+
} else if (object.kind == 2u) {
|
|
4138
|
+
current = intersect_box(testRay, object);
|
|
4139
|
+
}
|
|
4140
|
+
if (current.hit == 1u && current.distance < nearest) {
|
|
4141
|
+
return true;
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
2438
4144
|
|
|
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);
|
|
4145
|
+
let meshCandidate = intersect_bvh(testRay, nearest);
|
|
4146
|
+
return meshCandidate.hit == 1u && meshCandidate.distance < nearest;
|
|
4147
|
+
}
|
|
2444
4148
|
|
|
2445
|
-
|
|
2446
|
-
|
|
4149
|
+
fn surface_direct_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
4150
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4151
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4152
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4153
|
+
let lightSample = sample_environment_importance(vec2<f32>(
|
|
4154
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 41u)),
|
|
4155
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 43u))
|
|
4156
|
+
));
|
|
4157
|
+
if (lightSample.pdf <= 0.000001) {
|
|
4158
|
+
return vec3<f32>(0.0);
|
|
4159
|
+
}
|
|
4160
|
+
let lightDirection = safe_normalize(lightSample.direction, normal);
|
|
4161
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4162
|
+
if (nDotL <= 0.000001) {
|
|
4163
|
+
return vec3<f32>(0.0);
|
|
4164
|
+
}
|
|
4165
|
+
if (scene_visibility_blocked(origin, lightDirection, 1000000.0)) {
|
|
4166
|
+
return vec3<f32>(0.0);
|
|
4167
|
+
}
|
|
4168
|
+
let incidentRadiance = direct_environment_radiance(origin, lightDirection);
|
|
4169
|
+
if (max_component(incidentRadiance) <= 0.000001) {
|
|
4170
|
+
return vec3<f32>(0.0);
|
|
4171
|
+
}
|
|
4172
|
+
let bsdf = evaluate_surface_bsdf(hit, viewDirection, lightDirection);
|
|
4173
|
+
if (max_component(bsdf) <= 0.000001) {
|
|
4174
|
+
return vec3<f32>(0.0);
|
|
4175
|
+
}
|
|
4176
|
+
let bsdfPdf = evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection);
|
|
4177
|
+
let misWeight = power_heuristic(lightSample.pdf, bsdfPdf);
|
|
4178
|
+
let contribution =
|
|
4179
|
+
ray.throughput.xyz *
|
|
4180
|
+
bsdf *
|
|
4181
|
+
incidentRadiance *
|
|
4182
|
+
(nDotL * misWeight / max(lightSample.pdf, 0.000001));
|
|
4183
|
+
return clamp_sample_radiance(contribution);
|
|
2447
4184
|
}
|
|
2448
4185
|
|
|
2449
4186
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -2462,7 +4199,16 @@ fn default_mesh_range() -> MeshRange {
|
|
|
2462
4199
|
0u,
|
|
2463
4200
|
vec4<f32>(0.72, 0.72, 0.68, 1.0),
|
|
2464
4201
|
vec4<f32>(0.0),
|
|
2465
|
-
vec4<f32>(0.72, 0.0, 1.0, 1.45)
|
|
4202
|
+
vec4<f32>(0.72, 0.0, 1.0, 1.45),
|
|
4203
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4204
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
4205
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4206
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4207
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4208
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4209
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4210
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4211
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
2466
4212
|
);
|
|
2467
4213
|
}
|
|
2468
4214
|
|
|
@@ -2558,7 +4304,7 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2558
4304
|
mesh.flags,
|
|
2559
4305
|
mesh.materialRefId,
|
|
2560
4306
|
mesh.mediumRefId,
|
|
2561
|
-
|
|
4307
|
+
mesh.materialSlot,
|
|
2562
4308
|
0u,
|
|
2563
4309
|
vec4<f32>(vertex0.position.xyz, 0.0),
|
|
2564
4310
|
vec4<f32>(vertex1.position.xyz, 0.0),
|
|
@@ -2570,7 +4316,16 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2570
4316
|
vec4<f32>(uv2, 0.0, 0.0),
|
|
2571
4317
|
mesh.color,
|
|
2572
4318
|
mesh.emission,
|
|
2573
|
-
mesh.material
|
|
4319
|
+
mesh.material,
|
|
4320
|
+
mesh.materialResponse,
|
|
4321
|
+
mesh.materialExtension,
|
|
4322
|
+
mesh.specularColor,
|
|
4323
|
+
mesh.baseColorAtlas,
|
|
4324
|
+
mesh.metallicRoughnessAtlas,
|
|
4325
|
+
mesh.normalAtlas,
|
|
4326
|
+
mesh.occlusionAtlas,
|
|
4327
|
+
mesh.emissiveAtlas,
|
|
4328
|
+
mesh.textureSettings
|
|
2574
4329
|
);
|
|
2575
4330
|
|
|
2576
4331
|
let leafBase = config.triangleCount - 1u;
|
|
@@ -2729,7 +4484,8 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2729
4484
|
0u,
|
|
2730
4485
|
0u,
|
|
2731
4486
|
-1.0,
|
|
2732
|
-
|
|
4487
|
+
1.0,
|
|
4488
|
+
vec2<f32>(0.0),
|
|
2733
4489
|
vec4<f32>(ray.origin.xyz + ray.direction.xyz * 1000.0, 1.0),
|
|
2734
4490
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
2735
4491
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
@@ -2737,7 +4493,10 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2737
4493
|
vec4<f32>(0.0),
|
|
2738
4494
|
vec4<f32>(radiance, 1.0),
|
|
2739
4495
|
vec4<f32>(0.0),
|
|
2740
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
4496
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
4497
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4498
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
4499
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2741
4500
|
);
|
|
2742
4501
|
}
|
|
2743
4502
|
|
|
@@ -3032,6 +4791,19 @@ fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
|
|
|
3032
4791
|
return value / (vec3<f32>(1.0) + value);
|
|
3033
4792
|
}
|
|
3034
4793
|
|
|
4794
|
+
fn denoise_sample_count() -> f32 {
|
|
4795
|
+
return clamp(1.0 / max(config.projectionAndSampling.z, 0.000001), 1.0, 256.0);
|
|
4796
|
+
}
|
|
4797
|
+
|
|
4798
|
+
fn denoise_strength() -> f32 {
|
|
4799
|
+
let spp = denoise_sample_count();
|
|
4800
|
+
return clamp(0.44 / sqrt(spp), 0.08, 0.44);
|
|
4801
|
+
}
|
|
4802
|
+
|
|
4803
|
+
fn denoise_kernel_radius() -> i32 {
|
|
4804
|
+
return select(1i, 2i, denoise_sample_count() < 2.5);
|
|
4805
|
+
}
|
|
4806
|
+
|
|
3035
4807
|
@compute @workgroup_size(64)
|
|
3036
4808
|
fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
3037
4809
|
let index = globalId.x;
|
|
@@ -3070,7 +4842,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3070
4842
|
vec4<f32>(0.0),
|
|
3071
4843
|
vec4<f32>(0.0),
|
|
3072
4844
|
vec4<f32>(0.0),
|
|
3073
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
4845
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
4846
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4847
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
4848
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
3074
4849
|
);
|
|
3075
4850
|
var candidate = no_candidate();
|
|
3076
4851
|
var hitTriangle = TriangleRecord(
|
|
@@ -3092,7 +4867,16 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3092
4867
|
vec4<f32>(0.0),
|
|
3093
4868
|
vec4<f32>(0.0),
|
|
3094
4869
|
vec4<f32>(0.0),
|
|
3095
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
4870
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
4871
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4872
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
4873
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
4874
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4875
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4876
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4877
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4878
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4879
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
3096
4880
|
);
|
|
3097
4881
|
|
|
3098
4882
|
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
@@ -3125,16 +4909,28 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3125
4909
|
let position = ray.origin.xyz + ray.direction.xyz * candidate.distance;
|
|
3126
4910
|
let hitMaterialKind = select(hitObject.materialKind, hitTriangle.materialKind, candidate.triangleIndex != 0xffffffffu);
|
|
3127
4911
|
let hitObjectId = select(hitObject.objectId, hitTriangle.meshId, candidate.triangleIndex != 0xffffffffu);
|
|
3128
|
-
let
|
|
3129
|
-
|
|
3130
|
-
|
|
4912
|
+
let meshSurface = sample_surface_material(
|
|
4913
|
+
hitTriangle,
|
|
4914
|
+
candidate.uv,
|
|
4915
|
+
candidate.geometricNormal,
|
|
4916
|
+
candidate.shadingNormal
|
|
4917
|
+
);
|
|
4918
|
+
let hitColor = select(hitObject.color, meshSurface.color, candidate.triangleIndex != 0xffffffffu);
|
|
4919
|
+
let hitEmission = select(hitObject.emission, meshSurface.emission, candidate.triangleIndex != 0xffffffffu);
|
|
4920
|
+
let hitMaterial = select(hitObject.material, meshSurface.material, candidate.triangleIndex != 0xffffffffu);
|
|
4921
|
+
let hitMaterialResponse = select(hitObject.materialResponse, meshSurface.materialResponse, candidate.triangleIndex != 0xffffffffu);
|
|
4922
|
+
let hitMaterialExtension = select(hitObject.materialExtension, meshSurface.materialExtension, candidate.triangleIndex != 0xffffffffu);
|
|
4923
|
+
let hitSpecularColor = select(hitObject.specularColor, meshSurface.specularColor, candidate.triangleIndex != 0xffffffffu);
|
|
4924
|
+
let hitShadingNormal = select(candidate.shadingNormal, meshSurface.shadingNormal, candidate.triangleIndex != 0xffffffffu);
|
|
3131
4925
|
let hitPrimitiveId = select(candidate.primitiveId, hitTriangle.triangleId, candidate.triangleIndex != 0xffffffffu);
|
|
3132
4926
|
let hitMaterialRefId = select(candidate.materialRefId, hitTriangle.materialRefId, candidate.triangleIndex != 0xffffffffu);
|
|
3133
4927
|
let hitMediumRefId = select(candidate.mediumRefId, hitTriangle.mediumRefId, candidate.triangleIndex != 0xffffffffu);
|
|
4928
|
+
let hitMaterialSlot = select(0u, hitTriangle.materialSlot, candidate.triangleIndex != 0xffffffffu);
|
|
4929
|
+
let hitOcclusion = select(1.0, meshSurface.occlusion, candidate.triangleIndex != 0xffffffffu);
|
|
3134
4930
|
var hitType = 0u;
|
|
3135
4931
|
if (hitMaterialKind == 4u || emission_power(hitEmission) > 0.0001) {
|
|
3136
4932
|
hitType = 1u;
|
|
3137
|
-
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999) {
|
|
4933
|
+
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999 || hitMaterialExtension.z > 0.001) {
|
|
3138
4934
|
hitType = 3u;
|
|
3139
4935
|
}
|
|
3140
4936
|
atomicAdd(&counters.hitCount, 1u);
|
|
@@ -3148,19 +4944,23 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3148
4944
|
hitPrimitiveId,
|
|
3149
4945
|
hitMaterialRefId,
|
|
3150
4946
|
hitMediumRefId,
|
|
3151
|
-
|
|
4947
|
+
hitMaterialSlot,
|
|
3152
4948
|
0u,
|
|
3153
4949
|
0u,
|
|
3154
4950
|
candidate.distance,
|
|
3155
|
-
|
|
4951
|
+
hitOcclusion,
|
|
4952
|
+
vec2<f32>(0.0),
|
|
3156
4953
|
vec4<f32>(position, 1.0),
|
|
3157
4954
|
vec4<f32>(candidate.geometricNormal, 0.0),
|
|
3158
|
-
vec4<f32>(
|
|
4955
|
+
vec4<f32>(hitShadingNormal, 0.0),
|
|
3159
4956
|
vec4<f32>(candidate.barycentric, 0.0),
|
|
3160
4957
|
vec4<f32>(candidate.uv, 0.0, 0.0),
|
|
3161
4958
|
hitColor,
|
|
3162
4959
|
hitEmission,
|
|
3163
|
-
hitMaterial
|
|
4960
|
+
hitMaterial,
|
|
4961
|
+
hitMaterialResponse,
|
|
4962
|
+
hitMaterialExtension,
|
|
4963
|
+
hitSpecularColor
|
|
3164
4964
|
);
|
|
3165
4965
|
}
|
|
3166
4966
|
|
|
@@ -3229,60 +5029,106 @@ fn sample_environment_portal_direction(hit: HitRecord, seed: u32, fallback: vec3
|
|
|
3229
5029
|
}
|
|
3230
5030
|
|
|
3231
5031
|
fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult {
|
|
5032
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
5033
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
3232
5034
|
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3233
|
-
|
|
5035
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
5036
|
+
if (hit.materialKind == 1u && roughness <= 0.02) {
|
|
3234
5037
|
return ScatterResult(
|
|
3235
|
-
vec4<f32>(
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
hit.shadingNormal.xyz
|
|
3239
|
-
),
|
|
3240
|
-
0.0
|
|
3241
|
-
),
|
|
3242
|
-
0u,
|
|
3243
|
-
0u,
|
|
5038
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5039
|
+
1.0,
|
|
5040
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
3244
5041
|
0u,
|
|
3245
5042
|
0u
|
|
3246
5043
|
);
|
|
3247
5044
|
}
|
|
3248
5045
|
|
|
3249
|
-
if (hit.materialKind == 2u || hit.materialKind == 3u) {
|
|
5046
|
+
if (hit.materialKind == 2u || hit.materialKind == 3u || transmission > 0.001) {
|
|
3250
5047
|
let ior = max(hit.material.w, 1.01);
|
|
3251
5048
|
let etaRatio = select(ior, 1.0 / ior, hit.frontFace == 1u);
|
|
3252
|
-
let cosTheta = min(dot(-ray.direction.xyz,
|
|
5049
|
+
let cosTheta = min(dot(-ray.direction.xyz, normal), 1.0);
|
|
3253
5050
|
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3254
5051
|
let cannotRefract = etaRatio * sinTheta > 1.0;
|
|
3255
5052
|
let reflectChance = schlick(cosTheta, etaRatio);
|
|
3256
|
-
|
|
3257
|
-
|
|
5053
|
+
let transmissionReflectChance = select(
|
|
5054
|
+
reflectChance,
|
|
5055
|
+
max(reflectChance, 1.0 - transmission),
|
|
5056
|
+
transmission > 0.001
|
|
5057
|
+
);
|
|
5058
|
+
if (cannotRefract || random01(seed + 23u) < transmissionReflectChance) {
|
|
5059
|
+
return ScatterResult(
|
|
5060
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5061
|
+
1.0,
|
|
5062
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
5063
|
+
0u,
|
|
5064
|
+
0u
|
|
5065
|
+
);
|
|
3258
5066
|
}
|
|
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
|
-
)
|
|
5067
|
+
return ScatterResult(
|
|
5068
|
+
vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
|
|
5069
|
+
1.0,
|
|
5070
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
5071
|
+
0u,
|
|
5072
|
+
0u
|
|
5073
|
+
);
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
let guidedEmissiveAvailable = config.emissiveTriangleCount > 0u;
|
|
5077
|
+
let guidedPortalAvailable =
|
|
5078
|
+
config.environmentPortalCount > 0u && config.environmentPortalMode != 0u;
|
|
5079
|
+
let guidedSelector = random01(seed + 17u);
|
|
5080
|
+
if (guidedEmissiveAvailable && guidedSelector < 0.18) {
|
|
5081
|
+
let guidedDirection = sample_emissive_triangle_direction(hit, seed + 101u, normal);
|
|
5082
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5083
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5084
|
+
return ScatterResult(
|
|
5085
|
+
vec4<f32>(guidedDirection, 0.0),
|
|
5086
|
+
guidedPdf,
|
|
5087
|
+
RAY_FLAG_GUIDED_EMISSIVE,
|
|
5088
|
+
0u,
|
|
5089
|
+
0u
|
|
5090
|
+
);
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
if (guidedPortalAvailable && guidedSelector < 0.32) {
|
|
5094
|
+
let guidedDirection = sample_environment_portal_direction(hit, seed + 131u, normal);
|
|
5095
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5096
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5097
|
+
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf, 0u, 0u, 0u);
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
|
|
5101
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
5102
|
+
let selector = random01(seed + 31u);
|
|
5103
|
+
var lightDirection = normal;
|
|
5104
|
+
if (selector < weights.x) {
|
|
5105
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5106
|
+
vec2<f32>(random01(seed + 37u), random01(seed + 41u)),
|
|
5107
|
+
normal
|
|
5108
|
+
);
|
|
5109
|
+
} else if (selector < weights.x + weights.y) {
|
|
5110
|
+
let halfVector = importance_sample_ggx(
|
|
5111
|
+
vec2<f32>(random01(seed + 47u), random01(seed + 53u)),
|
|
5112
|
+
max(roughness, 0.02),
|
|
5113
|
+
normal
|
|
5114
|
+
);
|
|
5115
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5116
|
+
} else {
|
|
5117
|
+
let halfVector = importance_sample_ggx(
|
|
5118
|
+
vec2<f32>(random01(seed + 59u), random01(seed + 61u)),
|
|
5119
|
+
max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02),
|
|
5120
|
+
normal
|
|
5121
|
+
);
|
|
5122
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5123
|
+
}
|
|
5124
|
+
if (dot(normal, lightDirection) <= 0.000001) {
|
|
5125
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5126
|
+
vec2<f32>(random01(seed + 67u), random01(seed + 71u)),
|
|
5127
|
+
normal
|
|
5128
|
+
);
|
|
5129
|
+
}
|
|
5130
|
+
let pdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection), 0.000001);
|
|
5131
|
+
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf, 0u, 0u, 0u);
|
|
3286
5132
|
}
|
|
3287
5133
|
|
|
3288
5134
|
@compute @workgroup_size(64)
|
|
@@ -3312,10 +5158,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3312
5158
|
}
|
|
3313
5159
|
|
|
3314
5160
|
if (hit.hitType == 2u) {
|
|
5161
|
+
var sourceRadiance = hit.color.xyz;
|
|
5162
|
+
if ((ray.flags & RAY_FLAG_DELTA_SAMPLE) == 0u) {
|
|
5163
|
+
let bsdfPdf = max(ray.throughput.w, 0.000001);
|
|
5164
|
+
let lightPdf = environment_direction_pdf(ray.direction.xyz);
|
|
5165
|
+
let misWeight = power_heuristic(bsdfPdf, lightPdf);
|
|
5166
|
+
sourceRadiance = sourceRadiance * misWeight;
|
|
5167
|
+
}
|
|
3315
5168
|
if (deferred_path_resolve_enabled()) {
|
|
3316
|
-
record_deferred_terminal_source(ray,
|
|
5169
|
+
record_deferred_terminal_source(ray, sourceRadiance);
|
|
3317
5170
|
} else {
|
|
3318
|
-
contribution = clamp_sample_radiance(ray.throughput.xyz *
|
|
5171
|
+
contribution = clamp_sample_radiance(ray.throughput.xyz * sourceRadiance);
|
|
3319
5172
|
accumulation[ray.rayId] =
|
|
3320
5173
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3321
5174
|
}
|
|
@@ -3323,13 +5176,13 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3323
5176
|
return;
|
|
3324
5177
|
}
|
|
3325
5178
|
|
|
3326
|
-
let response = surface_path_response(hit);
|
|
5179
|
+
let response = stabilize_surface_path_response(ray, hit, surface_path_response(hit));
|
|
3327
5180
|
record_deferred_path_response(ray, response);
|
|
3328
5181
|
|
|
3329
5182
|
let shouldEstimateDirectEnvironment =
|
|
3330
|
-
!deferred_path_resolve_enabled() &&
|
|
3331
5183
|
(hit.materialKind == 0u || hit.materialKind == 1u) &&
|
|
3332
|
-
hit.material.z >= 0.95
|
|
5184
|
+
hit.material.z >= 0.95 &&
|
|
5185
|
+
ray.bounce < 2u;
|
|
3333
5186
|
if (shouldEstimateDirectEnvironment) {
|
|
3334
5187
|
let directEnvironment = surface_direct_environment_contribution(ray, hit);
|
|
3335
5188
|
accumulation[ray.rayId] =
|
|
@@ -3338,7 +5191,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3338
5191
|
|
|
3339
5192
|
if (ray.bounce + 1u >= config.maxDepth) {
|
|
3340
5193
|
if (deferred_path_resolve_enabled()) {
|
|
3341
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5194
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3342
5195
|
} else {
|
|
3343
5196
|
let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
3344
5197
|
accumulation[ray.rayId] =
|
|
@@ -3353,7 +5206,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3353
5206
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
3354
5207
|
if (nextIndex >= config.tilePixelCount) {
|
|
3355
5208
|
if (deferred_path_resolve_enabled()) {
|
|
3356
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5209
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3357
5210
|
} else {
|
|
3358
5211
|
let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
3359
5212
|
accumulation[ray.rayId] =
|
|
@@ -3374,7 +5227,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3374
5227
|
0u,
|
|
3375
5228
|
vec4<f32>(offset_origin(hit.position.xyz, hit.shadingNormal.xyz), 1.0),
|
|
3376
5229
|
scatter.direction,
|
|
3377
|
-
vec4<f32>(throughput,
|
|
5230
|
+
vec4<f32>(throughput, scatter.pdf)
|
|
3378
5231
|
);
|
|
3379
5232
|
}
|
|
3380
5233
|
|
|
@@ -3443,8 +5296,11 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3443
5296
|
|
|
3444
5297
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3445
5298
|
let center = textureLoad(denoiseInputRadiance, pixel, 0).xyz;
|
|
3446
|
-
|
|
3447
|
-
|
|
5299
|
+
let strength = denoise_strength();
|
|
5300
|
+
let kernelRadius = denoise_kernel_radius();
|
|
5301
|
+
let centerWeight = 1.7 - strength * 0.35;
|
|
5302
|
+
var sum = center * centerWeight;
|
|
5303
|
+
var totalWeight = centerWeight;
|
|
3448
5304
|
let centerRange = denoise_range_space(center);
|
|
3449
5305
|
|
|
3450
5306
|
for (var oy = -2i; oy <= 2i; oy = oy + 1i) {
|
|
@@ -3452,13 +5308,16 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3452
5308
|
if (ox == 0i && oy == 0i) {
|
|
3453
5309
|
continue;
|
|
3454
5310
|
}
|
|
5311
|
+
if (abs(ox) > kernelRadius || abs(oy) > kernelRadius) {
|
|
5312
|
+
continue;
|
|
5313
|
+
}
|
|
3455
5314
|
let sx = clamp(i32(x) + ox, 0i, i32(config.canvasWidth) - 1i);
|
|
3456
5315
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3457
5316
|
let sampleColor = textureLoad(denoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3458
5317
|
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.
|
|
5318
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (11.0 + strength * 6.0));
|
|
5319
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.62 + strength * 0.24));
|
|
5320
|
+
let diagonalWeight = select(1.0, 0.92, abs(ox) + abs(oy) > 1i);
|
|
3462
5321
|
let weight = rangeWeight * diagonalWeight * distanceWeight;
|
|
3463
5322
|
sum = sum + sampleColor * weight;
|
|
3464
5323
|
totalWeight = totalWeight + weight;
|
|
@@ -3466,8 +5325,9 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3466
5325
|
}
|
|
3467
5326
|
|
|
3468
5327
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3469
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3470
|
-
let
|
|
5328
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.1);
|
|
5329
|
+
let blend = min(0.3, strength * (0.62 + outlier * 0.12));
|
|
5330
|
+
let color = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3471
5331
|
textureStore(denoisedRadianceImage, pixel, vec4<f32>(color, 1.0));
|
|
3472
5332
|
}
|
|
3473
5333
|
|
|
@@ -3481,8 +5341,10 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3481
5341
|
|
|
3482
5342
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3483
5343
|
let center = textureLoad(finalDenoiseInputRadiance, pixel, 0).xyz;
|
|
3484
|
-
|
|
3485
|
-
|
|
5344
|
+
let strength = denoise_strength();
|
|
5345
|
+
let centerWeight = 1.35 - strength * 0.25;
|
|
5346
|
+
var sum = center * centerWeight;
|
|
5347
|
+
var totalWeight = centerWeight;
|
|
3486
5348
|
let centerRange = denoise_range_space(center);
|
|
3487
5349
|
|
|
3488
5350
|
for (var oy = -1i; oy <= 1i; oy = oy + 1i) {
|
|
@@ -3494,8 +5356,8 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3494
5356
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3495
5357
|
let sampleColor = textureLoad(finalDenoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3496
5358
|
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.
|
|
5359
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (12.0 + strength * 8.0));
|
|
5360
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.82 + strength * 0.28));
|
|
3499
5361
|
let weight = rangeWeight * distanceWeight;
|
|
3500
5362
|
sum = sum + sampleColor * weight;
|
|
3501
5363
|
totalWeight = totalWeight + weight;
|
|
@@ -3503,8 +5365,9 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3503
5365
|
}
|
|
3504
5366
|
|
|
3505
5367
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3506
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3507
|
-
let
|
|
5368
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.2);
|
|
5369
|
+
let blend = min(0.18, strength * (0.42 + outlier * 0.08));
|
|
5370
|
+
let radiance = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3508
5371
|
textureStore(denoisedOutputImage, pixel, vec4<f32>(tone_map_radiance(radiance), 1.0));
|
|
3509
5372
|
}
|
|
3510
5373
|
`;
|
|
@@ -3571,104 +5434,34 @@ function readGpuLimit(adapter, device, name) {
|
|
|
3571
5434
|
const deviceValue = Number(device?.limits?.[name]);
|
|
3572
5435
|
return Number.isFinite(deviceValue) ? deviceValue : null;
|
|
3573
5436
|
}
|
|
3574
|
-
function createAdapterInfoSnapshot(adapter) {
|
|
3575
|
-
const info = adapter?.info;
|
|
3576
|
-
if (!info || typeof info !== "object") {
|
|
3577
|
-
return null;
|
|
3578
|
-
}
|
|
3579
|
-
return Object.freeze({
|
|
3580
|
-
vendor: typeof info.vendor === "string" ? info.vendor : "",
|
|
3581
|
-
architecture: typeof info.architecture === "string" ? info.architecture : "",
|
|
3582
|
-
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"
|
|
5437
|
+
function createAdapterInfoSnapshot(adapter) {
|
|
5438
|
+
const info = adapter?.info;
|
|
5439
|
+
if (!info || typeof info !== "object") {
|
|
5440
|
+
return null;
|
|
5441
|
+
}
|
|
5442
|
+
return Object.freeze({
|
|
5443
|
+
vendor: typeof info.vendor === "string" ? info.vendor : "",
|
|
5444
|
+
architecture: typeof info.architecture === "string" ? info.architecture : "",
|
|
5445
|
+
device: typeof info.device === "string" ? info.device : "",
|
|
5446
|
+
description: typeof info.description === "string" ? info.description : ""
|
|
5447
|
+
});
|
|
5448
|
+
}
|
|
5449
|
+
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
5450
|
+
return Object.freeze({
|
|
5451
|
+
physicalCoreCount: null,
|
|
5452
|
+
physicalCoreCountAvailable: false,
|
|
5453
|
+
physicalCoreCountUnavailableReason: "WebGPU does not expose physical GPU core counts.",
|
|
5454
|
+
adapterInfo: createAdapterInfoSnapshot(adapter),
|
|
5455
|
+
adapterLimits: Object.freeze({
|
|
5456
|
+
maxComputeInvocationsPerWorkgroup: readGpuLimit(adapter, device, "maxComputeInvocationsPerWorkgroup"),
|
|
5457
|
+
maxComputeWorkgroupSizeX: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeX"),
|
|
5458
|
+
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
5459
|
+
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
5460
|
+
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
5461
|
+
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
5462
|
+
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize")
|
|
5463
|
+
}),
|
|
5464
|
+
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
3672
5465
|
});
|
|
3673
5466
|
}
|
|
3674
5467
|
function createEnvironmentMapSnapshot(environmentMap) {
|
|
@@ -3676,12 +5469,38 @@ function createEnvironmentMapSnapshot(environmentMap) {
|
|
|
3676
5469
|
enabled: environmentMap.enabled,
|
|
3677
5470
|
width: environmentMap.width,
|
|
3678
5471
|
height: environmentMap.height,
|
|
5472
|
+
mipLevelCount: environmentMap.mipLevelCount ?? 1,
|
|
3679
5473
|
projection: environmentMap.projection,
|
|
3680
5474
|
intensity: environmentMap.intensity,
|
|
3681
5475
|
rotationRadians: environmentMap.rotationRadians,
|
|
3682
|
-
ambientStrength: environmentMap.ambientStrength
|
|
5476
|
+
ambientStrength: environmentMap.ambientStrength,
|
|
5477
|
+
hasImportanceData: environmentMap.hasImportanceData === true
|
|
3683
5478
|
});
|
|
3684
5479
|
}
|
|
5480
|
+
function nowMs() {
|
|
5481
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
5482
|
+
return performance.now();
|
|
5483
|
+
}
|
|
5484
|
+
return Date.now();
|
|
5485
|
+
}
|
|
5486
|
+
function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs = null) {
|
|
5487
|
+
if (Number.isFinite(overrideTimeoutMs)) {
|
|
5488
|
+
return Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
5489
|
+
}
|
|
5490
|
+
const samplesPerPixel = Math.max(
|
|
5491
|
+
1,
|
|
5492
|
+
Number(config?.renderedSamplesPerPixel ?? config?.samplesPerPixel ?? 1)
|
|
5493
|
+
);
|
|
5494
|
+
const maxDepth = Math.max(1, Number(config?.maxDepth ?? 1));
|
|
5495
|
+
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
5496
|
+
const denoisePasses = config?.denoise ? samplesPerPixel < 4 ? 2 : 1 : 0;
|
|
5497
|
+
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
5498
|
+
const estimatedPasses = tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
5499
|
+
return Math.min(
|
|
5500
|
+
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
5501
|
+
GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5
|
|
5502
|
+
);
|
|
5503
|
+
}
|
|
3685
5504
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
3686
5505
|
assertAnalyticDisplayQualityPolicy(options);
|
|
3687
5506
|
const constants = getGpuUsageConstants();
|
|
@@ -3886,6 +5705,60 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3886
5705
|
config.environmentMap,
|
|
3887
5706
|
config.environmentColor
|
|
3888
5707
|
);
|
|
5708
|
+
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
5709
|
+
device,
|
|
5710
|
+
constants,
|
|
5711
|
+
config.environmentMap,
|
|
5712
|
+
config.environmentColor
|
|
5713
|
+
);
|
|
5714
|
+
config = Object.freeze({
|
|
5715
|
+
...config,
|
|
5716
|
+
environmentMap: Object.freeze({
|
|
5717
|
+
...config.environmentMap,
|
|
5718
|
+
width: environmentMapResource.width,
|
|
5719
|
+
height: environmentMapResource.height,
|
|
5720
|
+
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
5721
|
+
hasImportanceData: environmentSamplingResource.hasImportanceData
|
|
5722
|
+
})
|
|
5723
|
+
});
|
|
5724
|
+
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
5725
|
+
const baseColorAtlasResource = createAtlasTextureResource(
|
|
5726
|
+
device,
|
|
5727
|
+
constants,
|
|
5728
|
+
config.gpuMaterialSource.baseColorAtlas,
|
|
5729
|
+
"plasius.wavefront.materialAtlas.baseColor"
|
|
5730
|
+
);
|
|
5731
|
+
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
5732
|
+
device,
|
|
5733
|
+
constants,
|
|
5734
|
+
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
5735
|
+
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
5736
|
+
);
|
|
5737
|
+
const normalAtlasResource = createAtlasTextureResource(
|
|
5738
|
+
device,
|
|
5739
|
+
constants,
|
|
5740
|
+
config.gpuMaterialSource.normalAtlas,
|
|
5741
|
+
"plasius.wavefront.materialAtlas.normal"
|
|
5742
|
+
);
|
|
5743
|
+
const occlusionAtlasResource = createAtlasTextureResource(
|
|
5744
|
+
device,
|
|
5745
|
+
constants,
|
|
5746
|
+
config.gpuMaterialSource.occlusionAtlas,
|
|
5747
|
+
"plasius.wavefront.materialAtlas.occlusion"
|
|
5748
|
+
);
|
|
5749
|
+
const emissiveAtlasResource = createAtlasTextureResource(
|
|
5750
|
+
device,
|
|
5751
|
+
constants,
|
|
5752
|
+
config.gpuMaterialSource.emissiveAtlas,
|
|
5753
|
+
"plasius.wavefront.materialAtlas.emissive"
|
|
5754
|
+
);
|
|
5755
|
+
const materialAtlasSampler = device.createSampler({
|
|
5756
|
+
label: "plasius.wavefront.materialAtlasSampler",
|
|
5757
|
+
addressModeU: "clamp-to-edge",
|
|
5758
|
+
addressModeV: "clamp-to-edge",
|
|
5759
|
+
magFilter: "linear",
|
|
5760
|
+
minFilter: "linear"
|
|
5761
|
+
});
|
|
3889
5762
|
const traceBindGroupLayout = device.createBindGroupLayout({
|
|
3890
5763
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
3891
5764
|
entries: [
|
|
@@ -3915,7 +5788,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3915
5788
|
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
3916
5789
|
{ binding: 20, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
3917
5790
|
{ binding: 21, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
3918
|
-
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } }
|
|
5791
|
+
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } },
|
|
5792
|
+
{ binding: 23, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5793
|
+
{ binding: 24, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5794
|
+
{ binding: 25, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5795
|
+
{ binding: 26, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5796
|
+
{ binding: 27, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5797
|
+
{ binding: 28, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
5798
|
+
{ binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
5799
|
+
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
5800
|
+
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
|
|
3919
5801
|
]
|
|
3920
5802
|
});
|
|
3921
5803
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -3994,6 +5876,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3994
5876
|
label: "plasius.wavefront.computeShader",
|
|
3995
5877
|
code: WAVEFRONT_COMPUTE_WGSL
|
|
3996
5878
|
});
|
|
5879
|
+
await assertShaderModuleCompiles(computeShader, "plasius.wavefront.computeShader");
|
|
3997
5880
|
const pipelines = {
|
|
3998
5881
|
prepareMeshTrianglesAndLeaves: await createComputePipeline(
|
|
3999
5882
|
device,
|
|
@@ -4092,7 +5975,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4092
5975
|
{ binding: 19, resource: { buffer: environmentPortalBuffer } },
|
|
4093
5976
|
{ binding: 20, resource: environmentMapResource.view },
|
|
4094
5977
|
{ binding: 21, resource: environmentMapResource.sampler },
|
|
4095
|
-
{ binding: 22, resource: { buffer: pathVertexBuffer } }
|
|
5978
|
+
{ binding: 22, resource: { buffer: pathVertexBuffer } },
|
|
5979
|
+
{ binding: 23, resource: baseColorAtlasResource.view },
|
|
5980
|
+
{ binding: 24, resource: metallicRoughnessAtlasResource.view },
|
|
5981
|
+
{ binding: 25, resource: normalAtlasResource.view },
|
|
5982
|
+
{ binding: 26, resource: occlusionAtlasResource.view },
|
|
5983
|
+
{ binding: 27, resource: emissiveAtlasResource.view },
|
|
5984
|
+
{ binding: 28, resource: materialAtlasSampler },
|
|
5985
|
+
{ binding: 29, resource: brdfLutResource.view },
|
|
5986
|
+
{ binding: 30, resource: brdfLutResource.sampler },
|
|
5987
|
+
{ binding: 31, resource: environmentSamplingResource.view }
|
|
4096
5988
|
]
|
|
4097
5989
|
});
|
|
4098
5990
|
}
|
|
@@ -4145,6 +6037,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4145
6037
|
outputView,
|
|
4146
6038
|
"plasius.wavefront.bind.denoise.scratchToOutput"
|
|
4147
6039
|
);
|
|
6040
|
+
const denoiseDirectResolveBindGroup = createDenoiseResolveBindGroup(
|
|
6041
|
+
radianceView,
|
|
6042
|
+
outputView,
|
|
6043
|
+
"plasius.wavefront.bind.denoise.radianceToOutput"
|
|
6044
|
+
);
|
|
4148
6045
|
const presentBindGroupLayout = device.createBindGroupLayout({
|
|
4149
6046
|
label: "plasius.wavefront.presentBindGroupLayout",
|
|
4150
6047
|
entries: [
|
|
@@ -4182,23 +6079,128 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4182
6079
|
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
4183
6080
|
let accelerationBuildCount = 0;
|
|
4184
6081
|
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA;
|
|
6082
|
+
let lastCompletedFrameTimeMs = null;
|
|
6083
|
+
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
4185
6084
|
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
4186
6085
|
gpuAdapterParallelism,
|
|
4187
6086
|
createGpuParallelismCounters()
|
|
4188
6087
|
);
|
|
6088
|
+
function resolveRenderedSamplesPerPixel(renderOptions = {}, awaitGPUCompletion = true) {
|
|
6089
|
+
const targetSamplesPerPixel = clamp(
|
|
6090
|
+
readPositiveInteger(
|
|
6091
|
+
"samplesPerPixel",
|
|
6092
|
+
renderOptions.samplesPerPixel,
|
|
6093
|
+
config.samplesPerPixel
|
|
6094
|
+
),
|
|
6095
|
+
1,
|
|
6096
|
+
config.samplesPerPixel
|
|
6097
|
+
);
|
|
6098
|
+
const frameTimeBudgetMs = Number.isFinite(renderOptions.frameTimeBudgetMs) ? Math.max(0, Number(renderOptions.frameTimeBudgetMs)) : null;
|
|
6099
|
+
const minimumSamplesPerPixel = clamp(
|
|
6100
|
+
readPositiveInteger(
|
|
6101
|
+
"minimumSamplesPerPixel",
|
|
6102
|
+
renderOptions.minimumSamplesPerPixel,
|
|
6103
|
+
frameTimeBudgetMs !== null && targetSamplesPerPixel > 1 ? 1 : targetSamplesPerPixel
|
|
6104
|
+
),
|
|
6105
|
+
1,
|
|
6106
|
+
targetSamplesPerPixel
|
|
6107
|
+
);
|
|
6108
|
+
if (frameTimeBudgetMs === null || !awaitGPUCompletion || targetSamplesPerPixel <= minimumSamplesPerPixel) {
|
|
6109
|
+
return Object.freeze({
|
|
6110
|
+
renderedSamplesPerPixel: targetSamplesPerPixel,
|
|
6111
|
+
targetSamplesPerPixel,
|
|
6112
|
+
minimumSamplesPerPixel,
|
|
6113
|
+
frameTimeBudgetMs,
|
|
6114
|
+
budgetConstrained: false
|
|
6115
|
+
});
|
|
6116
|
+
}
|
|
6117
|
+
const estimatedSampleTimeMs = Number.isFinite(lastCompletedFrameTimeMs) && lastCompletedFrameTimeMs > 0 ? lastCompletedFrameTimeMs / Math.max(1, lastCompletedSamplesPerPixel) : null;
|
|
6118
|
+
if (!Number.isFinite(estimatedSampleTimeMs) || estimatedSampleTimeMs <= 0) {
|
|
6119
|
+
return Object.freeze({
|
|
6120
|
+
renderedSamplesPerPixel: minimumSamplesPerPixel,
|
|
6121
|
+
targetSamplesPerPixel,
|
|
6122
|
+
minimumSamplesPerPixel,
|
|
6123
|
+
frameTimeBudgetMs,
|
|
6124
|
+
budgetConstrained: minimumSamplesPerPixel < targetSamplesPerPixel
|
|
6125
|
+
});
|
|
6126
|
+
}
|
|
6127
|
+
const budgetLimitedSamples = clamp(
|
|
6128
|
+
Math.floor(frameTimeBudgetMs / estimatedSampleTimeMs),
|
|
6129
|
+
minimumSamplesPerPixel,
|
|
6130
|
+
targetSamplesPerPixel
|
|
6131
|
+
);
|
|
6132
|
+
return Object.freeze({
|
|
6133
|
+
renderedSamplesPerPixel: budgetLimitedSamples,
|
|
6134
|
+
targetSamplesPerPixel,
|
|
6135
|
+
minimumSamplesPerPixel,
|
|
6136
|
+
frameTimeBudgetMs,
|
|
6137
|
+
budgetConstrained: budgetLimitedSamples < targetSamplesPerPixel
|
|
6138
|
+
});
|
|
6139
|
+
}
|
|
6140
|
+
function createFrameStats({
|
|
6141
|
+
frameIndex,
|
|
6142
|
+
accelerationBuildSubmitted,
|
|
6143
|
+
frameSubmissionCount,
|
|
6144
|
+
parallelismCounters,
|
|
6145
|
+
renderedSamplesPerPixel,
|
|
6146
|
+
targetSamplesPerPixel,
|
|
6147
|
+
frameTimeBudgetMs,
|
|
6148
|
+
budgetConstrained
|
|
6149
|
+
}) {
|
|
6150
|
+
lastGpuParallelism = createGpuParallelismDiagnostics(gpuAdapterParallelism, parallelismCounters);
|
|
6151
|
+
const commandSubmissions = frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0);
|
|
6152
|
+
return Object.freeze({
|
|
6153
|
+
frame,
|
|
6154
|
+
frameIndex,
|
|
6155
|
+
width: config.width,
|
|
6156
|
+
height: config.height,
|
|
6157
|
+
maxDepth: config.maxDepth,
|
|
6158
|
+
tiles: tiles.length,
|
|
6159
|
+
tileSize: config.tileSize,
|
|
6160
|
+
samplesPerPixel: targetSamplesPerPixel,
|
|
6161
|
+
renderedSamplesPerPixel,
|
|
6162
|
+
frameTimeBudgetMs,
|
|
6163
|
+
budgetConstrained,
|
|
6164
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6165
|
+
screenRays: config.width * config.height,
|
|
6166
|
+
primaryRays: config.width * config.height * renderedSamplesPerPixel,
|
|
6167
|
+
sceneObjectCount: config.sceneObjectCount,
|
|
6168
|
+
triangleCount: config.triangleCount,
|
|
6169
|
+
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6170
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
6171
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
6172
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6173
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
6174
|
+
bvhNodeCount: config.bvhNodeCount,
|
|
6175
|
+
displayQuality: config.displayQuality,
|
|
6176
|
+
accelerationBuildMode: config.accelerationBuildMode,
|
|
6177
|
+
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
6178
|
+
accelerationBuildSubmitted,
|
|
6179
|
+
accelerationBuilt,
|
|
6180
|
+
accelerationBuildCount,
|
|
6181
|
+
commandSubmissions,
|
|
6182
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
6183
|
+
gpuParallelism: lastGpuParallelism,
|
|
6184
|
+
memory: config.memory
|
|
6185
|
+
});
|
|
6186
|
+
}
|
|
6187
|
+
function writeFrameConfigSlot(slot, tile, frameIndex, buildRange = {}) {
|
|
6188
|
+
if (slot >= frameConfigSlotCount) {
|
|
6189
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
6190
|
+
}
|
|
6191
|
+
const offset = slot * configBufferStride;
|
|
6192
|
+
device.queue.writeBuffer(
|
|
6193
|
+
configBuffer,
|
|
6194
|
+
offset,
|
|
6195
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
6196
|
+
);
|
|
6197
|
+
return offset;
|
|
6198
|
+
}
|
|
4189
6199
|
function createFrameConfigWriter(frameIndex) {
|
|
4190
6200
|
let slot = 0;
|
|
4191
6201
|
return (tile, buildRange = {}) => {
|
|
4192
|
-
|
|
4193
|
-
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
4194
|
-
}
|
|
4195
|
-
const offset = slot * configBufferStride;
|
|
6202
|
+
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
4196
6203
|
slot += 1;
|
|
4197
|
-
device.queue.writeBuffer(
|
|
4198
|
-
configBuffer,
|
|
4199
|
-
offset,
|
|
4200
|
-
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
4201
|
-
);
|
|
4202
6204
|
return offset;
|
|
4203
6205
|
};
|
|
4204
6206
|
}
|
|
@@ -4243,7 +6245,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4243
6245
|
passEncoder.setPipeline(pipelines.prepareMeshTrianglesAndLeaves);
|
|
4244
6246
|
const prepareWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4245
6247
|
passEncoder.dispatchWorkgroups(prepareWorkgroups);
|
|
4246
|
-
recordDirectDispatch(parallelism, [prepareWorkgroups]);
|
|
6248
|
+
recordDirectDispatch(parallelism, [prepareWorkgroups], WORKGROUP_SIZE);
|
|
4247
6249
|
passEncoder.setPipeline(pipelines.sortBvhLeafRefs);
|
|
4248
6250
|
for (let stageIndex = 0; stageIndex < config.bvhSortStages.length; stageIndex += 1) {
|
|
4249
6251
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [
|
|
@@ -4251,13 +6253,13 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4251
6253
|
]);
|
|
4252
6254
|
const sortWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4253
6255
|
passEncoder.dispatchWorkgroups(sortWorkgroups);
|
|
4254
|
-
recordDirectDispatch(parallelism, [sortWorkgroups]);
|
|
6256
|
+
recordDirectDispatch(parallelism, [sortWorkgroups], WORKGROUP_SIZE);
|
|
4255
6257
|
}
|
|
4256
6258
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [0]);
|
|
4257
6259
|
passEncoder.setPipeline(pipelines.writeSortedBvhLeaves);
|
|
4258
6260
|
const leafWriteWorkgroups = Math.ceil(config.triangleCount / WORKGROUP_SIZE);
|
|
4259
6261
|
passEncoder.dispatchWorkgroups(leafWriteWorkgroups);
|
|
4260
|
-
recordDirectDispatch(parallelism, [leafWriteWorkgroups]);
|
|
6262
|
+
recordDirectDispatch(parallelism, [leafWriteWorkgroups], WORKGROUP_SIZE);
|
|
4261
6263
|
passEncoder.setPipeline(pipelines.buildBvhInternalLevel);
|
|
4262
6264
|
for (let levelIndex = 0; levelIndex < config.bvhBuildLevels.length; levelIndex += 1) {
|
|
4263
6265
|
const buildLevel = config.bvhBuildLevels[levelIndex];
|
|
@@ -4266,7 +6268,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4266
6268
|
]);
|
|
4267
6269
|
const levelWorkgroups = Math.ceil(buildLevel.count / WORKGROUP_SIZE);
|
|
4268
6270
|
passEncoder.dispatchWorkgroups(levelWorkgroups);
|
|
4269
|
-
recordDirectDispatch(parallelism, [levelWorkgroups]);
|
|
6271
|
+
recordDirectDispatch(parallelism, [levelWorkgroups], WORKGROUP_SIZE);
|
|
4270
6272
|
}
|
|
4271
6273
|
passEncoder.end();
|
|
4272
6274
|
device.queue.submit([encoder.finish()]);
|
|
@@ -4282,7 +6284,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4282
6284
|
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4283
6285
|
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
4284
6286
|
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
4285
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6287
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4286
6288
|
generatePass.end();
|
|
4287
6289
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
4288
6290
|
encoder.copyBufferToBuffer(
|
|
@@ -4298,10 +6300,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4298
6300
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
4299
6301
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
4300
6302
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4301
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6303
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4302
6304
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
4303
6305
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4304
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6306
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4305
6307
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
4306
6308
|
passEncoder.dispatchWorkgroups(1);
|
|
4307
6309
|
recordDirectDispatch(parallelism, [1], 1);
|
|
@@ -4316,30 +6318,45 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4316
6318
|
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4317
6319
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
4318
6320
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
4319
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6321
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4320
6322
|
passEncoder.end();
|
|
4321
6323
|
}
|
|
4322
|
-
function encodeDenoise(encoder, configOffset, parallelism) {
|
|
6324
|
+
function encodeDenoise(encoder, configOffset, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4323
6325
|
if (!config.denoise) {
|
|
4324
6326
|
return;
|
|
4325
6327
|
}
|
|
4326
6328
|
const denoiseWorkgroupsX = Math.ceil(config.width / 8);
|
|
4327
6329
|
const denoiseWorkgroupsY = Math.ceil(config.height / 8);
|
|
4328
|
-
const
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
6330
|
+
const useTwoPassDenoise = renderedSamplesPerPixel < 4;
|
|
6331
|
+
if (useTwoPassDenoise) {
|
|
6332
|
+
const radiancePass = encoder.beginComputePass({
|
|
6333
|
+
label: "plasius.wavefront.denoiseRadiancePass"
|
|
6334
|
+
});
|
|
6335
|
+
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
6336
|
+
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
6337
|
+
radiancePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
6338
|
+
recordDirectDispatch(
|
|
6339
|
+
parallelism,
|
|
6340
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6341
|
+
WORKGROUP_SIZE
|
|
6342
|
+
);
|
|
6343
|
+
radiancePass.end();
|
|
6344
|
+
}
|
|
4336
6345
|
const resolvePass = encoder.beginComputePass({
|
|
4337
6346
|
label: "plasius.wavefront.denoiseResolvePass"
|
|
4338
6347
|
});
|
|
4339
|
-
resolvePass.setBindGroup(
|
|
6348
|
+
resolvePass.setBindGroup(
|
|
6349
|
+
0,
|
|
6350
|
+
useTwoPassDenoise ? denoiseResolveBindGroup : denoiseDirectResolveBindGroup,
|
|
6351
|
+
[configOffset]
|
|
6352
|
+
);
|
|
4340
6353
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
4341
6354
|
resolvePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
4342
|
-
recordDirectDispatch(
|
|
6355
|
+
recordDirectDispatch(
|
|
6356
|
+
parallelism,
|
|
6357
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6358
|
+
WORKGROUP_SIZE
|
|
6359
|
+
);
|
|
4343
6360
|
resolvePass.end();
|
|
4344
6361
|
}
|
|
4345
6362
|
function encodePresent(encoder) {
|
|
@@ -4360,98 +6377,213 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4360
6377
|
passEncoder.draw(3);
|
|
4361
6378
|
passEncoder.end();
|
|
4362
6379
|
}
|
|
4363
|
-
function dispatchFrame(frameIndex, parallelism) {
|
|
6380
|
+
function dispatchFrame(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4364
6381
|
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
6382
|
+
const batch = createGpuSubmissionBatcher({
|
|
6383
|
+
device,
|
|
6384
|
+
frameIndex,
|
|
6385
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission
|
|
4369
6386
|
});
|
|
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
6387
|
for (const tile of tiles) {
|
|
4389
|
-
for (let sampleIndex = 0; sampleIndex <
|
|
6388
|
+
for (let sampleIndex = 0; sampleIndex < renderedSamplesPerPixel; sampleIndex += 1) {
|
|
4390
6389
|
const configOffset = writeFrameConfig(tile, {
|
|
4391
6390
|
sampleIndex,
|
|
4392
|
-
sampleWeight: 1 /
|
|
6391
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
4393
6392
|
});
|
|
4394
|
-
encodeTileSample(
|
|
6393
|
+
encodeTileSample(
|
|
6394
|
+
batch.reserve(config.maxDepth + 1),
|
|
6395
|
+
tile,
|
|
6396
|
+
configOffset,
|
|
6397
|
+
parallelism
|
|
6398
|
+
);
|
|
4395
6399
|
if (config.deferredPathResolve) {
|
|
4396
|
-
encodeTileOutput(
|
|
6400
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
4397
6401
|
}
|
|
4398
6402
|
}
|
|
4399
6403
|
if (!config.deferredPathResolve) {
|
|
4400
6404
|
const outputConfigOffset = writeFrameConfig(tile, {
|
|
4401
6405
|
sampleIndex: 0,
|
|
4402
|
-
sampleWeight: 1 /
|
|
6406
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
4403
6407
|
});
|
|
4404
|
-
encodeTileOutput(
|
|
6408
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
4405
6409
|
}
|
|
4406
6410
|
}
|
|
4407
6411
|
if (config.denoise) {
|
|
4408
6412
|
const denoiseConfigOffset = writeFrameConfig(
|
|
4409
6413
|
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
4410
|
-
{ sampleIndex: 0, sampleWeight: 1 /
|
|
6414
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6415
|
+
);
|
|
6416
|
+
const denoisePassCount = renderedSamplesPerPixel < 4 ? 2 : 1;
|
|
6417
|
+
encodeDenoise(
|
|
6418
|
+
batch.reserve(denoisePassCount),
|
|
6419
|
+
denoiseConfigOffset,
|
|
6420
|
+
parallelism,
|
|
6421
|
+
renderedSamplesPerPixel
|
|
4411
6422
|
);
|
|
4412
|
-
encodeDenoise(reserveEncoder(), denoiseConfigOffset, parallelism);
|
|
4413
6423
|
}
|
|
4414
|
-
encodePresent(
|
|
4415
|
-
|
|
4416
|
-
return submissionCount;
|
|
6424
|
+
encodePresent(batch.reserve(1));
|
|
6425
|
+
return batch.flush();
|
|
4417
6426
|
}
|
|
4418
|
-
function renderOnce() {
|
|
6427
|
+
function renderOnce(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
6428
|
+
const frameStartTimeMs = nowMs();
|
|
4419
6429
|
frame += 1;
|
|
4420
6430
|
const frameIndex = frame + config.frameIndex;
|
|
6431
|
+
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
4421
6432
|
const parallelismCounters = createGpuParallelismCounters();
|
|
4422
6433
|
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
4423
|
-
const frameSubmissionCount = dispatchFrame(
|
|
4424
|
-
|
|
6434
|
+
const frameSubmissionCount = dispatchFrame(
|
|
6435
|
+
frameIndex,
|
|
6436
|
+
parallelismCounters,
|
|
6437
|
+
samplingPlan.renderedSamplesPerPixel
|
|
6438
|
+
);
|
|
6439
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
4425
6440
|
return Object.freeze({
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
6441
|
+
...createFrameStats({
|
|
6442
|
+
frameIndex,
|
|
6443
|
+
accelerationBuildSubmitted,
|
|
6444
|
+
frameSubmissionCount,
|
|
6445
|
+
parallelismCounters,
|
|
6446
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
6447
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
6448
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
6449
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
6450
|
+
}),
|
|
6451
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
6452
|
+
lastGpuParallelism,
|
|
6453
|
+
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
6454
|
+
frameTimeMs,
|
|
6455
|
+
false
|
|
6456
|
+
)
|
|
6457
|
+
});
|
|
6458
|
+
}
|
|
6459
|
+
async function waitForSubmittedGpuWork(options2 = {}) {
|
|
6460
|
+
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
6461
|
+
return true;
|
|
6462
|
+
}
|
|
6463
|
+
const timeoutMs = Math.max(
|
|
6464
|
+
1,
|
|
6465
|
+
Number.isFinite(options2.timeoutMs) ? Number(options2.timeoutMs) : GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
6466
|
+
);
|
|
6467
|
+
const allowTimeout = options2.allowTimeout !== false;
|
|
6468
|
+
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
6469
|
+
() => ({ status: "done" }),
|
|
6470
|
+
(error) => {
|
|
6471
|
+
throw error;
|
|
6472
|
+
}
|
|
6473
|
+
);
|
|
6474
|
+
const lossPromise = typeof device.lost?.then === "function" ? device.lost.then((info) => {
|
|
6475
|
+
throw new Error(
|
|
6476
|
+
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
6477
|
+
);
|
|
6478
|
+
}) : null;
|
|
6479
|
+
let timeoutHandle = null;
|
|
6480
|
+
let resolveTimeoutPromise = null;
|
|
6481
|
+
let timeoutSettled = false;
|
|
6482
|
+
const settleTimeoutPromise = (value) => {
|
|
6483
|
+
if (timeoutSettled) {
|
|
6484
|
+
return;
|
|
6485
|
+
}
|
|
6486
|
+
timeoutSettled = true;
|
|
6487
|
+
resolveTimeoutPromise?.(value);
|
|
6488
|
+
};
|
|
6489
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
6490
|
+
resolveTimeoutPromise = resolve;
|
|
6491
|
+
timeoutHandle = setTimeout(() => settleTimeoutPromise({ status: "timeout" }), timeoutMs);
|
|
6492
|
+
});
|
|
6493
|
+
let result;
|
|
6494
|
+
try {
|
|
6495
|
+
result = await Promise.race(
|
|
6496
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
6497
|
+
);
|
|
6498
|
+
} finally {
|
|
6499
|
+
if (timeoutHandle !== null) {
|
|
6500
|
+
clearTimeout(timeoutHandle);
|
|
6501
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
if (result?.status === "timeout") {
|
|
6505
|
+
if (!allowTimeout) {
|
|
6506
|
+
throw new Error(`Timed out after ${timeoutMs} ms waiting for submitted GPU work.`);
|
|
6507
|
+
}
|
|
6508
|
+
console.warn(
|
|
6509
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
|
|
6510
|
+
);
|
|
6511
|
+
return false;
|
|
6512
|
+
}
|
|
6513
|
+
return true;
|
|
6514
|
+
}
|
|
6515
|
+
function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
6516
|
+
const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
|
|
6517
|
+
const denoisePassCount = config.denoise ? renderedSamplesPerPixel < 4 ? 2 : 1 : 0;
|
|
6518
|
+
const tailPassCount = denoisePassCount + 1;
|
|
6519
|
+
const sampleBatchSize = Math.max(
|
|
6520
|
+
1,
|
|
6521
|
+
Math.floor(
|
|
6522
|
+
Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) / Math.max(samplePassesPerSample, 1)
|
|
6523
|
+
)
|
|
6524
|
+
);
|
|
6525
|
+
let submissionCount = 0;
|
|
6526
|
+
for (const tile of tiles) {
|
|
6527
|
+
for (let sampleStart = 0; sampleStart < renderedSamplesPerPixel; sampleStart += sampleBatchSize) {
|
|
6528
|
+
const sampleEnd = Math.min(renderedSamplesPerPixel, sampleStart + sampleBatchSize);
|
|
6529
|
+
const batch = createGpuSubmissionBatcher({
|
|
6530
|
+
device,
|
|
6531
|
+
frameIndex,
|
|
6532
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6533
|
+
startingSubmissionCount: submissionCount
|
|
6534
|
+
});
|
|
6535
|
+
let slot = 0;
|
|
6536
|
+
for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
|
|
6537
|
+
const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6538
|
+
sampleIndex,
|
|
6539
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
6540
|
+
});
|
|
6541
|
+
slot += 1;
|
|
6542
|
+
encodeTileSample(
|
|
6543
|
+
batch.reserve(config.maxDepth + 1),
|
|
6544
|
+
tile,
|
|
6545
|
+
configOffset,
|
|
6546
|
+
parallelism
|
|
6547
|
+
);
|
|
6548
|
+
if (config.deferredPathResolve) {
|
|
6549
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
6550
|
+
}
|
|
6551
|
+
}
|
|
6552
|
+
if (!config.deferredPathResolve && sampleEnd >= renderedSamplesPerPixel) {
|
|
6553
|
+
const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6554
|
+
sampleIndex: 0,
|
|
6555
|
+
sampleWeight: 1 / renderedSamplesPerPixel
|
|
6556
|
+
});
|
|
6557
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
6558
|
+
}
|
|
6559
|
+
batch.flush();
|
|
6560
|
+
submissionCount += batch.getSubmissionCount();
|
|
6561
|
+
}
|
|
6562
|
+
}
|
|
6563
|
+
const tail = createGpuSubmissionBatcher({
|
|
6564
|
+
device,
|
|
6565
|
+
frameIndex,
|
|
4433
6566
|
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
|
|
6567
|
+
startingSubmissionCount: submissionCount
|
|
4454
6568
|
});
|
|
6569
|
+
if (config.denoise) {
|
|
6570
|
+
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
6571
|
+
0,
|
|
6572
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
6573
|
+
frameIndex,
|
|
6574
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6575
|
+
);
|
|
6576
|
+
encodeDenoise(
|
|
6577
|
+
tail.reserve(denoisePassCount),
|
|
6578
|
+
denoiseConfigOffset,
|
|
6579
|
+
parallelism,
|
|
6580
|
+
renderedSamplesPerPixel
|
|
6581
|
+
);
|
|
6582
|
+
}
|
|
6583
|
+
encodePresent(tail.reserve(1));
|
|
6584
|
+
tail.flush();
|
|
6585
|
+
submissionCount += tail.getSubmissionCount();
|
|
6586
|
+
return submissionCount;
|
|
4455
6587
|
}
|
|
4456
6588
|
async function readOutputProbe(optionsForProbe = {}) {
|
|
4457
6589
|
const mapMode = constants.map;
|
|
@@ -4465,6 +6597,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4465
6597
|
size: 256,
|
|
4466
6598
|
usage: constants.buffer.COPY_DST | constants.buffer.MAP_READ
|
|
4467
6599
|
});
|
|
6600
|
+
await waitForSubmittedGpuWork({
|
|
6601
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
6602
|
+
allowTimeout: false
|
|
6603
|
+
});
|
|
4468
6604
|
const encoder = device.createCommandEncoder({
|
|
4469
6605
|
label: "plasius.wavefront.outputProbe.copy"
|
|
4470
6606
|
});
|
|
@@ -4474,6 +6610,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4474
6610
|
{ width: 1, height: 1, depthOrArrayLayers: 1 }
|
|
4475
6611
|
);
|
|
4476
6612
|
device.queue.submit([encoder.finish()]);
|
|
6613
|
+
await waitForSubmittedGpuWork({
|
|
6614
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
6615
|
+
allowTimeout: false
|
|
6616
|
+
});
|
|
4477
6617
|
await readback.mapAsync(mapMode.READ);
|
|
4478
6618
|
const bytes = new Uint8Array(readback.getMappedRange()).slice(0, 4);
|
|
4479
6619
|
readback.unmap();
|
|
@@ -4486,7 +6626,57 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4486
6626
|
});
|
|
4487
6627
|
}
|
|
4488
6628
|
async function renderFrame(renderOptions = {}) {
|
|
4489
|
-
const
|
|
6629
|
+
const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
|
|
6630
|
+
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
6631
|
+
const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
6632
|
+
const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
|
|
6633
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6634
|
+
tiles.length,
|
|
6635
|
+
renderOptions.submittedWorkTimeoutMs
|
|
6636
|
+
);
|
|
6637
|
+
const frameStartTimeMs = nowMs();
|
|
6638
|
+
const submissionWaitOptions = awaitGPUCompletion ? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false } : { timeoutMs: submittedWorkTimeoutMs };
|
|
6639
|
+
let frameStats;
|
|
6640
|
+
if (useThrottledHighSamplePath) {
|
|
6641
|
+
frame += 1;
|
|
6642
|
+
const frameIndex = frame + config.frameIndex;
|
|
6643
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
6644
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
6645
|
+
const frameSubmissionCount = dispatchFrameAwaitingGpu(
|
|
6646
|
+
frameIndex,
|
|
6647
|
+
parallelismCounters,
|
|
6648
|
+
samplingPlan.renderedSamplesPerPixel
|
|
6649
|
+
);
|
|
6650
|
+
frameStats = createFrameStats({
|
|
6651
|
+
frameIndex,
|
|
6652
|
+
accelerationBuildSubmitted,
|
|
6653
|
+
frameSubmissionCount,
|
|
6654
|
+
parallelismCounters,
|
|
6655
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
6656
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
6657
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
6658
|
+
budgetConstrained: samplingPlan.budgetConstrained
|
|
6659
|
+
});
|
|
6660
|
+
} else {
|
|
6661
|
+
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
6662
|
+
}
|
|
6663
|
+
if (awaitGPUCompletion) {
|
|
6664
|
+
await waitForSubmittedGpuWork(submissionWaitOptions);
|
|
6665
|
+
}
|
|
6666
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
6667
|
+
if (awaitGPUCompletion) {
|
|
6668
|
+
lastCompletedFrameTimeMs = frameTimeMs;
|
|
6669
|
+
lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
|
|
6670
|
+
}
|
|
6671
|
+
frameStats = Object.freeze({
|
|
6672
|
+
...frameStats,
|
|
6673
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
6674
|
+
frameStats.gpuParallelism,
|
|
6675
|
+
frameStats.commandSubmissions,
|
|
6676
|
+
frameTimeMs,
|
|
6677
|
+
awaitGPUCompletion
|
|
6678
|
+
)
|
|
6679
|
+
});
|
|
4490
6680
|
const probe = renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
4491
6681
|
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
4492
6682
|
return Object.freeze({
|
|
@@ -4507,10 +6697,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4507
6697
|
queueOverflow: 0
|
|
4508
6698
|
});
|
|
4509
6699
|
}
|
|
4510
|
-
function
|
|
4511
|
-
|
|
4512
|
-
packedScene = nextPackedScene;
|
|
4513
|
-
config = createWavefrontPathTracingComputeConfig({
|
|
6700
|
+
function rebuildLiveConfig(overrides = {}) {
|
|
6701
|
+
return createWavefrontPathTracingComputeConfig({
|
|
4514
6702
|
...options,
|
|
4515
6703
|
canvas,
|
|
4516
6704
|
width: config.width,
|
|
@@ -4521,26 +6709,23 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4521
6709
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4522
6710
|
sceneObjects: packedScene.objects,
|
|
4523
6711
|
camera: activeCameraOptions,
|
|
4524
|
-
|
|
6712
|
+
environmentMap: {
|
|
6713
|
+
...config.environmentMap
|
|
6714
|
+
},
|
|
6715
|
+
frameIndex: config.frameIndex,
|
|
6716
|
+
...overrides
|
|
4525
6717
|
});
|
|
6718
|
+
}
|
|
6719
|
+
function updateSceneObjects(sceneObjects) {
|
|
6720
|
+
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
6721
|
+
packedScene = nextPackedScene;
|
|
6722
|
+
config = rebuildLiveConfig();
|
|
4526
6723
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
4527
6724
|
return config;
|
|
4528
6725
|
}
|
|
4529
6726
|
function updateCamera(cameraOptions = {}) {
|
|
4530
6727
|
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
|
-
});
|
|
6728
|
+
config = rebuildLiveConfig();
|
|
4544
6729
|
return config;
|
|
4545
6730
|
}
|
|
4546
6731
|
function getSnapshot() {
|
|
@@ -4595,6 +6780,25 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4595
6780
|
if (environmentMapResource.ownsTexture) {
|
|
4596
6781
|
environmentMapResource.texture?.destroy?.();
|
|
4597
6782
|
}
|
|
6783
|
+
if (environmentSamplingResource.ownsTexture) {
|
|
6784
|
+
environmentSamplingResource.texture?.destroy?.();
|
|
6785
|
+
}
|
|
6786
|
+
brdfLutResource.texture?.destroy?.();
|
|
6787
|
+
if (baseColorAtlasResource.ownsTexture) {
|
|
6788
|
+
baseColorAtlasResource.texture?.destroy?.();
|
|
6789
|
+
}
|
|
6790
|
+
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
6791
|
+
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
6792
|
+
}
|
|
6793
|
+
if (normalAtlasResource.ownsTexture) {
|
|
6794
|
+
normalAtlasResource.texture?.destroy?.();
|
|
6795
|
+
}
|
|
6796
|
+
if (occlusionAtlasResource.ownsTexture) {
|
|
6797
|
+
occlusionAtlasResource.texture?.destroy?.();
|
|
6798
|
+
}
|
|
6799
|
+
if (emissiveAtlasResource.ownsTexture) {
|
|
6800
|
+
emissiveAtlasResource.texture?.destroy?.();
|
|
6801
|
+
}
|
|
4598
6802
|
context.unconfigure?.();
|
|
4599
6803
|
}
|
|
4600
6804
|
return Object.freeze({
|
|
@@ -4747,6 +6951,48 @@ var rendererAccelerationStructurePolicies = Object.freeze(
|
|
|
4747
6951
|
})
|
|
4748
6952
|
)
|
|
4749
6953
|
);
|
|
6954
|
+
function clampWavefrontAdaptiveSamplesPerPixel(value) {
|
|
6955
|
+
if (!Number.isFinite(value)) {
|
|
6956
|
+
return 1;
|
|
6957
|
+
}
|
|
6958
|
+
return Math.max(1, Math.min(256, Math.round(value)));
|
|
6959
|
+
}
|
|
6960
|
+
function createWavefrontAdaptiveSamplingLevels(options = {}) {
|
|
6961
|
+
const requestedSamplesPerPixel = clampWavefrontAdaptiveSamplesPerPixel(
|
|
6962
|
+
options.samplesPerPixel ?? 1
|
|
6963
|
+
);
|
|
6964
|
+
const minimumSamplesPerPixel = Math.min(
|
|
6965
|
+
requestedSamplesPerPixel,
|
|
6966
|
+
clampWavefrontAdaptiveSamplesPerPixel(options.minimumSamplesPerPixel ?? 1)
|
|
6967
|
+
);
|
|
6968
|
+
const frameTimeBudgetMs = Number.isFinite(options.frameTimeBudgetMs) ? Math.max(0, Number(options.frameTimeBudgetMs)) : 0;
|
|
6969
|
+
const levels = /* @__PURE__ */ new Set([minimumSamplesPerPixel, requestedSamplesPerPixel]);
|
|
6970
|
+
let currentSamplesPerPixel = minimumSamplesPerPixel;
|
|
6971
|
+
while (currentSamplesPerPixel < requestedSamplesPerPixel) {
|
|
6972
|
+
levels.add(currentSamplesPerPixel);
|
|
6973
|
+
currentSamplesPerPixel *= 2;
|
|
6974
|
+
}
|
|
6975
|
+
levels.add(Math.min(currentSamplesPerPixel, requestedSamplesPerPixel));
|
|
6976
|
+
return Object.freeze({
|
|
6977
|
+
requestedSamplesPerPixel,
|
|
6978
|
+
minimumSamplesPerPixel,
|
|
6979
|
+
frameTimeBudgetMs,
|
|
6980
|
+
levels: Object.freeze(
|
|
6981
|
+
[...levels].sort((left, right) => left - right).map(
|
|
6982
|
+
(samplesPerPixel) => Object.freeze({
|
|
6983
|
+
id: `${samplesPerPixel}spp`,
|
|
6984
|
+
label: `${samplesPerPixel} spp`,
|
|
6985
|
+
estimatedCostMs: samplesPerPixel,
|
|
6986
|
+
config: Object.freeze({
|
|
6987
|
+
samplesPerPixel,
|
|
6988
|
+
frameTimeBudgetMs,
|
|
6989
|
+
minimumSamplesPerPixel
|
|
6990
|
+
})
|
|
6991
|
+
})
|
|
6992
|
+
)
|
|
6993
|
+
)
|
|
6994
|
+
});
|
|
6995
|
+
}
|
|
4750
6996
|
function createWavefrontField(name, type, description) {
|
|
4751
6997
|
return Object.freeze({
|
|
4752
6998
|
name,
|
|
@@ -5924,9 +8170,11 @@ var defaultRendererClearColor = DEFAULT_CLEAR_COLOR;
|
|
|
5924
8170
|
createGpuRenderer,
|
|
5925
8171
|
createRayTracingRenderPlan,
|
|
5926
8172
|
createRendererDebugHooks,
|
|
8173
|
+
createWavefrontAdaptiveSamplingLevels,
|
|
5927
8174
|
createWavefrontBvhBuildLevels,
|
|
5928
8175
|
createWavefrontBvhSortStages,
|
|
5929
8176
|
createWavefrontEmissiveTriangleIndexSource,
|
|
8177
|
+
createWavefrontGpuMaterialSource,
|
|
5930
8178
|
createWavefrontGpuMeshSource,
|
|
5931
8179
|
createWavefrontMeshAcceleration,
|
|
5932
8180
|
createWavefrontPathTracingComputeConfig,
|