@plasius/gpu-renderer 0.2.46 → 0.2.48
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 +53 -0
- package/README.md +230 -0
- package/dist/index.cjs +512 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +510 -40
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/canonical-wavefront-meshes.js +226 -0
- package/src/index.d.ts +55 -0
- package/src/index.js +3 -0
- package/src/native-scene-renderer.js +152 -7
- package/src/native-scene-shader.js +68 -18
- package/src/native-surface-materials.js +124 -0
package/dist/index.js
CHANGED
|
@@ -556,12 +556,18 @@ struct Frame {
|
|
|
556
556
|
@group(0) @binding(0) var<uniform> frame: Frame;
|
|
557
557
|
@group(1) @binding(0) var shadowMap: texture_depth_2d;
|
|
558
558
|
@group(1) @binding(1) var shadowSampler: sampler_comparison;
|
|
559
|
-
@group(
|
|
560
|
-
@group(
|
|
559
|
+
@group(3) @binding(0) var reflectionMap: texture_2d<f32>;
|
|
560
|
+
@group(3) @binding(1) var reflectionSampler: sampler;
|
|
561
|
+
@group(2) @binding(0) var baseColourMap: texture_2d<f32>;
|
|
562
|
+
@group(2) @binding(1) var normalMap: texture_2d<f32>;
|
|
563
|
+
@group(2) @binding(2) var ormMap: texture_2d<f32>;
|
|
564
|
+
@group(2) @binding(3) var materialSampler: sampler;
|
|
565
|
+
@group(2) @binding(4) var<uniform> materialParameters: vec4f;
|
|
566
|
+
@group(2) @binding(5) var clearcoatMap: texture_2d<f32>;
|
|
561
567
|
struct Surface {
|
|
562
568
|
@builtin(position) clip: vec4f,
|
|
563
569
|
@location(0) world: vec3f, @location(1) normal: vec3f,
|
|
564
|
-
@location(2) colour: vec3f, @location(3) material: vec3f,
|
|
570
|
+
@location(2) colour: vec3f, @location(3) material: vec3f, @location(4) uv: vec2f,
|
|
565
571
|
};
|
|
566
572
|
fn project(p: vec3f) -> vec4f {
|
|
567
573
|
let d = p - frame.eye.xyz;
|
|
@@ -575,8 +581,8 @@ fn lightProject(p: vec3f) -> vec4f {
|
|
|
575
581
|
dot(d, frame.lightUp.xyz) / 34.0, dot(d, frame.lightForward.xyz) / 100.0, 1.0);
|
|
576
582
|
}
|
|
577
583
|
@vertex fn surfaceVertex(@location(0) p: vec3f, @location(1) n: vec3f,
|
|
578
|
-
@location(2) c: vec3f, @location(3) m: vec3f) -> Surface {
|
|
579
|
-
return Surface(project(p), p, n, c, m);
|
|
584
|
+
@location(2) c: vec3f, @location(3) m: vec3f, @location(4) uv: vec2f) -> Surface {
|
|
585
|
+
return Surface(project(p), p, n, c, m, uv);
|
|
580
586
|
}
|
|
581
587
|
@vertex fn shadowVertex(@location(0) p: vec3f) -> @builtin(position) vec4f {
|
|
582
588
|
return lightProject(p);
|
|
@@ -629,17 +635,25 @@ fn visibility(p: vec3f, n: vec3f) -> f32 {
|
|
|
629
635
|
}
|
|
630
636
|
return result / 9.0;
|
|
631
637
|
}
|
|
632
|
-
|
|
638
|
+
// Dielectric coating is evaluated independently from the coloured substrate.
|
|
639
|
+
// This returns the microfacet lobe without Fresnel, including the cosine term.
|
|
640
|
+
fn specularLobe(n: vec3f, v: vec3f, roughness: f32) -> f32 {
|
|
633
641
|
let l = frame.sun.xyz; let h = normalize(l + v);
|
|
634
642
|
let nl = max(dot(n,l),0.0); let nv = max(dot(n,v),0.001);
|
|
635
|
-
let nh = max(dot(n,h),0.0);
|
|
643
|
+
let nh = max(dot(n,h),0.0);
|
|
636
644
|
let a = max(0.035, roughness * roughness); let a2 = a * a;
|
|
637
645
|
let d = a2 / max(0.00001, 3.14159265 * pow(nh * nh * (a2 - 1.0) + 1.0, 2.0));
|
|
638
646
|
let k = pow(roughness + 1.0, 2.0) / 8.0;
|
|
639
647
|
let g = (nl / (nl * (1.0-k) + k)) * (nv / (nv * (1.0-k) + k));
|
|
648
|
+
return d * g / max(0.001, 4.0*nl*nv) * nl;
|
|
649
|
+
}
|
|
650
|
+
fn brdf(c: vec3f, n: vec3f, v: vec3f, roughness: f32, metal: f32) -> vec3f {
|
|
651
|
+
let h = normalize(frame.sun.xyz + v);
|
|
652
|
+
let nl = max(dot(n,frame.sun.xyz),0.0);
|
|
653
|
+
let vh = max(dot(v,h),0.0);
|
|
640
654
|
let f0 = mix(vec3f(0.04), c, metal);
|
|
641
655
|
let f = f0 + (1.0-f0) * pow(1.0-vh, 5.0);
|
|
642
|
-
return (
|
|
656
|
+
return (1.0-f) * (1.0-metal) * c / 3.14159265 * nl + specularLobe(n,v,roughness) * f;
|
|
643
657
|
}
|
|
644
658
|
fn fog(c: vec3f, p: vec3f) -> vec3f {
|
|
645
659
|
let amount = 1.0 - exp(-distance(frame.eye.xyz, p) * 0.0018);
|
|
@@ -647,23 +661,58 @@ fn fog(c: vec3f, p: vec3f) -> vec3f {
|
|
|
647
661
|
}
|
|
648
662
|
fn surfaceRadiance(s: Surface, front: bool) -> vec3f {
|
|
649
663
|
let facing = select(select(-1.0,1.0,front),select(1.0,-1.0,front),frame.info.y>0.5);
|
|
650
|
-
|
|
664
|
+
let coatNormal = normalize(s.normal) * facing;
|
|
665
|
+
var n = coatNormal;
|
|
651
666
|
let v = normalize(frame.eye.xyz - s.world);
|
|
652
|
-
|
|
653
|
-
|
|
667
|
+
// Derivatives and samples must be uniform across each fragment quad.
|
|
668
|
+
// Relative degeneracy: raw derivative magnitudes shrink with model scale and
|
|
669
|
+
// zoom. An absolute tangent cutoff silently disabled normal maps in close-ups.
|
|
670
|
+
let dpScale = max(length(dpdx(s.world)), length(dpdy(s.world)));
|
|
671
|
+
let uvScale = max(length(dpdx(s.uv)), length(dpdy(s.uv)));
|
|
672
|
+
let dx = dpdx(s.world) / max(dpScale, 1e-20);
|
|
673
|
+
let dy = dpdy(s.world) / max(dpScale, 1e-20);
|
|
674
|
+
let ux = dpdx(s.uv) / max(uvScale, 1e-20);
|
|
675
|
+
let uy = dpdy(s.uv) / max(uvScale, 1e-20);
|
|
676
|
+
let determinant = ux.x * uy.y - ux.y * uy.x;
|
|
677
|
+
let tangent = dx * uy.y - dy * ux.y;
|
|
678
|
+
let bitangent = dy * ux.x - dx * uy.x;
|
|
679
|
+
let sampledNormal = textureSample(normalMap, materialSampler, s.uv).xyz * 2.0 - 1.0;
|
|
680
|
+
var c = s.colour * textureSample(baseColourMap, materialSampler, s.uv).rgb;
|
|
681
|
+
let orm = textureSample(ormMap, materialSampler, s.uv).rgb;
|
|
682
|
+
let coatSample = textureSample(clearcoatMap, materialSampler, s.uv).rg;
|
|
683
|
+
let roughness = clamp(s.material.x * orm.g, 0.04, 1.0);
|
|
684
|
+
let metalness = clamp(s.material.y * orm.b, 0.0, 1.0);
|
|
685
|
+
let orthogonalTangent = tangent - n * dot(n,tangent);
|
|
686
|
+
let orthogonalBitangent = bitangent - n * dot(n,bitangent);
|
|
687
|
+
if (dpScale > 1e-20 && uvScale > 1e-20 && abs(determinant) > 1e-6 && dot(orthogonalTangent,orthogonalTangent) > 1e-12 && dot(orthogonalBitangent,orthogonalBitangent) > 1e-12) {
|
|
688
|
+
let t = normalize(orthogonalTangent) * sign(determinant);
|
|
689
|
+
let b = normalize(orthogonalBitangent) * sign(determinant);
|
|
690
|
+
n = normalize(t * sampledNormal.x * materialParameters.x + b * sampledNormal.y * materialParameters.x + n * max(0.001,sampledNormal.z));
|
|
691
|
+
}
|
|
692
|
+
if (materialParameters.y < 0.5 && s.material.z > 2.5) {
|
|
654
693
|
let rock = smoothstep(0.45,0.68,noise(s.world*0.27));
|
|
655
694
|
c = mix(c,vec3f(0.16,0.145,0.11),rock*0.7);
|
|
656
695
|
c *= 0.75+0.45*noise(s.world*2.4);
|
|
657
696
|
n = normalize(n+vec3f(noise(s.world*4.0)-0.5,0,noise(s.world*4.0+vec3f(13))-0.5)*0.28);
|
|
658
|
-
} else if (s.material.z > 0.5 && s.material.z < 1.5) {
|
|
697
|
+
} else if (materialParameters.y < 0.5 && s.material.z > 0.5 && s.material.z < 1.5) {
|
|
659
698
|
let grain = noise(s.world * vec3f(18, 3, 1.6));
|
|
660
699
|
c *= 0.82 + 0.30 * grain;
|
|
661
|
-
} else if (s.material.z > 1.5) { c *= 0.88 + 0.18 * noise(s.world * 12.0); }
|
|
700
|
+
} else if (materialParameters.y < 0.5 && s.material.z > 1.5) { c *= 0.88 + 0.18 * noise(s.world * 12.0); }
|
|
662
701
|
let hemi = mix(vec3f(0.06,0.05,0.035), vec3f(0.16,0.22,0.29), n.y * 0.5 + 0.5);
|
|
663
|
-
let direct = brdf(c, n, v,
|
|
664
|
-
let ambient = c * hemi * (1.0 -
|
|
665
|
-
let env = sky(reflect(-v,n)) * mix(vec3f(0.04), c,
|
|
666
|
-
|
|
702
|
+
let direct = brdf(c, n, v, roughness, metalness) * vec3f(3.0, 2.65, 2.15) * visibility(s.world, n);
|
|
703
|
+
let ambient = c * hemi * (1.0 - metalness * 0.7) * orm.r;
|
|
704
|
+
let env = sky(reflect(-v,n)) * mix(vec3f(0.04), c, metalness) * (1.0-roughness) * orm.r * 0.30;
|
|
705
|
+
let baseRadiance = direct + ambient + env;
|
|
706
|
+
let coatCoverage = materialParameters.z * coatSample.r;
|
|
707
|
+
if (coatCoverage <= 0.0) { return fog(baseRadiance,s.world); }
|
|
708
|
+
// KHR_materials_clearcoat's simple Fresnel layering: varnish has its own
|
|
709
|
+
// smooth normal and roughness. Its reflection never inherits the wood colour.
|
|
710
|
+
let coatFresnel = 0.04 + 0.96 * pow(1.0-clamp(abs(dot(coatNormal,v)),0.0,1.0),5.0);
|
|
711
|
+
let coatWeight = coatCoverage * coatFresnel;
|
|
712
|
+
let coatRoughness = clamp(materialParameters.w * coatSample.g,0.04,1.0);
|
|
713
|
+
let coatDirect = vec3f(3.0,2.65,2.15) * specularLobe(coatNormal,v,coatRoughness) * visibility(s.world,coatNormal);
|
|
714
|
+
let coatEnvironment = sky(reflect(-v,coatNormal)) * (1.0-coatRoughness) * orm.r * 0.30;
|
|
715
|
+
return fog(baseRadiance * (1.0-coatWeight) + (coatDirect+coatEnvironment) * coatWeight, s.world);
|
|
667
716
|
}
|
|
668
717
|
@fragment fn surfaceFragment(s: Surface, @builtin(front_facing) front: bool) -> @location(0) vec4f {
|
|
669
718
|
return vec4f(display(surfaceRadiance(s,front)),1);
|
|
@@ -717,12 +766,114 @@ struct WaterResult { @location(0) colour: vec4f, @builtin(frag_depth) depth: f32
|
|
|
717
766
|
return vec4f(sky(cameraRay(pixel.xy)),1);
|
|
718
767
|
}
|
|
719
768
|
@fragment fn reflectionSurface(s: Surface, @builtin(front_facing) front: bool) -> @location(0) vec4f {
|
|
769
|
+
let radiance = surfaceRadiance(s,front);
|
|
720
770
|
if (s.world.y < frame.viewport.z) { discard; }
|
|
721
|
-
return vec4f(
|
|
771
|
+
return vec4f(radiance,1);
|
|
722
772
|
}
|
|
723
773
|
`
|
|
724
774
|
);
|
|
725
775
|
|
|
776
|
+
// src/native-surface-materials.js
|
|
777
|
+
var MAX_BYTES = 64 * 1024 * 1024;
|
|
778
|
+
var MAPS = ["baseColor", "normal", "orm", "clearcoatMap"];
|
|
779
|
+
var defaults = {
|
|
780
|
+
baseColor: [255, 255, 255, 255],
|
|
781
|
+
normal: [128, 128, 255, 255],
|
|
782
|
+
orm: [255, 255, 255, 255],
|
|
783
|
+
clearcoatMap: [255, 255, 255, 255]
|
|
784
|
+
};
|
|
785
|
+
var dimension = (n) => Number.isInteger(n) && n >= 1 && n <= 2048 && (n & n - 1) === 0;
|
|
786
|
+
function validateNativeMaterials(materials) {
|
|
787
|
+
if (!Array.isArray(materials) || materials.length > 16)
|
|
788
|
+
throw new Error("materials must contain at most 16 entries.");
|
|
789
|
+
let bytes = 0;
|
|
790
|
+
for (const material of materials) {
|
|
791
|
+
if (!material || typeof material !== "object")
|
|
792
|
+
throw new Error("Each material must be an object.");
|
|
793
|
+
if (!Number.isFinite(material.normalScale ?? 1) || (material.normalScale ?? 1) < 0 || (material.normalScale ?? 1) > 4) {
|
|
794
|
+
throw new Error("material normalScale must be between 0 and 4.");
|
|
795
|
+
}
|
|
796
|
+
for (const key of ["clearcoat", "clearcoatRoughness"]) {
|
|
797
|
+
const value = material[key] ?? 0;
|
|
798
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
799
|
+
throw new Error(`material ${key} must be between 0 and 1.`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
for (const key of MAPS) {
|
|
803
|
+
const map = material[key];
|
|
804
|
+
if (map === void 0) continue;
|
|
805
|
+
if (!map || !dimension(map.width) || !dimension(map.height) || !(map.data instanceof Uint8Array || map.data instanceof Uint8ClampedArray) || map.data.length !== map.width * map.height * 4) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
`material ${key} texture must contain RGBA8 data with power-of-two dimensions between 1 and 2048.`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
bytes += map.data.byteLength;
|
|
811
|
+
if (bytes > MAX_BYTES)
|
|
812
|
+
throw new Error("material textures exceed the 64 MiB source budget.");
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
var linear = (x) => x <= 0.04045 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
|
817
|
+
var srgb = (x) => x <= 31308e-7 ? x * 12.92 : 1.055 * x ** (1 / 2.4) - 0.055;
|
|
818
|
+
function nextMip(image, colour) {
|
|
819
|
+
const width = Math.max(1, image.width / 2), height = Math.max(1, image.height / 2);
|
|
820
|
+
const data = new Uint8Array(width * height * 4);
|
|
821
|
+
for (let y = 0; y < height; y++)
|
|
822
|
+
for (let x = 0; x < width; x++)
|
|
823
|
+
for (let c = 0; c < 4; c++) {
|
|
824
|
+
let sum = 0;
|
|
825
|
+
for (let dy = 0; dy < 2; dy++)
|
|
826
|
+
for (let dx = 0; dx < 2; dx++) {
|
|
827
|
+
const offset = (Math.min(image.height - 1, y * 2 + dy) * image.width + Math.min(image.width - 1, x * 2 + dx)) * 4 + c;
|
|
828
|
+
const value = image.data[offset] / 255;
|
|
829
|
+
sum += colour && c < 3 ? linear(value) : value;
|
|
830
|
+
}
|
|
831
|
+
data[(y * width + x) * 4 + c] = Math.round(
|
|
832
|
+
255 * (colour && c < 3 ? srgb(sum / 4) : sum / 4)
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
return { width, height, data };
|
|
836
|
+
}
|
|
837
|
+
function createNativeMaterialTextures(device, materials, own) {
|
|
838
|
+
return [{}, ...materials].map((material, index) => {
|
|
839
|
+
const result = {
|
|
840
|
+
normalScale: material.normal ? material.normalScale ?? 1 : 0,
|
|
841
|
+
authored: ["baseColor", "normal", "orm"].some((key) => material[key]),
|
|
842
|
+
clearcoat: material.clearcoat ?? 0,
|
|
843
|
+
clearcoatRoughness: material.clearcoatRoughness ?? 0
|
|
844
|
+
};
|
|
845
|
+
for (const key of MAPS) {
|
|
846
|
+
let level = material[key] ?? {
|
|
847
|
+
width: 1,
|
|
848
|
+
height: 1,
|
|
849
|
+
data: new Uint8Array(defaults[key])
|
|
850
|
+
};
|
|
851
|
+
const mipLevelCount = 1 + Math.log2(Math.max(level.width, level.height));
|
|
852
|
+
const texture = own(
|
|
853
|
+
device.createTexture({
|
|
854
|
+
label: `native.material.${index}.${key}`,
|
|
855
|
+
size: { width: level.width, height: level.height },
|
|
856
|
+
format: key === "baseColor" ? "rgba8unorm-srgb" : "rgba8unorm",
|
|
857
|
+
mipLevelCount,
|
|
858
|
+
usage: 6
|
|
859
|
+
})
|
|
860
|
+
);
|
|
861
|
+
for (let mipLevel = 0; mipLevel < mipLevelCount; mipLevel++) {
|
|
862
|
+
device.queue.writeTexture(
|
|
863
|
+
{ texture, mipLevel },
|
|
864
|
+
level.data,
|
|
865
|
+
{ bytesPerRow: level.width * 4, rowsPerImage: level.height },
|
|
866
|
+
{ width: level.width, height: level.height, depthOrArrayLayers: 1 }
|
|
867
|
+
);
|
|
868
|
+
if (mipLevel + 1 < mipLevelCount)
|
|
869
|
+
level = nextMip(level, key === "baseColor");
|
|
870
|
+
}
|
|
871
|
+
result[key] = texture.createView();
|
|
872
|
+
}
|
|
873
|
+
return result;
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
726
877
|
// src/native-scene-renderer.js
|
|
727
878
|
var STRIDE = 12;
|
|
728
879
|
var UNIFORM_BYTES = 256;
|
|
@@ -738,7 +889,7 @@ var unit = (a) => {
|
|
|
738
889
|
return a.map((v) => v / length);
|
|
739
890
|
};
|
|
740
891
|
var finiteVector = (a) => Array.isArray(a) && a.length === 3 && a.every(Number.isFinite);
|
|
741
|
-
function validateFrame(frame, maxVertices) {
|
|
892
|
+
function validateFrame(frame, maxVertices, materialCount) {
|
|
742
893
|
const { vertices, camera, time = 0 } = frame ?? {};
|
|
743
894
|
if (!(vertices instanceof Float32Array) || vertices.length % (STRIDE * 3) !== 0 || vertices.length > maxVertices * STRIDE || !vertices.every(Number.isFinite)) {
|
|
744
895
|
throw new Error(
|
|
@@ -760,6 +911,30 @@ function validateFrame(frame, maxVertices) {
|
|
|
760
911
|
"wakes must contain at most four finite [x, z, heading] entries."
|
|
761
912
|
);
|
|
762
913
|
}
|
|
914
|
+
const count = vertices.length / STRIDE;
|
|
915
|
+
if (frame.texcoords !== void 0 && (!(frame.texcoords instanceof Float32Array) || frame.texcoords.length !== count * 2 || !frame.texcoords.every(Number.isFinite))) {
|
|
916
|
+
throw new Error(
|
|
917
|
+
"texcoords must contain two finite coordinates per vertex."
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
if (frame.surfaces !== void 0) {
|
|
921
|
+
if (!Array.isArray(frame.surfaces) || frame.surfaces.length > 256 || !frame.texcoords) {
|
|
922
|
+
throw new Error(
|
|
923
|
+
"surfaces require texcoords and at most 256 contiguous draw ranges."
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
let end = 0;
|
|
927
|
+
for (const range of frame.surfaces) {
|
|
928
|
+
if (!range || range.firstVertex !== end || !Number.isInteger(range.vertexCount) || range.vertexCount <= 0 || range.vertexCount % 3 !== 0 || !Number.isInteger(range.materialIndex) || range.materialIndex < 0 || range.materialIndex >= materialCount) {
|
|
929
|
+
throw new Error(
|
|
930
|
+
"surface ranges must cover triangles in order and reference a valid material."
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
end += range.vertexCount;
|
|
934
|
+
}
|
|
935
|
+
if (end !== count)
|
|
936
|
+
throw new Error("surface ranges must cover every vertex exactly once.");
|
|
937
|
+
}
|
|
763
938
|
return { ...frame, time };
|
|
764
939
|
}
|
|
765
940
|
function frameUniforms(frame, width, height, reflected = false) {
|
|
@@ -807,6 +982,9 @@ function frameUniforms(frame, width, height, reflected = false) {
|
|
|
807
982
|
}
|
|
808
983
|
async function createNativeSceneRenderer(options = {}) {
|
|
809
984
|
const maxVertices = options.maxVertices ?? 3e5;
|
|
985
|
+
const materials = options.materials ?? [];
|
|
986
|
+
validateNativeMaterials(materials);
|
|
987
|
+
const materialCount = materials.length;
|
|
810
988
|
if (!Number.isInteger(maxVertices) || maxVertices < 3 || maxVertices > 6e5) {
|
|
811
989
|
throw new Error("maxVertices must be an integer between 3 and 600000.");
|
|
812
990
|
}
|
|
@@ -815,6 +993,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
815
993
|
let available = true;
|
|
816
994
|
let targets = null;
|
|
817
995
|
let vertexBuffer = null;
|
|
996
|
+
let uvBuffer = null;
|
|
818
997
|
let vertexCapacity = 0;
|
|
819
998
|
let pendingFrame = null;
|
|
820
999
|
let submittedFrames = 0;
|
|
@@ -921,7 +1100,20 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
921
1100
|
if (p !== shadowPipeline) pass2.setBindGroup(1, shadowGroup);
|
|
922
1101
|
if (vertexCount > 0) {
|
|
923
1102
|
pass2.setVertexBuffer(0, vertexBuffer);
|
|
924
|
-
|
|
1103
|
+
if (p === shadowPipeline) {
|
|
1104
|
+
pass2.draw(vertexCount);
|
|
1105
|
+
} else {
|
|
1106
|
+
pass2.setVertexBuffer(1, uvBuffer);
|
|
1107
|
+
if (pendingFrame.surfaces) {
|
|
1108
|
+
for (const range of pendingFrame.surfaces) {
|
|
1109
|
+
pass2.setBindGroup(2, materialGroups[range.materialIndex + 1]);
|
|
1110
|
+
pass2.draw(range.vertexCount, 1, range.firstVertex);
|
|
1111
|
+
}
|
|
1112
|
+
} else {
|
|
1113
|
+
pass2.setBindGroup(2, materialGroups[0]);
|
|
1114
|
+
pass2.draw(vertexCount);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
925
1117
|
}
|
|
926
1118
|
};
|
|
927
1119
|
const shadowPass = encoder.beginRenderPass({
|
|
@@ -967,7 +1159,8 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
967
1159
|
if (pendingFrame.water !== false) {
|
|
968
1160
|
pass.setPipeline(waterPipeline);
|
|
969
1161
|
pass.setBindGroup(1, shadowGroup);
|
|
970
|
-
pass.setBindGroup(2,
|
|
1162
|
+
pass.setBindGroup(2, materialGroups[0]);
|
|
1163
|
+
pass.setBindGroup(3, targets.reflectionGroup);
|
|
971
1164
|
pass.draw(3);
|
|
972
1165
|
}
|
|
973
1166
|
drawGeometry(pass, surfacePipeline, mainGroup);
|
|
@@ -1017,6 +1210,63 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1017
1210
|
{ binding: 1, visibility: 2, sampler: { type: "filtering" } }
|
|
1018
1211
|
]
|
|
1019
1212
|
});
|
|
1213
|
+
const materialLayout = device.createBindGroupLayout({
|
|
1214
|
+
entries: [
|
|
1215
|
+
...[0, 1, 2, 5].map((binding) => ({
|
|
1216
|
+
binding,
|
|
1217
|
+
visibility: 2,
|
|
1218
|
+
texture: { sampleType: "float" }
|
|
1219
|
+
})),
|
|
1220
|
+
{ binding: 3, visibility: 2, sampler: { type: "filtering" } },
|
|
1221
|
+
{
|
|
1222
|
+
binding: 4,
|
|
1223
|
+
visibility: 2,
|
|
1224
|
+
buffer: { type: "uniform", minBindingSize: 16 }
|
|
1225
|
+
}
|
|
1226
|
+
]
|
|
1227
|
+
});
|
|
1228
|
+
const materialSampler = device.createSampler({
|
|
1229
|
+
addressModeU: "repeat",
|
|
1230
|
+
addressModeV: "repeat",
|
|
1231
|
+
minFilter: "linear",
|
|
1232
|
+
magFilter: "linear",
|
|
1233
|
+
mipmapFilter: "linear",
|
|
1234
|
+
maxAnisotropy: 4
|
|
1235
|
+
});
|
|
1236
|
+
const materialGroups = createNativeMaterialTextures(
|
|
1237
|
+
device,
|
|
1238
|
+
materials,
|
|
1239
|
+
own
|
|
1240
|
+
).map((material) => {
|
|
1241
|
+
const params = own(
|
|
1242
|
+
device.createBuffer({
|
|
1243
|
+
size: 16,
|
|
1244
|
+
usage: 72,
|
|
1245
|
+
label: "native.material-parameters"
|
|
1246
|
+
})
|
|
1247
|
+
);
|
|
1248
|
+
device.queue.writeBuffer(
|
|
1249
|
+
params,
|
|
1250
|
+
0,
|
|
1251
|
+
new Float32Array([
|
|
1252
|
+
material.normalScale,
|
|
1253
|
+
material.authored ? 1 : 0,
|
|
1254
|
+
material.clearcoat,
|
|
1255
|
+
material.clearcoatRoughness
|
|
1256
|
+
])
|
|
1257
|
+
);
|
|
1258
|
+
return device.createBindGroup({
|
|
1259
|
+
layout: materialLayout,
|
|
1260
|
+
entries: [
|
|
1261
|
+
{ binding: 0, resource: material.baseColor },
|
|
1262
|
+
{ binding: 1, resource: material.normal },
|
|
1263
|
+
{ binding: 2, resource: material.orm },
|
|
1264
|
+
{ binding: 3, resource: materialSampler },
|
|
1265
|
+
{ binding: 4, resource: { buffer: params } },
|
|
1266
|
+
{ binding: 5, resource: material.clearcoatMap }
|
|
1267
|
+
]
|
|
1268
|
+
});
|
|
1269
|
+
});
|
|
1020
1270
|
const uniformBuffer = own(
|
|
1021
1271
|
device.createBuffer({
|
|
1022
1272
|
label: "native.frame",
|
|
@@ -1081,7 +1331,19 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1081
1331
|
{
|
|
1082
1332
|
arrayStride: STRIDE * 4,
|
|
1083
1333
|
attributes: vertex === "shadowVertex" ? attributes.slice(0, 1) : attributes
|
|
1084
|
-
}
|
|
1334
|
+
},
|
|
1335
|
+
...vertex === "shadowVertex" ? [] : [
|
|
1336
|
+
{
|
|
1337
|
+
arrayStride: 8,
|
|
1338
|
+
attributes: [
|
|
1339
|
+
{
|
|
1340
|
+
shaderLocation: 4,
|
|
1341
|
+
offset: 0,
|
|
1342
|
+
format: "float32x2"
|
|
1343
|
+
}
|
|
1344
|
+
]
|
|
1345
|
+
}
|
|
1346
|
+
]
|
|
1085
1347
|
]
|
|
1086
1348
|
} : {}
|
|
1087
1349
|
},
|
|
@@ -1115,7 +1377,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1115
1377
|
"surfaceFragment",
|
|
1116
1378
|
format,
|
|
1117
1379
|
4,
|
|
1118
|
-
[uniformLayout, shadowLayout],
|
|
1380
|
+
[uniformLayout, shadowLayout, materialLayout],
|
|
1119
1381
|
true
|
|
1120
1382
|
);
|
|
1121
1383
|
const skyPipeline = await pipeline(
|
|
@@ -1132,7 +1394,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1132
1394
|
"waterFragment",
|
|
1133
1395
|
format,
|
|
1134
1396
|
4,
|
|
1135
|
-
[uniformLayout, shadowLayout, reflectionLayout]
|
|
1397
|
+
[uniformLayout, shadowLayout, materialLayout, reflectionLayout]
|
|
1136
1398
|
);
|
|
1137
1399
|
const reflectedSurfacePipeline = await pipeline(
|
|
1138
1400
|
"reflection-surface",
|
|
@@ -1140,7 +1402,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1140
1402
|
"reflectionSurface",
|
|
1141
1403
|
"rgba16float",
|
|
1142
1404
|
1,
|
|
1143
|
-
[uniformLayout, shadowLayout],
|
|
1405
|
+
[uniformLayout, shadowLayout, materialLayout],
|
|
1144
1406
|
true
|
|
1145
1407
|
);
|
|
1146
1408
|
const reflectedSkyPipeline = await pipeline(
|
|
@@ -1155,15 +1417,23 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1155
1417
|
render(input) {
|
|
1156
1418
|
if (destroyed) throw new Error("Native scene renderer was destroyed.");
|
|
1157
1419
|
if (!available) throw new Error("WebGPU device was lost.");
|
|
1158
|
-
pendingFrame = validateFrame(input, maxVertices);
|
|
1420
|
+
pendingFrame = validateFrame(input, maxVertices, materialCount);
|
|
1159
1421
|
const data = pendingFrame.vertices;
|
|
1160
1422
|
ensureTargets();
|
|
1161
1423
|
if (data.byteLength > vertexCapacity) {
|
|
1162
1424
|
if (vertexBuffer) release(vertexBuffer);
|
|
1425
|
+
if (uvBuffer) release(uvBuffer);
|
|
1163
1426
|
vertexCapacity = Math.min(
|
|
1164
1427
|
maxVertices * STRIDE * 4,
|
|
1165
1428
|
Math.max(4096, 2 ** Math.ceil(Math.log2(data.byteLength)))
|
|
1166
1429
|
);
|
|
1430
|
+
uvBuffer = own(
|
|
1431
|
+
device.createBuffer({
|
|
1432
|
+
label: "native.surface-uvs",
|
|
1433
|
+
size: Math.ceil(vertexCapacity / (STRIDE * 4)) * 8,
|
|
1434
|
+
usage: 40
|
|
1435
|
+
})
|
|
1436
|
+
);
|
|
1167
1437
|
vertexBuffer = own(
|
|
1168
1438
|
device.createBuffer({
|
|
1169
1439
|
label: "native.surface-vertices",
|
|
@@ -1172,6 +1442,8 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1172
1442
|
})
|
|
1173
1443
|
);
|
|
1174
1444
|
}
|
|
1445
|
+
if (pendingFrame.texcoords?.byteLength)
|
|
1446
|
+
device.queue.writeBuffer(uvBuffer, 0, pendingFrame.texcoords);
|
|
1175
1447
|
vertexCount = data.length / STRIDE;
|
|
1176
1448
|
if (data.byteLength) device.queue.writeBuffer(vertexBuffer, 0, data);
|
|
1177
1449
|
device.queue.writeBuffer(
|
|
@@ -1213,6 +1485,202 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1213
1485
|
}
|
|
1214
1486
|
}
|
|
1215
1487
|
|
|
1488
|
+
// src/canonical-wavefront-meshes.js
|
|
1489
|
+
var STANDARD_TEXTURE_BINDINGS = Object.freeze([
|
|
1490
|
+
["baseColor", "baseColorTexture"],
|
|
1491
|
+
["metallicRoughness", "metallicRoughnessTexture"],
|
|
1492
|
+
["normal", "normalTexture"],
|
|
1493
|
+
["occlusion", "occlusionTexture"],
|
|
1494
|
+
["emissive", "emissiveTexture"]
|
|
1495
|
+
]);
|
|
1496
|
+
var EXTENSION_TEXTURE_BINDINGS = Object.freeze([
|
|
1497
|
+
["clearcoat", "clearcoat", "clearcoatTexture"],
|
|
1498
|
+
["clearcoatRoughness", "clearcoat", "clearcoatRoughnessTexture"],
|
|
1499
|
+
["clearcoatNormal", "clearcoat", "clearcoatNormalTexture"],
|
|
1500
|
+
["transmission", "transmission", "transmissionTexture"],
|
|
1501
|
+
["sheenColor", "sheen", "sheenColorTexture"],
|
|
1502
|
+
["sheenRoughness", "sheen", "sheenRoughnessTexture"]
|
|
1503
|
+
]);
|
|
1504
|
+
function requireArray(value, label) {
|
|
1505
|
+
if (!Array.isArray(value)) {
|
|
1506
|
+
throw new TypeError(`${label} must be an array.`);
|
|
1507
|
+
}
|
|
1508
|
+
return value;
|
|
1509
|
+
}
|
|
1510
|
+
function requireMap(value, label) {
|
|
1511
|
+
if (!value || typeof value !== "object" || typeof value.get !== "function") {
|
|
1512
|
+
throw new TypeError(`${label} must provide a ReadonlyMap-compatible get method.`);
|
|
1513
|
+
}
|
|
1514
|
+
return value;
|
|
1515
|
+
}
|
|
1516
|
+
function textureSample(binding, textures, label, extra = {}) {
|
|
1517
|
+
if (binding === void 0 || binding === null) {
|
|
1518
|
+
return void 0;
|
|
1519
|
+
}
|
|
1520
|
+
const sample = textures.get(binding.textureId);
|
|
1521
|
+
if (!sample) {
|
|
1522
|
+
throw new Error(`Missing decoded texture sample ${binding.textureId} for ${label}.`);
|
|
1523
|
+
}
|
|
1524
|
+
const width = Number(sample.width);
|
|
1525
|
+
const height = Number(sample.height);
|
|
1526
|
+
const data = sample.data;
|
|
1527
|
+
const dataLength = data && typeof data.length === "number" ? data.length : -1;
|
|
1528
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1) {
|
|
1529
|
+
throw new TypeError(`Decoded texture sample ${binding.textureId} has invalid dimensions.`);
|
|
1530
|
+
}
|
|
1531
|
+
if (width > 4096 || height > 4096 || width * height > 4096 * 4096) {
|
|
1532
|
+
throw new RangeError(`Decoded texture sample ${binding.textureId} exceeds the 4096-pixel resource limit.`);
|
|
1533
|
+
}
|
|
1534
|
+
if (!(Array.isArray(data) || ArrayBuffer.isView(data)) || dataLength < width * height * 4) {
|
|
1535
|
+
throw new TypeError(`Decoded texture sample ${binding.textureId} must contain width \xD7 height \xD7 4 RGBA values.`);
|
|
1536
|
+
}
|
|
1537
|
+
return Object.freeze({
|
|
1538
|
+
...sample,
|
|
1539
|
+
...binding.texCoordSet === void 0 ? {} : { texCoord: binding.texCoordSet },
|
|
1540
|
+
...extra
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
function cloneMaterialExtensions(material, textures) {
|
|
1544
|
+
const extensions = { ...material.extensions ?? {} };
|
|
1545
|
+
if (material.workflow === "unlit") {
|
|
1546
|
+
const unlitKey = "KHR_materials_unlit";
|
|
1547
|
+
const unlitValue = extensions[unlitKey] ?? extensions.unlit ?? {};
|
|
1548
|
+
if (typeof unlitValue !== "object" || unlitValue === null || Array.isArray(unlitValue)) {
|
|
1549
|
+
throw new TypeError(`Canonical material extension ${unlitKey} must be an object.`);
|
|
1550
|
+
}
|
|
1551
|
+
extensions[unlitKey] = { ...unlitValue, enabled: true };
|
|
1552
|
+
}
|
|
1553
|
+
for (const [bindingName, extensionName, textureKey] of EXTENSION_TEXTURE_BINDINGS) {
|
|
1554
|
+
const binding = material.textures?.[bindingName];
|
|
1555
|
+
if (!binding) {
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
const extensionKey = `KHR_materials_${extensionName}`;
|
|
1559
|
+
const extensionValue = extensions[extensionKey] ?? extensions[extensionName] ?? {};
|
|
1560
|
+
if (typeof extensionValue !== "object" || extensionValue === null || Array.isArray(extensionValue)) {
|
|
1561
|
+
throw new TypeError(`Canonical material extension ${extensionKey} must be an object.`);
|
|
1562
|
+
}
|
|
1563
|
+
extensions[extensionKey] = {
|
|
1564
|
+
...extensionValue,
|
|
1565
|
+
[textureKey]: textureSample(binding, textures, `${extensionKey}.${textureKey}`)
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
return extensions;
|
|
1569
|
+
}
|
|
1570
|
+
function mapMaterial(material, materialRefId, textures) {
|
|
1571
|
+
if (!material) {
|
|
1572
|
+
return { materialRefId };
|
|
1573
|
+
}
|
|
1574
|
+
if (material.alphaMode === "mask") {
|
|
1575
|
+
throw new Error("Canonical alphaMode mask is unsupported by WavefrontMeshInput.");
|
|
1576
|
+
}
|
|
1577
|
+
if (material.workflow !== "metallic-roughness" && material.workflow !== "unlit") {
|
|
1578
|
+
throw new Error(`Unsupported canonical material workflow: ${material.workflow}.`);
|
|
1579
|
+
}
|
|
1580
|
+
const mapped = {
|
|
1581
|
+
materialRefId,
|
|
1582
|
+
...material.alphaMode === "blend" ? { materialKind: "transparent" } : {},
|
|
1583
|
+
baseColor: material.baseColorFactor,
|
|
1584
|
+
emission: material.emissiveFactor ? [...material.emissiveFactor, 1] : [0, 0, 0, 1],
|
|
1585
|
+
roughness: material.roughnessFactor,
|
|
1586
|
+
metallic: material.metallicFactor,
|
|
1587
|
+
opacity: material.alphaMode === "opaque" ? 1 : material.baseColorFactor?.[3],
|
|
1588
|
+
clearcoat: material.clearcoatFactor,
|
|
1589
|
+
clearcoatRoughness: material.clearcoatRoughnessFactor,
|
|
1590
|
+
transmission: material.transmissionFactor,
|
|
1591
|
+
sheenColor: material.sheenColorFactor,
|
|
1592
|
+
extensions: cloneMaterialExtensions(material, textures)
|
|
1593
|
+
};
|
|
1594
|
+
for (const [bindingName, inputName] of STANDARD_TEXTURE_BINDINGS) {
|
|
1595
|
+
const binding = material.textures?.[bindingName];
|
|
1596
|
+
if (!binding) {
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
const extra = bindingName === "normal" && material.normalScale !== void 0 ? { scale: material.normalScale } : bindingName === "occlusion" && material.occlusionStrength !== void 0 ? { strength: material.occlusionStrength } : {};
|
|
1600
|
+
mapped[inputName] = textureSample(binding, textures, inputName, extra);
|
|
1601
|
+
}
|
|
1602
|
+
return mapped;
|
|
1603
|
+
}
|
|
1604
|
+
function createCanonicalWavefrontMeshInputs({ document, geometry, textures = /* @__PURE__ */ new Map() } = {}) {
|
|
1605
|
+
if (!document || typeof document !== "object") {
|
|
1606
|
+
throw new TypeError("Canonical model document is required.");
|
|
1607
|
+
}
|
|
1608
|
+
const meshes = requireArray(document.meshes, "Canonical model meshes");
|
|
1609
|
+
const materials = requireArray(document.materials, "Canonical model materials");
|
|
1610
|
+
const geometryInputs = requireArray(geometry, "Renderer-ready geometry");
|
|
1611
|
+
const textureSamples = requireMap(textures, "Decoded texture samples");
|
|
1612
|
+
const geometryByPrimitiveId = /* @__PURE__ */ new Map();
|
|
1613
|
+
for (const entry of geometryInputs) {
|
|
1614
|
+
if (!entry || typeof entry.primitiveId !== "string" || entry.primitiveId.length === 0) {
|
|
1615
|
+
throw new TypeError("Renderer-ready geometry requires a non-empty primitiveId.");
|
|
1616
|
+
}
|
|
1617
|
+
if (geometryByPrimitiveId.has(entry.primitiveId)) {
|
|
1618
|
+
throw new Error(`Duplicate renderer-ready geometry for primitive ${entry.primitiveId}.`);
|
|
1619
|
+
}
|
|
1620
|
+
geometryByPrimitiveId.set(entry.primitiveId, entry);
|
|
1621
|
+
}
|
|
1622
|
+
const materialsById = /* @__PURE__ */ new Map();
|
|
1623
|
+
const materialRefIds = /* @__PURE__ */ new Map();
|
|
1624
|
+
materials.forEach((material, index) => {
|
|
1625
|
+
if (!material || typeof material.id !== "string" || material.id.length === 0) {
|
|
1626
|
+
throw new TypeError("Canonical materials require a non-empty id.");
|
|
1627
|
+
}
|
|
1628
|
+
if (materialsById.has(material.id)) {
|
|
1629
|
+
throw new Error(`Duplicate canonical material ${material.id}.`);
|
|
1630
|
+
}
|
|
1631
|
+
materialsById.set(material.id, material);
|
|
1632
|
+
materialRefIds.set(material.id, index + 1);
|
|
1633
|
+
});
|
|
1634
|
+
const output = [];
|
|
1635
|
+
const consumedPrimitiveIds = /* @__PURE__ */ new Set();
|
|
1636
|
+
const canonicalPrimitiveIds = /* @__PURE__ */ new Set();
|
|
1637
|
+
for (const mesh of meshes) {
|
|
1638
|
+
if (!mesh || typeof mesh.id !== "string") {
|
|
1639
|
+
throw new TypeError("Canonical meshes require a string id.");
|
|
1640
|
+
}
|
|
1641
|
+
for (const primitive of requireArray(mesh.primitives, `Primitives for mesh ${mesh.id}`)) {
|
|
1642
|
+
if (!primitive || typeof primitive.id !== "string" || primitive.id.length === 0) {
|
|
1643
|
+
throw new TypeError(`Canonical mesh ${mesh.id} contains a primitive without an id.`);
|
|
1644
|
+
}
|
|
1645
|
+
if (canonicalPrimitiveIds.has(primitive.id)) {
|
|
1646
|
+
throw new Error(`Duplicate canonical primitive ${primitive.id}.`);
|
|
1647
|
+
}
|
|
1648
|
+
canonicalPrimitiveIds.add(primitive.id);
|
|
1649
|
+
if (primitive.topology !== "triangles") {
|
|
1650
|
+
throw new Error(`Unsupported canonical primitive topology: ${primitive.topology}.`);
|
|
1651
|
+
}
|
|
1652
|
+
const source = geometryByPrimitiveId.get(primitive.id);
|
|
1653
|
+
if (!source) {
|
|
1654
|
+
throw new Error(`Missing renderer-ready geometry for primitive ${primitive.id}.`);
|
|
1655
|
+
}
|
|
1656
|
+
consumedPrimitiveIds.add(primitive.id);
|
|
1657
|
+
let material = null;
|
|
1658
|
+
let materialRefId = 0;
|
|
1659
|
+
if (primitive.materialId !== void 0) {
|
|
1660
|
+
material = materialsById.get(primitive.materialId);
|
|
1661
|
+
if (!material) {
|
|
1662
|
+
throw new Error(`Missing canonical material ${primitive.materialId} for primitive ${primitive.id}.`);
|
|
1663
|
+
}
|
|
1664
|
+
materialRefId = materialRefIds.get(primitive.materialId);
|
|
1665
|
+
}
|
|
1666
|
+
output.push(Object.freeze({
|
|
1667
|
+
id: output.length + 1,
|
|
1668
|
+
positions: source.positions,
|
|
1669
|
+
...source.indices === void 0 ? {} : { indices: source.indices },
|
|
1670
|
+
...source.normals === void 0 ? {} : { normals: source.normals },
|
|
1671
|
+
...(source.texcoords ?? source.uvs) === void 0 ? {} : { uvs: source.texcoords ?? source.uvs },
|
|
1672
|
+
...mapMaterial(material, materialRefId, textureSamples)
|
|
1673
|
+
}));
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
for (const primitiveId of geometryByPrimitiveId.keys()) {
|
|
1677
|
+
if (!consumedPrimitiveIds.has(primitiveId)) {
|
|
1678
|
+
throw new Error(`Renderer-ready geometry references unknown primitive ${primitiveId}.`);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
return Object.freeze(output);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1216
1684
|
// src/animated-scene-renderer.js
|
|
1217
1685
|
import { resolveCameraRigFrame } from "@plasius/gpu-camera";
|
|
1218
1686
|
|
|
@@ -3660,8 +4128,8 @@ function hashUint32(value) {
|
|
|
3660
4128
|
x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
|
|
3661
4129
|
return (x >>> 16 ^ x) >>> 0;
|
|
3662
4130
|
}
|
|
3663
|
-
function mixSeed(pixelId, sampleId, bounce, frameIndex,
|
|
3664
|
-
let x = (pixelId >>> 0) * 747796405 ^ (sampleId >>> 0) * 2891336453 ^ (bounce >>> 0) * 277803737 ^ (frameIndex >>> 0) * 1442695041 ^ (
|
|
4131
|
+
function mixSeed(pixelId, sampleId, bounce, frameIndex, dimension2) {
|
|
4132
|
+
let x = (pixelId >>> 0) * 747796405 ^ (sampleId >>> 0) * 2891336453 ^ (bounce >>> 0) * 277803737 ^ (frameIndex >>> 0) * 1442695041 ^ (dimension2 >>> 0) * 1597334677;
|
|
3665
4133
|
x >>>= 0;
|
|
3666
4134
|
x ^= x >>> 16;
|
|
3667
4135
|
x = x * 2146121005 >>> 0;
|
|
@@ -9540,25 +10008,25 @@ function toWgslConstName(name) {
|
|
|
9540
10008
|
return `SAMPLE_DIM_${name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase()}`;
|
|
9541
10009
|
}
|
|
9542
10010
|
var usedDimensions = /* @__PURE__ */ new Set();
|
|
9543
|
-
for (const [,
|
|
9544
|
-
if (usedDimensions.has(
|
|
9545
|
-
throw new Error(`Duplicate wavefront sample dimension ${
|
|
10011
|
+
for (const [, dimension2] of sampleDimensionEntries) {
|
|
10012
|
+
if (usedDimensions.has(dimension2)) {
|
|
10013
|
+
throw new Error(`Duplicate wavefront sample dimension ${dimension2}.`);
|
|
9546
10014
|
}
|
|
9547
|
-
usedDimensions.add(
|
|
10015
|
+
usedDimensions.add(dimension2);
|
|
9548
10016
|
}
|
|
9549
10017
|
var WAVEFRONT_SAMPLE_DIMENSIONS = Object.freeze(
|
|
9550
10018
|
Object.fromEntries(sampleDimensionEntries)
|
|
9551
10019
|
);
|
|
9552
10020
|
function listWavefrontSampleDimensions() {
|
|
9553
10021
|
return sampleDimensionEntries.map(
|
|
9554
|
-
([name,
|
|
10022
|
+
([name, dimension2]) => Object.freeze({
|
|
9555
10023
|
name,
|
|
9556
|
-
dimension,
|
|
10024
|
+
dimension: dimension2,
|
|
9557
10025
|
wgslName: toWgslConstName(name)
|
|
9558
10026
|
})
|
|
9559
10027
|
);
|
|
9560
10028
|
}
|
|
9561
|
-
var WAVEFRONT_SAMPLE_DIMENSIONS_WGSL = listWavefrontSampleDimensions().map(({ wgslName, dimension }) => `const ${wgslName}: u32 = ${
|
|
10029
|
+
var WAVEFRONT_SAMPLE_DIMENSIONS_WGSL = listWavefrontSampleDimensions().map(({ wgslName, dimension: dimension2 }) => `const ${wgslName}: u32 = ${dimension2}u;`).join("\n");
|
|
9562
10030
|
function fract(value) {
|
|
9563
10031
|
return value - Math.floor(value);
|
|
9564
10032
|
}
|
|
@@ -9571,19 +10039,19 @@ function radicalInverseVdc2(value) {
|
|
|
9571
10039
|
bits = ((bits & 16711935) << 8 | (bits & 4278255360) >>> 8) >>> 0;
|
|
9572
10040
|
return bits / 4294967296;
|
|
9573
10041
|
}
|
|
9574
|
-
function sampleWavefrontDimension1D(pixelId, sampleId, bounce, frameIndex,
|
|
9575
|
-
return random01FromSeed(mixSeed(pixelId, sampleId, bounce, frameIndex,
|
|
10042
|
+
function sampleWavefrontDimension1D(pixelId, sampleId, bounce, frameIndex, dimension2) {
|
|
10043
|
+
return random01FromSeed(mixSeed(pixelId, sampleId, bounce, frameIndex, dimension2));
|
|
9576
10044
|
}
|
|
9577
|
-
function sampleWavefrontDimension2D(pixelId, sampleId, bounce, frameIndex,
|
|
10045
|
+
function sampleWavefrontDimension2D(pixelId, sampleId, bounce, frameIndex, dimension2, strataCount) {
|
|
9578
10046
|
const strata = Math.max(1, Number.isFinite(strataCount) ? Math.trunc(strataCount) : 1);
|
|
9579
10047
|
const jitter = sampleWavefrontDimension1D(
|
|
9580
10048
|
pixelId,
|
|
9581
10049
|
sampleId,
|
|
9582
10050
|
bounce,
|
|
9583
10051
|
frameIndex,
|
|
9584
|
-
|
|
10052
|
+
dimension2
|
|
9585
10053
|
);
|
|
9586
|
-
const scramble = hashUint32(mixSeed(pixelId, sampleId, bounce, frameIndex,
|
|
10054
|
+
const scramble = hashUint32(mixSeed(pixelId, sampleId, bounce, frameIndex, dimension2));
|
|
9587
10055
|
const stratified = fract(((sampleId >>> 0) % strata + jitter) / strata);
|
|
9588
10056
|
const lowDiscrepancy = fract(radicalInverseVdc2(sampleId >>> 0 ^ scramble) + jitter);
|
|
9589
10057
|
return Object.freeze([stratified, lowDiscrepancy]);
|
|
@@ -14002,6 +14470,7 @@ export {
|
|
|
14002
14470
|
bucketFeedbackGameFrameTime,
|
|
14003
14471
|
bucketFeedbackGameViewport,
|
|
14004
14472
|
createAnimatedSceneRenderer,
|
|
14473
|
+
createCanonicalWavefrontMeshInputs,
|
|
14005
14474
|
createDefaultWavefrontSceneObjects,
|
|
14006
14475
|
createFeedbackGameDiagnosticSnapshot,
|
|
14007
14476
|
createGpuRenderer,
|
|
@@ -14060,6 +14529,7 @@ export {
|
|
|
14060
14529
|
supportsWebGpu,
|
|
14061
14530
|
traceWavefrontReferenceTriangles,
|
|
14062
14531
|
transitionMediumStack,
|
|
14532
|
+
validateNativeMaterials as validateNativeSurfaceMaterials,
|
|
14063
14533
|
wavefrontMaterialKinds,
|
|
14064
14534
|
wavefrontPathTracingComputeLimits,
|
|
14065
14535
|
wavefrontSceneObjectKinds
|