@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.cjs
CHANGED
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
bucketFeedbackGameFrameTime: () => bucketFeedbackGameFrameTime,
|
|
29
29
|
bucketFeedbackGameViewport: () => bucketFeedbackGameViewport,
|
|
30
30
|
createAnimatedSceneRenderer: () => createAnimatedSceneRenderer,
|
|
31
|
+
createCanonicalWavefrontMeshInputs: () => createCanonicalWavefrontMeshInputs,
|
|
31
32
|
createDefaultWavefrontSceneObjects: () => createDefaultWavefrontSceneObjects,
|
|
32
33
|
createFeedbackGameDiagnosticSnapshot: () => createFeedbackGameDiagnosticSnapshot,
|
|
33
34
|
createGpuRenderer: () => createGpuRenderer,
|
|
@@ -86,6 +87,7 @@ __export(index_exports, {
|
|
|
86
87
|
supportsWebGpu: () => supportsWebGpu,
|
|
87
88
|
traceWavefrontReferenceTriangles: () => traceWavefrontReferenceTriangles,
|
|
88
89
|
transitionMediumStack: () => transitionMediumStack,
|
|
90
|
+
validateNativeSurfaceMaterials: () => validateNativeMaterials,
|
|
89
91
|
wavefrontMaterialKinds: () => wavefrontMaterialKinds,
|
|
90
92
|
wavefrontPathTracingComputeLimits: () => wavefrontPathTracingComputeLimits,
|
|
91
93
|
wavefrontSceneObjectKinds: () => wavefrontSceneObjectKinds
|
|
@@ -650,12 +652,18 @@ struct Frame {
|
|
|
650
652
|
@group(0) @binding(0) var<uniform> frame: Frame;
|
|
651
653
|
@group(1) @binding(0) var shadowMap: texture_depth_2d;
|
|
652
654
|
@group(1) @binding(1) var shadowSampler: sampler_comparison;
|
|
653
|
-
@group(
|
|
654
|
-
@group(
|
|
655
|
+
@group(3) @binding(0) var reflectionMap: texture_2d<f32>;
|
|
656
|
+
@group(3) @binding(1) var reflectionSampler: sampler;
|
|
657
|
+
@group(2) @binding(0) var baseColourMap: texture_2d<f32>;
|
|
658
|
+
@group(2) @binding(1) var normalMap: texture_2d<f32>;
|
|
659
|
+
@group(2) @binding(2) var ormMap: texture_2d<f32>;
|
|
660
|
+
@group(2) @binding(3) var materialSampler: sampler;
|
|
661
|
+
@group(2) @binding(4) var<uniform> materialParameters: vec4f;
|
|
662
|
+
@group(2) @binding(5) var clearcoatMap: texture_2d<f32>;
|
|
655
663
|
struct Surface {
|
|
656
664
|
@builtin(position) clip: vec4f,
|
|
657
665
|
@location(0) world: vec3f, @location(1) normal: vec3f,
|
|
658
|
-
@location(2) colour: vec3f, @location(3) material: vec3f,
|
|
666
|
+
@location(2) colour: vec3f, @location(3) material: vec3f, @location(4) uv: vec2f,
|
|
659
667
|
};
|
|
660
668
|
fn project(p: vec3f) -> vec4f {
|
|
661
669
|
let d = p - frame.eye.xyz;
|
|
@@ -669,8 +677,8 @@ fn lightProject(p: vec3f) -> vec4f {
|
|
|
669
677
|
dot(d, frame.lightUp.xyz) / 34.0, dot(d, frame.lightForward.xyz) / 100.0, 1.0);
|
|
670
678
|
}
|
|
671
679
|
@vertex fn surfaceVertex(@location(0) p: vec3f, @location(1) n: vec3f,
|
|
672
|
-
@location(2) c: vec3f, @location(3) m: vec3f) -> Surface {
|
|
673
|
-
return Surface(project(p), p, n, c, m);
|
|
680
|
+
@location(2) c: vec3f, @location(3) m: vec3f, @location(4) uv: vec2f) -> Surface {
|
|
681
|
+
return Surface(project(p), p, n, c, m, uv);
|
|
674
682
|
}
|
|
675
683
|
@vertex fn shadowVertex(@location(0) p: vec3f) -> @builtin(position) vec4f {
|
|
676
684
|
return lightProject(p);
|
|
@@ -723,17 +731,25 @@ fn visibility(p: vec3f, n: vec3f) -> f32 {
|
|
|
723
731
|
}
|
|
724
732
|
return result / 9.0;
|
|
725
733
|
}
|
|
726
|
-
|
|
734
|
+
// Dielectric coating is evaluated independently from the coloured substrate.
|
|
735
|
+
// This returns the microfacet lobe without Fresnel, including the cosine term.
|
|
736
|
+
fn specularLobe(n: vec3f, v: vec3f, roughness: f32) -> f32 {
|
|
727
737
|
let l = frame.sun.xyz; let h = normalize(l + v);
|
|
728
738
|
let nl = max(dot(n,l),0.0); let nv = max(dot(n,v),0.001);
|
|
729
|
-
let nh = max(dot(n,h),0.0);
|
|
739
|
+
let nh = max(dot(n,h),0.0);
|
|
730
740
|
let a = max(0.035, roughness * roughness); let a2 = a * a;
|
|
731
741
|
let d = a2 / max(0.00001, 3.14159265 * pow(nh * nh * (a2 - 1.0) + 1.0, 2.0));
|
|
732
742
|
let k = pow(roughness + 1.0, 2.0) / 8.0;
|
|
733
743
|
let g = (nl / (nl * (1.0-k) + k)) * (nv / (nv * (1.0-k) + k));
|
|
744
|
+
return d * g / max(0.001, 4.0*nl*nv) * nl;
|
|
745
|
+
}
|
|
746
|
+
fn brdf(c: vec3f, n: vec3f, v: vec3f, roughness: f32, metal: f32) -> vec3f {
|
|
747
|
+
let h = normalize(frame.sun.xyz + v);
|
|
748
|
+
let nl = max(dot(n,frame.sun.xyz),0.0);
|
|
749
|
+
let vh = max(dot(v,h),0.0);
|
|
734
750
|
let f0 = mix(vec3f(0.04), c, metal);
|
|
735
751
|
let f = f0 + (1.0-f0) * pow(1.0-vh, 5.0);
|
|
736
|
-
return (
|
|
752
|
+
return (1.0-f) * (1.0-metal) * c / 3.14159265 * nl + specularLobe(n,v,roughness) * f;
|
|
737
753
|
}
|
|
738
754
|
fn fog(c: vec3f, p: vec3f) -> vec3f {
|
|
739
755
|
let amount = 1.0 - exp(-distance(frame.eye.xyz, p) * 0.0018);
|
|
@@ -741,23 +757,58 @@ fn fog(c: vec3f, p: vec3f) -> vec3f {
|
|
|
741
757
|
}
|
|
742
758
|
fn surfaceRadiance(s: Surface, front: bool) -> vec3f {
|
|
743
759
|
let facing = select(select(-1.0,1.0,front),select(1.0,-1.0,front),frame.info.y>0.5);
|
|
744
|
-
|
|
760
|
+
let coatNormal = normalize(s.normal) * facing;
|
|
761
|
+
var n = coatNormal;
|
|
745
762
|
let v = normalize(frame.eye.xyz - s.world);
|
|
746
|
-
|
|
747
|
-
|
|
763
|
+
// Derivatives and samples must be uniform across each fragment quad.
|
|
764
|
+
// Relative degeneracy: raw derivative magnitudes shrink with model scale and
|
|
765
|
+
// zoom. An absolute tangent cutoff silently disabled normal maps in close-ups.
|
|
766
|
+
let dpScale = max(length(dpdx(s.world)), length(dpdy(s.world)));
|
|
767
|
+
let uvScale = max(length(dpdx(s.uv)), length(dpdy(s.uv)));
|
|
768
|
+
let dx = dpdx(s.world) / max(dpScale, 1e-20);
|
|
769
|
+
let dy = dpdy(s.world) / max(dpScale, 1e-20);
|
|
770
|
+
let ux = dpdx(s.uv) / max(uvScale, 1e-20);
|
|
771
|
+
let uy = dpdy(s.uv) / max(uvScale, 1e-20);
|
|
772
|
+
let determinant = ux.x * uy.y - ux.y * uy.x;
|
|
773
|
+
let tangent = dx * uy.y - dy * ux.y;
|
|
774
|
+
let bitangent = dy * ux.x - dx * uy.x;
|
|
775
|
+
let sampledNormal = textureSample(normalMap, materialSampler, s.uv).xyz * 2.0 - 1.0;
|
|
776
|
+
var c = s.colour * textureSample(baseColourMap, materialSampler, s.uv).rgb;
|
|
777
|
+
let orm = textureSample(ormMap, materialSampler, s.uv).rgb;
|
|
778
|
+
let coatSample = textureSample(clearcoatMap, materialSampler, s.uv).rg;
|
|
779
|
+
let roughness = clamp(s.material.x * orm.g, 0.04, 1.0);
|
|
780
|
+
let metalness = clamp(s.material.y * orm.b, 0.0, 1.0);
|
|
781
|
+
let orthogonalTangent = tangent - n * dot(n,tangent);
|
|
782
|
+
let orthogonalBitangent = bitangent - n * dot(n,bitangent);
|
|
783
|
+
if (dpScale > 1e-20 && uvScale > 1e-20 && abs(determinant) > 1e-6 && dot(orthogonalTangent,orthogonalTangent) > 1e-12 && dot(orthogonalBitangent,orthogonalBitangent) > 1e-12) {
|
|
784
|
+
let t = normalize(orthogonalTangent) * sign(determinant);
|
|
785
|
+
let b = normalize(orthogonalBitangent) * sign(determinant);
|
|
786
|
+
n = normalize(t * sampledNormal.x * materialParameters.x + b * sampledNormal.y * materialParameters.x + n * max(0.001,sampledNormal.z));
|
|
787
|
+
}
|
|
788
|
+
if (materialParameters.y < 0.5 && s.material.z > 2.5) {
|
|
748
789
|
let rock = smoothstep(0.45,0.68,noise(s.world*0.27));
|
|
749
790
|
c = mix(c,vec3f(0.16,0.145,0.11),rock*0.7);
|
|
750
791
|
c *= 0.75+0.45*noise(s.world*2.4);
|
|
751
792
|
n = normalize(n+vec3f(noise(s.world*4.0)-0.5,0,noise(s.world*4.0+vec3f(13))-0.5)*0.28);
|
|
752
|
-
} else if (s.material.z > 0.5 && s.material.z < 1.5) {
|
|
793
|
+
} else if (materialParameters.y < 0.5 && s.material.z > 0.5 && s.material.z < 1.5) {
|
|
753
794
|
let grain = noise(s.world * vec3f(18, 3, 1.6));
|
|
754
795
|
c *= 0.82 + 0.30 * grain;
|
|
755
|
-
} else if (s.material.z > 1.5) { c *= 0.88 + 0.18 * noise(s.world * 12.0); }
|
|
796
|
+
} else if (materialParameters.y < 0.5 && s.material.z > 1.5) { c *= 0.88 + 0.18 * noise(s.world * 12.0); }
|
|
756
797
|
let hemi = mix(vec3f(0.06,0.05,0.035), vec3f(0.16,0.22,0.29), n.y * 0.5 + 0.5);
|
|
757
|
-
let direct = brdf(c, n, v,
|
|
758
|
-
let ambient = c * hemi * (1.0 -
|
|
759
|
-
let env = sky(reflect(-v,n)) * mix(vec3f(0.04), c,
|
|
760
|
-
|
|
798
|
+
let direct = brdf(c, n, v, roughness, metalness) * vec3f(3.0, 2.65, 2.15) * visibility(s.world, n);
|
|
799
|
+
let ambient = c * hemi * (1.0 - metalness * 0.7) * orm.r;
|
|
800
|
+
let env = sky(reflect(-v,n)) * mix(vec3f(0.04), c, metalness) * (1.0-roughness) * orm.r * 0.30;
|
|
801
|
+
let baseRadiance = direct + ambient + env;
|
|
802
|
+
let coatCoverage = materialParameters.z * coatSample.r;
|
|
803
|
+
if (coatCoverage <= 0.0) { return fog(baseRadiance,s.world); }
|
|
804
|
+
// KHR_materials_clearcoat's simple Fresnel layering: varnish has its own
|
|
805
|
+
// smooth normal and roughness. Its reflection never inherits the wood colour.
|
|
806
|
+
let coatFresnel = 0.04 + 0.96 * pow(1.0-clamp(abs(dot(coatNormal,v)),0.0,1.0),5.0);
|
|
807
|
+
let coatWeight = coatCoverage * coatFresnel;
|
|
808
|
+
let coatRoughness = clamp(materialParameters.w * coatSample.g,0.04,1.0);
|
|
809
|
+
let coatDirect = vec3f(3.0,2.65,2.15) * specularLobe(coatNormal,v,coatRoughness) * visibility(s.world,coatNormal);
|
|
810
|
+
let coatEnvironment = sky(reflect(-v,coatNormal)) * (1.0-coatRoughness) * orm.r * 0.30;
|
|
811
|
+
return fog(baseRadiance * (1.0-coatWeight) + (coatDirect+coatEnvironment) * coatWeight, s.world);
|
|
761
812
|
}
|
|
762
813
|
@fragment fn surfaceFragment(s: Surface, @builtin(front_facing) front: bool) -> @location(0) vec4f {
|
|
763
814
|
return vec4f(display(surfaceRadiance(s,front)),1);
|
|
@@ -811,12 +862,114 @@ struct WaterResult { @location(0) colour: vec4f, @builtin(frag_depth) depth: f32
|
|
|
811
862
|
return vec4f(sky(cameraRay(pixel.xy)),1);
|
|
812
863
|
}
|
|
813
864
|
@fragment fn reflectionSurface(s: Surface, @builtin(front_facing) front: bool) -> @location(0) vec4f {
|
|
865
|
+
let radiance = surfaceRadiance(s,front);
|
|
814
866
|
if (s.world.y < frame.viewport.z) { discard; }
|
|
815
|
-
return vec4f(
|
|
867
|
+
return vec4f(radiance,1);
|
|
816
868
|
}
|
|
817
869
|
`
|
|
818
870
|
);
|
|
819
871
|
|
|
872
|
+
// src/native-surface-materials.js
|
|
873
|
+
var MAX_BYTES = 64 * 1024 * 1024;
|
|
874
|
+
var MAPS = ["baseColor", "normal", "orm", "clearcoatMap"];
|
|
875
|
+
var defaults = {
|
|
876
|
+
baseColor: [255, 255, 255, 255],
|
|
877
|
+
normal: [128, 128, 255, 255],
|
|
878
|
+
orm: [255, 255, 255, 255],
|
|
879
|
+
clearcoatMap: [255, 255, 255, 255]
|
|
880
|
+
};
|
|
881
|
+
var dimension = (n) => Number.isInteger(n) && n >= 1 && n <= 2048 && (n & n - 1) === 0;
|
|
882
|
+
function validateNativeMaterials(materials) {
|
|
883
|
+
if (!Array.isArray(materials) || materials.length > 16)
|
|
884
|
+
throw new Error("materials must contain at most 16 entries.");
|
|
885
|
+
let bytes = 0;
|
|
886
|
+
for (const material of materials) {
|
|
887
|
+
if (!material || typeof material !== "object")
|
|
888
|
+
throw new Error("Each material must be an object.");
|
|
889
|
+
if (!Number.isFinite(material.normalScale ?? 1) || (material.normalScale ?? 1) < 0 || (material.normalScale ?? 1) > 4) {
|
|
890
|
+
throw new Error("material normalScale must be between 0 and 4.");
|
|
891
|
+
}
|
|
892
|
+
for (const key of ["clearcoat", "clearcoatRoughness"]) {
|
|
893
|
+
const value = material[key] ?? 0;
|
|
894
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
895
|
+
throw new Error(`material ${key} must be between 0 and 1.`);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
for (const key of MAPS) {
|
|
899
|
+
const map = material[key];
|
|
900
|
+
if (map === void 0) continue;
|
|
901
|
+
if (!map || !dimension(map.width) || !dimension(map.height) || !(map.data instanceof Uint8Array || map.data instanceof Uint8ClampedArray) || map.data.length !== map.width * map.height * 4) {
|
|
902
|
+
throw new Error(
|
|
903
|
+
`material ${key} texture must contain RGBA8 data with power-of-two dimensions between 1 and 2048.`
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
bytes += map.data.byteLength;
|
|
907
|
+
if (bytes > MAX_BYTES)
|
|
908
|
+
throw new Error("material textures exceed the 64 MiB source budget.");
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
var linear = (x) => x <= 0.04045 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
|
913
|
+
var srgb = (x) => x <= 31308e-7 ? x * 12.92 : 1.055 * x ** (1 / 2.4) - 0.055;
|
|
914
|
+
function nextMip(image, colour) {
|
|
915
|
+
const width = Math.max(1, image.width / 2), height = Math.max(1, image.height / 2);
|
|
916
|
+
const data = new Uint8Array(width * height * 4);
|
|
917
|
+
for (let y = 0; y < height; y++)
|
|
918
|
+
for (let x = 0; x < width; x++)
|
|
919
|
+
for (let c = 0; c < 4; c++) {
|
|
920
|
+
let sum = 0;
|
|
921
|
+
for (let dy = 0; dy < 2; dy++)
|
|
922
|
+
for (let dx = 0; dx < 2; dx++) {
|
|
923
|
+
const offset = (Math.min(image.height - 1, y * 2 + dy) * image.width + Math.min(image.width - 1, x * 2 + dx)) * 4 + c;
|
|
924
|
+
const value = image.data[offset] / 255;
|
|
925
|
+
sum += colour && c < 3 ? linear(value) : value;
|
|
926
|
+
}
|
|
927
|
+
data[(y * width + x) * 4 + c] = Math.round(
|
|
928
|
+
255 * (colour && c < 3 ? srgb(sum / 4) : sum / 4)
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
return { width, height, data };
|
|
932
|
+
}
|
|
933
|
+
function createNativeMaterialTextures(device, materials, own) {
|
|
934
|
+
return [{}, ...materials].map((material, index) => {
|
|
935
|
+
const result = {
|
|
936
|
+
normalScale: material.normal ? material.normalScale ?? 1 : 0,
|
|
937
|
+
authored: ["baseColor", "normal", "orm"].some((key) => material[key]),
|
|
938
|
+
clearcoat: material.clearcoat ?? 0,
|
|
939
|
+
clearcoatRoughness: material.clearcoatRoughness ?? 0
|
|
940
|
+
};
|
|
941
|
+
for (const key of MAPS) {
|
|
942
|
+
let level = material[key] ?? {
|
|
943
|
+
width: 1,
|
|
944
|
+
height: 1,
|
|
945
|
+
data: new Uint8Array(defaults[key])
|
|
946
|
+
};
|
|
947
|
+
const mipLevelCount = 1 + Math.log2(Math.max(level.width, level.height));
|
|
948
|
+
const texture = own(
|
|
949
|
+
device.createTexture({
|
|
950
|
+
label: `native.material.${index}.${key}`,
|
|
951
|
+
size: { width: level.width, height: level.height },
|
|
952
|
+
format: key === "baseColor" ? "rgba8unorm-srgb" : "rgba8unorm",
|
|
953
|
+
mipLevelCount,
|
|
954
|
+
usage: 6
|
|
955
|
+
})
|
|
956
|
+
);
|
|
957
|
+
for (let mipLevel = 0; mipLevel < mipLevelCount; mipLevel++) {
|
|
958
|
+
device.queue.writeTexture(
|
|
959
|
+
{ texture, mipLevel },
|
|
960
|
+
level.data,
|
|
961
|
+
{ bytesPerRow: level.width * 4, rowsPerImage: level.height },
|
|
962
|
+
{ width: level.width, height: level.height, depthOrArrayLayers: 1 }
|
|
963
|
+
);
|
|
964
|
+
if (mipLevel + 1 < mipLevelCount)
|
|
965
|
+
level = nextMip(level, key === "baseColor");
|
|
966
|
+
}
|
|
967
|
+
result[key] = texture.createView();
|
|
968
|
+
}
|
|
969
|
+
return result;
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
|
|
820
973
|
// src/native-scene-renderer.js
|
|
821
974
|
var STRIDE = 12;
|
|
822
975
|
var UNIFORM_BYTES = 256;
|
|
@@ -832,7 +985,7 @@ var unit = (a) => {
|
|
|
832
985
|
return a.map((v) => v / length);
|
|
833
986
|
};
|
|
834
987
|
var finiteVector = (a) => Array.isArray(a) && a.length === 3 && a.every(Number.isFinite);
|
|
835
|
-
function validateFrame(frame, maxVertices) {
|
|
988
|
+
function validateFrame(frame, maxVertices, materialCount) {
|
|
836
989
|
const { vertices, camera, time = 0 } = frame ?? {};
|
|
837
990
|
if (!(vertices instanceof Float32Array) || vertices.length % (STRIDE * 3) !== 0 || vertices.length > maxVertices * STRIDE || !vertices.every(Number.isFinite)) {
|
|
838
991
|
throw new Error(
|
|
@@ -854,6 +1007,30 @@ function validateFrame(frame, maxVertices) {
|
|
|
854
1007
|
"wakes must contain at most four finite [x, z, heading] entries."
|
|
855
1008
|
);
|
|
856
1009
|
}
|
|
1010
|
+
const count = vertices.length / STRIDE;
|
|
1011
|
+
if (frame.texcoords !== void 0 && (!(frame.texcoords instanceof Float32Array) || frame.texcoords.length !== count * 2 || !frame.texcoords.every(Number.isFinite))) {
|
|
1012
|
+
throw new Error(
|
|
1013
|
+
"texcoords must contain two finite coordinates per vertex."
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
if (frame.surfaces !== void 0) {
|
|
1017
|
+
if (!Array.isArray(frame.surfaces) || frame.surfaces.length > 256 || !frame.texcoords) {
|
|
1018
|
+
throw new Error(
|
|
1019
|
+
"surfaces require texcoords and at most 256 contiguous draw ranges."
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
let end = 0;
|
|
1023
|
+
for (const range of frame.surfaces) {
|
|
1024
|
+
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) {
|
|
1025
|
+
throw new Error(
|
|
1026
|
+
"surface ranges must cover triangles in order and reference a valid material."
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
end += range.vertexCount;
|
|
1030
|
+
}
|
|
1031
|
+
if (end !== count)
|
|
1032
|
+
throw new Error("surface ranges must cover every vertex exactly once.");
|
|
1033
|
+
}
|
|
857
1034
|
return { ...frame, time };
|
|
858
1035
|
}
|
|
859
1036
|
function frameUniforms(frame, width, height, reflected = false) {
|
|
@@ -901,6 +1078,9 @@ function frameUniforms(frame, width, height, reflected = false) {
|
|
|
901
1078
|
}
|
|
902
1079
|
async function createNativeSceneRenderer(options = {}) {
|
|
903
1080
|
const maxVertices = options.maxVertices ?? 3e5;
|
|
1081
|
+
const materials = options.materials ?? [];
|
|
1082
|
+
validateNativeMaterials(materials);
|
|
1083
|
+
const materialCount = materials.length;
|
|
904
1084
|
if (!Number.isInteger(maxVertices) || maxVertices < 3 || maxVertices > 6e5) {
|
|
905
1085
|
throw new Error("maxVertices must be an integer between 3 and 600000.");
|
|
906
1086
|
}
|
|
@@ -909,6 +1089,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
909
1089
|
let available = true;
|
|
910
1090
|
let targets = null;
|
|
911
1091
|
let vertexBuffer = null;
|
|
1092
|
+
let uvBuffer = null;
|
|
912
1093
|
let vertexCapacity = 0;
|
|
913
1094
|
let pendingFrame = null;
|
|
914
1095
|
let submittedFrames = 0;
|
|
@@ -1015,7 +1196,20 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1015
1196
|
if (p !== shadowPipeline) pass2.setBindGroup(1, shadowGroup);
|
|
1016
1197
|
if (vertexCount > 0) {
|
|
1017
1198
|
pass2.setVertexBuffer(0, vertexBuffer);
|
|
1018
|
-
|
|
1199
|
+
if (p === shadowPipeline) {
|
|
1200
|
+
pass2.draw(vertexCount);
|
|
1201
|
+
} else {
|
|
1202
|
+
pass2.setVertexBuffer(1, uvBuffer);
|
|
1203
|
+
if (pendingFrame.surfaces) {
|
|
1204
|
+
for (const range of pendingFrame.surfaces) {
|
|
1205
|
+
pass2.setBindGroup(2, materialGroups[range.materialIndex + 1]);
|
|
1206
|
+
pass2.draw(range.vertexCount, 1, range.firstVertex);
|
|
1207
|
+
}
|
|
1208
|
+
} else {
|
|
1209
|
+
pass2.setBindGroup(2, materialGroups[0]);
|
|
1210
|
+
pass2.draw(vertexCount);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1019
1213
|
}
|
|
1020
1214
|
};
|
|
1021
1215
|
const shadowPass = encoder.beginRenderPass({
|
|
@@ -1061,7 +1255,8 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1061
1255
|
if (pendingFrame.water !== false) {
|
|
1062
1256
|
pass.setPipeline(waterPipeline);
|
|
1063
1257
|
pass.setBindGroup(1, shadowGroup);
|
|
1064
|
-
pass.setBindGroup(2,
|
|
1258
|
+
pass.setBindGroup(2, materialGroups[0]);
|
|
1259
|
+
pass.setBindGroup(3, targets.reflectionGroup);
|
|
1065
1260
|
pass.draw(3);
|
|
1066
1261
|
}
|
|
1067
1262
|
drawGeometry(pass, surfacePipeline, mainGroup);
|
|
@@ -1111,6 +1306,63 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1111
1306
|
{ binding: 1, visibility: 2, sampler: { type: "filtering" } }
|
|
1112
1307
|
]
|
|
1113
1308
|
});
|
|
1309
|
+
const materialLayout = device.createBindGroupLayout({
|
|
1310
|
+
entries: [
|
|
1311
|
+
...[0, 1, 2, 5].map((binding) => ({
|
|
1312
|
+
binding,
|
|
1313
|
+
visibility: 2,
|
|
1314
|
+
texture: { sampleType: "float" }
|
|
1315
|
+
})),
|
|
1316
|
+
{ binding: 3, visibility: 2, sampler: { type: "filtering" } },
|
|
1317
|
+
{
|
|
1318
|
+
binding: 4,
|
|
1319
|
+
visibility: 2,
|
|
1320
|
+
buffer: { type: "uniform", minBindingSize: 16 }
|
|
1321
|
+
}
|
|
1322
|
+
]
|
|
1323
|
+
});
|
|
1324
|
+
const materialSampler = device.createSampler({
|
|
1325
|
+
addressModeU: "repeat",
|
|
1326
|
+
addressModeV: "repeat",
|
|
1327
|
+
minFilter: "linear",
|
|
1328
|
+
magFilter: "linear",
|
|
1329
|
+
mipmapFilter: "linear",
|
|
1330
|
+
maxAnisotropy: 4
|
|
1331
|
+
});
|
|
1332
|
+
const materialGroups = createNativeMaterialTextures(
|
|
1333
|
+
device,
|
|
1334
|
+
materials,
|
|
1335
|
+
own
|
|
1336
|
+
).map((material) => {
|
|
1337
|
+
const params = own(
|
|
1338
|
+
device.createBuffer({
|
|
1339
|
+
size: 16,
|
|
1340
|
+
usage: 72,
|
|
1341
|
+
label: "native.material-parameters"
|
|
1342
|
+
})
|
|
1343
|
+
);
|
|
1344
|
+
device.queue.writeBuffer(
|
|
1345
|
+
params,
|
|
1346
|
+
0,
|
|
1347
|
+
new Float32Array([
|
|
1348
|
+
material.normalScale,
|
|
1349
|
+
material.authored ? 1 : 0,
|
|
1350
|
+
material.clearcoat,
|
|
1351
|
+
material.clearcoatRoughness
|
|
1352
|
+
])
|
|
1353
|
+
);
|
|
1354
|
+
return device.createBindGroup({
|
|
1355
|
+
layout: materialLayout,
|
|
1356
|
+
entries: [
|
|
1357
|
+
{ binding: 0, resource: material.baseColor },
|
|
1358
|
+
{ binding: 1, resource: material.normal },
|
|
1359
|
+
{ binding: 2, resource: material.orm },
|
|
1360
|
+
{ binding: 3, resource: materialSampler },
|
|
1361
|
+
{ binding: 4, resource: { buffer: params } },
|
|
1362
|
+
{ binding: 5, resource: material.clearcoatMap }
|
|
1363
|
+
]
|
|
1364
|
+
});
|
|
1365
|
+
});
|
|
1114
1366
|
const uniformBuffer = own(
|
|
1115
1367
|
device.createBuffer({
|
|
1116
1368
|
label: "native.frame",
|
|
@@ -1175,7 +1427,19 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1175
1427
|
{
|
|
1176
1428
|
arrayStride: STRIDE * 4,
|
|
1177
1429
|
attributes: vertex === "shadowVertex" ? attributes.slice(0, 1) : attributes
|
|
1178
|
-
}
|
|
1430
|
+
},
|
|
1431
|
+
...vertex === "shadowVertex" ? [] : [
|
|
1432
|
+
{
|
|
1433
|
+
arrayStride: 8,
|
|
1434
|
+
attributes: [
|
|
1435
|
+
{
|
|
1436
|
+
shaderLocation: 4,
|
|
1437
|
+
offset: 0,
|
|
1438
|
+
format: "float32x2"
|
|
1439
|
+
}
|
|
1440
|
+
]
|
|
1441
|
+
}
|
|
1442
|
+
]
|
|
1179
1443
|
]
|
|
1180
1444
|
} : {}
|
|
1181
1445
|
},
|
|
@@ -1209,7 +1473,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1209
1473
|
"surfaceFragment",
|
|
1210
1474
|
format,
|
|
1211
1475
|
4,
|
|
1212
|
-
[uniformLayout, shadowLayout],
|
|
1476
|
+
[uniformLayout, shadowLayout, materialLayout],
|
|
1213
1477
|
true
|
|
1214
1478
|
);
|
|
1215
1479
|
const skyPipeline = await pipeline(
|
|
@@ -1226,7 +1490,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1226
1490
|
"waterFragment",
|
|
1227
1491
|
format,
|
|
1228
1492
|
4,
|
|
1229
|
-
[uniformLayout, shadowLayout, reflectionLayout]
|
|
1493
|
+
[uniformLayout, shadowLayout, materialLayout, reflectionLayout]
|
|
1230
1494
|
);
|
|
1231
1495
|
const reflectedSurfacePipeline = await pipeline(
|
|
1232
1496
|
"reflection-surface",
|
|
@@ -1234,7 +1498,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1234
1498
|
"reflectionSurface",
|
|
1235
1499
|
"rgba16float",
|
|
1236
1500
|
1,
|
|
1237
|
-
[uniformLayout, shadowLayout],
|
|
1501
|
+
[uniformLayout, shadowLayout, materialLayout],
|
|
1238
1502
|
true
|
|
1239
1503
|
);
|
|
1240
1504
|
const reflectedSkyPipeline = await pipeline(
|
|
@@ -1249,15 +1513,23 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1249
1513
|
render(input) {
|
|
1250
1514
|
if (destroyed) throw new Error("Native scene renderer was destroyed.");
|
|
1251
1515
|
if (!available) throw new Error("WebGPU device was lost.");
|
|
1252
|
-
pendingFrame = validateFrame(input, maxVertices);
|
|
1516
|
+
pendingFrame = validateFrame(input, maxVertices, materialCount);
|
|
1253
1517
|
const data = pendingFrame.vertices;
|
|
1254
1518
|
ensureTargets();
|
|
1255
1519
|
if (data.byteLength > vertexCapacity) {
|
|
1256
1520
|
if (vertexBuffer) release(vertexBuffer);
|
|
1521
|
+
if (uvBuffer) release(uvBuffer);
|
|
1257
1522
|
vertexCapacity = Math.min(
|
|
1258
1523
|
maxVertices * STRIDE * 4,
|
|
1259
1524
|
Math.max(4096, 2 ** Math.ceil(Math.log2(data.byteLength)))
|
|
1260
1525
|
);
|
|
1526
|
+
uvBuffer = own(
|
|
1527
|
+
device.createBuffer({
|
|
1528
|
+
label: "native.surface-uvs",
|
|
1529
|
+
size: Math.ceil(vertexCapacity / (STRIDE * 4)) * 8,
|
|
1530
|
+
usage: 40
|
|
1531
|
+
})
|
|
1532
|
+
);
|
|
1261
1533
|
vertexBuffer = own(
|
|
1262
1534
|
device.createBuffer({
|
|
1263
1535
|
label: "native.surface-vertices",
|
|
@@ -1266,6 +1538,8 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1266
1538
|
})
|
|
1267
1539
|
);
|
|
1268
1540
|
}
|
|
1541
|
+
if (pendingFrame.texcoords?.byteLength)
|
|
1542
|
+
device.queue.writeBuffer(uvBuffer, 0, pendingFrame.texcoords);
|
|
1269
1543
|
vertexCount = data.length / STRIDE;
|
|
1270
1544
|
if (data.byteLength) device.queue.writeBuffer(vertexBuffer, 0, data);
|
|
1271
1545
|
device.queue.writeBuffer(
|
|
@@ -1307,6 +1581,202 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1307
1581
|
}
|
|
1308
1582
|
}
|
|
1309
1583
|
|
|
1584
|
+
// src/canonical-wavefront-meshes.js
|
|
1585
|
+
var STANDARD_TEXTURE_BINDINGS = Object.freeze([
|
|
1586
|
+
["baseColor", "baseColorTexture"],
|
|
1587
|
+
["metallicRoughness", "metallicRoughnessTexture"],
|
|
1588
|
+
["normal", "normalTexture"],
|
|
1589
|
+
["occlusion", "occlusionTexture"],
|
|
1590
|
+
["emissive", "emissiveTexture"]
|
|
1591
|
+
]);
|
|
1592
|
+
var EXTENSION_TEXTURE_BINDINGS = Object.freeze([
|
|
1593
|
+
["clearcoat", "clearcoat", "clearcoatTexture"],
|
|
1594
|
+
["clearcoatRoughness", "clearcoat", "clearcoatRoughnessTexture"],
|
|
1595
|
+
["clearcoatNormal", "clearcoat", "clearcoatNormalTexture"],
|
|
1596
|
+
["transmission", "transmission", "transmissionTexture"],
|
|
1597
|
+
["sheenColor", "sheen", "sheenColorTexture"],
|
|
1598
|
+
["sheenRoughness", "sheen", "sheenRoughnessTexture"]
|
|
1599
|
+
]);
|
|
1600
|
+
function requireArray(value, label) {
|
|
1601
|
+
if (!Array.isArray(value)) {
|
|
1602
|
+
throw new TypeError(`${label} must be an array.`);
|
|
1603
|
+
}
|
|
1604
|
+
return value;
|
|
1605
|
+
}
|
|
1606
|
+
function requireMap(value, label) {
|
|
1607
|
+
if (!value || typeof value !== "object" || typeof value.get !== "function") {
|
|
1608
|
+
throw new TypeError(`${label} must provide a ReadonlyMap-compatible get method.`);
|
|
1609
|
+
}
|
|
1610
|
+
return value;
|
|
1611
|
+
}
|
|
1612
|
+
function textureSample(binding, textures, label, extra = {}) {
|
|
1613
|
+
if (binding === void 0 || binding === null) {
|
|
1614
|
+
return void 0;
|
|
1615
|
+
}
|
|
1616
|
+
const sample = textures.get(binding.textureId);
|
|
1617
|
+
if (!sample) {
|
|
1618
|
+
throw new Error(`Missing decoded texture sample ${binding.textureId} for ${label}.`);
|
|
1619
|
+
}
|
|
1620
|
+
const width = Number(sample.width);
|
|
1621
|
+
const height = Number(sample.height);
|
|
1622
|
+
const data = sample.data;
|
|
1623
|
+
const dataLength = data && typeof data.length === "number" ? data.length : -1;
|
|
1624
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1) {
|
|
1625
|
+
throw new TypeError(`Decoded texture sample ${binding.textureId} has invalid dimensions.`);
|
|
1626
|
+
}
|
|
1627
|
+
if (width > 4096 || height > 4096 || width * height > 4096 * 4096) {
|
|
1628
|
+
throw new RangeError(`Decoded texture sample ${binding.textureId} exceeds the 4096-pixel resource limit.`);
|
|
1629
|
+
}
|
|
1630
|
+
if (!(Array.isArray(data) || ArrayBuffer.isView(data)) || dataLength < width * height * 4) {
|
|
1631
|
+
throw new TypeError(`Decoded texture sample ${binding.textureId} must contain width \xD7 height \xD7 4 RGBA values.`);
|
|
1632
|
+
}
|
|
1633
|
+
return Object.freeze({
|
|
1634
|
+
...sample,
|
|
1635
|
+
...binding.texCoordSet === void 0 ? {} : { texCoord: binding.texCoordSet },
|
|
1636
|
+
...extra
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
function cloneMaterialExtensions(material, textures) {
|
|
1640
|
+
const extensions = { ...material.extensions ?? {} };
|
|
1641
|
+
if (material.workflow === "unlit") {
|
|
1642
|
+
const unlitKey = "KHR_materials_unlit";
|
|
1643
|
+
const unlitValue = extensions[unlitKey] ?? extensions.unlit ?? {};
|
|
1644
|
+
if (typeof unlitValue !== "object" || unlitValue === null || Array.isArray(unlitValue)) {
|
|
1645
|
+
throw new TypeError(`Canonical material extension ${unlitKey} must be an object.`);
|
|
1646
|
+
}
|
|
1647
|
+
extensions[unlitKey] = { ...unlitValue, enabled: true };
|
|
1648
|
+
}
|
|
1649
|
+
for (const [bindingName, extensionName, textureKey] of EXTENSION_TEXTURE_BINDINGS) {
|
|
1650
|
+
const binding = material.textures?.[bindingName];
|
|
1651
|
+
if (!binding) {
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
const extensionKey = `KHR_materials_${extensionName}`;
|
|
1655
|
+
const extensionValue = extensions[extensionKey] ?? extensions[extensionName] ?? {};
|
|
1656
|
+
if (typeof extensionValue !== "object" || extensionValue === null || Array.isArray(extensionValue)) {
|
|
1657
|
+
throw new TypeError(`Canonical material extension ${extensionKey} must be an object.`);
|
|
1658
|
+
}
|
|
1659
|
+
extensions[extensionKey] = {
|
|
1660
|
+
...extensionValue,
|
|
1661
|
+
[textureKey]: textureSample(binding, textures, `${extensionKey}.${textureKey}`)
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
return extensions;
|
|
1665
|
+
}
|
|
1666
|
+
function mapMaterial(material, materialRefId, textures) {
|
|
1667
|
+
if (!material) {
|
|
1668
|
+
return { materialRefId };
|
|
1669
|
+
}
|
|
1670
|
+
if (material.alphaMode === "mask") {
|
|
1671
|
+
throw new Error("Canonical alphaMode mask is unsupported by WavefrontMeshInput.");
|
|
1672
|
+
}
|
|
1673
|
+
if (material.workflow !== "metallic-roughness" && material.workflow !== "unlit") {
|
|
1674
|
+
throw new Error(`Unsupported canonical material workflow: ${material.workflow}.`);
|
|
1675
|
+
}
|
|
1676
|
+
const mapped = {
|
|
1677
|
+
materialRefId,
|
|
1678
|
+
...material.alphaMode === "blend" ? { materialKind: "transparent" } : {},
|
|
1679
|
+
baseColor: material.baseColorFactor,
|
|
1680
|
+
emission: material.emissiveFactor ? [...material.emissiveFactor, 1] : [0, 0, 0, 1],
|
|
1681
|
+
roughness: material.roughnessFactor,
|
|
1682
|
+
metallic: material.metallicFactor,
|
|
1683
|
+
opacity: material.alphaMode === "opaque" ? 1 : material.baseColorFactor?.[3],
|
|
1684
|
+
clearcoat: material.clearcoatFactor,
|
|
1685
|
+
clearcoatRoughness: material.clearcoatRoughnessFactor,
|
|
1686
|
+
transmission: material.transmissionFactor,
|
|
1687
|
+
sheenColor: material.sheenColorFactor,
|
|
1688
|
+
extensions: cloneMaterialExtensions(material, textures)
|
|
1689
|
+
};
|
|
1690
|
+
for (const [bindingName, inputName] of STANDARD_TEXTURE_BINDINGS) {
|
|
1691
|
+
const binding = material.textures?.[bindingName];
|
|
1692
|
+
if (!binding) {
|
|
1693
|
+
continue;
|
|
1694
|
+
}
|
|
1695
|
+
const extra = bindingName === "normal" && material.normalScale !== void 0 ? { scale: material.normalScale } : bindingName === "occlusion" && material.occlusionStrength !== void 0 ? { strength: material.occlusionStrength } : {};
|
|
1696
|
+
mapped[inputName] = textureSample(binding, textures, inputName, extra);
|
|
1697
|
+
}
|
|
1698
|
+
return mapped;
|
|
1699
|
+
}
|
|
1700
|
+
function createCanonicalWavefrontMeshInputs({ document, geometry, textures = /* @__PURE__ */ new Map() } = {}) {
|
|
1701
|
+
if (!document || typeof document !== "object") {
|
|
1702
|
+
throw new TypeError("Canonical model document is required.");
|
|
1703
|
+
}
|
|
1704
|
+
const meshes = requireArray(document.meshes, "Canonical model meshes");
|
|
1705
|
+
const materials = requireArray(document.materials, "Canonical model materials");
|
|
1706
|
+
const geometryInputs = requireArray(geometry, "Renderer-ready geometry");
|
|
1707
|
+
const textureSamples = requireMap(textures, "Decoded texture samples");
|
|
1708
|
+
const geometryByPrimitiveId = /* @__PURE__ */ new Map();
|
|
1709
|
+
for (const entry of geometryInputs) {
|
|
1710
|
+
if (!entry || typeof entry.primitiveId !== "string" || entry.primitiveId.length === 0) {
|
|
1711
|
+
throw new TypeError("Renderer-ready geometry requires a non-empty primitiveId.");
|
|
1712
|
+
}
|
|
1713
|
+
if (geometryByPrimitiveId.has(entry.primitiveId)) {
|
|
1714
|
+
throw new Error(`Duplicate renderer-ready geometry for primitive ${entry.primitiveId}.`);
|
|
1715
|
+
}
|
|
1716
|
+
geometryByPrimitiveId.set(entry.primitiveId, entry);
|
|
1717
|
+
}
|
|
1718
|
+
const materialsById = /* @__PURE__ */ new Map();
|
|
1719
|
+
const materialRefIds = /* @__PURE__ */ new Map();
|
|
1720
|
+
materials.forEach((material, index) => {
|
|
1721
|
+
if (!material || typeof material.id !== "string" || material.id.length === 0) {
|
|
1722
|
+
throw new TypeError("Canonical materials require a non-empty id.");
|
|
1723
|
+
}
|
|
1724
|
+
if (materialsById.has(material.id)) {
|
|
1725
|
+
throw new Error(`Duplicate canonical material ${material.id}.`);
|
|
1726
|
+
}
|
|
1727
|
+
materialsById.set(material.id, material);
|
|
1728
|
+
materialRefIds.set(material.id, index + 1);
|
|
1729
|
+
});
|
|
1730
|
+
const output = [];
|
|
1731
|
+
const consumedPrimitiveIds = /* @__PURE__ */ new Set();
|
|
1732
|
+
const canonicalPrimitiveIds = /* @__PURE__ */ new Set();
|
|
1733
|
+
for (const mesh of meshes) {
|
|
1734
|
+
if (!mesh || typeof mesh.id !== "string") {
|
|
1735
|
+
throw new TypeError("Canonical meshes require a string id.");
|
|
1736
|
+
}
|
|
1737
|
+
for (const primitive of requireArray(mesh.primitives, `Primitives for mesh ${mesh.id}`)) {
|
|
1738
|
+
if (!primitive || typeof primitive.id !== "string" || primitive.id.length === 0) {
|
|
1739
|
+
throw new TypeError(`Canonical mesh ${mesh.id} contains a primitive without an id.`);
|
|
1740
|
+
}
|
|
1741
|
+
if (canonicalPrimitiveIds.has(primitive.id)) {
|
|
1742
|
+
throw new Error(`Duplicate canonical primitive ${primitive.id}.`);
|
|
1743
|
+
}
|
|
1744
|
+
canonicalPrimitiveIds.add(primitive.id);
|
|
1745
|
+
if (primitive.topology !== "triangles") {
|
|
1746
|
+
throw new Error(`Unsupported canonical primitive topology: ${primitive.topology}.`);
|
|
1747
|
+
}
|
|
1748
|
+
const source = geometryByPrimitiveId.get(primitive.id);
|
|
1749
|
+
if (!source) {
|
|
1750
|
+
throw new Error(`Missing renderer-ready geometry for primitive ${primitive.id}.`);
|
|
1751
|
+
}
|
|
1752
|
+
consumedPrimitiveIds.add(primitive.id);
|
|
1753
|
+
let material = null;
|
|
1754
|
+
let materialRefId = 0;
|
|
1755
|
+
if (primitive.materialId !== void 0) {
|
|
1756
|
+
material = materialsById.get(primitive.materialId);
|
|
1757
|
+
if (!material) {
|
|
1758
|
+
throw new Error(`Missing canonical material ${primitive.materialId} for primitive ${primitive.id}.`);
|
|
1759
|
+
}
|
|
1760
|
+
materialRefId = materialRefIds.get(primitive.materialId);
|
|
1761
|
+
}
|
|
1762
|
+
output.push(Object.freeze({
|
|
1763
|
+
id: output.length + 1,
|
|
1764
|
+
positions: source.positions,
|
|
1765
|
+
...source.indices === void 0 ? {} : { indices: source.indices },
|
|
1766
|
+
...source.normals === void 0 ? {} : { normals: source.normals },
|
|
1767
|
+
...(source.texcoords ?? source.uvs) === void 0 ? {} : { uvs: source.texcoords ?? source.uvs },
|
|
1768
|
+
...mapMaterial(material, materialRefId, textureSamples)
|
|
1769
|
+
}));
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
for (const primitiveId of geometryByPrimitiveId.keys()) {
|
|
1773
|
+
if (!consumedPrimitiveIds.has(primitiveId)) {
|
|
1774
|
+
throw new Error(`Renderer-ready geometry references unknown primitive ${primitiveId}.`);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
return Object.freeze(output);
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1310
1780
|
// src/animated-scene-renderer.js
|
|
1311
1781
|
var import_gpu_camera = require("@plasius/gpu-camera");
|
|
1312
1782
|
|
|
@@ -3750,8 +4220,8 @@ function hashUint32(value) {
|
|
|
3750
4220
|
x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
|
|
3751
4221
|
return (x >>> 16 ^ x) >>> 0;
|
|
3752
4222
|
}
|
|
3753
|
-
function mixSeed(pixelId, sampleId, bounce, frameIndex,
|
|
3754
|
-
let x = (pixelId >>> 0) * 747796405 ^ (sampleId >>> 0) * 2891336453 ^ (bounce >>> 0) * 277803737 ^ (frameIndex >>> 0) * 1442695041 ^ (
|
|
4223
|
+
function mixSeed(pixelId, sampleId, bounce, frameIndex, dimension2) {
|
|
4224
|
+
let x = (pixelId >>> 0) * 747796405 ^ (sampleId >>> 0) * 2891336453 ^ (bounce >>> 0) * 277803737 ^ (frameIndex >>> 0) * 1442695041 ^ (dimension2 >>> 0) * 1597334677;
|
|
3755
4225
|
x >>>= 0;
|
|
3756
4226
|
x ^= x >>> 16;
|
|
3757
4227
|
x = x * 2146121005 >>> 0;
|
|
@@ -9630,25 +10100,25 @@ function toWgslConstName(name) {
|
|
|
9630
10100
|
return `SAMPLE_DIM_${name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase()}`;
|
|
9631
10101
|
}
|
|
9632
10102
|
var usedDimensions = /* @__PURE__ */ new Set();
|
|
9633
|
-
for (const [,
|
|
9634
|
-
if (usedDimensions.has(
|
|
9635
|
-
throw new Error(`Duplicate wavefront sample dimension ${
|
|
10103
|
+
for (const [, dimension2] of sampleDimensionEntries) {
|
|
10104
|
+
if (usedDimensions.has(dimension2)) {
|
|
10105
|
+
throw new Error(`Duplicate wavefront sample dimension ${dimension2}.`);
|
|
9636
10106
|
}
|
|
9637
|
-
usedDimensions.add(
|
|
10107
|
+
usedDimensions.add(dimension2);
|
|
9638
10108
|
}
|
|
9639
10109
|
var WAVEFRONT_SAMPLE_DIMENSIONS = Object.freeze(
|
|
9640
10110
|
Object.fromEntries(sampleDimensionEntries)
|
|
9641
10111
|
);
|
|
9642
10112
|
function listWavefrontSampleDimensions() {
|
|
9643
10113
|
return sampleDimensionEntries.map(
|
|
9644
|
-
([name,
|
|
10114
|
+
([name, dimension2]) => Object.freeze({
|
|
9645
10115
|
name,
|
|
9646
|
-
dimension,
|
|
10116
|
+
dimension: dimension2,
|
|
9647
10117
|
wgslName: toWgslConstName(name)
|
|
9648
10118
|
})
|
|
9649
10119
|
);
|
|
9650
10120
|
}
|
|
9651
|
-
var WAVEFRONT_SAMPLE_DIMENSIONS_WGSL = listWavefrontSampleDimensions().map(({ wgslName, dimension }) => `const ${wgslName}: u32 = ${
|
|
10121
|
+
var WAVEFRONT_SAMPLE_DIMENSIONS_WGSL = listWavefrontSampleDimensions().map(({ wgslName, dimension: dimension2 }) => `const ${wgslName}: u32 = ${dimension2}u;`).join("\n");
|
|
9652
10122
|
function fract(value) {
|
|
9653
10123
|
return value - Math.floor(value);
|
|
9654
10124
|
}
|
|
@@ -9661,19 +10131,19 @@ function radicalInverseVdc2(value) {
|
|
|
9661
10131
|
bits = ((bits & 16711935) << 8 | (bits & 4278255360) >>> 8) >>> 0;
|
|
9662
10132
|
return bits / 4294967296;
|
|
9663
10133
|
}
|
|
9664
|
-
function sampleWavefrontDimension1D(pixelId, sampleId, bounce, frameIndex,
|
|
9665
|
-
return random01FromSeed(mixSeed(pixelId, sampleId, bounce, frameIndex,
|
|
10134
|
+
function sampleWavefrontDimension1D(pixelId, sampleId, bounce, frameIndex, dimension2) {
|
|
10135
|
+
return random01FromSeed(mixSeed(pixelId, sampleId, bounce, frameIndex, dimension2));
|
|
9666
10136
|
}
|
|
9667
|
-
function sampleWavefrontDimension2D(pixelId, sampleId, bounce, frameIndex,
|
|
10137
|
+
function sampleWavefrontDimension2D(pixelId, sampleId, bounce, frameIndex, dimension2, strataCount) {
|
|
9668
10138
|
const strata = Math.max(1, Number.isFinite(strataCount) ? Math.trunc(strataCount) : 1);
|
|
9669
10139
|
const jitter = sampleWavefrontDimension1D(
|
|
9670
10140
|
pixelId,
|
|
9671
10141
|
sampleId,
|
|
9672
10142
|
bounce,
|
|
9673
10143
|
frameIndex,
|
|
9674
|
-
|
|
10144
|
+
dimension2
|
|
9675
10145
|
);
|
|
9676
|
-
const scramble = hashUint32(mixSeed(pixelId, sampleId, bounce, frameIndex,
|
|
10146
|
+
const scramble = hashUint32(mixSeed(pixelId, sampleId, bounce, frameIndex, dimension2));
|
|
9677
10147
|
const stratified = fract(((sampleId >>> 0) % strata + jitter) / strata);
|
|
9678
10148
|
const lowDiscrepancy = fract(radicalInverseVdc2(sampleId >>> 0 ^ scramble) + jitter);
|
|
9679
10149
|
return Object.freeze([stratified, lowDiscrepancy]);
|
|
@@ -14093,6 +14563,7 @@ function createRayTracingRenderPlan(options = {}) {
|
|
|
14093
14563
|
bucketFeedbackGameFrameTime,
|
|
14094
14564
|
bucketFeedbackGameViewport,
|
|
14095
14565
|
createAnimatedSceneRenderer,
|
|
14566
|
+
createCanonicalWavefrontMeshInputs,
|
|
14096
14567
|
createDefaultWavefrontSceneObjects,
|
|
14097
14568
|
createFeedbackGameDiagnosticSnapshot,
|
|
14098
14569
|
createGpuRenderer,
|
|
@@ -14151,6 +14622,7 @@ function createRayTracingRenderPlan(options = {}) {
|
|
|
14151
14622
|
supportsWebGpu,
|
|
14152
14623
|
traceWavefrontReferenceTriangles,
|
|
14153
14624
|
transitionMediumStack,
|
|
14625
|
+
validateNativeSurfaceMaterials,
|
|
14154
14626
|
wavefrontMaterialKinds,
|
|
14155
14627
|
wavefrontPathTracingComputeLimits,
|
|
14156
14628
|
wavefrontSceneObjectKinds
|