@plasius/gpu-renderer 0.2.47 → 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 +37 -0
- package/README.md +195 -0
- package/dist/index.cjs +314 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +313 -40
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.d.ts +30 -0
- package/src/index.js +2 -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
|
@@ -87,6 +87,7 @@ __export(index_exports, {
|
|
|
87
87
|
supportsWebGpu: () => supportsWebGpu,
|
|
88
88
|
traceWavefrontReferenceTriangles: () => traceWavefrontReferenceTriangles,
|
|
89
89
|
transitionMediumStack: () => transitionMediumStack,
|
|
90
|
+
validateNativeSurfaceMaterials: () => validateNativeMaterials,
|
|
90
91
|
wavefrontMaterialKinds: () => wavefrontMaterialKinds,
|
|
91
92
|
wavefrontPathTracingComputeLimits: () => wavefrontPathTracingComputeLimits,
|
|
92
93
|
wavefrontSceneObjectKinds: () => wavefrontSceneObjectKinds
|
|
@@ -651,12 +652,18 @@ struct Frame {
|
|
|
651
652
|
@group(0) @binding(0) var<uniform> frame: Frame;
|
|
652
653
|
@group(1) @binding(0) var shadowMap: texture_depth_2d;
|
|
653
654
|
@group(1) @binding(1) var shadowSampler: sampler_comparison;
|
|
654
|
-
@group(
|
|
655
|
-
@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>;
|
|
656
663
|
struct Surface {
|
|
657
664
|
@builtin(position) clip: vec4f,
|
|
658
665
|
@location(0) world: vec3f, @location(1) normal: vec3f,
|
|
659
|
-
@location(2) colour: vec3f, @location(3) material: vec3f,
|
|
666
|
+
@location(2) colour: vec3f, @location(3) material: vec3f, @location(4) uv: vec2f,
|
|
660
667
|
};
|
|
661
668
|
fn project(p: vec3f) -> vec4f {
|
|
662
669
|
let d = p - frame.eye.xyz;
|
|
@@ -670,8 +677,8 @@ fn lightProject(p: vec3f) -> vec4f {
|
|
|
670
677
|
dot(d, frame.lightUp.xyz) / 34.0, dot(d, frame.lightForward.xyz) / 100.0, 1.0);
|
|
671
678
|
}
|
|
672
679
|
@vertex fn surfaceVertex(@location(0) p: vec3f, @location(1) n: vec3f,
|
|
673
|
-
@location(2) c: vec3f, @location(3) m: vec3f) -> Surface {
|
|
674
|
-
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);
|
|
675
682
|
}
|
|
676
683
|
@vertex fn shadowVertex(@location(0) p: vec3f) -> @builtin(position) vec4f {
|
|
677
684
|
return lightProject(p);
|
|
@@ -724,17 +731,25 @@ fn visibility(p: vec3f, n: vec3f) -> f32 {
|
|
|
724
731
|
}
|
|
725
732
|
return result / 9.0;
|
|
726
733
|
}
|
|
727
|
-
|
|
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 {
|
|
728
737
|
let l = frame.sun.xyz; let h = normalize(l + v);
|
|
729
738
|
let nl = max(dot(n,l),0.0); let nv = max(dot(n,v),0.001);
|
|
730
|
-
let nh = max(dot(n,h),0.0);
|
|
739
|
+
let nh = max(dot(n,h),0.0);
|
|
731
740
|
let a = max(0.035, roughness * roughness); let a2 = a * a;
|
|
732
741
|
let d = a2 / max(0.00001, 3.14159265 * pow(nh * nh * (a2 - 1.0) + 1.0, 2.0));
|
|
733
742
|
let k = pow(roughness + 1.0, 2.0) / 8.0;
|
|
734
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);
|
|
735
750
|
let f0 = mix(vec3f(0.04), c, metal);
|
|
736
751
|
let f = f0 + (1.0-f0) * pow(1.0-vh, 5.0);
|
|
737
|
-
return (
|
|
752
|
+
return (1.0-f) * (1.0-metal) * c / 3.14159265 * nl + specularLobe(n,v,roughness) * f;
|
|
738
753
|
}
|
|
739
754
|
fn fog(c: vec3f, p: vec3f) -> vec3f {
|
|
740
755
|
let amount = 1.0 - exp(-distance(frame.eye.xyz, p) * 0.0018);
|
|
@@ -742,23 +757,58 @@ fn fog(c: vec3f, p: vec3f) -> vec3f {
|
|
|
742
757
|
}
|
|
743
758
|
fn surfaceRadiance(s: Surface, front: bool) -> vec3f {
|
|
744
759
|
let facing = select(select(-1.0,1.0,front),select(1.0,-1.0,front),frame.info.y>0.5);
|
|
745
|
-
|
|
760
|
+
let coatNormal = normalize(s.normal) * facing;
|
|
761
|
+
var n = coatNormal;
|
|
746
762
|
let v = normalize(frame.eye.xyz - s.world);
|
|
747
|
-
|
|
748
|
-
|
|
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) {
|
|
749
789
|
let rock = smoothstep(0.45,0.68,noise(s.world*0.27));
|
|
750
790
|
c = mix(c,vec3f(0.16,0.145,0.11),rock*0.7);
|
|
751
791
|
c *= 0.75+0.45*noise(s.world*2.4);
|
|
752
792
|
n = normalize(n+vec3f(noise(s.world*4.0)-0.5,0,noise(s.world*4.0+vec3f(13))-0.5)*0.28);
|
|
753
|
-
} 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) {
|
|
754
794
|
let grain = noise(s.world * vec3f(18, 3, 1.6));
|
|
755
795
|
c *= 0.82 + 0.30 * grain;
|
|
756
|
-
} 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); }
|
|
757
797
|
let hemi = mix(vec3f(0.06,0.05,0.035), vec3f(0.16,0.22,0.29), n.y * 0.5 + 0.5);
|
|
758
|
-
let direct = brdf(c, n, v,
|
|
759
|
-
let ambient = c * hemi * (1.0 -
|
|
760
|
-
let env = sky(reflect(-v,n)) * mix(vec3f(0.04), c,
|
|
761
|
-
|
|
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);
|
|
762
812
|
}
|
|
763
813
|
@fragment fn surfaceFragment(s: Surface, @builtin(front_facing) front: bool) -> @location(0) vec4f {
|
|
764
814
|
return vec4f(display(surfaceRadiance(s,front)),1);
|
|
@@ -812,12 +862,114 @@ struct WaterResult { @location(0) colour: vec4f, @builtin(frag_depth) depth: f32
|
|
|
812
862
|
return vec4f(sky(cameraRay(pixel.xy)),1);
|
|
813
863
|
}
|
|
814
864
|
@fragment fn reflectionSurface(s: Surface, @builtin(front_facing) front: bool) -> @location(0) vec4f {
|
|
865
|
+
let radiance = surfaceRadiance(s,front);
|
|
815
866
|
if (s.world.y < frame.viewport.z) { discard; }
|
|
816
|
-
return vec4f(
|
|
867
|
+
return vec4f(radiance,1);
|
|
817
868
|
}
|
|
818
869
|
`
|
|
819
870
|
);
|
|
820
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
|
+
|
|
821
973
|
// src/native-scene-renderer.js
|
|
822
974
|
var STRIDE = 12;
|
|
823
975
|
var UNIFORM_BYTES = 256;
|
|
@@ -833,7 +985,7 @@ var unit = (a) => {
|
|
|
833
985
|
return a.map((v) => v / length);
|
|
834
986
|
};
|
|
835
987
|
var finiteVector = (a) => Array.isArray(a) && a.length === 3 && a.every(Number.isFinite);
|
|
836
|
-
function validateFrame(frame, maxVertices) {
|
|
988
|
+
function validateFrame(frame, maxVertices, materialCount) {
|
|
837
989
|
const { vertices, camera, time = 0 } = frame ?? {};
|
|
838
990
|
if (!(vertices instanceof Float32Array) || vertices.length % (STRIDE * 3) !== 0 || vertices.length > maxVertices * STRIDE || !vertices.every(Number.isFinite)) {
|
|
839
991
|
throw new Error(
|
|
@@ -855,6 +1007,30 @@ function validateFrame(frame, maxVertices) {
|
|
|
855
1007
|
"wakes must contain at most four finite [x, z, heading] entries."
|
|
856
1008
|
);
|
|
857
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
|
+
}
|
|
858
1034
|
return { ...frame, time };
|
|
859
1035
|
}
|
|
860
1036
|
function frameUniforms(frame, width, height, reflected = false) {
|
|
@@ -902,6 +1078,9 @@ function frameUniforms(frame, width, height, reflected = false) {
|
|
|
902
1078
|
}
|
|
903
1079
|
async function createNativeSceneRenderer(options = {}) {
|
|
904
1080
|
const maxVertices = options.maxVertices ?? 3e5;
|
|
1081
|
+
const materials = options.materials ?? [];
|
|
1082
|
+
validateNativeMaterials(materials);
|
|
1083
|
+
const materialCount = materials.length;
|
|
905
1084
|
if (!Number.isInteger(maxVertices) || maxVertices < 3 || maxVertices > 6e5) {
|
|
906
1085
|
throw new Error("maxVertices must be an integer between 3 and 600000.");
|
|
907
1086
|
}
|
|
@@ -910,6 +1089,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
910
1089
|
let available = true;
|
|
911
1090
|
let targets = null;
|
|
912
1091
|
let vertexBuffer = null;
|
|
1092
|
+
let uvBuffer = null;
|
|
913
1093
|
let vertexCapacity = 0;
|
|
914
1094
|
let pendingFrame = null;
|
|
915
1095
|
let submittedFrames = 0;
|
|
@@ -1016,7 +1196,20 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1016
1196
|
if (p !== shadowPipeline) pass2.setBindGroup(1, shadowGroup);
|
|
1017
1197
|
if (vertexCount > 0) {
|
|
1018
1198
|
pass2.setVertexBuffer(0, vertexBuffer);
|
|
1019
|
-
|
|
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
|
+
}
|
|
1020
1213
|
}
|
|
1021
1214
|
};
|
|
1022
1215
|
const shadowPass = encoder.beginRenderPass({
|
|
@@ -1062,7 +1255,8 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1062
1255
|
if (pendingFrame.water !== false) {
|
|
1063
1256
|
pass.setPipeline(waterPipeline);
|
|
1064
1257
|
pass.setBindGroup(1, shadowGroup);
|
|
1065
|
-
pass.setBindGroup(2,
|
|
1258
|
+
pass.setBindGroup(2, materialGroups[0]);
|
|
1259
|
+
pass.setBindGroup(3, targets.reflectionGroup);
|
|
1066
1260
|
pass.draw(3);
|
|
1067
1261
|
}
|
|
1068
1262
|
drawGeometry(pass, surfacePipeline, mainGroup);
|
|
@@ -1112,6 +1306,63 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1112
1306
|
{ binding: 1, visibility: 2, sampler: { type: "filtering" } }
|
|
1113
1307
|
]
|
|
1114
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
|
+
});
|
|
1115
1366
|
const uniformBuffer = own(
|
|
1116
1367
|
device.createBuffer({
|
|
1117
1368
|
label: "native.frame",
|
|
@@ -1176,7 +1427,19 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1176
1427
|
{
|
|
1177
1428
|
arrayStride: STRIDE * 4,
|
|
1178
1429
|
attributes: vertex === "shadowVertex" ? attributes.slice(0, 1) : attributes
|
|
1179
|
-
}
|
|
1430
|
+
},
|
|
1431
|
+
...vertex === "shadowVertex" ? [] : [
|
|
1432
|
+
{
|
|
1433
|
+
arrayStride: 8,
|
|
1434
|
+
attributes: [
|
|
1435
|
+
{
|
|
1436
|
+
shaderLocation: 4,
|
|
1437
|
+
offset: 0,
|
|
1438
|
+
format: "float32x2"
|
|
1439
|
+
}
|
|
1440
|
+
]
|
|
1441
|
+
}
|
|
1442
|
+
]
|
|
1180
1443
|
]
|
|
1181
1444
|
} : {}
|
|
1182
1445
|
},
|
|
@@ -1210,7 +1473,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1210
1473
|
"surfaceFragment",
|
|
1211
1474
|
format,
|
|
1212
1475
|
4,
|
|
1213
|
-
[uniformLayout, shadowLayout],
|
|
1476
|
+
[uniformLayout, shadowLayout, materialLayout],
|
|
1214
1477
|
true
|
|
1215
1478
|
);
|
|
1216
1479
|
const skyPipeline = await pipeline(
|
|
@@ -1227,7 +1490,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1227
1490
|
"waterFragment",
|
|
1228
1491
|
format,
|
|
1229
1492
|
4,
|
|
1230
|
-
[uniformLayout, shadowLayout, reflectionLayout]
|
|
1493
|
+
[uniformLayout, shadowLayout, materialLayout, reflectionLayout]
|
|
1231
1494
|
);
|
|
1232
1495
|
const reflectedSurfacePipeline = await pipeline(
|
|
1233
1496
|
"reflection-surface",
|
|
@@ -1235,7 +1498,7 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1235
1498
|
"reflectionSurface",
|
|
1236
1499
|
"rgba16float",
|
|
1237
1500
|
1,
|
|
1238
|
-
[uniformLayout, shadowLayout],
|
|
1501
|
+
[uniformLayout, shadowLayout, materialLayout],
|
|
1239
1502
|
true
|
|
1240
1503
|
);
|
|
1241
1504
|
const reflectedSkyPipeline = await pipeline(
|
|
@@ -1250,15 +1513,23 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1250
1513
|
render(input) {
|
|
1251
1514
|
if (destroyed) throw new Error("Native scene renderer was destroyed.");
|
|
1252
1515
|
if (!available) throw new Error("WebGPU device was lost.");
|
|
1253
|
-
pendingFrame = validateFrame(input, maxVertices);
|
|
1516
|
+
pendingFrame = validateFrame(input, maxVertices, materialCount);
|
|
1254
1517
|
const data = pendingFrame.vertices;
|
|
1255
1518
|
ensureTargets();
|
|
1256
1519
|
if (data.byteLength > vertexCapacity) {
|
|
1257
1520
|
if (vertexBuffer) release(vertexBuffer);
|
|
1521
|
+
if (uvBuffer) release(uvBuffer);
|
|
1258
1522
|
vertexCapacity = Math.min(
|
|
1259
1523
|
maxVertices * STRIDE * 4,
|
|
1260
1524
|
Math.max(4096, 2 ** Math.ceil(Math.log2(data.byteLength)))
|
|
1261
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
|
+
);
|
|
1262
1533
|
vertexBuffer = own(
|
|
1263
1534
|
device.createBuffer({
|
|
1264
1535
|
label: "native.surface-vertices",
|
|
@@ -1267,6 +1538,8 @@ async function createNativeSceneRenderer(options = {}) {
|
|
|
1267
1538
|
})
|
|
1268
1539
|
);
|
|
1269
1540
|
}
|
|
1541
|
+
if (pendingFrame.texcoords?.byteLength)
|
|
1542
|
+
device.queue.writeBuffer(uvBuffer, 0, pendingFrame.texcoords);
|
|
1270
1543
|
vertexCount = data.length / STRIDE;
|
|
1271
1544
|
if (data.byteLength) device.queue.writeBuffer(vertexBuffer, 0, data);
|
|
1272
1545
|
device.queue.writeBuffer(
|
|
@@ -3947,8 +4220,8 @@ function hashUint32(value) {
|
|
|
3947
4220
|
x = ((x >>> 16 ^ x) >>> 0) * 73244475 >>> 0;
|
|
3948
4221
|
return (x >>> 16 ^ x) >>> 0;
|
|
3949
4222
|
}
|
|
3950
|
-
function mixSeed(pixelId, sampleId, bounce, frameIndex,
|
|
3951
|
-
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;
|
|
3952
4225
|
x >>>= 0;
|
|
3953
4226
|
x ^= x >>> 16;
|
|
3954
4227
|
x = x * 2146121005 >>> 0;
|
|
@@ -9827,25 +10100,25 @@ function toWgslConstName(name) {
|
|
|
9827
10100
|
return `SAMPLE_DIM_${name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase()}`;
|
|
9828
10101
|
}
|
|
9829
10102
|
var usedDimensions = /* @__PURE__ */ new Set();
|
|
9830
|
-
for (const [,
|
|
9831
|
-
if (usedDimensions.has(
|
|
9832
|
-
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}.`);
|
|
9833
10106
|
}
|
|
9834
|
-
usedDimensions.add(
|
|
10107
|
+
usedDimensions.add(dimension2);
|
|
9835
10108
|
}
|
|
9836
10109
|
var WAVEFRONT_SAMPLE_DIMENSIONS = Object.freeze(
|
|
9837
10110
|
Object.fromEntries(sampleDimensionEntries)
|
|
9838
10111
|
);
|
|
9839
10112
|
function listWavefrontSampleDimensions() {
|
|
9840
10113
|
return sampleDimensionEntries.map(
|
|
9841
|
-
([name,
|
|
10114
|
+
([name, dimension2]) => Object.freeze({
|
|
9842
10115
|
name,
|
|
9843
|
-
dimension,
|
|
10116
|
+
dimension: dimension2,
|
|
9844
10117
|
wgslName: toWgslConstName(name)
|
|
9845
10118
|
})
|
|
9846
10119
|
);
|
|
9847
10120
|
}
|
|
9848
|
-
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");
|
|
9849
10122
|
function fract(value) {
|
|
9850
10123
|
return value - Math.floor(value);
|
|
9851
10124
|
}
|
|
@@ -9858,19 +10131,19 @@ function radicalInverseVdc2(value) {
|
|
|
9858
10131
|
bits = ((bits & 16711935) << 8 | (bits & 4278255360) >>> 8) >>> 0;
|
|
9859
10132
|
return bits / 4294967296;
|
|
9860
10133
|
}
|
|
9861
|
-
function sampleWavefrontDimension1D(pixelId, sampleId, bounce, frameIndex,
|
|
9862
|
-
return random01FromSeed(mixSeed(pixelId, sampleId, bounce, frameIndex,
|
|
10134
|
+
function sampleWavefrontDimension1D(pixelId, sampleId, bounce, frameIndex, dimension2) {
|
|
10135
|
+
return random01FromSeed(mixSeed(pixelId, sampleId, bounce, frameIndex, dimension2));
|
|
9863
10136
|
}
|
|
9864
|
-
function sampleWavefrontDimension2D(pixelId, sampleId, bounce, frameIndex,
|
|
10137
|
+
function sampleWavefrontDimension2D(pixelId, sampleId, bounce, frameIndex, dimension2, strataCount) {
|
|
9865
10138
|
const strata = Math.max(1, Number.isFinite(strataCount) ? Math.trunc(strataCount) : 1);
|
|
9866
10139
|
const jitter = sampleWavefrontDimension1D(
|
|
9867
10140
|
pixelId,
|
|
9868
10141
|
sampleId,
|
|
9869
10142
|
bounce,
|
|
9870
10143
|
frameIndex,
|
|
9871
|
-
|
|
10144
|
+
dimension2
|
|
9872
10145
|
);
|
|
9873
|
-
const scramble = hashUint32(mixSeed(pixelId, sampleId, bounce, frameIndex,
|
|
10146
|
+
const scramble = hashUint32(mixSeed(pixelId, sampleId, bounce, frameIndex, dimension2));
|
|
9874
10147
|
const stratified = fract(((sampleId >>> 0) % strata + jitter) / strata);
|
|
9875
10148
|
const lowDiscrepancy = fract(radicalInverseVdc2(sampleId >>> 0 ^ scramble) + jitter);
|
|
9876
10149
|
return Object.freeze([stratified, lowDiscrepancy]);
|
|
@@ -14349,6 +14622,7 @@ function createRayTracingRenderPlan(options = {}) {
|
|
|
14349
14622
|
supportsWebGpu,
|
|
14350
14623
|
traceWavefrontReferenceTriangles,
|
|
14351
14624
|
transitionMediumStack,
|
|
14625
|
+
validateNativeSurfaceMaterials,
|
|
14352
14626
|
wavefrontMaterialKinds,
|
|
14353
14627
|
wavefrontPathTracingComputeLimits,
|
|
14354
14628
|
wavefrontSceneObjectKinds
|