@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/src/wavefront-compute.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createGpuParallelismCounters,
|
|
3
|
+
createGpuParallelismDiagnostics,
|
|
4
|
+
createGpuSubmissionBatcher,
|
|
5
|
+
createGpuWorkerJobDiagnostics,
|
|
6
|
+
recordDirectDispatch,
|
|
7
|
+
recordIndirectDispatch,
|
|
8
|
+
} from "./wavefront-frame-runtime.js"
|
|
9
|
+
|
|
1
10
|
const DEFAULT_WIDTH = 1280;
|
|
2
11
|
const DEFAULT_HEIGHT = 720;
|
|
3
12
|
const DEFAULT_MAX_DEPTH = 6;
|
|
4
13
|
const DEFAULT_TILE_SIZE = 128;
|
|
5
14
|
const DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
15
|
+
const MAX_SAMPLES_PER_PIXEL = 256;
|
|
16
|
+
const DEFAULT_BRDF_LUT_SIZE = 256;
|
|
6
17
|
const DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
7
18
|
const DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
8
19
|
const DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
@@ -11,22 +22,27 @@ export const rendererWavefrontComputeMode = "webgpu-compute";
|
|
|
11
22
|
export const rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
12
23
|
export const rendererWavefrontComputeStatsStride = 8;
|
|
13
24
|
const RAY_RECORD_BYTES = 80;
|
|
14
|
-
const HIT_RECORD_BYTES =
|
|
15
|
-
const SCENE_OBJECT_RECORD_BYTES =
|
|
25
|
+
const HIT_RECORD_BYTES = 256;
|
|
26
|
+
const SCENE_OBJECT_RECORD_BYTES = 144;
|
|
16
27
|
const MESH_VERTEX_RECORD_BYTES = 48;
|
|
17
|
-
const MESH_RANGE_RECORD_BYTES =
|
|
18
|
-
const TRIANGLE_RECORD_BYTES =
|
|
28
|
+
const MESH_RANGE_RECORD_BYTES = 240;
|
|
29
|
+
const TRIANGLE_RECORD_BYTES = 352;
|
|
30
|
+
const GPU_MATERIAL_RECORD_BYTES = 192;
|
|
19
31
|
const BVH_NODE_RECORD_BYTES = 48;
|
|
20
32
|
const BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
21
33
|
const EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
22
34
|
const ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
23
35
|
const ACCUMULATION_RECORD_BYTES = 16;
|
|
24
36
|
const PATH_VERTEX_RECORD_BYTES = 16;
|
|
25
|
-
const
|
|
37
|
+
const GPU_SUBMITTED_WORK_TIMEOUT_MS = 5_000;
|
|
38
|
+
const GPU_READBACK_COMPLETION_TIMEOUT_MS = 60_000;
|
|
39
|
+
const GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 60_000;
|
|
40
|
+
const CONFIG_BUFFER_BYTES = 320;
|
|
26
41
|
const COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
27
42
|
const INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
28
43
|
const COUNTER_BUFFER_BYTES = 32;
|
|
29
44
|
const TRACE_STORAGE_BUFFER_BINDINGS = 10;
|
|
45
|
+
const BRDF_LUT_UPLOAD_CACHE = new Map();
|
|
30
46
|
const HIT_TYPE_SURFACE = 0;
|
|
31
47
|
const HIT_TYPE_EMISSIVE = 1;
|
|
32
48
|
const MATERIAL_DIFFUSE = 0;
|
|
@@ -66,6 +82,7 @@ export const wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
66
82
|
meshVertexRecordBytes: MESH_VERTEX_RECORD_BYTES,
|
|
67
83
|
meshRangeRecordBytes: MESH_RANGE_RECORD_BYTES,
|
|
68
84
|
triangleRecordBytes: TRIANGLE_RECORD_BYTES,
|
|
85
|
+
materialRecordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
69
86
|
bvhNodeRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
70
87
|
bvhLeafReferenceRecordBytes: BVH_LEAF_REF_RECORD_BYTES,
|
|
71
88
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
@@ -164,6 +181,38 @@ function asColor(value, fallback = [1, 1, 1, 1]) {
|
|
|
164
181
|
];
|
|
165
182
|
}
|
|
166
183
|
|
|
184
|
+
function maxComponent(value) {
|
|
185
|
+
return Math.max(value?.[0] ?? 0, value?.[1] ?? 0, value?.[2] ?? 0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function deriveLegacySheenColor(baseColor, sheen, sheenTint) {
|
|
189
|
+
const sheenStrength = clamp(Number(sheen) || 0, 0, 1);
|
|
190
|
+
if (sheenStrength <= 0) {
|
|
191
|
+
return [0, 0, 0, 1];
|
|
192
|
+
}
|
|
193
|
+
const tint = clamp(Number(sheenTint) || 0, 0, 1);
|
|
194
|
+
const base = asColor(baseColor, [1, 1, 1, 1]);
|
|
195
|
+
return [
|
|
196
|
+
clamp((1 - tint) * sheenStrength + base[0] * tint * sheenStrength, 0, 1),
|
|
197
|
+
clamp((1 - tint) * sheenStrength + base[1] * tint * sheenStrength, 0, 1),
|
|
198
|
+
clamp((1 - tint) * sheenStrength + base[2] * tint * sheenStrength, 0, 1),
|
|
199
|
+
1,
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function resolveSheenColor(input, fallbackBaseColor) {
|
|
204
|
+
if (input?.sheenColor || input?.material?.sheenColor) {
|
|
205
|
+
return asColor(input.sheenColor ?? input.material?.sheenColor, [0, 0, 0, 1]).map((value, index) =>
|
|
206
|
+
index < 3 ? clamp(value, 0, 1) : 1
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return deriveLegacySheenColor(
|
|
210
|
+
fallbackBaseColor,
|
|
211
|
+
input?.sheen ?? input?.material?.sheen,
|
|
212
|
+
input?.sheenTint ?? input?.material?.sheenTint
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
167
216
|
function resolveEnvironmentMap(input = null) {
|
|
168
217
|
const source = input && typeof input === "object" ? input : null;
|
|
169
218
|
const hasTexture = Boolean(source?.view || source?.texture || source?.data);
|
|
@@ -173,6 +222,11 @@ function resolveEnvironmentMap(input = null) {
|
|
|
173
222
|
enabled: hasTexture && source?.enabled !== false,
|
|
174
223
|
width,
|
|
175
224
|
height,
|
|
225
|
+
mipLevelCount: readPositiveInteger(
|
|
226
|
+
"environmentMap.mipLevelCount",
|
|
227
|
+
source?.mipLevelCount,
|
|
228
|
+
1
|
|
229
|
+
),
|
|
176
230
|
format: typeof source?.format === "string" ? source.format : "rgba16float",
|
|
177
231
|
projection: typeof source?.projection === "string" ? source.projection : "equirectangular",
|
|
178
232
|
texture: source?.texture ?? null,
|
|
@@ -185,6 +239,7 @@ function resolveEnvironmentMap(input = null) {
|
|
|
185
239
|
0,
|
|
186
240
|
readFiniteNumber("environmentMap.ambientStrength", source?.ambientStrength, 0.32)
|
|
187
241
|
),
|
|
242
|
+
hasImportanceData: source?.hasImportanceData === true,
|
|
188
243
|
});
|
|
189
244
|
}
|
|
190
245
|
|
|
@@ -394,7 +449,8 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
394
449
|
input.halfExtent ?? input.halfExtents ?? input.extents ?? bounds?.halfExtent,
|
|
395
450
|
[0.5, 0.5, 0.5]
|
|
396
451
|
).map((value) => Math.max(value, 0.001));
|
|
397
|
-
const
|
|
452
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
453
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
398
454
|
const color = asColor(
|
|
399
455
|
input.color ??
|
|
400
456
|
input.baseColor ??
|
|
@@ -407,14 +463,30 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
407
463
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
408
464
|
[0, 0, 0, 1]
|
|
409
465
|
);
|
|
466
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
467
|
+
const transmission = clamp(
|
|
468
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
469
|
+
0,
|
|
470
|
+
1
|
|
471
|
+
);
|
|
472
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
473
|
+
const specularColor = asColor(
|
|
474
|
+
input.specularColor ?? input.material?.specularColor,
|
|
475
|
+
[1, 1, 1, 1]
|
|
476
|
+
).map((value, componentIndex) => (componentIndex < 3 ? clamp(value, 0, 1) : 1));
|
|
477
|
+
const resolvedMaterialKind =
|
|
478
|
+
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
479
|
+
? MATERIAL_EMISSIVE
|
|
480
|
+
: materialKindInput === undefined || materialKindInput === null
|
|
481
|
+
? transmission > 0.001 || opacity < 0.999
|
|
482
|
+
? MATERIAL_TRANSPARENT
|
|
483
|
+
: materialKind
|
|
484
|
+
: materialKind;
|
|
410
485
|
|
|
411
486
|
return Object.freeze({
|
|
412
487
|
id: readNonNegativeInteger("id", input.id, index + 1),
|
|
413
488
|
kind,
|
|
414
|
-
materialKind:
|
|
415
|
-
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
416
|
-
? MATERIAL_EMISSIVE
|
|
417
|
-
: materialKind,
|
|
489
|
+
materialKind: resolvedMaterialKind,
|
|
418
490
|
flags: readNonNegativeInteger("flags", input.flags, 0),
|
|
419
491
|
center: Object.freeze(center),
|
|
420
492
|
halfExtent: Object.freeze(halfExtent),
|
|
@@ -422,8 +494,24 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
422
494
|
emission: Object.freeze(emission),
|
|
423
495
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
424
496
|
metallic: clamp(readFiniteNumber("metallic", input.metallic ?? input.material?.metallic, 0), 0, 1),
|
|
425
|
-
opacity
|
|
497
|
+
opacity,
|
|
426
498
|
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
499
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
500
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
501
|
+
sheenColor: Object.freeze(sheenColor),
|
|
502
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
503
|
+
clearcoatRoughness: clamp(
|
|
504
|
+
readFiniteNumber(
|
|
505
|
+
"clearcoatRoughness",
|
|
506
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
507
|
+
0.08
|
|
508
|
+
),
|
|
509
|
+
0,
|
|
510
|
+
1
|
|
511
|
+
),
|
|
512
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
513
|
+
specularColor: Object.freeze(specularColor),
|
|
514
|
+
transmission,
|
|
427
515
|
});
|
|
428
516
|
}
|
|
429
517
|
|
|
@@ -507,7 +595,8 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
507
595
|
readFiniteNumber("mesh uv", value, 0)
|
|
508
596
|
)
|
|
509
597
|
: null;
|
|
510
|
-
const
|
|
598
|
+
const materialKindInput = input.materialKind ?? input.material?.kind;
|
|
599
|
+
const materialKind = readMaterialKind(materialKindInput);
|
|
511
600
|
const color = asColor(
|
|
512
601
|
input.color ??
|
|
513
602
|
input.baseColor ??
|
|
@@ -520,6 +609,25 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
520
609
|
input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
|
|
521
610
|
[0, 0, 0, 1]
|
|
522
611
|
);
|
|
612
|
+
const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
|
|
613
|
+
const transmission = clamp(
|
|
614
|
+
readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, 0),
|
|
615
|
+
0,
|
|
616
|
+
1
|
|
617
|
+
);
|
|
618
|
+
const sheenColor = resolveSheenColor(input, color);
|
|
619
|
+
const specularColor = asColor(
|
|
620
|
+
input.specularColor ?? input.material?.specularColor,
|
|
621
|
+
[1, 1, 1, 1]
|
|
622
|
+
).map((value, componentIndex) => (componentIndex < 3 ? clamp(value, 0, 1) : 1));
|
|
623
|
+
const resolvedMaterialKind =
|
|
624
|
+
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
625
|
+
? MATERIAL_EMISSIVE
|
|
626
|
+
: materialKindInput === undefined || materialKindInput === null
|
|
627
|
+
? transmission > 0.001 || opacity < 0.999
|
|
628
|
+
? MATERIAL_TRANSPARENT
|
|
629
|
+
: materialKind
|
|
630
|
+
: materialKind;
|
|
523
631
|
|
|
524
632
|
return Object.freeze({
|
|
525
633
|
id: readNonNegativeInteger("mesh id", input.id, meshIndex + 1),
|
|
@@ -527,10 +635,7 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
527
635
|
indices: Object.freeze(indices),
|
|
528
636
|
normals: normals ? Object.freeze(normals) : null,
|
|
529
637
|
uvs: uvs ? Object.freeze(uvs) : null,
|
|
530
|
-
materialKind:
|
|
531
|
-
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
532
|
-
? MATERIAL_EMISSIVE
|
|
533
|
-
: materialKind,
|
|
638
|
+
materialKind: resolvedMaterialKind,
|
|
534
639
|
flags: readNonNegativeInteger("mesh flags", input.flags, 0),
|
|
535
640
|
materialRefId: readNonNegativeInteger(
|
|
536
641
|
"mesh materialRefId",
|
|
@@ -546,11 +651,189 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
546
651
|
emission: Object.freeze(emission),
|
|
547
652
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
548
653
|
metallic: clamp(readFiniteNumber("metallic", input.metallic ?? input.material?.metallic, 0), 0, 1),
|
|
549
|
-
opacity
|
|
654
|
+
opacity,
|
|
550
655
|
ior: clamp(readFiniteNumber("ior", input.ior ?? input.material?.ior, 1.45), 1, 3),
|
|
656
|
+
sheen: clamp(readFiniteNumber("sheen", input.sheen ?? input.material?.sheen, 0), 0, 1),
|
|
657
|
+
sheenTint: clamp(readFiniteNumber("sheenTint", input.sheenTint ?? input.material?.sheenTint, 0), 0, 1),
|
|
658
|
+
sheenColor: Object.freeze(sheenColor),
|
|
659
|
+
clearcoat: clamp(readFiniteNumber("clearcoat", input.clearcoat ?? input.material?.clearcoat, 0), 0, 1),
|
|
660
|
+
clearcoatRoughness: clamp(
|
|
661
|
+
readFiniteNumber(
|
|
662
|
+
"clearcoatRoughness",
|
|
663
|
+
input.clearcoatRoughness ?? input.material?.clearcoatRoughness,
|
|
664
|
+
0.08
|
|
665
|
+
),
|
|
666
|
+
0,
|
|
667
|
+
1
|
|
668
|
+
),
|
|
669
|
+
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
670
|
+
specularColor: Object.freeze(specularColor),
|
|
671
|
+
transmission,
|
|
672
|
+
baseColorTexture: input.baseColorTexture ?? input.material?.baseColorTexture ?? null,
|
|
673
|
+
metallicRoughnessTexture:
|
|
674
|
+
input.metallicRoughnessTexture ?? input.material?.metallicRoughnessTexture ?? null,
|
|
675
|
+
normalTexture: input.normalTexture ?? input.material?.normalTexture ?? null,
|
|
676
|
+
occlusionTexture: input.occlusionTexture ?? input.material?.occlusionTexture ?? null,
|
|
677
|
+
emissiveTexture: input.emissiveTexture ?? input.material?.emissiveTexture ?? null,
|
|
551
678
|
});
|
|
552
679
|
}
|
|
553
680
|
|
|
681
|
+
function clampUnit(value) {
|
|
682
|
+
return clamp(Number(value) || 0, 0, 1);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function srgbToLinear(value) {
|
|
686
|
+
const channel = clampUnit(value);
|
|
687
|
+
if (channel <= 0.04045) {
|
|
688
|
+
return channel / 12.92;
|
|
689
|
+
}
|
|
690
|
+
return ((channel + 0.055) / 1.055) ** 2.4;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function sampleTextureRgba(texture, uv = [0, 0], colorSpace = "linear") {
|
|
694
|
+
if (
|
|
695
|
+
!texture ||
|
|
696
|
+
!Number.isFinite(texture.width) ||
|
|
697
|
+
!Number.isFinite(texture.height) ||
|
|
698
|
+
!texture.data ||
|
|
699
|
+
texture.width <= 0 ||
|
|
700
|
+
texture.height <= 0
|
|
701
|
+
) {
|
|
702
|
+
return [1, 1, 1, 1];
|
|
703
|
+
}
|
|
704
|
+
const u = ((uv[0] % 1) + 1) % 1;
|
|
705
|
+
const v = ((uv[1] % 1) + 1) % 1;
|
|
706
|
+
const x = Math.min(texture.width - 1, Math.max(0, Math.round(u * (texture.width - 1))));
|
|
707
|
+
const y = Math.min(texture.height - 1, Math.max(0, Math.round((1 - v) * (texture.height - 1))));
|
|
708
|
+
const offset = (y * texture.width + x) * 4;
|
|
709
|
+
const data = texture.data;
|
|
710
|
+
const scale = resolveTextureSampleScale(data);
|
|
711
|
+
const defaultChannel = scale === 1 ? 1 : Math.round(1 / scale);
|
|
712
|
+
const color = [
|
|
713
|
+
(data[offset] ?? defaultChannel) * scale,
|
|
714
|
+
(data[offset + 1] ?? defaultChannel) * scale,
|
|
715
|
+
(data[offset + 2] ?? defaultChannel) * scale,
|
|
716
|
+
(data[offset + 3] ?? defaultChannel) * scale,
|
|
717
|
+
];
|
|
718
|
+
if (colorSpace === "srgb") {
|
|
719
|
+
return [srgbToLinear(color[0]), srgbToLinear(color[1]), srgbToLinear(color[2]), color[3]];
|
|
720
|
+
}
|
|
721
|
+
return color;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function resolveTextureSampleScale(data) {
|
|
725
|
+
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
|
|
726
|
+
return 1 / 255;
|
|
727
|
+
}
|
|
728
|
+
if (data instanceof Uint16Array) {
|
|
729
|
+
return 1 / 65535;
|
|
730
|
+
}
|
|
731
|
+
if (Array.isArray(data) && data.some((value) => Number(value) > 1)) {
|
|
732
|
+
return 1 / 255;
|
|
733
|
+
}
|
|
734
|
+
return 1;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function normalizeVectorOrFallback(vector, fallback) {
|
|
738
|
+
return normalize(Array.isArray(vector) ? vector : fallback, fallback);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, fallbackNormal) {
|
|
742
|
+
const edge1 = subtract(v1, v0);
|
|
743
|
+
const edge2 = subtract(v2, v0);
|
|
744
|
+
const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
|
|
745
|
+
const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
|
|
746
|
+
const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
|
|
747
|
+
if (Math.abs(determinant) < 1e-6) {
|
|
748
|
+
const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
749
|
+
const tangent = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
|
|
750
|
+
const bitangent = normalize(cross(fallbackNormal, tangent), [0, 0, 1]);
|
|
751
|
+
return { tangent, bitangent };
|
|
752
|
+
}
|
|
753
|
+
const inverse = 1 / determinant;
|
|
754
|
+
const tangent = normalize(
|
|
755
|
+
[
|
|
756
|
+
inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
|
|
757
|
+
inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
|
|
758
|
+
inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1]),
|
|
759
|
+
],
|
|
760
|
+
[1, 0, 0]
|
|
761
|
+
);
|
|
762
|
+
const bitangent = normalize(
|
|
763
|
+
[
|
|
764
|
+
inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
|
|
765
|
+
inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
|
|
766
|
+
inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0]),
|
|
767
|
+
],
|
|
768
|
+
[0, 0, 1]
|
|
769
|
+
);
|
|
770
|
+
return { tangent, bitangent };
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
|
|
774
|
+
if (!normalTexture) {
|
|
775
|
+
return normalizeVectorOrFallback(normal, [0, 1, 0]);
|
|
776
|
+
}
|
|
777
|
+
const sample = sampleTextureRgba(normalTexture, uv, "linear");
|
|
778
|
+
const strength = clampUnit(normalTexture.scale ?? 1);
|
|
779
|
+
const tangentNormal = normalize(
|
|
780
|
+
[
|
|
781
|
+
(sample[0] * 2 - 1) * strength,
|
|
782
|
+
(sample[1] * 2 - 1) * strength,
|
|
783
|
+
1 + (sample[2] * 2 - 1 - 1) * strength,
|
|
784
|
+
],
|
|
785
|
+
[0, 0, 1]
|
|
786
|
+
);
|
|
787
|
+
return normalize(
|
|
788
|
+
[
|
|
789
|
+
tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
|
|
790
|
+
tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
|
|
791
|
+
tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2],
|
|
792
|
+
],
|
|
793
|
+
normal
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function sampleBaseColor(mesh, uv) {
|
|
798
|
+
const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
|
|
799
|
+
return [
|
|
800
|
+
clampUnit(mesh.color[0] * sample[0]),
|
|
801
|
+
clampUnit(mesh.color[1] * sample[1]),
|
|
802
|
+
clampUnit(mesh.color[2] * sample[2]),
|
|
803
|
+
clampUnit((mesh.color[3] ?? 1) * sample[3]),
|
|
804
|
+
];
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function sampleSurfaceMaterial(mesh, uv) {
|
|
808
|
+
const textureSample = mesh.metallicRoughnessTexture
|
|
809
|
+
? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear")
|
|
810
|
+
: [1, 1, 1, 1];
|
|
811
|
+
return {
|
|
812
|
+
roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
|
|
813
|
+
metallic: clamp(mesh.metallic * textureSample[2], 0, 1),
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function averageColors(colors) {
|
|
818
|
+
const count = Math.max(colors.length, 1);
|
|
819
|
+
return colors.reduce(
|
|
820
|
+
(accumulator, color) => [
|
|
821
|
+
accumulator[0] + color[0] / count,
|
|
822
|
+
accumulator[1] + color[1] / count,
|
|
823
|
+
accumulator[2] + color[2] / count,
|
|
824
|
+
accumulator[3] + color[3] / count,
|
|
825
|
+
],
|
|
826
|
+
[0, 0, 0, 0]
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function averageNumbers(values, fallback = 0) {
|
|
831
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
832
|
+
return fallback;
|
|
833
|
+
}
|
|
834
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
835
|
+
}
|
|
836
|
+
|
|
554
837
|
function createMeshTriangleRecords(meshes) {
|
|
555
838
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
556
839
|
let nextTriangleId = 0;
|
|
@@ -571,6 +854,16 @@ function createMeshTriangleRecords(meshes) {
|
|
|
571
854
|
const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
|
|
572
855
|
const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
|
|
573
856
|
const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
|
|
857
|
+
const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
|
|
858
|
+
const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
|
|
859
|
+
const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
|
|
860
|
+
const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
|
|
861
|
+
const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
|
|
862
|
+
const sampledMaterials = [
|
|
863
|
+
sampleSurfaceMaterial(mesh, uv0),
|
|
864
|
+
sampleSurfaceMaterial(mesh, uv1),
|
|
865
|
+
sampleSurfaceMaterial(mesh, uv2),
|
|
866
|
+
];
|
|
574
867
|
const bounds = triangleBounds(v0, v1, v2);
|
|
575
868
|
|
|
576
869
|
triangles.push(
|
|
@@ -581,18 +874,42 @@ function createMeshTriangleRecords(meshes) {
|
|
|
581
874
|
flags: mesh.flags,
|
|
582
875
|
materialRefId: mesh.materialRefId,
|
|
583
876
|
mediumRefId: mesh.mediumRefId,
|
|
877
|
+
materialSlot: meshIndex,
|
|
584
878
|
v0: Object.freeze(v0),
|
|
585
879
|
v1: Object.freeze(v1),
|
|
586
880
|
v2: Object.freeze(v2),
|
|
587
|
-
n0: Object.freeze(
|
|
588
|
-
n1: Object.freeze(
|
|
589
|
-
n2: Object.freeze(
|
|
881
|
+
n0: Object.freeze(shadedN0),
|
|
882
|
+
n1: Object.freeze(shadedN1),
|
|
883
|
+
n2: Object.freeze(shadedN2),
|
|
590
884
|
uv0: Object.freeze(uv0),
|
|
591
885
|
uv1: Object.freeze(uv1),
|
|
592
886
|
uv2: Object.freeze(uv2),
|
|
593
|
-
color:
|
|
887
|
+
color: Object.freeze(averageColors(sampledColors)),
|
|
594
888
|
emission: mesh.emission,
|
|
595
|
-
material: Object.freeze([
|
|
889
|
+
material: Object.freeze([
|
|
890
|
+
averageNumbers(sampledMaterials.map((sample) => sample.roughness), mesh.roughness),
|
|
891
|
+
averageNumbers(sampledMaterials.map((sample) => sample.metallic), mesh.metallic),
|
|
892
|
+
mesh.opacity,
|
|
893
|
+
mesh.ior,
|
|
894
|
+
]),
|
|
895
|
+
materialResponse: Object.freeze([
|
|
896
|
+
mesh.sheenColor[0] ?? 0,
|
|
897
|
+
mesh.sheenColor[1] ?? 0,
|
|
898
|
+
mesh.sheenColor[2] ?? 0,
|
|
899
|
+
mesh.clearcoat,
|
|
900
|
+
]),
|
|
901
|
+
materialExtension: Object.freeze([
|
|
902
|
+
mesh.clearcoatRoughness,
|
|
903
|
+
mesh.specular,
|
|
904
|
+
mesh.transmission,
|
|
905
|
+
0,
|
|
906
|
+
]),
|
|
907
|
+
specularColor: Object.freeze([
|
|
908
|
+
mesh.specularColor[0] ?? 1,
|
|
909
|
+
mesh.specularColor[1] ?? 1,
|
|
910
|
+
mesh.specularColor[2] ?? 1,
|
|
911
|
+
1,
|
|
912
|
+
]),
|
|
596
913
|
bounds: Object.freeze({
|
|
597
914
|
min: Object.freeze(bounds.min),
|
|
598
915
|
max: Object.freeze(bounds.max),
|
|
@@ -713,6 +1030,245 @@ function nextPowerOfTwo(value) {
|
|
|
713
1030
|
return 2 ** Math.ceil(Math.log2(value));
|
|
714
1031
|
}
|
|
715
1032
|
|
|
1033
|
+
function textureComponentToByte(value, fallback) {
|
|
1034
|
+
const numeric = Number(value);
|
|
1035
|
+
if (!Number.isFinite(numeric)) {
|
|
1036
|
+
return fallback;
|
|
1037
|
+
}
|
|
1038
|
+
if (numeric >= 0 && numeric <= 1) {
|
|
1039
|
+
return Math.max(0, Math.min(255, Math.round(numeric * 255)));
|
|
1040
|
+
}
|
|
1041
|
+
return Math.max(0, Math.min(255, Math.round(numeric)));
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function createSolidTextureSample(width, height, rgba) {
|
|
1045
|
+
const data = new Uint8Array(width * height * 4);
|
|
1046
|
+
for (let offset = 0; offset < data.length; offset += 4) {
|
|
1047
|
+
data[offset] = rgba[0];
|
|
1048
|
+
data[offset + 1] = rgba[1];
|
|
1049
|
+
data[offset + 2] = rgba[2];
|
|
1050
|
+
data[offset + 3] = rgba[3];
|
|
1051
|
+
}
|
|
1052
|
+
return Object.freeze({
|
|
1053
|
+
width,
|
|
1054
|
+
height,
|
|
1055
|
+
data,
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function normalizeTextureSampleInput(texture, fallbackColor) {
|
|
1060
|
+
if (
|
|
1061
|
+
!texture ||
|
|
1062
|
+
!Number.isFinite(texture.width) ||
|
|
1063
|
+
!Number.isFinite(texture.height) ||
|
|
1064
|
+
texture.width <= 0 ||
|
|
1065
|
+
texture.height <= 0
|
|
1066
|
+
) {
|
|
1067
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
const pixelCount = Math.trunc(texture.width) * Math.trunc(texture.height) * 4;
|
|
1071
|
+
const source =
|
|
1072
|
+
ArrayBuffer.isView(texture.data) || Array.isArray(texture.data) ? texture.data : null;
|
|
1073
|
+
if (!source || source.length < pixelCount) {
|
|
1074
|
+
return createSolidTextureSample(1, 1, fallbackColor);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const data = new Uint8Array(pixelCount);
|
|
1078
|
+
for (let index = 0; index < pixelCount; index += 1) {
|
|
1079
|
+
data[index] = textureComponentToByte(source[index], fallbackColor[index % 4]);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
return Object.freeze({
|
|
1083
|
+
width: Math.trunc(texture.width),
|
|
1084
|
+
height: Math.trunc(texture.height),
|
|
1085
|
+
data,
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function buildTextureAtlas(textures, fallbackColor) {
|
|
1090
|
+
const padding = 1;
|
|
1091
|
+
const defaultTexture = createSolidTextureSample(1, 1, fallbackColor);
|
|
1092
|
+
const uniqueEntries = [{ source: null, texture: defaultTexture }];
|
|
1093
|
+
const bySource = new Map();
|
|
1094
|
+
|
|
1095
|
+
for (const texture of Array.isArray(textures) ? textures : []) {
|
|
1096
|
+
if (!texture || bySource.has(texture)) {
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
const normalized = normalizeTextureSampleInput(texture, fallbackColor);
|
|
1100
|
+
bySource.set(texture, uniqueEntries.length);
|
|
1101
|
+
uniqueEntries.push({ source: texture, texture: normalized });
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const totalArea = uniqueEntries.reduce((sum, entry) => {
|
|
1105
|
+
return sum + (entry.texture.width + padding * 2) * (entry.texture.height + padding * 2);
|
|
1106
|
+
}, 0);
|
|
1107
|
+
const maxTileWidth = uniqueEntries.reduce((maxWidth, entry) => {
|
|
1108
|
+
return Math.max(maxWidth, entry.texture.width + padding * 2);
|
|
1109
|
+
}, 1);
|
|
1110
|
+
const targetWidth = Math.max(
|
|
1111
|
+
maxTileWidth,
|
|
1112
|
+
nextPowerOfTwo(Math.max(maxTileWidth, Math.ceil(Math.sqrt(totalArea))))
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1115
|
+
let cursorX = 0;
|
|
1116
|
+
let cursorY = 0;
|
|
1117
|
+
let rowHeight = 0;
|
|
1118
|
+
let atlasWidth = 0;
|
|
1119
|
+
const placements = uniqueEntries.map((entry) => {
|
|
1120
|
+
const tileWidth = entry.texture.width + padding * 2;
|
|
1121
|
+
const tileHeight = entry.texture.height + padding * 2;
|
|
1122
|
+
if (cursorX > 0 && cursorX + tileWidth > targetWidth) {
|
|
1123
|
+
cursorX = 0;
|
|
1124
|
+
cursorY += rowHeight;
|
|
1125
|
+
rowHeight = 0;
|
|
1126
|
+
}
|
|
1127
|
+
const placement = Object.freeze({
|
|
1128
|
+
x: cursorX,
|
|
1129
|
+
y: cursorY,
|
|
1130
|
+
tileWidth,
|
|
1131
|
+
tileHeight,
|
|
1132
|
+
width: entry.texture.width,
|
|
1133
|
+
height: entry.texture.height,
|
|
1134
|
+
});
|
|
1135
|
+
cursorX += tileWidth;
|
|
1136
|
+
atlasWidth = Math.max(atlasWidth, cursorX);
|
|
1137
|
+
rowHeight = Math.max(rowHeight, tileHeight);
|
|
1138
|
+
return placement;
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
const atlasHeight = Math.max(1, cursorY + rowHeight);
|
|
1142
|
+
const atlasData = new Uint8Array(Math.max(1, atlasWidth * atlasHeight * 4));
|
|
1143
|
+
|
|
1144
|
+
const writePixel = (x, y, rgba) => {
|
|
1145
|
+
const offset = (y * atlasWidth + x) * 4;
|
|
1146
|
+
atlasData[offset] = rgba[0];
|
|
1147
|
+
atlasData[offset + 1] = rgba[1];
|
|
1148
|
+
atlasData[offset + 2] = rgba[2];
|
|
1149
|
+
atlasData[offset + 3] = rgba[3];
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
const rects = placements.map((placement, entryIndex) => {
|
|
1153
|
+
const { texture } = uniqueEntries[entryIndex];
|
|
1154
|
+
for (let y = 0; y < placement.tileHeight; y += 1) {
|
|
1155
|
+
for (let x = 0; x < placement.tileWidth; x += 1) {
|
|
1156
|
+
const sampleX = Math.max(0, Math.min(texture.width - 1, x - padding));
|
|
1157
|
+
const sampleY = Math.max(0, Math.min(texture.height - 1, y - padding));
|
|
1158
|
+
const sourceOffset = (sampleY * texture.width + sampleX) * 4;
|
|
1159
|
+
writePixel(placement.x + x, placement.y + y, texture.data.slice(sourceOffset, sourceOffset + 4));
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return Object.freeze([
|
|
1163
|
+
(placement.x + padding) / Math.max(1, atlasWidth),
|
|
1164
|
+
(placement.y + padding) / Math.max(1, atlasHeight),
|
|
1165
|
+
placement.width / Math.max(1, atlasWidth),
|
|
1166
|
+
placement.height / Math.max(1, atlasHeight),
|
|
1167
|
+
]);
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
const rectBySource = new Map();
|
|
1171
|
+
uniqueEntries.forEach((entry, index) => {
|
|
1172
|
+
if (entry.source) {
|
|
1173
|
+
rectBySource.set(entry.source, rects[index]);
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
return Object.freeze({
|
|
1178
|
+
width: Math.max(1, atlasWidth),
|
|
1179
|
+
height: Math.max(1, atlasHeight),
|
|
1180
|
+
data: atlasData,
|
|
1181
|
+
defaultRect: rects[0],
|
|
1182
|
+
resolveRect(texture) {
|
|
1183
|
+
return rectBySource.get(texture) ?? rects[0];
|
|
1184
|
+
},
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
export function createWavefrontGpuMaterialSource(meshes = []) {
|
|
1189
|
+
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
1190
|
+
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1191
|
+
const baseColorAtlas = buildTextureAtlas(
|
|
1192
|
+
normalized.map((mesh) => mesh.baseColorTexture),
|
|
1193
|
+
[255, 255, 255, 255]
|
|
1194
|
+
);
|
|
1195
|
+
const metallicRoughnessAtlas = buildTextureAtlas(
|
|
1196
|
+
normalized.map((mesh) => mesh.metallicRoughnessTexture),
|
|
1197
|
+
[255, 255, 255, 255]
|
|
1198
|
+
);
|
|
1199
|
+
const normalAtlas = buildTextureAtlas(
|
|
1200
|
+
normalized.map((mesh) => mesh.normalTexture),
|
|
1201
|
+
[128, 128, 255, 255]
|
|
1202
|
+
);
|
|
1203
|
+
const occlusionAtlas = buildTextureAtlas(
|
|
1204
|
+
normalized.map((mesh) => mesh.occlusionTexture),
|
|
1205
|
+
[255, 255, 255, 255]
|
|
1206
|
+
);
|
|
1207
|
+
const emissiveAtlas = buildTextureAtlas(
|
|
1208
|
+
normalized.map((mesh) => mesh.emissiveTexture),
|
|
1209
|
+
[255, 255, 255, 255]
|
|
1210
|
+
);
|
|
1211
|
+
const bytes = new ArrayBuffer(Math.max(1, normalized.length) * GPU_MATERIAL_RECORD_BYTES);
|
|
1212
|
+
const floatView = new Float32Array(bytes);
|
|
1213
|
+
|
|
1214
|
+
normalized.forEach((mesh, meshIndex) => {
|
|
1215
|
+
const byteOffset = meshIndex * GPU_MATERIAL_RECORD_BYTES;
|
|
1216
|
+
writeVec4(floatView, byteOffset, mesh.color);
|
|
1217
|
+
writeVec4(floatView, byteOffset + 16, mesh.emission);
|
|
1218
|
+
writeVec4(floatView, byteOffset + 32, [
|
|
1219
|
+
mesh.roughness,
|
|
1220
|
+
mesh.metallic,
|
|
1221
|
+
mesh.opacity,
|
|
1222
|
+
mesh.ior,
|
|
1223
|
+
]);
|
|
1224
|
+
writeVec4(floatView, byteOffset + 48, [
|
|
1225
|
+
mesh.sheenColor[0] ?? 0,
|
|
1226
|
+
mesh.sheenColor[1] ?? 0,
|
|
1227
|
+
mesh.sheenColor[2] ?? 0,
|
|
1228
|
+
mesh.clearcoat,
|
|
1229
|
+
]);
|
|
1230
|
+
writeVec4(floatView, byteOffset + 64, [
|
|
1231
|
+
mesh.clearcoatRoughness,
|
|
1232
|
+
mesh.specular,
|
|
1233
|
+
mesh.transmission,
|
|
1234
|
+
0,
|
|
1235
|
+
]);
|
|
1236
|
+
writeVec4(floatView, byteOffset + 80, [
|
|
1237
|
+
mesh.specularColor[0] ?? 1,
|
|
1238
|
+
mesh.specularColor[1] ?? 1,
|
|
1239
|
+
mesh.specularColor[2] ?? 1,
|
|
1240
|
+
1,
|
|
1241
|
+
]);
|
|
1242
|
+
writeVec4(floatView, byteOffset + 96, baseColorAtlas.resolveRect(mesh.baseColorTexture));
|
|
1243
|
+
writeVec4(
|
|
1244
|
+
floatView,
|
|
1245
|
+
byteOffset + 112,
|
|
1246
|
+
metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1247
|
+
);
|
|
1248
|
+
writeVec4(floatView, byteOffset + 128, normalAtlas.resolveRect(mesh.normalTexture));
|
|
1249
|
+
writeVec4(floatView, byteOffset + 144, occlusionAtlas.resolveRect(mesh.occlusionTexture));
|
|
1250
|
+
writeVec4(floatView, byteOffset + 160, emissiveAtlas.resolveRect(mesh.emissiveTexture));
|
|
1251
|
+
writeVec4(floatView, byteOffset + 176, [
|
|
1252
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1253
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1254
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1255
|
+
0,
|
|
1256
|
+
]);
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
return Object.freeze({
|
|
1260
|
+
buffer: bytes,
|
|
1261
|
+
count: normalized.length,
|
|
1262
|
+
recordBytes: GPU_MATERIAL_RECORD_BYTES,
|
|
1263
|
+
records: Object.freeze(normalized),
|
|
1264
|
+
baseColorAtlas,
|
|
1265
|
+
metallicRoughnessAtlas,
|
|
1266
|
+
normalAtlas,
|
|
1267
|
+
occlusionAtlas,
|
|
1268
|
+
emissiveAtlas,
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
|
|
716
1272
|
function estimateBvhLeafSortCapacity(triangleCount) {
|
|
717
1273
|
return triangleCount <= 0 ? 0 : nextPowerOfTwo(triangleCount);
|
|
718
1274
|
}
|
|
@@ -783,9 +1339,10 @@ function resolveAccelerationBuildMode(options = {}) {
|
|
|
783
1339
|
return mode;
|
|
784
1340
|
}
|
|
785
1341
|
|
|
786
|
-
export function createWavefrontGpuMeshSource(meshes = []) {
|
|
1342
|
+
export function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput = null) {
|
|
787
1343
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
788
1344
|
const normalized = source.map((meshInput, meshIndex) => normalizeWavefrontMesh(meshInput, meshIndex));
|
|
1345
|
+
const gpuMaterialSource = gpuMaterialSourceInput ?? createWavefrontGpuMaterialSource(normalized);
|
|
789
1346
|
const vertexCount = normalized.reduce((count, mesh) => count + mesh.positions.length / 3, 0);
|
|
790
1347
|
const indexCount = normalized.reduce((count, mesh) => count + mesh.indices.length, 0);
|
|
791
1348
|
const triangleCount = Math.floor(indexCount / 3);
|
|
@@ -842,7 +1399,7 @@ export function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
842
1399
|
meshUints[meshOffset + 8] = mesh.indices.length / 3;
|
|
843
1400
|
meshUints[meshOffset + 9] = meshVertexBase;
|
|
844
1401
|
meshUints[meshOffset + 10] = meshVertexCount;
|
|
845
|
-
meshUints[meshOffset + 11] =
|
|
1402
|
+
meshUints[meshOffset + 11] = meshIndex;
|
|
846
1403
|
const floatOffset = meshOffset;
|
|
847
1404
|
writeVec4(meshFloats, floatOffset * 4 + 48, mesh.color);
|
|
848
1405
|
writeVec4(meshFloats, floatOffset * 4 + 64, mesh.emission);
|
|
@@ -852,6 +1409,55 @@ export function createWavefrontGpuMeshSource(meshes = []) {
|
|
|
852
1409
|
mesh.opacity,
|
|
853
1410
|
mesh.ior,
|
|
854
1411
|
]);
|
|
1412
|
+
writeVec4(meshFloats, floatOffset * 4 + 96, [
|
|
1413
|
+
mesh.sheenColor[0] ?? 0,
|
|
1414
|
+
mesh.sheenColor[1] ?? 0,
|
|
1415
|
+
mesh.sheenColor[2] ?? 0,
|
|
1416
|
+
mesh.clearcoat,
|
|
1417
|
+
]);
|
|
1418
|
+
writeVec4(meshFloats, floatOffset * 4 + 112, [
|
|
1419
|
+
mesh.clearcoatRoughness,
|
|
1420
|
+
mesh.specular,
|
|
1421
|
+
mesh.transmission,
|
|
1422
|
+
0,
|
|
1423
|
+
]);
|
|
1424
|
+
writeVec4(meshFloats, floatOffset * 4 + 128, [
|
|
1425
|
+
mesh.specularColor[0] ?? 1,
|
|
1426
|
+
mesh.specularColor[1] ?? 1,
|
|
1427
|
+
mesh.specularColor[2] ?? 1,
|
|
1428
|
+
1,
|
|
1429
|
+
]);
|
|
1430
|
+
writeVec4(
|
|
1431
|
+
meshFloats,
|
|
1432
|
+
floatOffset * 4 + 144,
|
|
1433
|
+
gpuMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
|
|
1434
|
+
);
|
|
1435
|
+
writeVec4(
|
|
1436
|
+
meshFloats,
|
|
1437
|
+
floatOffset * 4 + 160,
|
|
1438
|
+
gpuMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1439
|
+
);
|
|
1440
|
+
writeVec4(
|
|
1441
|
+
meshFloats,
|
|
1442
|
+
floatOffset * 4 + 176,
|
|
1443
|
+
gpuMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
|
|
1444
|
+
);
|
|
1445
|
+
writeVec4(
|
|
1446
|
+
meshFloats,
|
|
1447
|
+
floatOffset * 4 + 192,
|
|
1448
|
+
gpuMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
|
|
1449
|
+
);
|
|
1450
|
+
writeVec4(
|
|
1451
|
+
meshFloats,
|
|
1452
|
+
floatOffset * 4 + 208,
|
|
1453
|
+
gpuMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
|
|
1454
|
+
);
|
|
1455
|
+
writeVec4(meshFloats, floatOffset * 4 + 224, [
|
|
1456
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1457
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1458
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1459
|
+
0,
|
|
1460
|
+
]);
|
|
855
1461
|
|
|
856
1462
|
vertexCursor += meshVertexCount;
|
|
857
1463
|
indexCursor += mesh.indices.length;
|
|
@@ -1188,12 +1794,14 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1188
1794
|
options.environmentPortalCapacity,
|
|
1189
1795
|
0
|
|
1190
1796
|
);
|
|
1797
|
+
const materialCapacity = readNonNegativeInteger("materialCapacity", options.materialCapacity, 0);
|
|
1191
1798
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
1192
1799
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
1193
1800
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
1194
1801
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
1195
1802
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
1196
1803
|
const triangleBytes = triangleCapacity * TRIANGLE_RECORD_BYTES;
|
|
1804
|
+
const materialTableBytes = materialCapacity * GPU_MATERIAL_RECORD_BYTES;
|
|
1197
1805
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
1198
1806
|
const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
|
|
1199
1807
|
const emissiveTriangleMetadataBytes =
|
|
@@ -1209,6 +1817,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1209
1817
|
pathVertexBytes,
|
|
1210
1818
|
sceneObjectBytes,
|
|
1211
1819
|
triangleBytes,
|
|
1820
|
+
materialTableBytes,
|
|
1212
1821
|
bvhNodeBytes,
|
|
1213
1822
|
bvhLeafReferenceBytes,
|
|
1214
1823
|
emissiveTriangleMetadataBytes,
|
|
@@ -1223,6 +1832,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1223
1832
|
pathVertexBytes +
|
|
1224
1833
|
sceneObjectBytes +
|
|
1225
1834
|
triangleBytes +
|
|
1835
|
+
materialTableBytes +
|
|
1226
1836
|
bvhNodeBytes +
|
|
1227
1837
|
bvhLeafReferenceBytes +
|
|
1228
1838
|
emissiveTriangleMetadataBytes +
|
|
@@ -1244,7 +1854,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1244
1854
|
const samplesPerPixel = clamp(
|
|
1245
1855
|
readPositiveInteger("samplesPerPixel", options.samplesPerPixel, DEFAULT_SAMPLES_PER_PIXEL),
|
|
1246
1856
|
1,
|
|
1247
|
-
|
|
1857
|
+
MAX_SAMPLES_PER_PIXEL
|
|
1248
1858
|
);
|
|
1249
1859
|
const maxFramePassesPerSubmission = clamp(
|
|
1250
1860
|
readPositiveInteger(
|
|
@@ -1262,9 +1872,13 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1262
1872
|
);
|
|
1263
1873
|
const meshes = normalizeMeshes(options);
|
|
1264
1874
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
1875
|
+
const gpuMaterialSource =
|
|
1876
|
+
meshes.length > 0
|
|
1877
|
+
? createWavefrontGpuMaterialSource(meshes)
|
|
1878
|
+
: createWavefrontGpuMaterialSource([]);
|
|
1265
1879
|
const gpuMeshSource =
|
|
1266
1880
|
meshes.length > 0
|
|
1267
|
-
? createWavefrontGpuMeshSource(meshes)
|
|
1881
|
+
? createWavefrontGpuMeshSource(meshes, gpuMaterialSource)
|
|
1268
1882
|
: createWavefrontGpuMeshSource([]);
|
|
1269
1883
|
const meshAcceleration =
|
|
1270
1884
|
accelerationBuildMode === "cpu-debug"
|
|
@@ -1360,6 +1974,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1360
1974
|
accelerationBuildMode,
|
|
1361
1975
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
1362
1976
|
gpuMeshSource,
|
|
1977
|
+
gpuMaterialSource,
|
|
1363
1978
|
meshAcceleration,
|
|
1364
1979
|
emissiveTriangleIndices,
|
|
1365
1980
|
emissiveTriangleCount: emissiveTriangleIndices.count,
|
|
@@ -1390,6 +2005,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1390
2005
|
maxDepth,
|
|
1391
2006
|
sceneObjectCapacity,
|
|
1392
2007
|
triangleCapacity,
|
|
2008
|
+
materialCapacity: gpuMaterialSource.count,
|
|
1393
2009
|
bvhNodeCapacity,
|
|
1394
2010
|
bvhLeafSortCapacity,
|
|
1395
2011
|
emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
|
|
@@ -1483,6 +2099,24 @@ export function packWavefrontSceneObjects(sceneObjects, capacity = sceneObjects.
|
|
|
1483
2099
|
object.opacity,
|
|
1484
2100
|
object.ior,
|
|
1485
2101
|
]);
|
|
2102
|
+
writeVec4(floatView, byteOffset + 96, [
|
|
2103
|
+
object.sheenColor[0] ?? 0,
|
|
2104
|
+
object.sheenColor[1] ?? 0,
|
|
2105
|
+
object.sheenColor[2] ?? 0,
|
|
2106
|
+
object.clearcoat,
|
|
2107
|
+
]);
|
|
2108
|
+
writeVec4(floatView, byteOffset + 112, [
|
|
2109
|
+
object.clearcoatRoughness,
|
|
2110
|
+
object.specular,
|
|
2111
|
+
object.transmission,
|
|
2112
|
+
0,
|
|
2113
|
+
]);
|
|
2114
|
+
writeVec4(floatView, byteOffset + 128, [
|
|
2115
|
+
object.specularColor[0] ?? 1,
|
|
2116
|
+
object.specularColor[1] ?? 1,
|
|
2117
|
+
object.specularColor[2] ?? 1,
|
|
2118
|
+
1,
|
|
2119
|
+
]);
|
|
1486
2120
|
});
|
|
1487
2121
|
|
|
1488
2122
|
return Object.freeze({
|
|
@@ -1511,7 +2145,7 @@ export function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1511
2145
|
uintView[u32 + 3] = triangle.flags;
|
|
1512
2146
|
uintView[u32 + 4] = triangle.materialRefId;
|
|
1513
2147
|
uintView[u32 + 5] = triangle.mediumRefId;
|
|
1514
|
-
uintView[u32 + 6] = 0;
|
|
2148
|
+
uintView[u32 + 6] = triangle.materialSlot ?? 0;
|
|
1515
2149
|
uintView[u32 + 7] = 0;
|
|
1516
2150
|
writeVec4(floatView, byteOffset + 32, [...triangle.v0, 0]);
|
|
1517
2151
|
writeVec4(floatView, byteOffset + 48, [...triangle.v1, 0]);
|
|
@@ -1524,6 +2158,15 @@ export function packWavefrontTriangles(triangles, capacity = triangles.length) {
|
|
|
1524
2158
|
writeVec4(floatView, byteOffset + 160, triangle.color);
|
|
1525
2159
|
writeVec4(floatView, byteOffset + 176, triangle.emission);
|
|
1526
2160
|
writeVec4(floatView, byteOffset + 192, triangle.material);
|
|
2161
|
+
writeVec4(floatView, byteOffset + 208, triangle.materialResponse);
|
|
2162
|
+
writeVec4(floatView, byteOffset + 224, triangle.materialExtension ?? [0.08, 1, 0, 0]);
|
|
2163
|
+
writeVec4(floatView, byteOffset + 240, triangle.specularColor ?? [1, 1, 1, 1]);
|
|
2164
|
+
writeVec4(floatView, byteOffset + 256, triangle.baseColorAtlas ?? [0, 0, 1, 1]);
|
|
2165
|
+
writeVec4(floatView, byteOffset + 272, triangle.metallicRoughnessAtlas ?? [0, 0, 1, 1]);
|
|
2166
|
+
writeVec4(floatView, byteOffset + 288, triangle.normalAtlas ?? [0, 0, 1, 1]);
|
|
2167
|
+
writeVec4(floatView, byteOffset + 304, triangle.occlusionAtlas ?? [0, 0, 1, 1]);
|
|
2168
|
+
writeVec4(floatView, byteOffset + 320, triangle.emissiveAtlas ?? [0, 0, 1, 1]);
|
|
2169
|
+
writeVec4(floatView, byteOffset + 336, triangle.textureSettings ?? [1, 1, 1, 0]);
|
|
1527
2170
|
});
|
|
1528
2171
|
|
|
1529
2172
|
return Object.freeze({
|
|
@@ -1623,6 +2266,12 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1623
2266
|
0,
|
|
1624
2267
|
0,
|
|
1625
2268
|
]);
|
|
2269
|
+
writeVec4(floatView, 304, [
|
|
2270
|
+
config.environmentMap.width ?? 1,
|
|
2271
|
+
config.environmentMap.height ?? 1,
|
|
2272
|
+
config.environmentMap.mipLevelCount ?? 1,
|
|
2273
|
+
config.environmentMap.hasImportanceData ? 1 : 0,
|
|
2274
|
+
]);
|
|
1626
2275
|
return bytes;
|
|
1627
2276
|
}
|
|
1628
2277
|
|
|
@@ -1813,6 +2462,7 @@ export function intersectWavefrontReferenceTriangle(ray, triangle, options = {})
|
|
|
1813
2462
|
color: triangle.color,
|
|
1814
2463
|
emission: triangle.emission,
|
|
1815
2464
|
material: triangle.material,
|
|
2465
|
+
materialResponse: triangle.materialResponse,
|
|
1816
2466
|
});
|
|
1817
2467
|
}
|
|
1818
2468
|
|
|
@@ -1840,6 +2490,7 @@ function createWavefrontReferenceEnvironmentHit(config, ray) {
|
|
|
1840
2490
|
color: Object.freeze([0, 0, 0, 0]),
|
|
1841
2491
|
emission: radiance,
|
|
1842
2492
|
material: Object.freeze([1, 0, 1, 1]),
|
|
2493
|
+
materialResponse: Object.freeze([0, 0, 0, 0]),
|
|
1843
2494
|
});
|
|
1844
2495
|
}
|
|
1845
2496
|
|
|
@@ -1932,44 +2583,26 @@ function environmentMapIntegerScale(data) {
|
|
|
1932
2583
|
return 1;
|
|
1933
2584
|
}
|
|
1934
2585
|
|
|
1935
|
-
function
|
|
1936
|
-
if (!
|
|
1937
|
-
return
|
|
2586
|
+
function environmentMapHasSamplingData(environmentMap) {
|
|
2587
|
+
if (!environmentMap || !environmentMap.data) {
|
|
2588
|
+
return false;
|
|
1938
2589
|
}
|
|
1939
|
-
const
|
|
1940
|
-
|
|
2590
|
+
const width = Math.max(1, environmentMap.width ?? 1);
|
|
2591
|
+
const height = Math.max(1, environmentMap.height ?? 1);
|
|
2592
|
+
return environmentMap.data.length >= width * height * 4;
|
|
1941
2593
|
}
|
|
1942
2594
|
|
|
1943
|
-
function
|
|
1944
|
-
const width = Math.max(1,
|
|
1945
|
-
const height = Math.max(1,
|
|
1946
|
-
const
|
|
1947
|
-
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2595
|
+
function createRgba8TextureUpload(source) {
|
|
2596
|
+
const width = Math.max(1, Math.trunc(source.width));
|
|
2597
|
+
const height = Math.max(1, Math.trunc(source.height));
|
|
2598
|
+
const bytesPerRow = alignTo(width * 4, 256);
|
|
1948
2599
|
const bytes = new Uint8Array(bytesPerRow * height);
|
|
1949
|
-
const data =
|
|
1950
|
-
const integerScale = environmentMapIntegerScale(data);
|
|
1951
|
-
const view = new DataView(bytes.buffer);
|
|
1952
|
-
const writeComponent = (targetOffset, sourceOffset, fallback) => {
|
|
1953
|
-
view.setUint16(
|
|
1954
|
-
targetOffset,
|
|
1955
|
-
float32ToFloat16Bits(
|
|
1956
|
-
readEnvironmentMapComponent(data, sourceOffset, fallback, integerScale)
|
|
1957
|
-
),
|
|
1958
|
-
true
|
|
1959
|
-
);
|
|
1960
|
-
};
|
|
1961
|
-
|
|
2600
|
+
const data = source.data instanceof Uint8Array ? source.data : new Uint8Array(source.data);
|
|
1962
2601
|
for (let y = 0; y < height; y += 1) {
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
writeComponent(targetOffset, sourceOffset, fallbackColor[0]);
|
|
1967
|
-
writeComponent(targetOffset + 2, sourceOffset + 1, fallbackColor[1]);
|
|
1968
|
-
writeComponent(targetOffset + 4, sourceOffset + 2, fallbackColor[2]);
|
|
1969
|
-
writeComponent(targetOffset + 6, sourceOffset + 3, fallbackColor[3] ?? 1);
|
|
1970
|
-
}
|
|
2602
|
+
const sourceOffset = y * width * 4;
|
|
2603
|
+
const targetOffset = y * bytesPerRow;
|
|
2604
|
+
bytes.set(data.subarray(sourceOffset, sourceOffset + width * 4), targetOffset);
|
|
1971
2605
|
}
|
|
1972
|
-
|
|
1973
2606
|
return Object.freeze({
|
|
1974
2607
|
bytes,
|
|
1975
2608
|
bytesPerRow,
|
|
@@ -1978,42 +2611,506 @@ function createEnvironmentMapUploadBytes(environmentMap, fallbackColor) {
|
|
|
1978
2611
|
});
|
|
1979
2612
|
}
|
|
1980
2613
|
|
|
1981
|
-
function
|
|
1982
|
-
if (
|
|
1983
|
-
return
|
|
1984
|
-
view: environmentMap.view,
|
|
1985
|
-
sampler: environmentMap.sampler ?? device.createSampler({
|
|
1986
|
-
label: "plasius.wavefront.environmentMapSampler",
|
|
1987
|
-
addressModeU: "repeat",
|
|
1988
|
-
addressModeV: "clamp-to-edge",
|
|
1989
|
-
magFilter: "linear",
|
|
1990
|
-
minFilter: "linear",
|
|
1991
|
-
}),
|
|
1992
|
-
texture: null,
|
|
1993
|
-
ownsTexture: false,
|
|
1994
|
-
});
|
|
2614
|
+
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
2615
|
+
if (!data || index >= data.length) {
|
|
2616
|
+
return fallback;
|
|
1995
2617
|
}
|
|
2618
|
+
const value = Number(data[index]);
|
|
2619
|
+
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
2620
|
+
}
|
|
1996
2621
|
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
sampler: environmentMap.sampler ?? device.createSampler({
|
|
2001
|
-
label: "plasius.wavefront.environmentMapSampler",
|
|
2002
|
-
addressModeU: "repeat",
|
|
2003
|
-
addressModeV: "clamp-to-edge",
|
|
2004
|
-
magFilter: "linear",
|
|
2005
|
-
minFilter: "linear",
|
|
2006
|
-
}),
|
|
2007
|
-
texture: environmentMap.texture,
|
|
2008
|
-
ownsTexture: false,
|
|
2009
|
-
});
|
|
2010
|
-
}
|
|
2622
|
+
function reflectVector(direction, normal) {
|
|
2623
|
+
return subtract(direction, scale(normal, 2 * dot(direction, normal)));
|
|
2624
|
+
}
|
|
2011
2625
|
|
|
2012
|
-
|
|
2013
|
-
const
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2626
|
+
function buildOrthonormalBasis(normal) {
|
|
2627
|
+
const tangentFallback = Math.abs(normal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
2628
|
+
const tangent = normalize(cross(tangentFallback, normal), [1, 0, 0]);
|
|
2629
|
+
const bitangent = normalize(cross(normal, tangent), [0, 0, 1]);
|
|
2630
|
+
return { tangent, bitangent };
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
function localToWorld(local, normal) {
|
|
2634
|
+
const basis = buildOrthonormalBasis(normal);
|
|
2635
|
+
return normalize(
|
|
2636
|
+
add(
|
|
2637
|
+
add(scale(basis.tangent, local[0]), scale(basis.bitangent, local[1])),
|
|
2638
|
+
scale(normal, local[2])
|
|
2639
|
+
),
|
|
2640
|
+
normal
|
|
2641
|
+
);
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
function radicalInverseVdc(bits) {
|
|
2645
|
+
let value = bits >>> 0;
|
|
2646
|
+
value = ((value << 16) | (value >>> 16)) >>> 0;
|
|
2647
|
+
value = (((value & 0x55555555) << 1) | ((value & 0xaaaaaaaa) >>> 1)) >>> 0;
|
|
2648
|
+
value = (((value & 0x33333333) << 2) | ((value & 0xcccccccc) >>> 2)) >>> 0;
|
|
2649
|
+
value = (((value & 0x0f0f0f0f) << 4) | ((value & 0xf0f0f0f0) >>> 4)) >>> 0;
|
|
2650
|
+
value = (((value & 0x00ff00ff) << 8) | ((value & 0xff00ff00) >>> 8)) >>> 0;
|
|
2651
|
+
return value * 2.3283064365386963e-10;
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
function hammersley(index, count) {
|
|
2655
|
+
return [index / Math.max(count, 1), radicalInverseVdc(index)];
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
function importanceSampleGgx(sample, roughness, normal) {
|
|
2659
|
+
const alpha = Math.max(roughness * roughness, 0.0001);
|
|
2660
|
+
const phi = 2 * Math.PI * sample[0];
|
|
2661
|
+
const cosTheta = Math.sqrt((1 - sample[1]) / (1 + (alpha * alpha - 1) * sample[1]));
|
|
2662
|
+
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
2663
|
+
const halfVector = localToWorld(
|
|
2664
|
+
[Math.cos(phi) * sinTheta, Math.sin(phi) * sinTheta, cosTheta],
|
|
2665
|
+
normal
|
|
2666
|
+
);
|
|
2667
|
+
return normalize(halfVector, normal);
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
function distributionGgx(nDotH, roughness) {
|
|
2671
|
+
const alpha = Math.max(roughness * roughness, 0.0001);
|
|
2672
|
+
const alpha2 = alpha * alpha;
|
|
2673
|
+
const denom = (nDotH * nDotH) * (alpha2 - 1) + 1;
|
|
2674
|
+
return alpha2 / Math.max(Math.PI * denom * denom, 0.000001);
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
function geometrySchlickGgx(nDotV, roughness) {
|
|
2678
|
+
const k = ((roughness + 1) * (roughness + 1)) / 8;
|
|
2679
|
+
return nDotV / Math.max(nDotV * (1 - k) + k, 0.000001);
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
function geometrySmith(nDotV, nDotL, roughness) {
|
|
2683
|
+
return geometrySchlickGgx(nDotV, roughness) * geometrySchlickGgx(nDotL, roughness);
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
2687
|
+
const viewDirection = [Math.sqrt(Math.max(0, 1 - nDotV * nDotV)), 0, nDotV];
|
|
2688
|
+
const normal = [0, 0, 1];
|
|
2689
|
+
let scaleTerm = 0;
|
|
2690
|
+
let biasTerm = 0;
|
|
2691
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
2692
|
+
const xi = hammersley(index, sampleCount);
|
|
2693
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2694
|
+
const vDotH = Math.max(dot(viewDirection, halfVector), 0);
|
|
2695
|
+
const lightDirection = normalize(
|
|
2696
|
+
subtract(scale(halfVector, 2 * vDotH), viewDirection),
|
|
2697
|
+
normal
|
|
2698
|
+
);
|
|
2699
|
+
const nDotL = Math.max(lightDirection[2], 0);
|
|
2700
|
+
const nDotH = Math.max(halfVector[2], 0);
|
|
2701
|
+
if (nDotL <= 0 || nDotH <= 0 || vDotH <= 0) {
|
|
2702
|
+
continue;
|
|
2703
|
+
}
|
|
2704
|
+
const geometry = geometrySmith(nDotV, nDotL, roughness);
|
|
2705
|
+
const visibility = (geometry * vDotH) / Math.max(nDotH * nDotV, 0.000001);
|
|
2706
|
+
const fresnel = (1 - vDotH) ** 5;
|
|
2707
|
+
scaleTerm += (1 - fresnel) * visibility;
|
|
2708
|
+
biasTerm += fresnel * visibility;
|
|
2709
|
+
}
|
|
2710
|
+
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
function createBrdfLutUploadBytes(size = DEFAULT_BRDF_LUT_SIZE, sampleCount = 1024) {
|
|
2714
|
+
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
2715
|
+
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
2716
|
+
if (cached) {
|
|
2717
|
+
return cached;
|
|
2718
|
+
}
|
|
2719
|
+
const width = Math.max(1, Math.trunc(size));
|
|
2720
|
+
const height = Math.max(1, Math.trunc(size));
|
|
2721
|
+
const rowBytes = width * 8;
|
|
2722
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2723
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
2724
|
+
const view = new DataView(bytes.buffer);
|
|
2725
|
+
for (let y = 0; y < height; y += 1) {
|
|
2726
|
+
const roughness = (y + 0.5) / height;
|
|
2727
|
+
for (let x = 0; x < width; x += 1) {
|
|
2728
|
+
const nDotV = Math.max((x + 0.5) / width, 0.0001);
|
|
2729
|
+
const [scaleTerm, biasTerm] = integrateBrdfSample(nDotV, roughness, sampleCount);
|
|
2730
|
+
const offset = y * bytesPerRow + x * 8;
|
|
2731
|
+
view.setUint16(offset, float32ToFloat16Bits(scaleTerm), true);
|
|
2732
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(biasTerm), true);
|
|
2733
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(0), true);
|
|
2734
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
const upload = Object.freeze({ bytes, bytesPerRow, width, height });
|
|
2738
|
+
BRDF_LUT_UPLOAD_CACHE.set(cacheKey, upload);
|
|
2739
|
+
return upload;
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
function createLinearEnvironmentPixels(environmentMap, fallbackColor) {
|
|
2743
|
+
const width = Math.max(1, environmentMap.width);
|
|
2744
|
+
const height = Math.max(1, environmentMap.height);
|
|
2745
|
+
const pixels = new Float32Array(width * height * 4);
|
|
2746
|
+
const data = environmentMap.data;
|
|
2747
|
+
const integerScale = environmentMapIntegerScale(data);
|
|
2748
|
+
for (let index = 0; index < width * height; index += 1) {
|
|
2749
|
+
const sourceOffset = index * 4;
|
|
2750
|
+
const targetOffset = index * 4;
|
|
2751
|
+
pixels[targetOffset] = readEnvironmentMapComponent(data, sourceOffset, fallbackColor[0], integerScale);
|
|
2752
|
+
pixels[targetOffset + 1] = readEnvironmentMapComponent(data, sourceOffset + 1, fallbackColor[1], integerScale);
|
|
2753
|
+
pixels[targetOffset + 2] = readEnvironmentMapComponent(data, sourceOffset + 2, fallbackColor[2], integerScale);
|
|
2754
|
+
pixels[targetOffset + 3] = readEnvironmentMapComponent(data, sourceOffset + 3, fallbackColor[3] ?? 1, integerScale);
|
|
2755
|
+
}
|
|
2756
|
+
return pixels;
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
function environmentUvToDirection(u, v, rotationRadians = 0) {
|
|
2760
|
+
const angle = (u - rotationRadians / (2 * Math.PI) - 0.5) * 2 * Math.PI;
|
|
2761
|
+
const theta = v * Math.PI;
|
|
2762
|
+
const sinTheta = Math.sin(theta);
|
|
2763
|
+
return [
|
|
2764
|
+
Math.cos(angle) * sinTheta,
|
|
2765
|
+
Math.cos(theta),
|
|
2766
|
+
Math.sin(angle) * sinTheta,
|
|
2767
|
+
];
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
function sampleEnvironmentPixelsBilinear(pixels, width, height, u, v) {
|
|
2771
|
+
const wrappedU = ((u % 1) + 1) % 1;
|
|
2772
|
+
const clampedV = clamp(v, 0, 1);
|
|
2773
|
+
const x = wrappedU * width - 0.5;
|
|
2774
|
+
const y = clampedV * height - 0.5;
|
|
2775
|
+
const x0 = ((Math.floor(x) % width) + width) % width;
|
|
2776
|
+
const y0 = clamp(Math.floor(y), 0, height - 1);
|
|
2777
|
+
const x1 = (x0 + 1) % width;
|
|
2778
|
+
const y1 = clamp(y0 + 1, 0, height - 1);
|
|
2779
|
+
const tx = x - Math.floor(x);
|
|
2780
|
+
const ty = y - Math.floor(y);
|
|
2781
|
+
const read = (px, py) => {
|
|
2782
|
+
const offset = (py * width + px) * 4;
|
|
2783
|
+
return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]];
|
|
2784
|
+
};
|
|
2785
|
+
const a = read(x0, y0);
|
|
2786
|
+
const b = read(x1, y0);
|
|
2787
|
+
const c = read(x0, y1);
|
|
2788
|
+
const d = read(x1, y1);
|
|
2789
|
+
const mixPair = (first, second, factor) => first * (1 - factor) + second * factor;
|
|
2790
|
+
return [
|
|
2791
|
+
mixPair(mixPair(a[0], b[0], tx), mixPair(c[0], d[0], tx), ty),
|
|
2792
|
+
mixPair(mixPair(a[1], b[1], tx), mixPair(c[1], d[1], tx), ty),
|
|
2793
|
+
mixPair(mixPair(a[2], b[2], tx), mixPair(c[2], d[2], tx), ty),
|
|
2794
|
+
mixPair(mixPair(a[3], b[3], tx), mixPair(c[3], d[3], tx), ty),
|
|
2795
|
+
];
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
function directionToEnvironmentUv(direction, rotationRadians = 0) {
|
|
2799
|
+
const unitDirection = normalize(direction, [0, 1, 0]);
|
|
2800
|
+
const rotationTurns = rotationRadians / (2 * Math.PI);
|
|
2801
|
+
const u = ((((Math.atan2(unitDirection[2], unitDirection[0]) / (2 * Math.PI)) + 0.5 + rotationTurns) % 1) + 1) % 1;
|
|
2802
|
+
const v = Math.acos(clamp(unitDirection[1], -1, 1)) / Math.PI;
|
|
2803
|
+
return [u, clamp(v, 0, 1)];
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
function sampleEnvironmentRadiance(pixels, width, height, direction, rotationRadians = 0) {
|
|
2807
|
+
const [u, v] = directionToEnvironmentUv(direction, rotationRadians);
|
|
2808
|
+
return sampleEnvironmentPixelsBilinear(pixels, width, height, u, v);
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
function createFloat16RgbaUploadFromLevels(levels) {
|
|
2812
|
+
return levels.map((level) => {
|
|
2813
|
+
const rowBytes = level.width * 8;
|
|
2814
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2815
|
+
const bytes = new Uint8Array(bytesPerRow * level.height);
|
|
2816
|
+
const view = new DataView(bytes.buffer);
|
|
2817
|
+
for (let y = 0; y < level.height; y += 1) {
|
|
2818
|
+
for (let x = 0; x < level.width; x += 1) {
|
|
2819
|
+
const sourceOffset = (y * level.width + x) * 4;
|
|
2820
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
2821
|
+
view.setUint16(targetOffset, float32ToFloat16Bits(level.data[sourceOffset]), true);
|
|
2822
|
+
view.setUint16(targetOffset + 2, float32ToFloat16Bits(level.data[sourceOffset + 1]), true);
|
|
2823
|
+
view.setUint16(targetOffset + 4, float32ToFloat16Bits(level.data[sourceOffset + 2]), true);
|
|
2824
|
+
view.setUint16(targetOffset + 6, float32ToFloat16Bits(level.data[sourceOffset + 3]), true);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
return Object.freeze({ bytes, bytesPerRow, width: level.width, height: level.height });
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
function createPrefilteredEnvironmentLevels(environmentMap, fallbackColor) {
|
|
2832
|
+
const sourcePixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
2833
|
+
const sourceWidth = Math.max(1, environmentMap.width);
|
|
2834
|
+
const sourceHeight = Math.max(1, environmentMap.height);
|
|
2835
|
+
const mipLevelCount = Math.max(1, Math.floor(Math.log2(Math.max(sourceWidth, sourceHeight))) + 1);
|
|
2836
|
+
const levels = [
|
|
2837
|
+
Object.freeze({
|
|
2838
|
+
width: sourceWidth,
|
|
2839
|
+
height: sourceHeight,
|
|
2840
|
+
data: sourcePixels,
|
|
2841
|
+
}),
|
|
2842
|
+
];
|
|
2843
|
+
for (let mipLevel = 1; mipLevel < mipLevelCount; mipLevel += 1) {
|
|
2844
|
+
const width = Math.max(1, sourceWidth >> mipLevel);
|
|
2845
|
+
const height = Math.max(1, sourceHeight >> mipLevel);
|
|
2846
|
+
const roughness = mipLevelCount <= 1 ? 0 : mipLevel / (mipLevelCount - 1);
|
|
2847
|
+
const data = new Float32Array(width * height * 4);
|
|
2848
|
+
const sampleCount = roughness < 0.25 ? 64 : roughness < 0.6 ? 96 : 128;
|
|
2849
|
+
for (let y = 0; y < height; y += 1) {
|
|
2850
|
+
for (let x = 0; x < width; x += 1) {
|
|
2851
|
+
const direction = environmentUvToDirection((x + 0.5) / width, (y + 0.5) / height, environmentMap.rotationRadians);
|
|
2852
|
+
const normal = normalize(direction, [0, 1, 0]);
|
|
2853
|
+
const viewDirection = normal;
|
|
2854
|
+
let totalWeight = 0;
|
|
2855
|
+
const accum = [0, 0, 0];
|
|
2856
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
2857
|
+
const xi = hammersley(sampleIndex, sampleCount);
|
|
2858
|
+
const halfVector = importanceSampleGgx(xi, roughness, normal);
|
|
2859
|
+
const viewDotHalf = Math.max(dot(viewDirection, halfVector), 0);
|
|
2860
|
+
const lightDirection = normalize(
|
|
2861
|
+
subtract(scale(halfVector, 2 * viewDotHalf), viewDirection),
|
|
2862
|
+
normal
|
|
2863
|
+
);
|
|
2864
|
+
const nDotL = Math.max(dot(normal, lightDirection), 0);
|
|
2865
|
+
if (nDotL <= 0.000001) {
|
|
2866
|
+
continue;
|
|
2867
|
+
}
|
|
2868
|
+
const radiance = sampleEnvironmentRadiance(
|
|
2869
|
+
sourcePixels,
|
|
2870
|
+
sourceWidth,
|
|
2871
|
+
sourceHeight,
|
|
2872
|
+
lightDirection,
|
|
2873
|
+
environmentMap.rotationRadians
|
|
2874
|
+
);
|
|
2875
|
+
accum[0] += radiance[0] * nDotL;
|
|
2876
|
+
accum[1] += radiance[1] * nDotL;
|
|
2877
|
+
accum[2] += radiance[2] * nDotL;
|
|
2878
|
+
totalWeight += nDotL;
|
|
2879
|
+
}
|
|
2880
|
+
const offset = (y * width + x) * 4;
|
|
2881
|
+
data[offset] = accum[0] / Math.max(totalWeight, 0.000001);
|
|
2882
|
+
data[offset + 1] = accum[1] / Math.max(totalWeight, 0.000001);
|
|
2883
|
+
data[offset + 2] = accum[2] / Math.max(totalWeight, 0.000001);
|
|
2884
|
+
data[offset + 3] = 1;
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
levels.push(Object.freeze({ width, height, data }));
|
|
2888
|
+
}
|
|
2889
|
+
return Object.freeze({
|
|
2890
|
+
levels,
|
|
2891
|
+
mipLevelCount,
|
|
2892
|
+
width: sourceWidth,
|
|
2893
|
+
height: sourceHeight,
|
|
2894
|
+
});
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
function createEnvironmentSamplingTables(environmentMap, fallbackColor) {
|
|
2898
|
+
if (!environmentMapHasSamplingData(environmentMap)) {
|
|
2899
|
+
return Object.freeze({
|
|
2900
|
+
width: 1,
|
|
2901
|
+
height: 1,
|
|
2902
|
+
pdf: new Float32Array([1]),
|
|
2903
|
+
marginalCdf: new Float32Array([1]),
|
|
2904
|
+
conditionalCdf: new Float32Array([1]),
|
|
2905
|
+
hasImportanceData: false,
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
const pixels = createLinearEnvironmentPixels(environmentMap, fallbackColor);
|
|
2909
|
+
const width = Math.max(1, environmentMap.width);
|
|
2910
|
+
const height = Math.max(1, environmentMap.height);
|
|
2911
|
+
const pdf = new Float32Array(width * height);
|
|
2912
|
+
const marginalCdf = new Float32Array(height);
|
|
2913
|
+
const conditionalCdf = new Float32Array(width * height);
|
|
2914
|
+
const rowSums = new Float32Array(height);
|
|
2915
|
+
let totalWeight = 0;
|
|
2916
|
+
for (let y = 0; y < height; y += 1) {
|
|
2917
|
+
const theta = ((y + 0.5) / height) * Math.PI;
|
|
2918
|
+
const sinTheta = Math.max(Math.sin(theta), 0.0001);
|
|
2919
|
+
let rowWeight = 0;
|
|
2920
|
+
for (let x = 0; x < width; x += 1) {
|
|
2921
|
+
const offset = (y * width + x) * 4;
|
|
2922
|
+
const luminance = pixels[offset] * 0.2126 + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722;
|
|
2923
|
+
const weight = Math.max(luminance * sinTheta, 0.000001);
|
|
2924
|
+
pdf[y * width + x] = weight;
|
|
2925
|
+
rowWeight += weight;
|
|
2926
|
+
conditionalCdf[y * width + x] = rowWeight;
|
|
2927
|
+
}
|
|
2928
|
+
rowSums[y] = rowWeight;
|
|
2929
|
+
totalWeight += rowWeight;
|
|
2930
|
+
if (rowWeight > 0) {
|
|
2931
|
+
for (let x = 0; x < width; x += 1) {
|
|
2932
|
+
conditionalCdf[y * width + x] /= rowWeight;
|
|
2933
|
+
}
|
|
2934
|
+
} else {
|
|
2935
|
+
for (let x = 0; x < width; x += 1) {
|
|
2936
|
+
conditionalCdf[y * width + x] = (x + 1) / width;
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
marginalCdf[y] = totalWeight;
|
|
2940
|
+
}
|
|
2941
|
+
for (let y = 0; y < height; y += 1) {
|
|
2942
|
+
marginalCdf[y] /= Math.max(totalWeight, 0.000001);
|
|
2943
|
+
}
|
|
2944
|
+
for (let index = 0; index < pdf.length; index += 1) {
|
|
2945
|
+
pdf[index] /= Math.max(totalWeight, 0.000001);
|
|
2946
|
+
}
|
|
2947
|
+
return Object.freeze({
|
|
2948
|
+
width,
|
|
2949
|
+
height,
|
|
2950
|
+
pdf,
|
|
2951
|
+
marginalCdf,
|
|
2952
|
+
conditionalCdf,
|
|
2953
|
+
hasImportanceData: true,
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
function createEnvironmentMapUploadBytes(environmentMap, fallbackColor) {
|
|
2958
|
+
const width = Math.max(1, environmentMap.width);
|
|
2959
|
+
const height = Math.max(1, environmentMap.height);
|
|
2960
|
+
const rowBytes = width * 8;
|
|
2961
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
2962
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
2963
|
+
const data = environmentMap.data;
|
|
2964
|
+
const integerScale = environmentMapIntegerScale(data);
|
|
2965
|
+
const view = new DataView(bytes.buffer);
|
|
2966
|
+
const writeComponent = (targetOffset, sourceOffset, fallback) => {
|
|
2967
|
+
view.setUint16(
|
|
2968
|
+
targetOffset,
|
|
2969
|
+
float32ToFloat16Bits(
|
|
2970
|
+
readEnvironmentMapComponent(data, sourceOffset, fallback, integerScale)
|
|
2971
|
+
),
|
|
2972
|
+
true
|
|
2973
|
+
);
|
|
2974
|
+
};
|
|
2975
|
+
|
|
2976
|
+
for (let y = 0; y < height; y += 1) {
|
|
2977
|
+
for (let x = 0; x < width; x += 1) {
|
|
2978
|
+
const sourceOffset = (y * width + x) * 4;
|
|
2979
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
2980
|
+
writeComponent(targetOffset, sourceOffset, fallbackColor[0]);
|
|
2981
|
+
writeComponent(targetOffset + 2, sourceOffset + 1, fallbackColor[1]);
|
|
2982
|
+
writeComponent(targetOffset + 4, sourceOffset + 2, fallbackColor[2]);
|
|
2983
|
+
writeComponent(targetOffset + 6, sourceOffset + 3, fallbackColor[3] ?? 1);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
const upload = Object.freeze({
|
|
2988
|
+
bytes,
|
|
2989
|
+
bytesPerRow,
|
|
2990
|
+
width,
|
|
2991
|
+
height,
|
|
2992
|
+
});
|
|
2993
|
+
return upload;
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
2997
|
+
if (environmentMap.view) {
|
|
2998
|
+
return Object.freeze({
|
|
2999
|
+
view: environmentMap.view,
|
|
3000
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
3001
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
3002
|
+
addressModeU: "repeat",
|
|
3003
|
+
addressModeV: "clamp-to-edge",
|
|
3004
|
+
magFilter: "linear",
|
|
3005
|
+
minFilter: "linear",
|
|
3006
|
+
mipmapFilter: "linear",
|
|
3007
|
+
}),
|
|
3008
|
+
texture: null,
|
|
3009
|
+
ownsTexture: false,
|
|
3010
|
+
width: Math.max(1, environmentMap.width),
|
|
3011
|
+
height: Math.max(1, environmentMap.height),
|
|
3012
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1),
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
if (environmentMap.texture && typeof environmentMap.texture.createView === "function") {
|
|
3017
|
+
return Object.freeze({
|
|
3018
|
+
view: environmentMap.texture.createView(),
|
|
3019
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
3020
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
3021
|
+
addressModeU: "repeat",
|
|
3022
|
+
addressModeV: "clamp-to-edge",
|
|
3023
|
+
magFilter: "linear",
|
|
3024
|
+
minFilter: "linear",
|
|
3025
|
+
mipmapFilter: "linear",
|
|
3026
|
+
}),
|
|
3027
|
+
texture: environmentMap.texture,
|
|
3028
|
+
ownsTexture: false,
|
|
3029
|
+
width: Math.max(1, environmentMap.width),
|
|
3030
|
+
height: Math.max(1, environmentMap.height),
|
|
3031
|
+
mipLevelCount: Math.max(1, environmentMap.mipLevelCount ?? 1),
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
const prefiltered = createPrefilteredEnvironmentLevels(environmentMap, fallbackColor);
|
|
3036
|
+
const uploads = createFloat16RgbaUploadFromLevels(prefiltered.levels);
|
|
3037
|
+
const texture = device.createTexture({
|
|
3038
|
+
label: environmentMap.enabled
|
|
3039
|
+
? "plasius.wavefront.environmentMap"
|
|
3040
|
+
: "plasius.wavefront.environmentMapFallback",
|
|
3041
|
+
size: { width: prefiltered.width, height: prefiltered.height },
|
|
3042
|
+
format: "rgba16float",
|
|
3043
|
+
mipLevelCount: prefiltered.mipLevelCount,
|
|
3044
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST,
|
|
3045
|
+
});
|
|
3046
|
+
uploads.forEach((upload, mipLevel) => {
|
|
3047
|
+
device.queue.writeTexture(
|
|
3048
|
+
{ texture, mipLevel },
|
|
3049
|
+
upload.bytes,
|
|
3050
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3051
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
3052
|
+
);
|
|
3053
|
+
});
|
|
3054
|
+
return Object.freeze({
|
|
3055
|
+
view: texture.createView(),
|
|
3056
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
3057
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
3058
|
+
addressModeU: "repeat",
|
|
3059
|
+
addressModeV: "clamp-to-edge",
|
|
3060
|
+
magFilter: "linear",
|
|
3061
|
+
minFilter: "linear",
|
|
3062
|
+
mipmapFilter: "linear",
|
|
3063
|
+
}),
|
|
3064
|
+
texture,
|
|
3065
|
+
ownsTexture: true,
|
|
3066
|
+
width: prefiltered.width,
|
|
3067
|
+
height: prefiltered.height,
|
|
3068
|
+
mipLevelCount: prefiltered.mipLevelCount,
|
|
3069
|
+
});
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
function createEnvironmentSamplingTextureResource(device, constants, environmentMap, fallbackColor) {
|
|
3073
|
+
const tables = createEnvironmentSamplingTables(environmentMap, fallbackColor);
|
|
3074
|
+
const rowBytes = tables.width * 8;
|
|
3075
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
3076
|
+
const bytes = new Uint8Array(bytesPerRow * tables.height);
|
|
3077
|
+
const view = new DataView(bytes.buffer);
|
|
3078
|
+
for (let y = 0; y < tables.height; y += 1) {
|
|
3079
|
+
for (let x = 0; x < tables.width; x += 1) {
|
|
3080
|
+
const probability = tables.pdf[y * tables.width + x];
|
|
3081
|
+
const conditional = tables.conditionalCdf[y * tables.width + x];
|
|
3082
|
+
const marginal = tables.marginalCdf[y];
|
|
3083
|
+
const offset = y * bytesPerRow + x * 8;
|
|
3084
|
+
view.setUint16(offset, float32ToFloat16Bits(probability), true);
|
|
3085
|
+
view.setUint16(offset + 2, float32ToFloat16Bits(conditional), true);
|
|
3086
|
+
view.setUint16(offset + 4, float32ToFloat16Bits(marginal), true);
|
|
3087
|
+
view.setUint16(offset + 6, float32ToFloat16Bits(1), true);
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
const texture = device.createTexture({
|
|
3091
|
+
label: "plasius.wavefront.environmentSampling",
|
|
3092
|
+
size: { width: tables.width, height: tables.height },
|
|
3093
|
+
format: "rgba16float",
|
|
3094
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST,
|
|
3095
|
+
});
|
|
3096
|
+
device.queue.writeTexture(
|
|
3097
|
+
{ texture },
|
|
3098
|
+
bytes,
|
|
3099
|
+
{ bytesPerRow, rowsPerImage: tables.height },
|
|
3100
|
+
{ width: tables.width, height: tables.height, depthOrArrayLayers: 1 }
|
|
3101
|
+
);
|
|
3102
|
+
return Object.freeze({
|
|
3103
|
+
view: texture.createView(),
|
|
3104
|
+
texture,
|
|
3105
|
+
ownsTexture: true,
|
|
3106
|
+
hasImportanceData: tables.hasImportanceData,
|
|
3107
|
+
});
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE) {
|
|
3111
|
+
const upload = createBrdfLutUploadBytes(size);
|
|
3112
|
+
const texture = device.createTexture({
|
|
3113
|
+
label: "plasius.wavefront.brdfLut",
|
|
2017
3114
|
size: { width: upload.width, height: upload.height },
|
|
2018
3115
|
format: "rgba16float",
|
|
2019
3116
|
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST,
|
|
@@ -2026,15 +3123,38 @@ function createEnvironmentMapResource(device, constants, environmentMap, fallbac
|
|
|
2026
3123
|
);
|
|
2027
3124
|
return Object.freeze({
|
|
2028
3125
|
view: texture.createView(),
|
|
2029
|
-
sampler:
|
|
2030
|
-
label: "plasius.wavefront.
|
|
2031
|
-
addressModeU: "
|
|
3126
|
+
sampler: device.createSampler({
|
|
3127
|
+
label: "plasius.wavefront.brdfLutSampler",
|
|
3128
|
+
addressModeU: "clamp-to-edge",
|
|
2032
3129
|
addressModeV: "clamp-to-edge",
|
|
2033
3130
|
magFilter: "linear",
|
|
2034
3131
|
minFilter: "linear",
|
|
2035
3132
|
}),
|
|
2036
3133
|
texture,
|
|
2037
3134
|
ownsTexture: true,
|
|
3135
|
+
width: upload.width,
|
|
3136
|
+
height: upload.height,
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
3141
|
+
const upload = createRgba8TextureUpload(atlas);
|
|
3142
|
+
const texture = device.createTexture({
|
|
3143
|
+
label,
|
|
3144
|
+
size: { width: upload.width, height: upload.height },
|
|
3145
|
+
format: "rgba8unorm",
|
|
3146
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST,
|
|
3147
|
+
});
|
|
3148
|
+
device.queue.writeTexture(
|
|
3149
|
+
{ texture },
|
|
3150
|
+
upload.bytes,
|
|
3151
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3152
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
3153
|
+
);
|
|
3154
|
+
return Object.freeze({
|
|
3155
|
+
texture,
|
|
3156
|
+
view: texture.createView(),
|
|
3157
|
+
ownsTexture: true,
|
|
2038
3158
|
});
|
|
2039
3159
|
}
|
|
2040
3160
|
|
|
@@ -2084,6 +3204,26 @@ async function createComputePipeline(device, shaderModule, layout, entryPoint, l
|
|
|
2084
3204
|
}
|
|
2085
3205
|
}
|
|
2086
3206
|
|
|
3207
|
+
async function assertShaderModuleCompiles(shaderModule, label) {
|
|
3208
|
+
if (typeof shaderModule?.compilationInfo !== "function") {
|
|
3209
|
+
return;
|
|
3210
|
+
}
|
|
3211
|
+
const info = await shaderModule.compilationInfo();
|
|
3212
|
+
const messages = Array.isArray(info?.messages) ? info.messages : [];
|
|
3213
|
+
const errors = messages.filter((message) => message?.type === "error");
|
|
3214
|
+
if (errors.length <= 0) {
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
const diagnostics = errors
|
|
3218
|
+
.map((message) => {
|
|
3219
|
+
const line = Number.isFinite(message.lineNum) ? message.lineNum : "?";
|
|
3220
|
+
const column = Number.isFinite(message.linePos) ? message.linePos : "?";
|
|
3221
|
+
return `line ${line}:${column} ${message.message}`;
|
|
3222
|
+
})
|
|
3223
|
+
.join("\n");
|
|
3224
|
+
throw new Error(`WGSL compilation preflight failed for ${label}:\n${diagnostics}`);
|
|
3225
|
+
}
|
|
3226
|
+
|
|
2087
3227
|
async function createRenderPipeline(device, descriptor) {
|
|
2088
3228
|
if (typeof device.createRenderPipelineAsync === "function") {
|
|
2089
3229
|
return device.createRenderPipelineAsync(descriptor);
|
|
@@ -2093,6 +3233,7 @@ async function createRenderPipeline(device, descriptor) {
|
|
|
2093
3233
|
|
|
2094
3234
|
const WAVEFRONT_COMPUTE_WGSL = `
|
|
2095
3235
|
const RAY_FLAG_GUIDED_EMISSIVE: u32 = 1u;
|
|
3236
|
+
const RAY_FLAG_DELTA_SAMPLE: u32 = 2u;
|
|
2096
3237
|
|
|
2097
3238
|
struct RayRecord {
|
|
2098
3239
|
rayId: u32,
|
|
@@ -2118,11 +3259,12 @@ struct HitRecord {
|
|
|
2118
3259
|
primitiveId: u32,
|
|
2119
3260
|
materialRefId: u32,
|
|
2120
3261
|
mediumRefId: u32,
|
|
3262
|
+
materialSlot: u32,
|
|
2121
3263
|
pad0: u32,
|
|
2122
3264
|
pad1: u32,
|
|
2123
|
-
pad2: u32,
|
|
2124
3265
|
distance: f32,
|
|
2125
|
-
|
|
3266
|
+
occlusion: f32,
|
|
3267
|
+
pad2: vec2<f32>,
|
|
2126
3268
|
position: vec4<f32>,
|
|
2127
3269
|
geometricNormal: vec4<f32>,
|
|
2128
3270
|
shadingNormal: vec4<f32>,
|
|
@@ -2131,6 +3273,9 @@ struct HitRecord {
|
|
|
2131
3273
|
color: vec4<f32>,
|
|
2132
3274
|
emission: vec4<f32>,
|
|
2133
3275
|
material: vec4<f32>,
|
|
3276
|
+
materialResponse: vec4<f32>,
|
|
3277
|
+
materialExtension: vec4<f32>,
|
|
3278
|
+
specularColor: vec4<f32>,
|
|
2134
3279
|
};
|
|
2135
3280
|
|
|
2136
3281
|
struct SceneObject {
|
|
@@ -2143,6 +3288,9 @@ struct SceneObject {
|
|
|
2143
3288
|
color: vec4<f32>,
|
|
2144
3289
|
emission: vec4<f32>,
|
|
2145
3290
|
material: vec4<f32>,
|
|
3291
|
+
materialResponse: vec4<f32>,
|
|
3292
|
+
materialExtension: vec4<f32>,
|
|
3293
|
+
specularColor: vec4<f32>,
|
|
2146
3294
|
};
|
|
2147
3295
|
|
|
2148
3296
|
struct TriangleRecord {
|
|
@@ -2152,7 +3300,7 @@ struct TriangleRecord {
|
|
|
2152
3300
|
flags: u32,
|
|
2153
3301
|
materialRefId: u32,
|
|
2154
3302
|
mediumRefId: u32,
|
|
2155
|
-
|
|
3303
|
+
materialSlot: u32,
|
|
2156
3304
|
pad1: u32,
|
|
2157
3305
|
v0: vec4<f32>,
|
|
2158
3306
|
v1: vec4<f32>,
|
|
@@ -2165,6 +3313,15 @@ struct TriangleRecord {
|
|
|
2165
3313
|
color: vec4<f32>,
|
|
2166
3314
|
emission: vec4<f32>,
|
|
2167
3315
|
material: vec4<f32>,
|
|
3316
|
+
materialResponse: vec4<f32>,
|
|
3317
|
+
materialExtension: vec4<f32>,
|
|
3318
|
+
specularColor: vec4<f32>,
|
|
3319
|
+
baseColorAtlas: vec4<f32>,
|
|
3320
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3321
|
+
normalAtlas: vec4<f32>,
|
|
3322
|
+
occlusionAtlas: vec4<f32>,
|
|
3323
|
+
emissiveAtlas: vec4<f32>,
|
|
3324
|
+
textureSettings: vec4<f32>,
|
|
2168
3325
|
};
|
|
2169
3326
|
|
|
2170
3327
|
struct BvhNode {
|
|
@@ -2185,10 +3342,10 @@ struct BvhLeafRef {
|
|
|
2185
3342
|
|
|
2186
3343
|
struct ScatterResult {
|
|
2187
3344
|
direction: vec4<f32>,
|
|
3345
|
+
pdf: f32,
|
|
2188
3346
|
flags: u32,
|
|
2189
3347
|
pad0: u32,
|
|
2190
3348
|
pad1: u32,
|
|
2191
|
-
pad2: u32,
|
|
2192
3349
|
};
|
|
2193
3350
|
|
|
2194
3351
|
struct MeshVertex {
|
|
@@ -2209,10 +3366,19 @@ struct MeshRange {
|
|
|
2209
3366
|
triangleCount: u32,
|
|
2210
3367
|
firstVertex: u32,
|
|
2211
3368
|
vertexCount: u32,
|
|
2212
|
-
|
|
3369
|
+
materialSlot: u32,
|
|
2213
3370
|
color: vec4<f32>,
|
|
2214
3371
|
emission: vec4<f32>,
|
|
2215
3372
|
material: vec4<f32>,
|
|
3373
|
+
materialResponse: vec4<f32>,
|
|
3374
|
+
materialExtension: vec4<f32>,
|
|
3375
|
+
specularColor: vec4<f32>,
|
|
3376
|
+
baseColorAtlas: vec4<f32>,
|
|
3377
|
+
metallicRoughnessAtlas: vec4<f32>,
|
|
3378
|
+
normalAtlas: vec4<f32>,
|
|
3379
|
+
occlusionAtlas: vec4<f32>,
|
|
3380
|
+
emissiveAtlas: vec4<f32>,
|
|
3381
|
+
textureSettings: vec4<f32>,
|
|
2216
3382
|
};
|
|
2217
3383
|
|
|
2218
3384
|
struct FrameConfig {
|
|
@@ -2253,6 +3419,7 @@ struct FrameConfig {
|
|
|
2253
3419
|
_portalPad1: u32,
|
|
2254
3420
|
environmentMapSettings: vec4<f32>,
|
|
2255
3421
|
pathResolveSettings: vec4<f32>,
|
|
3422
|
+
environmentMapMeta: vec4<f32>,
|
|
2256
3423
|
};
|
|
2257
3424
|
|
|
2258
3425
|
struct Counters {
|
|
@@ -2315,6 +3482,15 @@ struct EnvironmentPortal {
|
|
|
2315
3482
|
@group(0) @binding(20) var environmentMapTexture: texture_2d<f32>;
|
|
2316
3483
|
@group(0) @binding(21) var environmentMapSampler: sampler;
|
|
2317
3484
|
@group(0) @binding(22) var<storage, read_write> pathVertices: array<vec4<f32>>;
|
|
3485
|
+
@group(0) @binding(23) var baseColorAtlasTexture: texture_2d<f32>;
|
|
3486
|
+
@group(0) @binding(24) var metallicRoughnessAtlasTexture: texture_2d<f32>;
|
|
3487
|
+
@group(0) @binding(25) var normalAtlasTexture: texture_2d<f32>;
|
|
3488
|
+
@group(0) @binding(26) var occlusionAtlasTexture: texture_2d<f32>;
|
|
3489
|
+
@group(0) @binding(27) var emissiveAtlasTexture: texture_2d<f32>;
|
|
3490
|
+
@group(0) @binding(28) var materialAtlasSampler: sampler;
|
|
3491
|
+
@group(0) @binding(29) var brdfLutTexture: texture_2d<f32>;
|
|
3492
|
+
@group(0) @binding(30) var brdfLutSampler: sampler;
|
|
3493
|
+
@group(0) @binding(31) var environmentSamplingTexture: texture_2d<f32>;
|
|
2318
3494
|
|
|
2319
3495
|
fn hash_u32(value: u32) -> u32 {
|
|
2320
3496
|
var x = value;
|
|
@@ -2351,6 +3527,146 @@ fn safe_normalize(value: vec3<f32>, fallback: vec3<f32>) -> vec3<f32> {
|
|
|
2351
3527
|
return value / len;
|
|
2352
3528
|
}
|
|
2353
3529
|
|
|
3530
|
+
struct TangentBasis {
|
|
3531
|
+
tangent: vec3<f32>,
|
|
3532
|
+
bitangent: vec3<f32>,
|
|
3533
|
+
};
|
|
3534
|
+
|
|
3535
|
+
struct SurfaceMaterialSample {
|
|
3536
|
+
color: vec4<f32>,
|
|
3537
|
+
emission: vec4<f32>,
|
|
3538
|
+
material: vec4<f32>,
|
|
3539
|
+
materialResponse: vec4<f32>,
|
|
3540
|
+
materialExtension: vec4<f32>,
|
|
3541
|
+
specularColor: vec4<f32>,
|
|
3542
|
+
shadingNormal: vec3<f32>,
|
|
3543
|
+
occlusion: f32,
|
|
3544
|
+
};
|
|
3545
|
+
|
|
3546
|
+
fn srgb_to_linear_channel(value: f32) -> f32 {
|
|
3547
|
+
if (value <= 0.04045) {
|
|
3548
|
+
return value / 12.92;
|
|
3549
|
+
}
|
|
3550
|
+
return pow((value + 0.055) / 1.055, 2.4);
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
fn srgb_to_linear_vec3(value: vec3<f32>) -> vec3<f32> {
|
|
3554
|
+
return vec3<f32>(
|
|
3555
|
+
srgb_to_linear_channel(value.x),
|
|
3556
|
+
srgb_to_linear_channel(value.y),
|
|
3557
|
+
srgb_to_linear_channel(value.z)
|
|
3558
|
+
);
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
fn wrap_uv(uv: vec2<f32>) -> vec2<f32> {
|
|
3562
|
+
return fract(fract(uv) + vec2<f32>(1.0));
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
fn atlas_sample_uv(rect: vec4<f32>, uv: vec2<f32>) -> vec2<f32> {
|
|
3566
|
+
let local = wrap_uv(uv);
|
|
3567
|
+
let clamped = clamp(local, vec2<f32>(0.001), vec2<f32>(0.999));
|
|
3568
|
+
return rect.xy + clamped * rect.zw;
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
fn sample_atlas(textureRef: texture_2d<f32>, rect: vec4<f32>, uv: vec2<f32>) -> vec4<f32> {
|
|
3572
|
+
return textureSampleLevel(textureRef, materialAtlasSampler, atlas_sample_uv(rect, uv), 0.0);
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
fn build_triangle_tangent_basis(
|
|
3576
|
+
triangle: TriangleRecord,
|
|
3577
|
+
fallbackNormal: vec3<f32>
|
|
3578
|
+
) -> TangentBasis {
|
|
3579
|
+
let edge1 = triangle.v1.xyz - triangle.v0.xyz;
|
|
3580
|
+
let edge2 = triangle.v2.xyz - triangle.v0.xyz;
|
|
3581
|
+
let uv0 = triangle.uv0uv1.xy;
|
|
3582
|
+
let uv1 = triangle.uv0uv1.zw;
|
|
3583
|
+
let uv2 = triangle.uv2Pad.xy;
|
|
3584
|
+
let deltaUv1 = uv1 - uv0;
|
|
3585
|
+
let deltaUv2 = uv2 - uv0;
|
|
3586
|
+
let determinant = deltaUv1.x * deltaUv2.y - deltaUv1.y * deltaUv2.x;
|
|
3587
|
+
if (abs(determinant) <= 0.000001) {
|
|
3588
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(fallbackNormal.y) >= 0.999);
|
|
3589
|
+
let tangent = safe_normalize(cross(tangentFallback, fallbackNormal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3590
|
+
let bitangent = safe_normalize(cross(fallbackNormal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3591
|
+
return TangentBasis(tangent, bitangent);
|
|
3592
|
+
}
|
|
3593
|
+
let inverse = 1.0 / determinant;
|
|
3594
|
+
let tangent = safe_normalize(
|
|
3595
|
+
inverse * (edge1 * deltaUv2.y - edge2 * deltaUv1.y),
|
|
3596
|
+
vec3<f32>(1.0, 0.0, 0.0)
|
|
3597
|
+
);
|
|
3598
|
+
let bitangent = safe_normalize(
|
|
3599
|
+
inverse * (-edge1 * deltaUv2.x + edge2 * deltaUv1.x),
|
|
3600
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3601
|
+
);
|
|
3602
|
+
return TangentBasis(tangent, bitangent);
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
fn sample_surface_material(
|
|
3606
|
+
triangle: TriangleRecord,
|
|
3607
|
+
uv: vec2<f32>,
|
|
3608
|
+
geometricNormal: vec3<f32>,
|
|
3609
|
+
shadingNormal: vec3<f32>
|
|
3610
|
+
) -> SurfaceMaterialSample {
|
|
3611
|
+
let baseColorTexel = sample_atlas(baseColorAtlasTexture, triangle.baseColorAtlas, uv);
|
|
3612
|
+
let baseColor = vec4<f32>(
|
|
3613
|
+
clamp(triangle.color.rgb * srgb_to_linear_vec3(baseColorTexel.rgb), vec3<f32>(0.0), vec3<f32>(1.0)),
|
|
3614
|
+
clamp(triangle.color.a * baseColorTexel.a, 0.0, 1.0)
|
|
3615
|
+
);
|
|
3616
|
+
let metallicRoughnessTexel = sample_atlas(
|
|
3617
|
+
metallicRoughnessAtlasTexture,
|
|
3618
|
+
triangle.metallicRoughnessAtlas,
|
|
3619
|
+
uv
|
|
3620
|
+
);
|
|
3621
|
+
let normalTexel = sample_atlas(normalAtlasTexture, triangle.normalAtlas, uv);
|
|
3622
|
+
let occlusionTexel = sample_atlas(occlusionAtlasTexture, triangle.occlusionAtlas, uv);
|
|
3623
|
+
let emissiveTexel = sample_atlas(emissiveAtlasTexture, triangle.emissiveAtlas, uv);
|
|
3624
|
+
let normalScale = clamp(triangle.textureSettings.x, 0.0, 1.0);
|
|
3625
|
+
let tangentBasis = build_triangle_tangent_basis(triangle, geometricNormal);
|
|
3626
|
+
let tangentNormal = safe_normalize(
|
|
3627
|
+
vec3<f32>(
|
|
3628
|
+
(normalTexel.x * 2.0 - 1.0) * normalScale,
|
|
3629
|
+
(normalTexel.y * 2.0 - 1.0) * normalScale,
|
|
3630
|
+
1.0 + ((normalTexel.z * 2.0 - 1.0) - 1.0) * normalScale
|
|
3631
|
+
),
|
|
3632
|
+
vec3<f32>(0.0, 0.0, 1.0)
|
|
3633
|
+
);
|
|
3634
|
+
let mappedNormal = safe_normalize(
|
|
3635
|
+
tangentBasis.tangent * tangentNormal.x +
|
|
3636
|
+
tangentBasis.bitangent * tangentNormal.y +
|
|
3637
|
+
shadingNormal * tangentNormal.z,
|
|
3638
|
+
shadingNormal
|
|
3639
|
+
);
|
|
3640
|
+
let emission = vec4<f32>(
|
|
3641
|
+
max(
|
|
3642
|
+
triangle.emission.rgb *
|
|
3643
|
+
srgb_to_linear_vec3(emissiveTexel.rgb) *
|
|
3644
|
+
max(triangle.textureSettings.z, 0.0),
|
|
3645
|
+
vec3<f32>(0.0)
|
|
3646
|
+
),
|
|
3647
|
+
clamp(triangle.emission.a * emissiveTexel.a, 0.0, 1.0)
|
|
3648
|
+
);
|
|
3649
|
+
return SurfaceMaterialSample(
|
|
3650
|
+
baseColor,
|
|
3651
|
+
emission,
|
|
3652
|
+
vec4<f32>(
|
|
3653
|
+
clamp(triangle.material.x * metallicRoughnessTexel.y, 0.0, 1.0),
|
|
3654
|
+
clamp(triangle.material.y * metallicRoughnessTexel.z, 0.0, 1.0),
|
|
3655
|
+
clamp(triangle.material.z * baseColor.a, 0.0, 1.0),
|
|
3656
|
+
clamp(triangle.material.w, 1.0, 3.0)
|
|
3657
|
+
),
|
|
3658
|
+
triangle.materialResponse,
|
|
3659
|
+
triangle.materialExtension,
|
|
3660
|
+
triangle.specularColor,
|
|
3661
|
+
repair_shading_normal(geometricNormal, mappedNormal),
|
|
3662
|
+
clamp(
|
|
3663
|
+
mix(1.0, occlusionTexel.x, clamp(triangle.textureSettings.y, 0.0, 1.0)),
|
|
3664
|
+
0.0,
|
|
3665
|
+
1.0
|
|
3666
|
+
)
|
|
3667
|
+
);
|
|
3668
|
+
}
|
|
3669
|
+
|
|
2354
3670
|
fn saturate(value: f32) -> f32 {
|
|
2355
3671
|
return clamp(value, 0.0, 1.0);
|
|
2356
3672
|
}
|
|
@@ -2359,6 +3675,10 @@ fn max_component(value: vec3<f32>) -> f32 {
|
|
|
2359
3675
|
return max(max(value.x, value.y), value.z);
|
|
2360
3676
|
}
|
|
2361
3677
|
|
|
3678
|
+
fn radiance_luminance(value: vec3<f32>) -> f32 {
|
|
3679
|
+
return dot(value, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
3680
|
+
}
|
|
3681
|
+
|
|
2362
3682
|
fn environment_map_enabled() -> bool {
|
|
2363
3683
|
return config.environmentMapSettings.x > 0.5;
|
|
2364
3684
|
}
|
|
@@ -2487,6 +3807,343 @@ fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
|
2487
3807
|
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
2488
3808
|
}
|
|
2489
3809
|
|
|
3810
|
+
fn direct_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
3811
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3812
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
3813
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
3814
|
+
if (
|
|
3815
|
+
config.environmentPortalCount > 0u &&
|
|
3816
|
+
config.environmentPortalMode == 2u &&
|
|
3817
|
+
!portalHit
|
|
3818
|
+
) {
|
|
3819
|
+
return vec3<f32>(0.0);
|
|
3820
|
+
}
|
|
3821
|
+
return base_environment_radiance(rayDirection) *
|
|
3822
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
fn radical_inverse_vdc(bitsValue: u32) -> f32 {
|
|
3826
|
+
var bits = bitsValue;
|
|
3827
|
+
bits = (bits << 16u) | (bits >> 16u);
|
|
3828
|
+
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xaaaaaaaau) >> 1u);
|
|
3829
|
+
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xccccccccu) >> 2u);
|
|
3830
|
+
bits = ((bits & 0x0f0f0f0fu) << 4u) | ((bits & 0xf0f0f0f0u) >> 4u);
|
|
3831
|
+
bits = ((bits & 0x00ff00ffu) << 8u) | ((bits & 0xff00ff00u) >> 8u);
|
|
3832
|
+
return f32(bits) * 2.3283064365386963e-10;
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
fn hammersley_2d(index: u32, count: u32) -> vec2<f32> {
|
|
3836
|
+
return vec2<f32>(f32(index) / max(f32(count), 1.0), radical_inverse_vdc(index));
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
fn build_basis_tangent(normal: vec3<f32>) -> vec3<f32> {
|
|
3840
|
+
let tangentFallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(normal.y) >= 0.999);
|
|
3841
|
+
return safe_normalize(cross(tangentFallback, normal), vec3<f32>(1.0, 0.0, 0.0));
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
fn local_to_world(local: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
3845
|
+
let tangent = build_basis_tangent(normal);
|
|
3846
|
+
let bitangent = safe_normalize(cross(normal, tangent), vec3<f32>(0.0, 0.0, 1.0));
|
|
3847
|
+
return safe_normalize(tangent * local.x + bitangent * local.y + normal * local.z, normal);
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
fn cosine_sample_hemisphere(sample: vec2<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
3851
|
+
let phi = 6.28318530718 * sample.x;
|
|
3852
|
+
let radius = sqrt(sample.y);
|
|
3853
|
+
let x = cos(phi) * radius;
|
|
3854
|
+
let y = sin(phi) * radius;
|
|
3855
|
+
let z = sqrt(max(0.0, 1.0 - sample.y));
|
|
3856
|
+
return local_to_world(vec3<f32>(x, y, z), normal);
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
fn importance_sample_ggx(sample: vec2<f32>, roughness: f32, normal: vec3<f32>) -> vec3<f32> {
|
|
3860
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
3861
|
+
let phi = 6.28318530718 * sample.x;
|
|
3862
|
+
let cosTheta = sqrt((1.0 - sample.y) / max(1.0 + (alpha * alpha - 1.0) * sample.y, 0.0001));
|
|
3863
|
+
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3864
|
+
let localHalf = vec3<f32>(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
|
3865
|
+
return local_to_world(localHalf, normal);
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
fn distribution_ggx(normal: vec3<f32>, halfVector: vec3<f32>, roughness: f32) -> f32 {
|
|
3869
|
+
let alpha = max(roughness * roughness, 0.0001);
|
|
3870
|
+
let alpha2 = alpha * alpha;
|
|
3871
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
3872
|
+
let denominator = nDotH * nDotH * (alpha2 - 1.0) + 1.0;
|
|
3873
|
+
return alpha2 / max(3.14159265359 * denominator * denominator, 0.000001);
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
fn geometry_schlick_ggx(nDotValue: f32, roughness: f32) -> f32 {
|
|
3877
|
+
let k = ((roughness + 1.0) * (roughness + 1.0)) / 8.0;
|
|
3878
|
+
return nDotValue / max(nDotValue * (1.0 - k) + k, 0.000001);
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
fn geometry_smith(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
3882
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
3883
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
3884
|
+
return geometry_schlick_ggx(nDotV, roughness) * geometry_schlick_ggx(nDotL, roughness);
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
fn fresnel_schlick(cosine: f32, f0: vec3<f32>) -> vec3<f32> {
|
|
3888
|
+
return f0 + (vec3<f32>(1.0) - f0) * pow(1.0 - cosine, 5.0);
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
fn sample_brdf_lut(nDotV: f32, roughness: f32) -> vec2<f32> {
|
|
3892
|
+
let uv = vec2<f32>(clamp(nDotV, 0.0, 1.0), clamp(roughness, 0.0, 1.0));
|
|
3893
|
+
return textureSampleLevel(brdfLutTexture, brdfLutSampler, uv, 0.0).xy;
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3896
|
+
fn prefiltered_environment_radiance(direction: vec3<f32>, roughness: f32) -> vec3<f32> {
|
|
3897
|
+
let uv = environment_map_uv(direction);
|
|
3898
|
+
let maxLevel = max(config.environmentMapMeta.z - 1.0, 0.0);
|
|
3899
|
+
let lod = clamp(roughness, 0.0, 1.0) * maxLevel;
|
|
3900
|
+
let texel = max(textureSampleLevel(environmentMapTexture, environmentMapSampler, uv, lod).rgb, vec3<f32>(0.0));
|
|
3901
|
+
return texel * max(config.environmentMapSettings.y, 0.0);
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
fn environment_pdf_dimensions() -> vec2<u32> {
|
|
3905
|
+
return vec2<u32>(
|
|
3906
|
+
max(u32(config.environmentMapMeta.x), 1u),
|
|
3907
|
+
max(u32(config.environmentMapMeta.y), 1u)
|
|
3908
|
+
);
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
fn environment_importance_sampling_enabled() -> bool {
|
|
3912
|
+
return config.environmentMapMeta.w > 0.5;
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
fn uniform_sphere_pdf() -> f32 {
|
|
3916
|
+
return 1.0 / (4.0 * 3.14159265359);
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
fn sample_uniform_sphere_direction(sample: vec2<f32>) -> vec3<f32> {
|
|
3920
|
+
let z = 1.0 - 2.0 * sample.y;
|
|
3921
|
+
let radial = sqrt(max(1.0 - z * z, 0.0));
|
|
3922
|
+
let phi = sample.x * 6.28318530718;
|
|
3923
|
+
return vec3<f32>(cos(phi) * radial, z, sin(phi) * radial);
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
fn environment_sampling_texel(x: u32, y: u32) -> vec4<f32> {
|
|
3927
|
+
return textureLoad(environmentSamplingTexture, vec2<i32>(i32(x), i32(y)), 0);
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
fn environment_pdf_texel(x: u32, y: u32) -> f32 {
|
|
3931
|
+
return environment_sampling_texel(x, y).x;
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
fn environment_row_cdf_texel(y: u32) -> f32 {
|
|
3935
|
+
return environment_sampling_texel(0u, y).z;
|
|
3936
|
+
}
|
|
3937
|
+
|
|
3938
|
+
fn environment_column_cdf_texel(x: u32, y: u32) -> f32 {
|
|
3939
|
+
return environment_sampling_texel(x, y).y;
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
fn environment_direction_pdf(direction: vec3<f32>) -> f32 {
|
|
3943
|
+
if (!environment_importance_sampling_enabled()) {
|
|
3944
|
+
return uniform_sphere_pdf();
|
|
3945
|
+
}
|
|
3946
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
3947
|
+
let uv = environment_map_uv(rayDirection);
|
|
3948
|
+
let dimensions = environment_pdf_dimensions();
|
|
3949
|
+
let width = max(f32(dimensions.x), 1.0);
|
|
3950
|
+
let height = max(f32(dimensions.y), 1.0);
|
|
3951
|
+
let x = min(u32(uv.x * width), dimensions.x - 1u);
|
|
3952
|
+
let y = min(u32(uv.y * height), dimensions.y - 1u);
|
|
3953
|
+
let discretePdf = max(environment_pdf_texel(x, y), 0.0);
|
|
3954
|
+
let sinTheta = sqrt(max(1.0 - rayDirection.y * rayDirection.y, 0.0));
|
|
3955
|
+
let solidAngle = max((2.0 * 3.14159265359 * 3.14159265359 * sinTheta) / (width * height), 0.000001);
|
|
3956
|
+
return discretePdf / solidAngle;
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
fn sample_row_cdf(count: u32, sampleValue: f32) -> u32 {
|
|
3960
|
+
if (count == 0u) {
|
|
3961
|
+
return 0u;
|
|
3962
|
+
}
|
|
3963
|
+
var low = 0u;
|
|
3964
|
+
var high = count - 1u;
|
|
3965
|
+
loop {
|
|
3966
|
+
if (low >= high) {
|
|
3967
|
+
break;
|
|
3968
|
+
}
|
|
3969
|
+
let mid = (low + high) / 2u;
|
|
3970
|
+
let cdfValue = environment_row_cdf_texel(mid);
|
|
3971
|
+
if (sampleValue <= cdfValue) {
|
|
3972
|
+
high = mid;
|
|
3973
|
+
} else {
|
|
3974
|
+
low = mid + 1u;
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
return min(low, count - 1u);
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
fn sample_column_cdf(row: u32, count: u32, sampleValue: f32) -> u32 {
|
|
3981
|
+
if (count == 0u) {
|
|
3982
|
+
return 0u;
|
|
3983
|
+
}
|
|
3984
|
+
var low = 0u;
|
|
3985
|
+
var high = count - 1u;
|
|
3986
|
+
loop {
|
|
3987
|
+
if (low >= high) {
|
|
3988
|
+
break;
|
|
3989
|
+
}
|
|
3990
|
+
let mid = (low + high) / 2u;
|
|
3991
|
+
let cdfValue = environment_column_cdf_texel(mid, row);
|
|
3992
|
+
if (sampleValue <= cdfValue) {
|
|
3993
|
+
high = mid;
|
|
3994
|
+
} else {
|
|
3995
|
+
low = mid + 1u;
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
return min(low, count - 1u);
|
|
3999
|
+
}
|
|
4000
|
+
|
|
4001
|
+
struct EnvironmentSample {
|
|
4002
|
+
direction: vec3<f32>,
|
|
4003
|
+
radiance: vec3<f32>,
|
|
4004
|
+
pdf: f32,
|
|
4005
|
+
};
|
|
4006
|
+
|
|
4007
|
+
fn sample_environment_importance(sample: vec2<f32>) -> EnvironmentSample {
|
|
4008
|
+
if (!environment_importance_sampling_enabled()) {
|
|
4009
|
+
let direction = sample_uniform_sphere_direction(sample);
|
|
4010
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), uniform_sphere_pdf());
|
|
4011
|
+
}
|
|
4012
|
+
let dimensions = environment_pdf_dimensions();
|
|
4013
|
+
let row = sample_row_cdf(dimensions.y, sample.y);
|
|
4014
|
+
let column = sample_column_cdf(row, dimensions.x, sample.x);
|
|
4015
|
+
let uv = vec2<f32>(
|
|
4016
|
+
(f32(column) + 0.5) / max(f32(dimensions.x), 1.0),
|
|
4017
|
+
(f32(row) + 0.5) / max(f32(dimensions.y), 1.0)
|
|
4018
|
+
);
|
|
4019
|
+
let theta = uv.y * 3.14159265359;
|
|
4020
|
+
let phi = (uv.x - 0.5 - config.environmentMapSettings.z / 6.28318530718) * 6.28318530718;
|
|
4021
|
+
let sinTheta = sin(theta);
|
|
4022
|
+
let direction = vec3<f32>(cos(phi) * sinTheta, cos(theta), sin(phi) * sinTheta);
|
|
4023
|
+
let pdf = environment_direction_pdf(direction);
|
|
4024
|
+
return EnvironmentSample(direction, base_environment_radiance(direction), pdf);
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
fn power_heuristic(pdfA: f32, pdfB: f32) -> f32 {
|
|
4028
|
+
let a2 = pdfA * pdfA;
|
|
4029
|
+
let b2 = pdfB * pdfB;
|
|
4030
|
+
return a2 / max(a2 + b2, 0.000001);
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
fn visible_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
4034
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4035
|
+
let visible = !scene_visibility_blocked(origin, rayDirection, 1000000.0);
|
|
4036
|
+
return select(vec3<f32>(0.0), direct_environment_radiance(origin, rayDirection), visible);
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
fn glossy_environment_direction(
|
|
4040
|
+
incidentDirection: vec3<f32>,
|
|
4041
|
+
normal: vec3<f32>,
|
|
4042
|
+
roughness: f32,
|
|
4043
|
+
normalBlendScale: f32
|
|
4044
|
+
) -> vec3<f32> {
|
|
4045
|
+
let reflectionDirection = reflect(incidentDirection, normal);
|
|
4046
|
+
let blend = clamp(roughness * roughness * normalBlendScale, 0.0, 0.92);
|
|
4047
|
+
return safe_normalize(mix(reflectionDirection, normal, blend), normal);
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
fn surface_glossiness(hit: HitRecord) -> f32 {
|
|
4051
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4052
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4053
|
+
let sheen = clamp(max_component(hit.materialResponse.xyz), 0.0, 1.0);
|
|
4054
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4055
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4056
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
4057
|
+
let baseGloss =
|
|
4058
|
+
max(
|
|
4059
|
+
clearcoat,
|
|
4060
|
+
max(sheen * 0.72, max(specularWeight * (0.38 + metallic * 0.62), transmission))
|
|
4061
|
+
);
|
|
4062
|
+
return clamp(baseGloss * (1.0 - roughness * 0.72) + metallic * (1.0 - roughness) * 0.35, 0.0, 1.0);
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
fn surface_specular_f0(hit: HitRecord, surfaceColor: vec3<f32>) -> vec3<f32> {
|
|
4066
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4067
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4068
|
+
let specularColor = clamp(hit.specularColor.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4069
|
+
let dielectricF0 = vec3<f32>(0.04) * specularWeight * specularColor;
|
|
4070
|
+
return mix(dielectricF0, surfaceColor, metallic);
|
|
4071
|
+
}
|
|
4072
|
+
|
|
4073
|
+
fn surface_bsdf_sampling_weights(hit: HitRecord) -> vec3<f32> {
|
|
4074
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4075
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4076
|
+
let specularWeight = clamp(hit.materialExtension.y, 0.0, 1.0);
|
|
4077
|
+
let diffuseWeight = clamp(
|
|
4078
|
+
(1.0 - metallic) * max(1.0 - specularWeight * 0.5 - clearcoat * 0.25, 0.15),
|
|
4079
|
+
0.0,
|
|
4080
|
+
1.0
|
|
4081
|
+
);
|
|
4082
|
+
let specWeight = clamp(max(metallic, specularWeight * 0.75) * (1.0 - clearcoat * 0.5), 0.0, 1.0);
|
|
4083
|
+
let clearcoatWeight = clamp(clearcoat, 0.0, 1.0);
|
|
4084
|
+
let totalWeight = max(diffuseWeight + specWeight + clearcoatWeight, 0.000001);
|
|
4085
|
+
return vec3<f32>(
|
|
4086
|
+
diffuseWeight / totalWeight,
|
|
4087
|
+
specWeight / totalWeight,
|
|
4088
|
+
clearcoatWeight / totalWeight
|
|
4089
|
+
);
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4092
|
+
fn evaluate_surface_bsdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> vec3<f32> {
|
|
4093
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4094
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4095
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4096
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
4097
|
+
let clearcoat = clamp(hit.materialResponse.w, 0.0, 1.0);
|
|
4098
|
+
let clearcoatRoughness = clamp(hit.materialExtension.x, 0.0, 1.0);
|
|
4099
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
4100
|
+
let nDotV = saturate(dot(normal, viewDirection));
|
|
4101
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4102
|
+
if (nDotV <= 0.0 || nDotL <= 0.0) {
|
|
4103
|
+
return vec3<f32>(0.0);
|
|
4104
|
+
}
|
|
4105
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
4106
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
4107
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
4108
|
+
let fresnel = fresnel_schlick(vDotH, f0);
|
|
4109
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
4110
|
+
let geometry = geometry_smith(normal, viewDirection, lightDirection, roughness);
|
|
4111
|
+
let specular = (distribution * geometry * fresnel) / max(4.0 * nDotV * nDotL, 0.000001);
|
|
4112
|
+
let diffuseWeight = (1.0 - metallic) * (1.0 - clearcoat * 0.24) * (1.0 - clamp(max_component(fresnel), 0.0, 0.98));
|
|
4113
|
+
let diffuse = surfaceColor * diffuseWeight / 3.14159265359;
|
|
4114
|
+
let clearcoatHalf = safe_normalize(viewDirection + lightDirection, normal);
|
|
4115
|
+
let clearcoatDistribution = distribution_ggx(normal, clearcoatHalf, max(clearcoatRoughness, 0.02));
|
|
4116
|
+
let clearcoatGeometry = geometry_smith(normal, viewDirection, lightDirection, max(clearcoatRoughness, 0.02));
|
|
4117
|
+
let clearcoatFresnel = fresnel_schlick(saturate(dot(viewDirection, clearcoatHalf)), vec3<f32>(0.04));
|
|
4118
|
+
let clearcoatTerm =
|
|
4119
|
+
(clearcoatDistribution * clearcoatGeometry * clearcoatFresnel) /
|
|
4120
|
+
max(4.0 * nDotV * nDotL, 0.000001) *
|
|
4121
|
+
clearcoat;
|
|
4122
|
+
return (diffuse + specular + clearcoatTerm) * mix(0.42, 1.0, occlusion);
|
|
4123
|
+
}
|
|
4124
|
+
|
|
4125
|
+
fn diffuse_pdf(normal: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
4126
|
+
return saturate(dot(normal, lightDirection)) / 3.14159265359;
|
|
4127
|
+
}
|
|
4128
|
+
|
|
4129
|
+
fn ggx_pdf(normal: vec3<f32>, viewDirection: vec3<f32>, lightDirection: vec3<f32>, roughness: f32) -> f32 {
|
|
4130
|
+
let halfVector = safe_normalize(viewDirection + lightDirection, normal);
|
|
4131
|
+
let nDotH = saturate(dot(normal, halfVector));
|
|
4132
|
+
let vDotH = saturate(dot(viewDirection, halfVector));
|
|
4133
|
+
let distribution = distribution_ggx(normal, halfVector, roughness);
|
|
4134
|
+
return (distribution * nDotH) / max(4.0 * vDotH, 0.000001);
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4137
|
+
fn evaluate_surface_bsdf_pdf(hit: HitRecord, viewDirection: vec3<f32>, lightDirection: vec3<f32>) -> f32 {
|
|
4138
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4139
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4140
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
4141
|
+
let diffuseTerm = diffuse_pdf(normal, lightDirection);
|
|
4142
|
+
let specTerm = ggx_pdf(normal, viewDirection, lightDirection, max(roughness, 0.02));
|
|
4143
|
+
let clearcoatTerm = ggx_pdf(normal, viewDirection, lightDirection, max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02));
|
|
4144
|
+
return weights.x * diffuseTerm + weights.y * specTerm + weights.z * clearcoatTerm;
|
|
4145
|
+
}
|
|
4146
|
+
|
|
2490
4147
|
fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
2491
4148
|
let portalScale = environment_portal_radiance_scale(origin, safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0)));
|
|
2492
4149
|
if (
|
|
@@ -2502,9 +4159,45 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
2502
4159
|
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
2503
4160
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
2504
4161
|
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
4162
|
+
let occlusion = clamp(hit.occlusion, 0.0, 1.0);
|
|
2505
4163
|
let materialEnergy = select(0.68, 0.92, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2506
4164
|
let transparentEnergy = select(materialEnergy, 0.9, hit.hitType == 3u);
|
|
2507
|
-
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy;
|
|
4165
|
+
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy * mix(0.55, 1.0, occlusion);
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
fn bounded_path_response_luminance(ray: RayRecord, hit: HitRecord) -> f32 {
|
|
4169
|
+
let daylightFloor = max(config.pathResolveSettings.y, 0.0) * 0.08;
|
|
4170
|
+
let hdriFloor = max(config.environmentMapSettings.w, 0.0) * 0.02;
|
|
4171
|
+
let sceneFloor = max(daylightFloor, hdriFloor);
|
|
4172
|
+
if (sceneFloor <= 0.000001) {
|
|
4173
|
+
return 0.0;
|
|
4174
|
+
}
|
|
4175
|
+
let bounceRatio = select(
|
|
4176
|
+
0.0,
|
|
4177
|
+
f32(ray.bounce) / max(f32(config.maxDepth - 1u), 1.0),
|
|
4178
|
+
config.maxDepth > 1u
|
|
4179
|
+
);
|
|
4180
|
+
let bounceScale = 1.0 - bounceRatio * 0.55;
|
|
4181
|
+
let materialScale = select(1.0, 0.34, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
4182
|
+
let transparentScale = select(materialScale, 0.58, hit.hitType == 3u);
|
|
4183
|
+
let opacityScale = mix(0.55, 1.0, clamp(hit.material.z, 0.0, 1.0));
|
|
4184
|
+
return sceneFloor * bounceScale * transparentScale * opacityScale;
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4187
|
+
fn stabilize_surface_path_response(ray: RayRecord, hit: HitRecord, response: vec3<f32>) -> vec3<f32> {
|
|
4188
|
+
let minimumLuminance = bounded_path_response_luminance(ray, hit);
|
|
4189
|
+
let responseLuminance = radiance_luminance(response);
|
|
4190
|
+
if (minimumLuminance <= 0.000001 || responseLuminance >= minimumLuminance) {
|
|
4191
|
+
return response;
|
|
4192
|
+
}
|
|
4193
|
+
let tintBase = max(response, max(hit.color.xyz * 0.65, config.ambientColor.xyz * 0.35));
|
|
4194
|
+
let tint = tintBase / max(max_component(tintBase), 0.0001);
|
|
4195
|
+
let lifted = select(
|
|
4196
|
+
tint * minimumLuminance,
|
|
4197
|
+
response * (minimumLuminance / max(responseLuminance, 0.0001)),
|
|
4198
|
+
responseLuminance > 0.0001
|
|
4199
|
+
);
|
|
4200
|
+
return clamp(lifted, vec3<f32>(0.0), vec3<f32>(0.98));
|
|
2508
4201
|
}
|
|
2509
4202
|
|
|
2510
4203
|
fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
@@ -2523,12 +4216,24 @@ fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
|
2523
4216
|
return clamp_sample_radiance(sunTint * baseline * skyFacing * directionalWeight * 0.04);
|
|
2524
4217
|
}
|
|
2525
4218
|
|
|
2526
|
-
fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
4219
|
+
fn terminal_surface_environment_source(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2527
4220
|
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2528
|
-
let
|
|
2529
|
-
|
|
2530
|
-
|
|
4221
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4222
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
4223
|
+
let glossiness = surface_glossiness(hit);
|
|
4224
|
+
let normalEnvironment = gated_environment_radiance(origin, normal);
|
|
4225
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4226
|
+
let reflectionDirection = glossy_environment_direction(
|
|
4227
|
+
ray.direction.xyz,
|
|
4228
|
+
normal,
|
|
4229
|
+
roughness,
|
|
4230
|
+
mix(0.88, 0.38, glossiness)
|
|
2531
4231
|
);
|
|
4232
|
+
let reflectionEnvironment = prefiltered_environment_radiance(reflectionDirection, roughness);
|
|
4233
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4234
|
+
let f0 = surface_specular_f0(hit, surfaceColor);
|
|
4235
|
+
let brdfTerm = sample_brdf_lut(saturate(dot(normal, viewDirection)), roughness);
|
|
4236
|
+
let specularEnvironment = reflectionEnvironment * (f0 * brdfTerm.x + vec3<f32>(brdfTerm.y));
|
|
2532
4237
|
let sunlitFloor = sunlit_baseline_radiance(normal);
|
|
2533
4238
|
let ambientFloor = select(
|
|
2534
4239
|
max(config.ambientColor.xyz, sunlitFloor * 0.82),
|
|
@@ -2540,17 +4245,23 @@ fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
|
2540
4245
|
max(config.environmentMapSettings.w, max(0.12, config.pathResolveSettings.y * 0.42)),
|
|
2541
4246
|
environment_map_enabled()
|
|
2542
4247
|
);
|
|
2543
|
-
let
|
|
4248
|
+
let glossyEnvironment = max(
|
|
4249
|
+
normalEnvironment,
|
|
4250
|
+
max(reflectionEnvironment * mix(0.24, 0.92, glossiness), specularEnvironment)
|
|
4251
|
+
);
|
|
4252
|
+
let environmentFloor = max(ambientFloor, max(sunlitFloor, glossyEnvironment * environmentInfluence));
|
|
2544
4253
|
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
2545
4254
|
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
2546
4255
|
}
|
|
2547
4256
|
|
|
2548
4257
|
fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2549
4258
|
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
4259
|
+
let occlusion = mix(0.75, 1.0, clamp(hit.occlusion, 0.0, 1.0));
|
|
2550
4260
|
return clamp_sample_radiance(
|
|
2551
4261
|
ray.throughput.xyz *
|
|
2552
4262
|
surfaceColor *
|
|
2553
|
-
terminal_surface_environment_source(hit)
|
|
4263
|
+
terminal_surface_environment_source(ray, hit) *
|
|
4264
|
+
occlusion
|
|
2554
4265
|
);
|
|
2555
4266
|
}
|
|
2556
4267
|
|
|
@@ -2583,6 +4294,10 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2583
4294
|
);
|
|
2584
4295
|
let area = max(portal.position.w, 0.0001);
|
|
2585
4296
|
let distanceFalloff = clamp(area / max(distanceSquared, area * 0.25), 0.0, 2.5);
|
|
4297
|
+
let traceDistance = max(sqrt(distanceSquared) - 0.01, 0.01);
|
|
4298
|
+
if (scene_visibility_blocked(origin, direction, traceDistance)) {
|
|
4299
|
+
continue;
|
|
4300
|
+
}
|
|
2586
4301
|
irradiance = irradiance +
|
|
2587
4302
|
portal.color.rgb *
|
|
2588
4303
|
portal.normal.w *
|
|
@@ -2594,48 +4309,79 @@ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) ->
|
|
|
2594
4309
|
return irradiance;
|
|
2595
4310
|
}
|
|
2596
4311
|
|
|
2597
|
-
fn
|
|
2598
|
-
let
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
);
|
|
2612
|
-
let environmentIrradianceScale = select(
|
|
2613
|
-
max(0.16, config.pathResolveSettings.y * 0.45),
|
|
2614
|
-
max(config.environmentMapSettings.w, max(0.16, config.pathResolveSettings.y * 0.45)),
|
|
2615
|
-
environment_map_enabled()
|
|
4312
|
+
fn visibility_test_ray(origin: vec3<f32>, direction: vec3<f32>) -> RayRecord {
|
|
4313
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
4314
|
+
return RayRecord(
|
|
4315
|
+
0u,
|
|
4316
|
+
0u,
|
|
4317
|
+
0u,
|
|
4318
|
+
0u,
|
|
4319
|
+
0u,
|
|
4320
|
+
0u,
|
|
4321
|
+
0u,
|
|
4322
|
+
0u,
|
|
4323
|
+
vec4<f32>(origin, 1.0),
|
|
4324
|
+
vec4<f32>(rayDirection, 0.0),
|
|
4325
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2616
4326
|
);
|
|
2617
|
-
|
|
4327
|
+
}
|
|
2618
4328
|
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
);
|
|
2623
|
-
let sunFacing = saturate(dot(normal, sunDirection));
|
|
2624
|
-
let sunRadiance = gated_environment_radiance(origin, sunDirection);
|
|
2625
|
-
let sunIrradiance = sunRadiance * sunFacing * 0.2;
|
|
2626
|
-
let portalIrradiance = direct_environment_portal_irradiance(origin, normal);
|
|
4329
|
+
fn scene_visibility_blocked(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> bool {
|
|
4330
|
+
let testRay = visibility_test_ray(origin, direction);
|
|
4331
|
+
let nearest = max(maxDistance, 0.001);
|
|
2627
4332
|
|
|
2628
|
-
|
|
2629
|
-
|
|
4333
|
+
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
4334
|
+
let object = sceneObjects[objectIndex];
|
|
4335
|
+
var current = no_candidate();
|
|
4336
|
+
if (object.kind == 1u) {
|
|
4337
|
+
current = intersect_sphere(testRay, object);
|
|
4338
|
+
} else if (object.kind == 2u) {
|
|
4339
|
+
current = intersect_box(testRay, object);
|
|
4340
|
+
}
|
|
4341
|
+
if (current.hit == 1u && current.distance < nearest) {
|
|
4342
|
+
return true;
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
2630
4345
|
|
|
2631
|
-
let
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
let specularTint = mix(vec3<f32>(0.04), surfaceColor, metallic);
|
|
2635
|
-
let specular = specularTint * sunRadiance * specularFacing * select(0.16, 0.48, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
4346
|
+
let meshCandidate = intersect_bvh(testRay, nearest);
|
|
4347
|
+
return meshCandidate.hit == 1u && meshCandidate.distance < nearest;
|
|
4348
|
+
}
|
|
2636
4349
|
|
|
2637
|
-
|
|
2638
|
-
|
|
4350
|
+
fn surface_direct_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
4351
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
4352
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
4353
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
4354
|
+
let lightSample = sample_environment_importance(vec2<f32>(
|
|
4355
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 41u)),
|
|
4356
|
+
random01(mix_seed(ray.sourcePixelId, ray.sampleId, ray.bounce, config.frameIndex, 43u))
|
|
4357
|
+
));
|
|
4358
|
+
if (lightSample.pdf <= 0.000001) {
|
|
4359
|
+
return vec3<f32>(0.0);
|
|
4360
|
+
}
|
|
4361
|
+
let lightDirection = safe_normalize(lightSample.direction, normal);
|
|
4362
|
+
let nDotL = saturate(dot(normal, lightDirection));
|
|
4363
|
+
if (nDotL <= 0.000001) {
|
|
4364
|
+
return vec3<f32>(0.0);
|
|
4365
|
+
}
|
|
4366
|
+
if (scene_visibility_blocked(origin, lightDirection, 1000000.0)) {
|
|
4367
|
+
return vec3<f32>(0.0);
|
|
4368
|
+
}
|
|
4369
|
+
let incidentRadiance = direct_environment_radiance(origin, lightDirection);
|
|
4370
|
+
if (max_component(incidentRadiance) <= 0.000001) {
|
|
4371
|
+
return vec3<f32>(0.0);
|
|
4372
|
+
}
|
|
4373
|
+
let bsdf = evaluate_surface_bsdf(hit, viewDirection, lightDirection);
|
|
4374
|
+
if (max_component(bsdf) <= 0.000001) {
|
|
4375
|
+
return vec3<f32>(0.0);
|
|
4376
|
+
}
|
|
4377
|
+
let bsdfPdf = evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection);
|
|
4378
|
+
let misWeight = power_heuristic(lightSample.pdf, bsdfPdf);
|
|
4379
|
+
let contribution =
|
|
4380
|
+
ray.throughput.xyz *
|
|
4381
|
+
bsdf *
|
|
4382
|
+
incidentRadiance *
|
|
4383
|
+
(nDotL * misWeight / max(lightSample.pdf, 0.000001));
|
|
4384
|
+
return clamp_sample_radiance(contribution);
|
|
2639
4385
|
}
|
|
2640
4386
|
|
|
2641
4387
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -2654,7 +4400,16 @@ fn default_mesh_range() -> MeshRange {
|
|
|
2654
4400
|
0u,
|
|
2655
4401
|
vec4<f32>(0.72, 0.72, 0.68, 1.0),
|
|
2656
4402
|
vec4<f32>(0.0),
|
|
2657
|
-
vec4<f32>(0.72, 0.0, 1.0, 1.45)
|
|
4403
|
+
vec4<f32>(0.72, 0.0, 1.0, 1.45),
|
|
4404
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4405
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
4406
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4407
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4408
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4409
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4410
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4411
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
4412
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
2658
4413
|
);
|
|
2659
4414
|
}
|
|
2660
4415
|
|
|
@@ -2750,7 +4505,7 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2750
4505
|
mesh.flags,
|
|
2751
4506
|
mesh.materialRefId,
|
|
2752
4507
|
mesh.mediumRefId,
|
|
2753
|
-
|
|
4508
|
+
mesh.materialSlot,
|
|
2754
4509
|
0u,
|
|
2755
4510
|
vec4<f32>(vertex0.position.xyz, 0.0),
|
|
2756
4511
|
vec4<f32>(vertex1.position.xyz, 0.0),
|
|
@@ -2762,7 +4517,16 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
2762
4517
|
vec4<f32>(uv2, 0.0, 0.0),
|
|
2763
4518
|
mesh.color,
|
|
2764
4519
|
mesh.emission,
|
|
2765
|
-
mesh.material
|
|
4520
|
+
mesh.material,
|
|
4521
|
+
mesh.materialResponse,
|
|
4522
|
+
mesh.materialExtension,
|
|
4523
|
+
mesh.specularColor,
|
|
4524
|
+
mesh.baseColorAtlas,
|
|
4525
|
+
mesh.metallicRoughnessAtlas,
|
|
4526
|
+
mesh.normalAtlas,
|
|
4527
|
+
mesh.occlusionAtlas,
|
|
4528
|
+
mesh.emissiveAtlas,
|
|
4529
|
+
mesh.textureSettings
|
|
2766
4530
|
);
|
|
2767
4531
|
|
|
2768
4532
|
let leafBase = config.triangleCount - 1u;
|
|
@@ -2921,7 +4685,8 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2921
4685
|
0u,
|
|
2922
4686
|
0u,
|
|
2923
4687
|
-1.0,
|
|
2924
|
-
|
|
4688
|
+
1.0,
|
|
4689
|
+
vec2<f32>(0.0),
|
|
2925
4690
|
vec4<f32>(ray.origin.xyz + ray.direction.xyz * 1000.0, 1.0),
|
|
2926
4691
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
2927
4692
|
vec4<f32>(-ray.direction.xyz, 0.0),
|
|
@@ -2929,7 +4694,10 @@ fn make_miss(ray: RayRecord) -> HitRecord {
|
|
|
2929
4694
|
vec4<f32>(0.0),
|
|
2930
4695
|
vec4<f32>(radiance, 1.0),
|
|
2931
4696
|
vec4<f32>(0.0),
|
|
2932
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
4697
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
4698
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
4699
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
4700
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
2933
4701
|
);
|
|
2934
4702
|
}
|
|
2935
4703
|
|
|
@@ -3224,6 +4992,19 @@ fn denoise_range_space(value: vec3<f32>) -> vec3<f32> {
|
|
|
3224
4992
|
return value / (vec3<f32>(1.0) + value);
|
|
3225
4993
|
}
|
|
3226
4994
|
|
|
4995
|
+
fn denoise_sample_count() -> f32 {
|
|
4996
|
+
return clamp(1.0 / max(config.projectionAndSampling.z, 0.000001), 1.0, 256.0);
|
|
4997
|
+
}
|
|
4998
|
+
|
|
4999
|
+
fn denoise_strength() -> f32 {
|
|
5000
|
+
let spp = denoise_sample_count();
|
|
5001
|
+
return clamp(0.44 / sqrt(spp), 0.08, 0.44);
|
|
5002
|
+
}
|
|
5003
|
+
|
|
5004
|
+
fn denoise_kernel_radius() -> i32 {
|
|
5005
|
+
return select(1i, 2i, denoise_sample_count() < 2.5);
|
|
5006
|
+
}
|
|
5007
|
+
|
|
3227
5008
|
@compute @workgroup_size(64)
|
|
3228
5009
|
fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
3229
5010
|
let index = globalId.x;
|
|
@@ -3262,7 +5043,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3262
5043
|
vec4<f32>(0.0),
|
|
3263
5044
|
vec4<f32>(0.0),
|
|
3264
5045
|
vec4<f32>(0.0),
|
|
3265
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
5046
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
5047
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
5048
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
5049
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
3266
5050
|
);
|
|
3267
5051
|
var candidate = no_candidate();
|
|
3268
5052
|
var hitTriangle = TriangleRecord(
|
|
@@ -3284,7 +5068,16 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3284
5068
|
vec4<f32>(0.0),
|
|
3285
5069
|
vec4<f32>(0.0),
|
|
3286
5070
|
vec4<f32>(0.0),
|
|
3287
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0)
|
|
5071
|
+
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
5072
|
+
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
5073
|
+
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
5074
|
+
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
5075
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5076
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5077
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5078
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5079
|
+
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
5080
|
+
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
3288
5081
|
);
|
|
3289
5082
|
|
|
3290
5083
|
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
@@ -3317,16 +5110,28 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3317
5110
|
let position = ray.origin.xyz + ray.direction.xyz * candidate.distance;
|
|
3318
5111
|
let hitMaterialKind = select(hitObject.materialKind, hitTriangle.materialKind, candidate.triangleIndex != 0xffffffffu);
|
|
3319
5112
|
let hitObjectId = select(hitObject.objectId, hitTriangle.meshId, candidate.triangleIndex != 0xffffffffu);
|
|
3320
|
-
let
|
|
3321
|
-
|
|
3322
|
-
|
|
5113
|
+
let meshSurface = sample_surface_material(
|
|
5114
|
+
hitTriangle,
|
|
5115
|
+
candidate.uv,
|
|
5116
|
+
candidate.geometricNormal,
|
|
5117
|
+
candidate.shadingNormal
|
|
5118
|
+
);
|
|
5119
|
+
let hitColor = select(hitObject.color, meshSurface.color, candidate.triangleIndex != 0xffffffffu);
|
|
5120
|
+
let hitEmission = select(hitObject.emission, meshSurface.emission, candidate.triangleIndex != 0xffffffffu);
|
|
5121
|
+
let hitMaterial = select(hitObject.material, meshSurface.material, candidate.triangleIndex != 0xffffffffu);
|
|
5122
|
+
let hitMaterialResponse = select(hitObject.materialResponse, meshSurface.materialResponse, candidate.triangleIndex != 0xffffffffu);
|
|
5123
|
+
let hitMaterialExtension = select(hitObject.materialExtension, meshSurface.materialExtension, candidate.triangleIndex != 0xffffffffu);
|
|
5124
|
+
let hitSpecularColor = select(hitObject.specularColor, meshSurface.specularColor, candidate.triangleIndex != 0xffffffffu);
|
|
5125
|
+
let hitShadingNormal = select(candidate.shadingNormal, meshSurface.shadingNormal, candidate.triangleIndex != 0xffffffffu);
|
|
3323
5126
|
let hitPrimitiveId = select(candidate.primitiveId, hitTriangle.triangleId, candidate.triangleIndex != 0xffffffffu);
|
|
3324
5127
|
let hitMaterialRefId = select(candidate.materialRefId, hitTriangle.materialRefId, candidate.triangleIndex != 0xffffffffu);
|
|
3325
5128
|
let hitMediumRefId = select(candidate.mediumRefId, hitTriangle.mediumRefId, candidate.triangleIndex != 0xffffffffu);
|
|
5129
|
+
let hitMaterialSlot = select(0u, hitTriangle.materialSlot, candidate.triangleIndex != 0xffffffffu);
|
|
5130
|
+
let hitOcclusion = select(1.0, meshSurface.occlusion, candidate.triangleIndex != 0xffffffffu);
|
|
3326
5131
|
var hitType = 0u;
|
|
3327
5132
|
if (hitMaterialKind == 4u || emission_power(hitEmission) > 0.0001) {
|
|
3328
5133
|
hitType = 1u;
|
|
3329
|
-
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999) {
|
|
5134
|
+
} else if (hitMaterialKind == 3u || hitMaterial.z < 0.999 || hitMaterialExtension.z > 0.001) {
|
|
3330
5135
|
hitType = 3u;
|
|
3331
5136
|
}
|
|
3332
5137
|
atomicAdd(&counters.hitCount, 1u);
|
|
@@ -3340,19 +5145,23 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3340
5145
|
hitPrimitiveId,
|
|
3341
5146
|
hitMaterialRefId,
|
|
3342
5147
|
hitMediumRefId,
|
|
3343
|
-
|
|
5148
|
+
hitMaterialSlot,
|
|
3344
5149
|
0u,
|
|
3345
5150
|
0u,
|
|
3346
5151
|
candidate.distance,
|
|
3347
|
-
|
|
5152
|
+
hitOcclusion,
|
|
5153
|
+
vec2<f32>(0.0),
|
|
3348
5154
|
vec4<f32>(position, 1.0),
|
|
3349
5155
|
vec4<f32>(candidate.geometricNormal, 0.0),
|
|
3350
|
-
vec4<f32>(
|
|
5156
|
+
vec4<f32>(hitShadingNormal, 0.0),
|
|
3351
5157
|
vec4<f32>(candidate.barycentric, 0.0),
|
|
3352
5158
|
vec4<f32>(candidate.uv, 0.0, 0.0),
|
|
3353
5159
|
hitColor,
|
|
3354
5160
|
hitEmission,
|
|
3355
|
-
hitMaterial
|
|
5161
|
+
hitMaterial,
|
|
5162
|
+
hitMaterialResponse,
|
|
5163
|
+
hitMaterialExtension,
|
|
5164
|
+
hitSpecularColor
|
|
3356
5165
|
);
|
|
3357
5166
|
}
|
|
3358
5167
|
|
|
@@ -3421,60 +5230,106 @@ fn sample_environment_portal_direction(hit: HitRecord, seed: u32, fallback: vec3
|
|
|
3421
5230
|
}
|
|
3422
5231
|
|
|
3423
5232
|
fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult {
|
|
5233
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
5234
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
3424
5235
|
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
3425
|
-
|
|
5236
|
+
let transmission = clamp(hit.materialExtension.z, 0.0, 1.0);
|
|
5237
|
+
if (hit.materialKind == 1u && roughness <= 0.02) {
|
|
3426
5238
|
return ScatterResult(
|
|
3427
|
-
vec4<f32>(
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
hit.shadingNormal.xyz
|
|
3431
|
-
),
|
|
3432
|
-
0.0
|
|
3433
|
-
),
|
|
3434
|
-
0u,
|
|
3435
|
-
0u,
|
|
5239
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5240
|
+
1.0,
|
|
5241
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
3436
5242
|
0u,
|
|
3437
5243
|
0u
|
|
3438
5244
|
);
|
|
3439
5245
|
}
|
|
3440
5246
|
|
|
3441
|
-
if (hit.materialKind == 2u || hit.materialKind == 3u) {
|
|
5247
|
+
if (hit.materialKind == 2u || hit.materialKind == 3u || transmission > 0.001) {
|
|
3442
5248
|
let ior = max(hit.material.w, 1.01);
|
|
3443
5249
|
let etaRatio = select(ior, 1.0 / ior, hit.frontFace == 1u);
|
|
3444
|
-
let cosTheta = min(dot(-ray.direction.xyz,
|
|
5250
|
+
let cosTheta = min(dot(-ray.direction.xyz, normal), 1.0);
|
|
3445
5251
|
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
3446
5252
|
let cannotRefract = etaRatio * sinTheta > 1.0;
|
|
3447
5253
|
let reflectChance = schlick(cosTheta, etaRatio);
|
|
3448
|
-
|
|
3449
|
-
|
|
5254
|
+
let transmissionReflectChance = select(
|
|
5255
|
+
reflectChance,
|
|
5256
|
+
max(reflectChance, 1.0 - transmission),
|
|
5257
|
+
transmission > 0.001
|
|
5258
|
+
);
|
|
5259
|
+
if (cannotRefract || random01(seed + 23u) < transmissionReflectChance) {
|
|
5260
|
+
return ScatterResult(
|
|
5261
|
+
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5262
|
+
1.0,
|
|
5263
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
5264
|
+
0u,
|
|
5265
|
+
0u
|
|
5266
|
+
);
|
|
3450
5267
|
}
|
|
3451
|
-
return ScatterResult(
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
let
|
|
3461
|
-
let
|
|
3462
|
-
|
|
3463
|
-
let
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
)
|
|
5268
|
+
return ScatterResult(
|
|
5269
|
+
vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
|
|
5270
|
+
1.0,
|
|
5271
|
+
RAY_FLAG_DELTA_SAMPLE,
|
|
5272
|
+
0u,
|
|
5273
|
+
0u
|
|
5274
|
+
);
|
|
5275
|
+
}
|
|
5276
|
+
|
|
5277
|
+
let guidedEmissiveAvailable = config.emissiveTriangleCount > 0u;
|
|
5278
|
+
let guidedPortalAvailable =
|
|
5279
|
+
config.environmentPortalCount > 0u && config.environmentPortalMode != 0u;
|
|
5280
|
+
let guidedSelector = random01(seed + 17u);
|
|
5281
|
+
if (guidedEmissiveAvailable && guidedSelector < 0.18) {
|
|
5282
|
+
let guidedDirection = sample_emissive_triangle_direction(hit, seed + 101u, normal);
|
|
5283
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5284
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5285
|
+
return ScatterResult(
|
|
5286
|
+
vec4<f32>(guidedDirection, 0.0),
|
|
5287
|
+
guidedPdf,
|
|
5288
|
+
RAY_FLAG_GUIDED_EMISSIVE,
|
|
5289
|
+
0u,
|
|
5290
|
+
0u
|
|
5291
|
+
);
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
5294
|
+
if (guidedPortalAvailable && guidedSelector < 0.32) {
|
|
5295
|
+
let guidedDirection = sample_environment_portal_direction(hit, seed + 131u, normal);
|
|
5296
|
+
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5297
|
+
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5298
|
+
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf, 0u, 0u, 0u);
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
|
|
5302
|
+
let weights = surface_bsdf_sampling_weights(hit);
|
|
5303
|
+
let selector = random01(seed + 31u);
|
|
5304
|
+
var lightDirection = normal;
|
|
5305
|
+
if (selector < weights.x) {
|
|
5306
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5307
|
+
vec2<f32>(random01(seed + 37u), random01(seed + 41u)),
|
|
5308
|
+
normal
|
|
5309
|
+
);
|
|
5310
|
+
} else if (selector < weights.x + weights.y) {
|
|
5311
|
+
let halfVector = importance_sample_ggx(
|
|
5312
|
+
vec2<f32>(random01(seed + 47u), random01(seed + 53u)),
|
|
5313
|
+
max(roughness, 0.02),
|
|
5314
|
+
normal
|
|
5315
|
+
);
|
|
5316
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5317
|
+
} else {
|
|
5318
|
+
let halfVector = importance_sample_ggx(
|
|
5319
|
+
vec2<f32>(random01(seed + 59u), random01(seed + 61u)),
|
|
5320
|
+
max(clamp(hit.materialExtension.x, 0.0, 1.0), 0.02),
|
|
5321
|
+
normal
|
|
5322
|
+
);
|
|
5323
|
+
lightDirection = safe_normalize(reflect(-viewDirection, halfVector), normal);
|
|
5324
|
+
}
|
|
5325
|
+
if (dot(normal, lightDirection) <= 0.000001) {
|
|
5326
|
+
lightDirection = cosine_sample_hemisphere(
|
|
5327
|
+
vec2<f32>(random01(seed + 67u), random01(seed + 71u)),
|
|
5328
|
+
normal
|
|
5329
|
+
);
|
|
5330
|
+
}
|
|
5331
|
+
let pdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection), 0.000001);
|
|
5332
|
+
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf, 0u, 0u, 0u);
|
|
3478
5333
|
}
|
|
3479
5334
|
|
|
3480
5335
|
@compute @workgroup_size(64)
|
|
@@ -3504,10 +5359,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3504
5359
|
}
|
|
3505
5360
|
|
|
3506
5361
|
if (hit.hitType == 2u) {
|
|
5362
|
+
var sourceRadiance = hit.color.xyz;
|
|
5363
|
+
if ((ray.flags & RAY_FLAG_DELTA_SAMPLE) == 0u) {
|
|
5364
|
+
let bsdfPdf = max(ray.throughput.w, 0.000001);
|
|
5365
|
+
let lightPdf = environment_direction_pdf(ray.direction.xyz);
|
|
5366
|
+
let misWeight = power_heuristic(bsdfPdf, lightPdf);
|
|
5367
|
+
sourceRadiance = sourceRadiance * misWeight;
|
|
5368
|
+
}
|
|
3507
5369
|
if (deferred_path_resolve_enabled()) {
|
|
3508
|
-
record_deferred_terminal_source(ray,
|
|
5370
|
+
record_deferred_terminal_source(ray, sourceRadiance);
|
|
3509
5371
|
} else {
|
|
3510
|
-
contribution = clamp_sample_radiance(ray.throughput.xyz *
|
|
5372
|
+
contribution = clamp_sample_radiance(ray.throughput.xyz * sourceRadiance);
|
|
3511
5373
|
accumulation[ray.rayId] =
|
|
3512
5374
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3513
5375
|
}
|
|
@@ -3515,13 +5377,13 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3515
5377
|
return;
|
|
3516
5378
|
}
|
|
3517
5379
|
|
|
3518
|
-
let response = surface_path_response(hit);
|
|
5380
|
+
let response = stabilize_surface_path_response(ray, hit, surface_path_response(hit));
|
|
3519
5381
|
record_deferred_path_response(ray, response);
|
|
3520
5382
|
|
|
3521
5383
|
let shouldEstimateDirectEnvironment =
|
|
3522
|
-
!deferred_path_resolve_enabled() &&
|
|
3523
5384
|
(hit.materialKind == 0u || hit.materialKind == 1u) &&
|
|
3524
|
-
hit.material.z >= 0.95
|
|
5385
|
+
hit.material.z >= 0.95 &&
|
|
5386
|
+
ray.bounce < 2u;
|
|
3525
5387
|
if (shouldEstimateDirectEnvironment) {
|
|
3526
5388
|
let directEnvironment = surface_direct_environment_contribution(ray, hit);
|
|
3527
5389
|
accumulation[ray.rayId] =
|
|
@@ -3530,7 +5392,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3530
5392
|
|
|
3531
5393
|
if (ray.bounce + 1u >= config.maxDepth) {
|
|
3532
5394
|
if (deferred_path_resolve_enabled()) {
|
|
3533
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5395
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3534
5396
|
} else {
|
|
3535
5397
|
let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
3536
5398
|
accumulation[ray.rayId] =
|
|
@@ -3545,7 +5407,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3545
5407
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
3546
5408
|
if (nextIndex >= config.tilePixelCount) {
|
|
3547
5409
|
if (deferred_path_resolve_enabled()) {
|
|
3548
|
-
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
5410
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
3549
5411
|
} else {
|
|
3550
5412
|
let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
3551
5413
|
accumulation[ray.rayId] =
|
|
@@ -3566,7 +5428,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3566
5428
|
0u,
|
|
3567
5429
|
vec4<f32>(offset_origin(hit.position.xyz, hit.shadingNormal.xyz), 1.0),
|
|
3568
5430
|
scatter.direction,
|
|
3569
|
-
vec4<f32>(throughput,
|
|
5431
|
+
vec4<f32>(throughput, scatter.pdf)
|
|
3570
5432
|
);
|
|
3571
5433
|
}
|
|
3572
5434
|
|
|
@@ -3635,8 +5497,11 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3635
5497
|
|
|
3636
5498
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3637
5499
|
let center = textureLoad(denoiseInputRadiance, pixel, 0).xyz;
|
|
3638
|
-
|
|
3639
|
-
|
|
5500
|
+
let strength = denoise_strength();
|
|
5501
|
+
let kernelRadius = denoise_kernel_radius();
|
|
5502
|
+
let centerWeight = 1.7 - strength * 0.35;
|
|
5503
|
+
var sum = center * centerWeight;
|
|
5504
|
+
var totalWeight = centerWeight;
|
|
3640
5505
|
let centerRange = denoise_range_space(center);
|
|
3641
5506
|
|
|
3642
5507
|
for (var oy = -2i; oy <= 2i; oy = oy + 1i) {
|
|
@@ -3644,13 +5509,16 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3644
5509
|
if (ox == 0i && oy == 0i) {
|
|
3645
5510
|
continue;
|
|
3646
5511
|
}
|
|
5512
|
+
if (abs(ox) > kernelRadius || abs(oy) > kernelRadius) {
|
|
5513
|
+
continue;
|
|
5514
|
+
}
|
|
3647
5515
|
let sx = clamp(i32(x) + ox, 0i, i32(config.canvasWidth) - 1i);
|
|
3648
5516
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3649
5517
|
let sampleColor = textureLoad(denoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3650
5518
|
let colorDistance = length(denoise_range_space(sampleColor) - centerRange);
|
|
3651
|
-
let rangeWeight = 1.0 / (1.0 + colorDistance *
|
|
3652
|
-
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * 0.24);
|
|
3653
|
-
let diagonalWeight = select(1.0, 0.
|
|
5519
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (11.0 + strength * 6.0));
|
|
5520
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.62 + strength * 0.24));
|
|
5521
|
+
let diagonalWeight = select(1.0, 0.92, abs(ox) + abs(oy) > 1i);
|
|
3654
5522
|
let weight = rangeWeight * diagonalWeight * distanceWeight;
|
|
3655
5523
|
sum = sum + sampleColor * weight;
|
|
3656
5524
|
totalWeight = totalWeight + weight;
|
|
@@ -3658,8 +5526,9 @@ fn denoiseLinearRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
3658
5526
|
}
|
|
3659
5527
|
|
|
3660
5528
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3661
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3662
|
-
let
|
|
5529
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.1);
|
|
5530
|
+
let blend = min(0.3, strength * (0.62 + outlier * 0.12));
|
|
5531
|
+
let color = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3663
5532
|
textureStore(denoisedRadianceImage, pixel, vec4<f32>(color, 1.0));
|
|
3664
5533
|
}
|
|
3665
5534
|
|
|
@@ -3673,8 +5542,10 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3673
5542
|
|
|
3674
5543
|
let pixel = vec2<i32>(i32(x), i32(y));
|
|
3675
5544
|
let center = textureLoad(finalDenoiseInputRadiance, pixel, 0).xyz;
|
|
3676
|
-
|
|
3677
|
-
|
|
5545
|
+
let strength = denoise_strength();
|
|
5546
|
+
let centerWeight = 1.35 - strength * 0.25;
|
|
5547
|
+
var sum = center * centerWeight;
|
|
5548
|
+
var totalWeight = centerWeight;
|
|
3678
5549
|
let centerRange = denoise_range_space(center);
|
|
3679
5550
|
|
|
3680
5551
|
for (var oy = -1i; oy <= 1i; oy = oy + 1i) {
|
|
@@ -3686,8 +5557,8 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3686
5557
|
let sy = clamp(i32(y) + oy, 0i, i32(config.canvasHeight) - 1i);
|
|
3687
5558
|
let sampleColor = textureLoad(finalDenoiseInputRadiance, vec2<i32>(sx, sy), 0).xyz;
|
|
3688
5559
|
let colorDistance = length(denoise_range_space(sampleColor) - centerRange);
|
|
3689
|
-
let rangeWeight = 1.0 / (1.0 + colorDistance *
|
|
3690
|
-
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * 0.
|
|
5560
|
+
let rangeWeight = 1.0 / (1.0 + colorDistance * (12.0 + strength * 8.0));
|
|
5561
|
+
let distanceWeight = 1.0 / (1.0 + f32(ox * ox + oy * oy) * (0.82 + strength * 0.28));
|
|
3691
5562
|
let weight = rangeWeight * distanceWeight;
|
|
3692
5563
|
sum = sum + sampleColor * weight;
|
|
3693
5564
|
totalWeight = totalWeight + weight;
|
|
@@ -3695,8 +5566,9 @@ fn resolveDenoisedOutputImage(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
3695
5566
|
}
|
|
3696
5567
|
|
|
3697
5568
|
let filtered = sum / max(totalWeight, 0.0001);
|
|
3698
|
-
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.
|
|
3699
|
-
let
|
|
5569
|
+
let outlier = saturate(length(denoise_range_space(center) - denoise_range_space(filtered)) * 2.2);
|
|
5570
|
+
let blend = min(0.18, strength * (0.42 + outlier * 0.08));
|
|
5571
|
+
let radiance = min(mix(center, filtered, blend), vec3<f32>(16.0));
|
|
3700
5572
|
textureStore(denoisedOutputImage, pixel, vec4<f32>(tone_map_radiance(radiance), 1.0));
|
|
3701
5573
|
}
|
|
3702
5574
|
`;
|
|
@@ -3801,96 +5673,47 @@ function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
|
3801
5673
|
});
|
|
3802
5674
|
}
|
|
3803
5675
|
|
|
3804
|
-
function createGpuParallelismCounters() {
|
|
3805
|
-
return {
|
|
3806
|
-
directDispatches: 0,
|
|
3807
|
-
directWorkgroups: 0,
|
|
3808
|
-
directShaderInvocations: 0,
|
|
3809
|
-
multiWorkgroupDispatches: 0,
|
|
3810
|
-
largestDirectWorkgroupsPerDispatch: 0,
|
|
3811
|
-
indirectDispatches: 0,
|
|
3812
|
-
estimatedIndirectWorkgroupsUpperBound: 0,
|
|
3813
|
-
estimatedIndirectShaderInvocationsUpperBound: 0,
|
|
3814
|
-
indirectDispatchesWithMultiWorkgroupCapacity: 0,
|
|
3815
|
-
largestEstimatedIndirectWorkgroupsPerDispatch: 0,
|
|
3816
|
-
};
|
|
3817
|
-
}
|
|
3818
|
-
|
|
3819
|
-
function countDispatchWorkgroups(groups) {
|
|
3820
|
-
return groups.reduce((product, value) => {
|
|
3821
|
-
const numeric = Number(value ?? 1);
|
|
3822
|
-
const count = Number.isFinite(numeric) ? Math.max(1, Math.trunc(numeric)) : 1;
|
|
3823
|
-
return product * count;
|
|
3824
|
-
}, 1);
|
|
3825
|
-
}
|
|
3826
|
-
|
|
3827
|
-
function recordDirectDispatch(parallelism, groups, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3828
|
-
const workgroups = countDispatchWorkgroups(groups);
|
|
3829
|
-
parallelism.directDispatches += 1;
|
|
3830
|
-
parallelism.directWorkgroups += workgroups;
|
|
3831
|
-
parallelism.directShaderInvocations += workgroups * invocationsPerWorkgroup;
|
|
3832
|
-
parallelism.largestDirectWorkgroupsPerDispatch = Math.max(
|
|
3833
|
-
parallelism.largestDirectWorkgroupsPerDispatch,
|
|
3834
|
-
workgroups
|
|
3835
|
-
);
|
|
3836
|
-
if (workgroups > 1) {
|
|
3837
|
-
parallelism.multiWorkgroupDispatches += 1;
|
|
3838
|
-
}
|
|
3839
|
-
}
|
|
3840
|
-
|
|
3841
|
-
function recordIndirectDispatch(parallelism, estimatedWorkgroupsUpperBound, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3842
|
-
const workgroups = Math.max(1, Math.trunc(Number(estimatedWorkgroupsUpperBound) || 1));
|
|
3843
|
-
parallelism.indirectDispatches += 1;
|
|
3844
|
-
parallelism.estimatedIndirectWorkgroupsUpperBound += workgroups;
|
|
3845
|
-
parallelism.estimatedIndirectShaderInvocationsUpperBound += workgroups * invocationsPerWorkgroup;
|
|
3846
|
-
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch = Math.max(
|
|
3847
|
-
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3848
|
-
workgroups
|
|
3849
|
-
);
|
|
3850
|
-
if (workgroups > 1) {
|
|
3851
|
-
parallelism.indirectDispatchesWithMultiWorkgroupCapacity += 1;
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
|
|
3855
|
-
function createGpuParallelismDiagnostics(adapterDiagnostics, counters) {
|
|
3856
|
-
const totalEstimatedWorkgroupsUpperBound =
|
|
3857
|
-
counters.directWorkgroups + counters.estimatedIndirectWorkgroupsUpperBound;
|
|
3858
|
-
const totalEstimatedShaderInvocationsUpperBound =
|
|
3859
|
-
counters.directShaderInvocations + counters.estimatedIndirectShaderInvocationsUpperBound;
|
|
3860
|
-
const exposesMultiWorkgroupParallelism =
|
|
3861
|
-
counters.multiWorkgroupDispatches > 0 || counters.indirectDispatchesWithMultiWorkgroupCapacity > 0;
|
|
3862
|
-
return Object.freeze({
|
|
3863
|
-
...adapterDiagnostics,
|
|
3864
|
-
directDispatches: counters.directDispatches,
|
|
3865
|
-
directWorkgroups: counters.directWorkgroups,
|
|
3866
|
-
directShaderInvocations: counters.directShaderInvocations,
|
|
3867
|
-
multiWorkgroupDispatches: counters.multiWorkgroupDispatches,
|
|
3868
|
-
largestDirectWorkgroupsPerDispatch: counters.largestDirectWorkgroupsPerDispatch,
|
|
3869
|
-
indirectDispatches: counters.indirectDispatches,
|
|
3870
|
-
estimatedIndirectWorkgroupsUpperBound: counters.estimatedIndirectWorkgroupsUpperBound,
|
|
3871
|
-
estimatedIndirectShaderInvocationsUpperBound: counters.estimatedIndirectShaderInvocationsUpperBound,
|
|
3872
|
-
indirectDispatchesWithMultiWorkgroupCapacity: counters.indirectDispatchesWithMultiWorkgroupCapacity,
|
|
3873
|
-
largestEstimatedIndirectWorkgroupsPerDispatch: counters.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3874
|
-
totalEstimatedWorkgroupsUpperBound,
|
|
3875
|
-
totalEstimatedShaderInvocationsUpperBound,
|
|
3876
|
-
exposesMultiWorkgroupParallelism,
|
|
3877
|
-
likelyUsesMoreThanOnePhysicalGpuCore: null,
|
|
3878
|
-
coreUtilizationStatus: "not-exposed-by-webgpu",
|
|
3879
|
-
});
|
|
3880
|
-
}
|
|
3881
|
-
|
|
3882
5676
|
function createEnvironmentMapSnapshot(environmentMap) {
|
|
3883
5677
|
return Object.freeze({
|
|
3884
5678
|
enabled: environmentMap.enabled,
|
|
3885
5679
|
width: environmentMap.width,
|
|
3886
5680
|
height: environmentMap.height,
|
|
5681
|
+
mipLevelCount: environmentMap.mipLevelCount ?? 1,
|
|
3887
5682
|
projection: environmentMap.projection,
|
|
3888
5683
|
intensity: environmentMap.intensity,
|
|
3889
5684
|
rotationRadians: environmentMap.rotationRadians,
|
|
3890
5685
|
ambientStrength: environmentMap.ambientStrength,
|
|
5686
|
+
hasImportanceData: environmentMap.hasImportanceData === true,
|
|
3891
5687
|
});
|
|
3892
5688
|
}
|
|
3893
5689
|
|
|
5690
|
+
function nowMs() {
|
|
5691
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
5692
|
+
return performance.now();
|
|
5693
|
+
}
|
|
5694
|
+
return Date.now();
|
|
5695
|
+
}
|
|
5696
|
+
|
|
5697
|
+
function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs = null) {
|
|
5698
|
+
if (Number.isFinite(overrideTimeoutMs)) {
|
|
5699
|
+
return Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
5700
|
+
}
|
|
5701
|
+
const samplesPerPixel = Math.max(
|
|
5702
|
+
1,
|
|
5703
|
+
Number(config?.renderedSamplesPerPixel ?? config?.samplesPerPixel ?? 1)
|
|
5704
|
+
);
|
|
5705
|
+
const maxDepth = Math.max(1, Number(config?.maxDepth ?? 1));
|
|
5706
|
+
const deferredResolvePasses = config?.deferredPathResolve ? 1 : 0;
|
|
5707
|
+
const denoisePasses = config?.denoise ? (samplesPerPixel < 4 ? 2 : 1) : 0;
|
|
5708
|
+
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
5709
|
+
const estimatedPasses =
|
|
5710
|
+
tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
5711
|
+
return Math.min(
|
|
5712
|
+
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
5713
|
+
GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5
|
|
5714
|
+
);
|
|
5715
|
+
}
|
|
5716
|
+
|
|
3894
5717
|
export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
3895
5718
|
assertAnalyticDisplayQualityPolicy(options);
|
|
3896
5719
|
const constants = getGpuUsageConstants();
|
|
@@ -4116,6 +5939,60 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4116
5939
|
config.environmentMap,
|
|
4117
5940
|
config.environmentColor
|
|
4118
5941
|
);
|
|
5942
|
+
const environmentSamplingResource = createEnvironmentSamplingTextureResource(
|
|
5943
|
+
device,
|
|
5944
|
+
constants,
|
|
5945
|
+
config.environmentMap,
|
|
5946
|
+
config.environmentColor
|
|
5947
|
+
);
|
|
5948
|
+
config = Object.freeze({
|
|
5949
|
+
...config,
|
|
5950
|
+
environmentMap: Object.freeze({
|
|
5951
|
+
...config.environmentMap,
|
|
5952
|
+
width: environmentMapResource.width,
|
|
5953
|
+
height: environmentMapResource.height,
|
|
5954
|
+
mipLevelCount: environmentMapResource.mipLevelCount,
|
|
5955
|
+
hasImportanceData: environmentSamplingResource.hasImportanceData,
|
|
5956
|
+
}),
|
|
5957
|
+
});
|
|
5958
|
+
const brdfLutResource = createBrdfLutResource(device, constants);
|
|
5959
|
+
const baseColorAtlasResource = createAtlasTextureResource(
|
|
5960
|
+
device,
|
|
5961
|
+
constants,
|
|
5962
|
+
config.gpuMaterialSource.baseColorAtlas,
|
|
5963
|
+
"plasius.wavefront.materialAtlas.baseColor"
|
|
5964
|
+
);
|
|
5965
|
+
const metallicRoughnessAtlasResource = createAtlasTextureResource(
|
|
5966
|
+
device,
|
|
5967
|
+
constants,
|
|
5968
|
+
config.gpuMaterialSource.metallicRoughnessAtlas,
|
|
5969
|
+
"plasius.wavefront.materialAtlas.metallicRoughness"
|
|
5970
|
+
);
|
|
5971
|
+
const normalAtlasResource = createAtlasTextureResource(
|
|
5972
|
+
device,
|
|
5973
|
+
constants,
|
|
5974
|
+
config.gpuMaterialSource.normalAtlas,
|
|
5975
|
+
"plasius.wavefront.materialAtlas.normal"
|
|
5976
|
+
);
|
|
5977
|
+
const occlusionAtlasResource = createAtlasTextureResource(
|
|
5978
|
+
device,
|
|
5979
|
+
constants,
|
|
5980
|
+
config.gpuMaterialSource.occlusionAtlas,
|
|
5981
|
+
"plasius.wavefront.materialAtlas.occlusion"
|
|
5982
|
+
);
|
|
5983
|
+
const emissiveAtlasResource = createAtlasTextureResource(
|
|
5984
|
+
device,
|
|
5985
|
+
constants,
|
|
5986
|
+
config.gpuMaterialSource.emissiveAtlas,
|
|
5987
|
+
"plasius.wavefront.materialAtlas.emissive"
|
|
5988
|
+
);
|
|
5989
|
+
const materialAtlasSampler = device.createSampler({
|
|
5990
|
+
label: "plasius.wavefront.materialAtlasSampler",
|
|
5991
|
+
addressModeU: "clamp-to-edge",
|
|
5992
|
+
addressModeV: "clamp-to-edge",
|
|
5993
|
+
magFilter: "linear",
|
|
5994
|
+
minFilter: "linear",
|
|
5995
|
+
});
|
|
4119
5996
|
|
|
4120
5997
|
const traceBindGroupLayout = device.createBindGroupLayout({
|
|
4121
5998
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
@@ -4147,6 +6024,15 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4147
6024
|
{ binding: 20, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
4148
6025
|
{ binding: 21, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
4149
6026
|
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } },
|
|
6027
|
+
{ binding: 23, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6028
|
+
{ binding: 24, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6029
|
+
{ binding: 25, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6030
|
+
{ binding: 26, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6031
|
+
{ binding: 27, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6032
|
+
{ binding: 28, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
6033
|
+
{ binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6034
|
+
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
6035
|
+
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
4150
6036
|
],
|
|
4151
6037
|
});
|
|
4152
6038
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -4225,6 +6111,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4225
6111
|
label: "plasius.wavefront.computeShader",
|
|
4226
6112
|
code: WAVEFRONT_COMPUTE_WGSL,
|
|
4227
6113
|
});
|
|
6114
|
+
await assertShaderModuleCompiles(computeShader, "plasius.wavefront.computeShader");
|
|
4228
6115
|
|
|
4229
6116
|
const pipelines = {
|
|
4230
6117
|
prepareMeshTrianglesAndLeaves: await createComputePipeline(
|
|
@@ -4326,6 +6213,15 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4326
6213
|
{ binding: 20, resource: environmentMapResource.view },
|
|
4327
6214
|
{ binding: 21, resource: environmentMapResource.sampler },
|
|
4328
6215
|
{ binding: 22, resource: { buffer: pathVertexBuffer } },
|
|
6216
|
+
{ binding: 23, resource: baseColorAtlasResource.view },
|
|
6217
|
+
{ binding: 24, resource: metallicRoughnessAtlasResource.view },
|
|
6218
|
+
{ binding: 25, resource: normalAtlasResource.view },
|
|
6219
|
+
{ binding: 26, resource: occlusionAtlasResource.view },
|
|
6220
|
+
{ binding: 27, resource: emissiveAtlasResource.view },
|
|
6221
|
+
{ binding: 28, resource: materialAtlasSampler },
|
|
6222
|
+
{ binding: 29, resource: brdfLutResource.view },
|
|
6223
|
+
{ binding: 30, resource: brdfLutResource.sampler },
|
|
6224
|
+
{ binding: 31, resource: environmentSamplingResource.view },
|
|
4329
6225
|
],
|
|
4330
6226
|
});
|
|
4331
6227
|
}
|
|
@@ -4381,6 +6277,11 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4381
6277
|
outputView,
|
|
4382
6278
|
"plasius.wavefront.bind.denoise.scratchToOutput"
|
|
4383
6279
|
);
|
|
6280
|
+
const denoiseDirectResolveBindGroup = createDenoiseResolveBindGroup(
|
|
6281
|
+
radianceView,
|
|
6282
|
+
outputView,
|
|
6283
|
+
"plasius.wavefront.bind.denoise.radianceToOutput"
|
|
6284
|
+
);
|
|
4384
6285
|
|
|
4385
6286
|
const presentBindGroupLayout = device.createBindGroupLayout({
|
|
4386
6287
|
label: "plasius.wavefront.presentBindGroupLayout",
|
|
@@ -4420,24 +6321,137 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4420
6321
|
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
4421
6322
|
let accelerationBuildCount = 0;
|
|
4422
6323
|
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA;
|
|
6324
|
+
let lastCompletedFrameTimeMs = null;
|
|
6325
|
+
let lastCompletedSamplesPerPixel = Math.max(1, config.samplesPerPixel);
|
|
4423
6326
|
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
4424
6327
|
gpuAdapterParallelism,
|
|
4425
6328
|
createGpuParallelismCounters()
|
|
4426
6329
|
);
|
|
4427
6330
|
|
|
6331
|
+
function resolveRenderedSamplesPerPixel(renderOptions = {}, awaitGPUCompletion = true) {
|
|
6332
|
+
const targetSamplesPerPixel = clamp(
|
|
6333
|
+
readPositiveInteger(
|
|
6334
|
+
"samplesPerPixel",
|
|
6335
|
+
renderOptions.samplesPerPixel,
|
|
6336
|
+
config.samplesPerPixel
|
|
6337
|
+
),
|
|
6338
|
+
1,
|
|
6339
|
+
config.samplesPerPixel
|
|
6340
|
+
);
|
|
6341
|
+
const frameTimeBudgetMs = Number.isFinite(renderOptions.frameTimeBudgetMs)
|
|
6342
|
+
? Math.max(0, Number(renderOptions.frameTimeBudgetMs))
|
|
6343
|
+
: null;
|
|
6344
|
+
const minimumSamplesPerPixel = clamp(
|
|
6345
|
+
readPositiveInteger(
|
|
6346
|
+
"minimumSamplesPerPixel",
|
|
6347
|
+
renderOptions.minimumSamplesPerPixel,
|
|
6348
|
+
frameTimeBudgetMs !== null && targetSamplesPerPixel > 1 ? 1 : targetSamplesPerPixel
|
|
6349
|
+
),
|
|
6350
|
+
1,
|
|
6351
|
+
targetSamplesPerPixel
|
|
6352
|
+
);
|
|
6353
|
+
if (frameTimeBudgetMs === null || !awaitGPUCompletion || targetSamplesPerPixel <= minimumSamplesPerPixel) {
|
|
6354
|
+
return Object.freeze({
|
|
6355
|
+
renderedSamplesPerPixel: targetSamplesPerPixel,
|
|
6356
|
+
targetSamplesPerPixel,
|
|
6357
|
+
minimumSamplesPerPixel,
|
|
6358
|
+
frameTimeBudgetMs,
|
|
6359
|
+
budgetConstrained: false,
|
|
6360
|
+
});
|
|
6361
|
+
}
|
|
6362
|
+
const estimatedSampleTimeMs =
|
|
6363
|
+
Number.isFinite(lastCompletedFrameTimeMs) && lastCompletedFrameTimeMs > 0
|
|
6364
|
+
? lastCompletedFrameTimeMs / Math.max(1, lastCompletedSamplesPerPixel)
|
|
6365
|
+
: null;
|
|
6366
|
+
if (!Number.isFinite(estimatedSampleTimeMs) || estimatedSampleTimeMs <= 0) {
|
|
6367
|
+
return Object.freeze({
|
|
6368
|
+
renderedSamplesPerPixel: minimumSamplesPerPixel,
|
|
6369
|
+
targetSamplesPerPixel,
|
|
6370
|
+
minimumSamplesPerPixel,
|
|
6371
|
+
frameTimeBudgetMs,
|
|
6372
|
+
budgetConstrained: minimumSamplesPerPixel < targetSamplesPerPixel,
|
|
6373
|
+
});
|
|
6374
|
+
}
|
|
6375
|
+
const budgetLimitedSamples = clamp(
|
|
6376
|
+
Math.floor(frameTimeBudgetMs / estimatedSampleTimeMs),
|
|
6377
|
+
minimumSamplesPerPixel,
|
|
6378
|
+
targetSamplesPerPixel
|
|
6379
|
+
);
|
|
6380
|
+
return Object.freeze({
|
|
6381
|
+
renderedSamplesPerPixel: budgetLimitedSamples,
|
|
6382
|
+
targetSamplesPerPixel,
|
|
6383
|
+
minimumSamplesPerPixel,
|
|
6384
|
+
frameTimeBudgetMs,
|
|
6385
|
+
budgetConstrained: budgetLimitedSamples < targetSamplesPerPixel,
|
|
6386
|
+
});
|
|
6387
|
+
}
|
|
6388
|
+
|
|
6389
|
+
function createFrameStats({
|
|
6390
|
+
frameIndex,
|
|
6391
|
+
accelerationBuildSubmitted,
|
|
6392
|
+
frameSubmissionCount,
|
|
6393
|
+
parallelismCounters,
|
|
6394
|
+
renderedSamplesPerPixel,
|
|
6395
|
+
targetSamplesPerPixel,
|
|
6396
|
+
frameTimeBudgetMs,
|
|
6397
|
+
budgetConstrained,
|
|
6398
|
+
}) {
|
|
6399
|
+
lastGpuParallelism = createGpuParallelismDiagnostics(gpuAdapterParallelism, parallelismCounters);
|
|
6400
|
+
const commandSubmissions = frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0);
|
|
6401
|
+
return Object.freeze({
|
|
6402
|
+
frame,
|
|
6403
|
+
frameIndex,
|
|
6404
|
+
width: config.width,
|
|
6405
|
+
height: config.height,
|
|
6406
|
+
maxDepth: config.maxDepth,
|
|
6407
|
+
tiles: tiles.length,
|
|
6408
|
+
tileSize: config.tileSize,
|
|
6409
|
+
samplesPerPixel: targetSamplesPerPixel,
|
|
6410
|
+
renderedSamplesPerPixel,
|
|
6411
|
+
frameTimeBudgetMs,
|
|
6412
|
+
budgetConstrained,
|
|
6413
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6414
|
+
screenRays: config.width * config.height,
|
|
6415
|
+
primaryRays: config.width * config.height * renderedSamplesPerPixel,
|
|
6416
|
+
sceneObjectCount: config.sceneObjectCount,
|
|
6417
|
+
triangleCount: config.triangleCount,
|
|
6418
|
+
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6419
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
6420
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
6421
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6422
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
6423
|
+
bvhNodeCount: config.bvhNodeCount,
|
|
6424
|
+
displayQuality: config.displayQuality,
|
|
6425
|
+
accelerationBuildMode: config.accelerationBuildMode,
|
|
6426
|
+
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
6427
|
+
accelerationBuildSubmitted,
|
|
6428
|
+
accelerationBuilt,
|
|
6429
|
+
accelerationBuildCount,
|
|
6430
|
+
commandSubmissions,
|
|
6431
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
6432
|
+
gpuParallelism: lastGpuParallelism,
|
|
6433
|
+
memory: config.memory,
|
|
6434
|
+
});
|
|
6435
|
+
}
|
|
6436
|
+
|
|
6437
|
+
function writeFrameConfigSlot(slot, tile, frameIndex, buildRange = {}) {
|
|
6438
|
+
if (slot >= frameConfigSlotCount) {
|
|
6439
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
6440
|
+
}
|
|
6441
|
+
const offset = slot * configBufferStride;
|
|
6442
|
+
device.queue.writeBuffer(
|
|
6443
|
+
configBuffer,
|
|
6444
|
+
offset,
|
|
6445
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
6446
|
+
);
|
|
6447
|
+
return offset;
|
|
6448
|
+
}
|
|
6449
|
+
|
|
4428
6450
|
function createFrameConfigWriter(frameIndex) {
|
|
4429
6451
|
let slot = 0;
|
|
4430
6452
|
return (tile, buildRange = {}) => {
|
|
4431
|
-
|
|
4432
|
-
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
4433
|
-
}
|
|
4434
|
-
const offset = slot * configBufferStride;
|
|
6453
|
+
const offset = writeFrameConfigSlot(slot, tile, frameIndex, buildRange);
|
|
4435
6454
|
slot += 1;
|
|
4436
|
-
device.queue.writeBuffer(
|
|
4437
|
-
configBuffer,
|
|
4438
|
-
offset,
|
|
4439
|
-
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
4440
|
-
);
|
|
4441
6455
|
return offset;
|
|
4442
6456
|
};
|
|
4443
6457
|
}
|
|
@@ -4483,7 +6497,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4483
6497
|
passEncoder.setPipeline(pipelines.prepareMeshTrianglesAndLeaves);
|
|
4484
6498
|
const prepareWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4485
6499
|
passEncoder.dispatchWorkgroups(prepareWorkgroups);
|
|
4486
|
-
recordDirectDispatch(parallelism, [prepareWorkgroups]);
|
|
6500
|
+
recordDirectDispatch(parallelism, [prepareWorkgroups], WORKGROUP_SIZE);
|
|
4487
6501
|
passEncoder.setPipeline(pipelines.sortBvhLeafRefs);
|
|
4488
6502
|
for (let stageIndex = 0; stageIndex < config.bvhSortStages.length; stageIndex += 1) {
|
|
4489
6503
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [
|
|
@@ -4491,13 +6505,13 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4491
6505
|
]);
|
|
4492
6506
|
const sortWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4493
6507
|
passEncoder.dispatchWorkgroups(sortWorkgroups);
|
|
4494
|
-
recordDirectDispatch(parallelism, [sortWorkgroups]);
|
|
6508
|
+
recordDirectDispatch(parallelism, [sortWorkgroups], WORKGROUP_SIZE);
|
|
4495
6509
|
}
|
|
4496
6510
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [0]);
|
|
4497
6511
|
passEncoder.setPipeline(pipelines.writeSortedBvhLeaves);
|
|
4498
6512
|
const leafWriteWorkgroups = Math.ceil(config.triangleCount / WORKGROUP_SIZE);
|
|
4499
6513
|
passEncoder.dispatchWorkgroups(leafWriteWorkgroups);
|
|
4500
|
-
recordDirectDispatch(parallelism, [leafWriteWorkgroups]);
|
|
6514
|
+
recordDirectDispatch(parallelism, [leafWriteWorkgroups], WORKGROUP_SIZE);
|
|
4501
6515
|
passEncoder.setPipeline(pipelines.buildBvhInternalLevel);
|
|
4502
6516
|
for (let levelIndex = 0; levelIndex < config.bvhBuildLevels.length; levelIndex += 1) {
|
|
4503
6517
|
const buildLevel = config.bvhBuildLevels[levelIndex];
|
|
@@ -4506,7 +6520,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4506
6520
|
]);
|
|
4507
6521
|
const levelWorkgroups = Math.ceil(buildLevel.count / WORKGROUP_SIZE);
|
|
4508
6522
|
passEncoder.dispatchWorkgroups(levelWorkgroups);
|
|
4509
|
-
recordDirectDispatch(parallelism, [levelWorkgroups]);
|
|
6523
|
+
recordDirectDispatch(parallelism, [levelWorkgroups], WORKGROUP_SIZE);
|
|
4510
6524
|
}
|
|
4511
6525
|
passEncoder.end();
|
|
4512
6526
|
device.queue.submit([encoder.finish()]);
|
|
@@ -4524,7 +6538,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4524
6538
|
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4525
6539
|
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
4526
6540
|
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
4527
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6541
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4528
6542
|
generatePass.end();
|
|
4529
6543
|
|
|
4530
6544
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
@@ -4541,10 +6555,10 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4541
6555
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
4542
6556
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
4543
6557
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4544
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6558
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4545
6559
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
4546
6560
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4547
|
-
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
6561
|
+
recordIndirectDispatch(parallelism, tileWorkgroups, WORKGROUP_SIZE);
|
|
4548
6562
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
4549
6563
|
passEncoder.dispatchWorkgroups(1);
|
|
4550
6564
|
recordDirectDispatch(parallelism, [1], 1);
|
|
@@ -4561,32 +6575,47 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4561
6575
|
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
4562
6576
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
4563
6577
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
4564
|
-
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
6578
|
+
recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
|
|
4565
6579
|
passEncoder.end();
|
|
4566
6580
|
}
|
|
4567
6581
|
|
|
4568
|
-
function encodeDenoise(encoder, configOffset, parallelism) {
|
|
6582
|
+
function encodeDenoise(encoder, configOffset, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4569
6583
|
if (!config.denoise) {
|
|
4570
6584
|
return;
|
|
4571
6585
|
}
|
|
4572
6586
|
const denoiseWorkgroupsX = Math.ceil(config.width / 8);
|
|
4573
6587
|
const denoiseWorkgroupsY = Math.ceil(config.height / 8);
|
|
4574
|
-
const
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
6588
|
+
const useTwoPassDenoise = renderedSamplesPerPixel < 4;
|
|
6589
|
+
if (useTwoPassDenoise) {
|
|
6590
|
+
const radiancePass = encoder.beginComputePass({
|
|
6591
|
+
label: "plasius.wavefront.denoiseRadiancePass",
|
|
6592
|
+
});
|
|
6593
|
+
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
6594
|
+
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
6595
|
+
radiancePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
6596
|
+
recordDirectDispatch(
|
|
6597
|
+
parallelism,
|
|
6598
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6599
|
+
WORKGROUP_SIZE
|
|
6600
|
+
);
|
|
6601
|
+
radiancePass.end();
|
|
6602
|
+
}
|
|
4582
6603
|
|
|
4583
6604
|
const resolvePass = encoder.beginComputePass({
|
|
4584
6605
|
label: "plasius.wavefront.denoiseResolvePass",
|
|
4585
6606
|
});
|
|
4586
|
-
resolvePass.setBindGroup(
|
|
6607
|
+
resolvePass.setBindGroup(
|
|
6608
|
+
0,
|
|
6609
|
+
useTwoPassDenoise ? denoiseResolveBindGroup : denoiseDirectResolveBindGroup,
|
|
6610
|
+
[configOffset]
|
|
6611
|
+
);
|
|
4587
6612
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
4588
6613
|
resolvePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
4589
|
-
recordDirectDispatch(
|
|
6614
|
+
recordDirectDispatch(
|
|
6615
|
+
parallelism,
|
|
6616
|
+
[denoiseWorkgroupsX, denoiseWorkgroupsY],
|
|
6617
|
+
WORKGROUP_SIZE
|
|
6618
|
+
);
|
|
4590
6619
|
resolvePass.end();
|
|
4591
6620
|
}
|
|
4592
6621
|
|
|
@@ -4609,105 +6638,233 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4609
6638
|
passEncoder.end();
|
|
4610
6639
|
}
|
|
4611
6640
|
|
|
4612
|
-
function dispatchFrame(frameIndex, parallelism) {
|
|
6641
|
+
function dispatchFrame(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel) {
|
|
4613
6642
|
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
6643
|
+
const batch = createGpuSubmissionBatcher({
|
|
6644
|
+
device,
|
|
6645
|
+
frameIndex,
|
|
6646
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
4618
6647
|
});
|
|
4619
6648
|
|
|
4620
|
-
function submitCurrentEncoder() {
|
|
4621
|
-
if (encodedFramePasses <= 0) {
|
|
4622
|
-
return;
|
|
4623
|
-
}
|
|
4624
|
-
device.queue.submit([encoder.finish()]);
|
|
4625
|
-
submissionCount += 1;
|
|
4626
|
-
encodedFramePasses = 0;
|
|
4627
|
-
encoder = device.createCommandEncoder({
|
|
4628
|
-
label: `plasius.wavefront.frame.${frameIndex}.batched.${submissionCount + 1}`,
|
|
4629
|
-
});
|
|
4630
|
-
}
|
|
4631
|
-
|
|
4632
|
-
function reserveEncoder(passCount = 1) {
|
|
4633
|
-
if (
|
|
4634
|
-
encodedFramePasses > 0 &&
|
|
4635
|
-
encodedFramePasses + passCount > config.maxFramePassesPerSubmission
|
|
4636
|
-
) {
|
|
4637
|
-
submitCurrentEncoder();
|
|
4638
|
-
}
|
|
4639
|
-
encodedFramePasses += passCount;
|
|
4640
|
-
return encoder;
|
|
4641
|
-
}
|
|
4642
|
-
|
|
4643
6649
|
for (const tile of tiles) {
|
|
4644
|
-
for (let sampleIndex = 0; sampleIndex <
|
|
6650
|
+
for (let sampleIndex = 0; sampleIndex < renderedSamplesPerPixel; sampleIndex += 1) {
|
|
4645
6651
|
const configOffset = writeFrameConfig(tile, {
|
|
4646
6652
|
sampleIndex,
|
|
4647
|
-
sampleWeight: 1 /
|
|
6653
|
+
sampleWeight: 1 / renderedSamplesPerPixel,
|
|
4648
6654
|
});
|
|
4649
|
-
encodeTileSample(
|
|
6655
|
+
encodeTileSample(
|
|
6656
|
+
batch.reserve(config.maxDepth + 1),
|
|
6657
|
+
tile,
|
|
6658
|
+
configOffset,
|
|
6659
|
+
parallelism
|
|
6660
|
+
);
|
|
4650
6661
|
if (config.deferredPathResolve) {
|
|
4651
|
-
encodeTileOutput(
|
|
6662
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
4652
6663
|
}
|
|
4653
6664
|
}
|
|
4654
6665
|
if (!config.deferredPathResolve) {
|
|
4655
6666
|
const outputConfigOffset = writeFrameConfig(tile, {
|
|
4656
6667
|
sampleIndex: 0,
|
|
4657
|
-
sampleWeight: 1 /
|
|
6668
|
+
sampleWeight: 1 / renderedSamplesPerPixel,
|
|
4658
6669
|
});
|
|
4659
|
-
encodeTileOutput(
|
|
6670
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
4660
6671
|
}
|
|
4661
6672
|
}
|
|
4662
6673
|
if (config.denoise) {
|
|
4663
6674
|
const denoiseConfigOffset = writeFrameConfig(
|
|
4664
6675
|
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
4665
|
-
{ sampleIndex: 0, sampleWeight: 1 /
|
|
6676
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6677
|
+
);
|
|
6678
|
+
const denoisePassCount = renderedSamplesPerPixel < 4 ? 2 : 1;
|
|
6679
|
+
encodeDenoise(
|
|
6680
|
+
batch.reserve(denoisePassCount),
|
|
6681
|
+
denoiseConfigOffset,
|
|
6682
|
+
parallelism,
|
|
6683
|
+
renderedSamplesPerPixel
|
|
4666
6684
|
);
|
|
4667
|
-
encodeDenoise(reserveEncoder(), denoiseConfigOffset, parallelism);
|
|
4668
6685
|
}
|
|
4669
|
-
encodePresent(
|
|
4670
|
-
|
|
4671
|
-
return submissionCount;
|
|
6686
|
+
encodePresent(batch.reserve(1));
|
|
6687
|
+
return batch.flush();
|
|
4672
6688
|
}
|
|
4673
6689
|
|
|
4674
|
-
function renderOnce() {
|
|
6690
|
+
function renderOnce(renderOptions = {}, resolvedSamplingPlan = null) {
|
|
6691
|
+
const frameStartTimeMs = nowMs();
|
|
4675
6692
|
frame += 1;
|
|
4676
6693
|
const frameIndex = frame + config.frameIndex;
|
|
6694
|
+
const samplingPlan = resolvedSamplingPlan ?? resolveRenderedSamplesPerPixel(renderOptions, false);
|
|
4677
6695
|
const parallelismCounters = createGpuParallelismCounters();
|
|
4678
6696
|
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
4679
|
-
const frameSubmissionCount = dispatchFrame(
|
|
4680
|
-
|
|
6697
|
+
const frameSubmissionCount = dispatchFrame(
|
|
6698
|
+
frameIndex,
|
|
6699
|
+
parallelismCounters,
|
|
6700
|
+
samplingPlan.renderedSamplesPerPixel
|
|
6701
|
+
);
|
|
6702
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
4681
6703
|
return Object.freeze({
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
6704
|
+
...createFrameStats({
|
|
6705
|
+
frameIndex,
|
|
6706
|
+
accelerationBuildSubmitted,
|
|
6707
|
+
frameSubmissionCount,
|
|
6708
|
+
parallelismCounters,
|
|
6709
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
6710
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
6711
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
6712
|
+
budgetConstrained: samplingPlan.budgetConstrained,
|
|
6713
|
+
}),
|
|
6714
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
6715
|
+
lastGpuParallelism,
|
|
6716
|
+
frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
6717
|
+
frameTimeMs,
|
|
6718
|
+
false
|
|
6719
|
+
),
|
|
6720
|
+
});
|
|
6721
|
+
}
|
|
6722
|
+
|
|
6723
|
+
async function waitForSubmittedGpuWork(options = {}) {
|
|
6724
|
+
if (typeof device.queue.onSubmittedWorkDone !== "function") {
|
|
6725
|
+
return true;
|
|
6726
|
+
}
|
|
6727
|
+
const timeoutMs = Math.max(
|
|
6728
|
+
1,
|
|
6729
|
+
Number.isFinite(options.timeoutMs)
|
|
6730
|
+
? Number(options.timeoutMs)
|
|
6731
|
+
: GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
6732
|
+
);
|
|
6733
|
+
const allowTimeout = options.allowTimeout !== false;
|
|
6734
|
+
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
6735
|
+
() => ({ status: "done" }),
|
|
6736
|
+
(error) => {
|
|
6737
|
+
throw error;
|
|
6738
|
+
}
|
|
6739
|
+
);
|
|
6740
|
+
const lossPromise =
|
|
6741
|
+
typeof device.lost?.then === "function"
|
|
6742
|
+
? device.lost.then((info) => {
|
|
6743
|
+
throw new Error(
|
|
6744
|
+
`WebGPU device lost while waiting for submitted work (${info?.reason ?? "unknown"}).`
|
|
6745
|
+
);
|
|
6746
|
+
})
|
|
6747
|
+
: null;
|
|
6748
|
+
let timeoutHandle = null;
|
|
6749
|
+
let resolveTimeoutPromise = null;
|
|
6750
|
+
let timeoutSettled = false;
|
|
6751
|
+
const settleTimeoutPromise = (value) => {
|
|
6752
|
+
if (timeoutSettled) {
|
|
6753
|
+
return;
|
|
6754
|
+
}
|
|
6755
|
+
timeoutSettled = true;
|
|
6756
|
+
resolveTimeoutPromise?.(value);
|
|
6757
|
+
};
|
|
6758
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
6759
|
+
resolveTimeoutPromise = resolve;
|
|
6760
|
+
timeoutHandle = setTimeout(() => settleTimeoutPromise({ status: "timeout" }), timeoutMs);
|
|
6761
|
+
});
|
|
6762
|
+
let result;
|
|
6763
|
+
try {
|
|
6764
|
+
result = await Promise.race(
|
|
6765
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
6766
|
+
);
|
|
6767
|
+
} finally {
|
|
6768
|
+
if (timeoutHandle !== null) {
|
|
6769
|
+
clearTimeout(timeoutHandle);
|
|
6770
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
6771
|
+
}
|
|
6772
|
+
}
|
|
6773
|
+
if (result?.status === "timeout") {
|
|
6774
|
+
if (!allowTimeout) {
|
|
6775
|
+
throw new Error(`Timed out after ${timeoutMs} ms waiting for submitted GPU work.`);
|
|
6776
|
+
}
|
|
6777
|
+
console.warn(
|
|
6778
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
|
|
6779
|
+
);
|
|
6780
|
+
return false;
|
|
6781
|
+
}
|
|
6782
|
+
return true;
|
|
6783
|
+
}
|
|
6784
|
+
|
|
6785
|
+
function dispatchFrameAwaitingGpu(
|
|
6786
|
+
frameIndex,
|
|
6787
|
+
parallelism,
|
|
6788
|
+
renderedSamplesPerPixel = config.samplesPerPixel
|
|
6789
|
+
) {
|
|
6790
|
+
const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
|
|
6791
|
+
const denoisePassCount = config.denoise ? (renderedSamplesPerPixel < 4 ? 2 : 1) : 0;
|
|
6792
|
+
const tailPassCount = denoisePassCount + 1;
|
|
6793
|
+
const sampleBatchSize = Math.max(
|
|
6794
|
+
1,
|
|
6795
|
+
Math.floor(
|
|
6796
|
+
Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) /
|
|
6797
|
+
Math.max(samplePassesPerSample, 1)
|
|
6798
|
+
)
|
|
6799
|
+
);
|
|
6800
|
+
let submissionCount = 0;
|
|
6801
|
+
|
|
6802
|
+
for (const tile of tiles) {
|
|
6803
|
+
for (
|
|
6804
|
+
let sampleStart = 0;
|
|
6805
|
+
sampleStart < renderedSamplesPerPixel;
|
|
6806
|
+
sampleStart += sampleBatchSize
|
|
6807
|
+
) {
|
|
6808
|
+
const sampleEnd = Math.min(renderedSamplesPerPixel, sampleStart + sampleBatchSize);
|
|
6809
|
+
const batch = createGpuSubmissionBatcher({
|
|
6810
|
+
device,
|
|
6811
|
+
frameIndex,
|
|
6812
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6813
|
+
startingSubmissionCount: submissionCount,
|
|
6814
|
+
});
|
|
6815
|
+
let slot = 0;
|
|
6816
|
+
for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
|
|
6817
|
+
const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6818
|
+
sampleIndex,
|
|
6819
|
+
sampleWeight: 1 / renderedSamplesPerPixel,
|
|
6820
|
+
});
|
|
6821
|
+
slot += 1;
|
|
6822
|
+
encodeTileSample(
|
|
6823
|
+
batch.reserve(config.maxDepth + 1),
|
|
6824
|
+
tile,
|
|
6825
|
+
configOffset,
|
|
6826
|
+
parallelism
|
|
6827
|
+
);
|
|
6828
|
+
if (config.deferredPathResolve) {
|
|
6829
|
+
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
6830
|
+
}
|
|
6831
|
+
}
|
|
6832
|
+
if (!config.deferredPathResolve && sampleEnd >= renderedSamplesPerPixel) {
|
|
6833
|
+
const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6834
|
+
sampleIndex: 0,
|
|
6835
|
+
sampleWeight: 1 / renderedSamplesPerPixel,
|
|
6836
|
+
});
|
|
6837
|
+
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
6838
|
+
}
|
|
6839
|
+
batch.flush();
|
|
6840
|
+
submissionCount += batch.getSubmissionCount();
|
|
6841
|
+
}
|
|
6842
|
+
}
|
|
6843
|
+
|
|
6844
|
+
const tail = createGpuSubmissionBatcher({
|
|
6845
|
+
device,
|
|
6846
|
+
frameIndex,
|
|
4689
6847
|
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
4690
|
-
|
|
4691
|
-
primaryRays: config.width * config.height * config.samplesPerPixel,
|
|
4692
|
-
sceneObjectCount: config.sceneObjectCount,
|
|
4693
|
-
triangleCount: config.triangleCount,
|
|
4694
|
-
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
4695
|
-
environmentPortalCount: config.environmentPortalCount,
|
|
4696
|
-
environmentPortalMode: config.environmentPortalMode,
|
|
4697
|
-
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4698
|
-
deferredPathResolve: config.deferredPathResolve,
|
|
4699
|
-
bvhNodeCount: config.bvhNodeCount,
|
|
4700
|
-
displayQuality: config.displayQuality,
|
|
4701
|
-
accelerationBuildMode: config.accelerationBuildMode,
|
|
4702
|
-
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
4703
|
-
accelerationBuildSubmitted,
|
|
4704
|
-
accelerationBuilt,
|
|
4705
|
-
accelerationBuildCount,
|
|
4706
|
-
commandSubmissions: frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
4707
|
-
frameConfigSlots: frameConfigSlotCount,
|
|
4708
|
-
gpuParallelism: lastGpuParallelism,
|
|
4709
|
-
memory: config.memory,
|
|
6848
|
+
startingSubmissionCount: submissionCount,
|
|
4710
6849
|
});
|
|
6850
|
+
if (config.denoise) {
|
|
6851
|
+
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
6852
|
+
0,
|
|
6853
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
6854
|
+
frameIndex,
|
|
6855
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
6856
|
+
);
|
|
6857
|
+
encodeDenoise(
|
|
6858
|
+
tail.reserve(denoisePassCount),
|
|
6859
|
+
denoiseConfigOffset,
|
|
6860
|
+
parallelism,
|
|
6861
|
+
renderedSamplesPerPixel
|
|
6862
|
+
);
|
|
6863
|
+
}
|
|
6864
|
+
encodePresent(tail.reserve(1));
|
|
6865
|
+
tail.flush();
|
|
6866
|
+
submissionCount += tail.getSubmissionCount();
|
|
6867
|
+
return submissionCount;
|
|
4711
6868
|
}
|
|
4712
6869
|
|
|
4713
6870
|
async function readOutputProbe(optionsForProbe = {}) {
|
|
@@ -4722,6 +6879,10 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4722
6879
|
size: 256,
|
|
4723
6880
|
usage: constants.buffer.COPY_DST | constants.buffer.MAP_READ,
|
|
4724
6881
|
});
|
|
6882
|
+
await waitForSubmittedGpuWork({
|
|
6883
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
6884
|
+
allowTimeout: false,
|
|
6885
|
+
});
|
|
4725
6886
|
const encoder = device.createCommandEncoder({
|
|
4726
6887
|
label: "plasius.wavefront.outputProbe.copy",
|
|
4727
6888
|
});
|
|
@@ -4731,6 +6892,10 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4731
6892
|
{ width: 1, height: 1, depthOrArrayLayers: 1 }
|
|
4732
6893
|
);
|
|
4733
6894
|
device.queue.submit([encoder.finish()]);
|
|
6895
|
+
await waitForSubmittedGpuWork({
|
|
6896
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
6897
|
+
allowTimeout: false,
|
|
6898
|
+
});
|
|
4734
6899
|
await readback.mapAsync(mapMode.READ);
|
|
4735
6900
|
const bytes = new Uint8Array(readback.getMappedRange()).slice(0, 4);
|
|
4736
6901
|
readback.unmap();
|
|
@@ -4744,7 +6909,60 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4744
6909
|
}
|
|
4745
6910
|
|
|
4746
6911
|
async function renderFrame(renderOptions = {}) {
|
|
4747
|
-
const
|
|
6912
|
+
const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
|
|
6913
|
+
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
6914
|
+
const useThrottledHighSamplePath =
|
|
6915
|
+
awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
6916
|
+
const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
|
|
6917
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6918
|
+
tiles.length,
|
|
6919
|
+
renderOptions.submittedWorkTimeoutMs
|
|
6920
|
+
);
|
|
6921
|
+
const frameStartTimeMs = nowMs();
|
|
6922
|
+
const submissionWaitOptions = awaitGPUCompletion
|
|
6923
|
+
? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false }
|
|
6924
|
+
: { timeoutMs: submittedWorkTimeoutMs };
|
|
6925
|
+
let frameStats;
|
|
6926
|
+
if (useThrottledHighSamplePath) {
|
|
6927
|
+
frame += 1;
|
|
6928
|
+
const frameIndex = frame + config.frameIndex;
|
|
6929
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
6930
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
6931
|
+
const frameSubmissionCount = dispatchFrameAwaitingGpu(
|
|
6932
|
+
frameIndex,
|
|
6933
|
+
parallelismCounters,
|
|
6934
|
+
samplingPlan.renderedSamplesPerPixel
|
|
6935
|
+
);
|
|
6936
|
+
frameStats = createFrameStats({
|
|
6937
|
+
frameIndex,
|
|
6938
|
+
accelerationBuildSubmitted,
|
|
6939
|
+
frameSubmissionCount,
|
|
6940
|
+
parallelismCounters,
|
|
6941
|
+
renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
|
|
6942
|
+
targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
|
|
6943
|
+
frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
|
|
6944
|
+
budgetConstrained: samplingPlan.budgetConstrained,
|
|
6945
|
+
});
|
|
6946
|
+
} else {
|
|
6947
|
+
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
6948
|
+
}
|
|
6949
|
+
if (awaitGPUCompletion) {
|
|
6950
|
+
await waitForSubmittedGpuWork(submissionWaitOptions);
|
|
6951
|
+
}
|
|
6952
|
+
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
6953
|
+
if (awaitGPUCompletion) {
|
|
6954
|
+
lastCompletedFrameTimeMs = frameTimeMs;
|
|
6955
|
+
lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
|
|
6956
|
+
}
|
|
6957
|
+
frameStats = Object.freeze({
|
|
6958
|
+
...frameStats,
|
|
6959
|
+
gpuWorkerJobs: createGpuWorkerJobDiagnostics(
|
|
6960
|
+
frameStats.gpuParallelism,
|
|
6961
|
+
frameStats.commandSubmissions,
|
|
6962
|
+
frameTimeMs,
|
|
6963
|
+
awaitGPUCompletion
|
|
6964
|
+
),
|
|
6965
|
+
});
|
|
4748
6966
|
const probe =
|
|
4749
6967
|
renderOptions.readOutputProbe === false ? null : await readOutputProbe(renderOptions.probe);
|
|
4750
6968
|
const maxChannel = probe ? Math.max(...probe.rgba.slice(0, 3)) : 0;
|
|
@@ -4769,10 +6987,8 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4769
6987
|
});
|
|
4770
6988
|
}
|
|
4771
6989
|
|
|
4772
|
-
function
|
|
4773
|
-
|
|
4774
|
-
packedScene = nextPackedScene;
|
|
4775
|
-
config = createWavefrontPathTracingComputeConfig({
|
|
6990
|
+
function rebuildLiveConfig(overrides = {}) {
|
|
6991
|
+
return createWavefrontPathTracingComputeConfig({
|
|
4776
6992
|
...options,
|
|
4777
6993
|
canvas,
|
|
4778
6994
|
width: config.width,
|
|
@@ -4783,27 +6999,25 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4783
6999
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4784
7000
|
sceneObjects: packedScene.objects,
|
|
4785
7001
|
camera: activeCameraOptions,
|
|
7002
|
+
environmentMap: {
|
|
7003
|
+
...config.environmentMap,
|
|
7004
|
+
},
|
|
4786
7005
|
frameIndex: config.frameIndex,
|
|
7006
|
+
...overrides,
|
|
4787
7007
|
});
|
|
7008
|
+
}
|
|
7009
|
+
|
|
7010
|
+
function updateSceneObjects(sceneObjects) {
|
|
7011
|
+
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
7012
|
+
packedScene = nextPackedScene;
|
|
7013
|
+
config = rebuildLiveConfig();
|
|
4788
7014
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
4789
7015
|
return config;
|
|
4790
7016
|
}
|
|
4791
7017
|
|
|
4792
7018
|
function updateCamera(cameraOptions = {}) {
|
|
4793
7019
|
activeCameraOptions = cameraOptions;
|
|
4794
|
-
config =
|
|
4795
|
-
...options,
|
|
4796
|
-
canvas,
|
|
4797
|
-
width: config.width,
|
|
4798
|
-
height: config.height,
|
|
4799
|
-
maxDepth: config.maxDepth,
|
|
4800
|
-
tileSize: config.tileSize,
|
|
4801
|
-
samplesPerPixel: config.samplesPerPixel,
|
|
4802
|
-
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4803
|
-
sceneObjects: packedScene.objects,
|
|
4804
|
-
camera: activeCameraOptions,
|
|
4805
|
-
frameIndex: config.frameIndex,
|
|
4806
|
-
});
|
|
7020
|
+
config = rebuildLiveConfig();
|
|
4807
7021
|
return config;
|
|
4808
7022
|
}
|
|
4809
7023
|
|
|
@@ -4856,9 +7070,28 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
4856
7070
|
activeDispatchBuffer.destroy?.();
|
|
4857
7071
|
radianceTexture.destroy?.();
|
|
4858
7072
|
denoiseScratchTexture.destroy?.();
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
7073
|
+
outputTexture.destroy?.();
|
|
7074
|
+
if (environmentMapResource.ownsTexture) {
|
|
7075
|
+
environmentMapResource.texture?.destroy?.();
|
|
7076
|
+
}
|
|
7077
|
+
if (environmentSamplingResource.ownsTexture) {
|
|
7078
|
+
environmentSamplingResource.texture?.destroy?.();
|
|
7079
|
+
}
|
|
7080
|
+
brdfLutResource.texture?.destroy?.();
|
|
7081
|
+
if (baseColorAtlasResource.ownsTexture) {
|
|
7082
|
+
baseColorAtlasResource.texture?.destroy?.();
|
|
7083
|
+
}
|
|
7084
|
+
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
7085
|
+
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
7086
|
+
}
|
|
7087
|
+
if (normalAtlasResource.ownsTexture) {
|
|
7088
|
+
normalAtlasResource.texture?.destroy?.();
|
|
7089
|
+
}
|
|
7090
|
+
if (occlusionAtlasResource.ownsTexture) {
|
|
7091
|
+
occlusionAtlasResource.texture?.destroy?.();
|
|
7092
|
+
}
|
|
7093
|
+
if (emissiveAtlasResource.ownsTexture) {
|
|
7094
|
+
emissiveAtlasResource.texture?.destroy?.();
|
|
4862
7095
|
}
|
|
4863
7096
|
context.unconfigure?.();
|
|
4864
7097
|
}
|