poppygl 0.0.33 → 0.0.34
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/README.md +6 -4
- package/dist/index.d.ts +10 -2
- package/dist/index.js +237 -103
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,10 +86,12 @@ Realistic mode supports base-color textures and scalar glTF metallic/roughness
|
|
|
86
86
|
values. Normal maps, metallic/roughness textures, and custom HDR environments
|
|
87
87
|
are not yet supported. Transparent and alpha-cutout surfaces do not cast shadows.
|
|
88
88
|
|
|
89
|
-
Use realistic mode for saved images. At 600×600 with 2× supersampling,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
Use realistic mode for saved images. At 600×600 with 2× supersampling, the RP2040
|
|
90
|
+
motor-controller benchmark takes 2.26–5.49 seconds per warm render across three
|
|
91
|
+
views, a 2.11–2.76× speedup over the previous realistic renderer on the same
|
|
92
|
+
machine. Blender comparison error increases by at most 0.47%. See
|
|
93
|
+
[benchmark results and reproduction steps](BENCHMARK.md#rp2040-motor-controller-500-sample-blender-baseline-and-2-gate).
|
|
94
|
+
Render times vary with model complexity and hardware.
|
|
93
95
|
|
|
94
96
|
### Camera and background
|
|
95
97
|
|
package/dist/index.d.ts
CHANGED
|
@@ -181,9 +181,13 @@ declare class StudioLighting {
|
|
|
181
181
|
private eye;
|
|
182
182
|
private shadowDirections;
|
|
183
183
|
private diffuseCache;
|
|
184
|
+
private reflectionSamples;
|
|
185
|
+
private normalDiffuseCache;
|
|
186
|
+
private specularCache;
|
|
187
|
+
private blendedSpecular;
|
|
184
188
|
constructor(drawCalls: DrawCall[], camera: Camera);
|
|
185
189
|
private bounceRadiance;
|
|
186
|
-
shade(position: V3, n: V3, base: V3, material: Material,
|
|
190
|
+
shade(position: V3, n: V3, base: V3, material: Material, pixelX?: number, pixelY?: number): V3;
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
interface LightSettings {
|
|
@@ -212,7 +216,11 @@ declare class SoftwareRenderer {
|
|
|
212
216
|
drawLines(mesh: DrawCall, camera: Camera, gammaOut?: boolean, options?: LineRenderOptions): void;
|
|
213
217
|
sampleTextureNearest(img: BitmapLike | null, u: number, v: number): MutableRGBA;
|
|
214
218
|
perspInterp(attrs: number[][], invWs: number[], lambdas: number[]): number[];
|
|
215
|
-
drawMesh(mesh: DrawCall, camera: Camera, light: LightSettings, material: Material, cullBackFaces?: boolean, gammaOut?: boolean, studio?: StudioLighting
|
|
219
|
+
drawMesh(mesh: DrawCall, camera: Camera, light: LightSettings, material: Material, cullBackFaces?: boolean, gammaOut?: boolean, studio?: StudioLighting, visibility?: {
|
|
220
|
+
winners: Int32Array;
|
|
221
|
+
triangleOffset: number;
|
|
222
|
+
depthOnly: boolean;
|
|
223
|
+
}): number;
|
|
216
224
|
}
|
|
217
225
|
|
|
218
226
|
/**
|
package/dist/index.js
CHANGED
|
@@ -598,14 +598,18 @@ var GeometryDenoiser = class {
|
|
|
598
598
|
this.albedo = new Float32Array(count * 3);
|
|
599
599
|
this.radiance = new Float32Array(count * 3);
|
|
600
600
|
this.distance = new Float32Array(count);
|
|
601
|
+
this.horizontal = new Float32Array(count * 3);
|
|
601
602
|
}
|
|
602
603
|
ids;
|
|
603
604
|
normals;
|
|
604
605
|
albedo;
|
|
605
606
|
radiance;
|
|
606
607
|
distance;
|
|
608
|
+
horizontal;
|
|
607
609
|
materials = /* @__PURE__ */ new WeakMap();
|
|
608
610
|
nextId = 1;
|
|
611
|
+
first = Infinity;
|
|
612
|
+
last = -1;
|
|
609
613
|
materialId(material) {
|
|
610
614
|
let id = this.materials.get(material);
|
|
611
615
|
if (!id) {
|
|
@@ -615,6 +619,8 @@ var GeometryDenoiser = class {
|
|
|
615
619
|
return id;
|
|
616
620
|
}
|
|
617
621
|
record(index, id, n, albedo, radiance, distance) {
|
|
622
|
+
if (index < this.first) this.first = index;
|
|
623
|
+
if (index > this.last) this.last = index;
|
|
618
624
|
this.ids[index] = id;
|
|
619
625
|
this.normals.set(n, index * 3);
|
|
620
626
|
this.albedo.set(albedo, index * 3);
|
|
@@ -622,40 +628,54 @@ var GeometryDenoiser = class {
|
|
|
622
628
|
this.distance[index] = distance;
|
|
623
629
|
}
|
|
624
630
|
apply(output, gamma) {
|
|
625
|
-
const kernel = [1,
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
631
|
+
const kernel = [1, 8, 28, 56, 70, 56, 28, 8, 1];
|
|
632
|
+
const radius = 4;
|
|
633
|
+
const n = this.normals, a = this.albedo, ids = this.ids;
|
|
634
|
+
const firstRow = Math.floor(this.first / this.width);
|
|
635
|
+
const lastRow = Math.floor(this.last / this.width);
|
|
636
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
637
|
+
const source = pass === 0 ? this.radiance : this.horizontal;
|
|
638
|
+
for (let y = firstRow; y <= lastRow; y++)
|
|
639
|
+
for (let x = 0; x < this.width; x++) {
|
|
640
|
+
const index = y * this.width + x, id = ids[index];
|
|
641
|
+
if (id === 0) continue;
|
|
642
|
+
const k = index * 3, distance = this.distance[index];
|
|
643
|
+
const nx = n[k], ny = n[k + 1], nz = n[k + 2];
|
|
644
|
+
const ar = a[k], ag = a[k + 1], ab = a[k + 2];
|
|
645
|
+
let r = 0, g = 0, b = 0, total = 0;
|
|
646
|
+
for (let delta = -radius; delta <= radius; delta++) {
|
|
647
|
+
const px = pass === 0 ? x + delta : x, py = pass === 0 ? y : y + delta;
|
|
635
648
|
if (px < 0 || py < 0 || px >= this.width || py >= this.height)
|
|
636
649
|
continue;
|
|
637
650
|
const neighbor = py * this.width + px, p = neighbor * 3;
|
|
638
|
-
if (
|
|
639
|
-
if (Math.abs(
|
|
651
|
+
if (ids[neighbor] !== id) continue;
|
|
652
|
+
if (Math.abs(distance - this.distance[neighbor]) > distance * 0.02)
|
|
640
653
|
continue;
|
|
641
|
-
if (
|
|
654
|
+
if (nx * n[p] + ny * n[p + 1] + nz * n[p + 2] < 0.98) continue;
|
|
655
|
+
if (Math.abs(ar - a[p]) + Math.abs(ag - a[p + 1]) + Math.abs(ab - a[p + 2]) > 0.03)
|
|
642
656
|
continue;
|
|
643
|
-
|
|
644
|
-
continue;
|
|
645
|
-
const weight = kernel[dy + 2] * kernel[dx + 2];
|
|
657
|
+
const weight = kernel[delta + radius];
|
|
646
658
|
total += weight;
|
|
647
|
-
r +=
|
|
648
|
-
g +=
|
|
649
|
-
b +=
|
|
659
|
+
r += source[p] * weight;
|
|
660
|
+
g += source[p + 1] * weight;
|
|
661
|
+
b += source[p + 2] * weight;
|
|
650
662
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
663
|
+
if (pass === 0) {
|
|
664
|
+
this.horizontal[k] = r / total;
|
|
665
|
+
this.horizontal[k + 1] = g / total;
|
|
666
|
+
this.horizontal[k + 2] = b / total;
|
|
667
|
+
} else {
|
|
668
|
+
const encode2 = (value) => Math.floor(
|
|
669
|
+
255 * (gamma ? srgbEncodeLinear01(
|
|
670
|
+
Math.max(0, Math.min(1, value / total))
|
|
671
|
+
) : Math.max(0, Math.min(1, value / total)))
|
|
672
|
+
);
|
|
673
|
+
output[index * 4] = encode2(r);
|
|
674
|
+
output[index * 4 + 1] = encode2(g);
|
|
675
|
+
output[index * 4 + 2] = encode2(b);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
659
679
|
}
|
|
660
680
|
};
|
|
661
681
|
|
|
@@ -835,9 +855,15 @@ var SoftwareRenderer = class {
|
|
|
835
855
|
}
|
|
836
856
|
return out;
|
|
837
857
|
}
|
|
838
|
-
drawMesh(mesh, camera, light, material, cullBackFaces = true, gammaOut = true, studio) {
|
|
858
|
+
drawMesh(mesh, camera, light, material, cullBackFaces = true, gammaOut = true, studio, visibility) {
|
|
839
859
|
let { positions, normals, uvs, indices, model, colors } = mesh;
|
|
840
860
|
const materialId = this.denoiser?.materialId(material) ?? 0;
|
|
861
|
+
const lightDir = light.dir ?? DEFAULT_LIGHT_DIR;
|
|
862
|
+
const ambient = clamp(light.ambient ?? DEFAULT_RENDER_OPTIONS.ambient, 0, 1);
|
|
863
|
+
const L = studio ? null : vec32.normalize(
|
|
864
|
+
vec32.create(),
|
|
865
|
+
vec32.fromValues(lightDir[0], lightDir[1], lightDir[2])
|
|
866
|
+
);
|
|
841
867
|
const view = camera.view;
|
|
842
868
|
const proj = camera.proj;
|
|
843
869
|
const mvp = mat43.create();
|
|
@@ -864,12 +890,17 @@ var SoftwareRenderer = class {
|
|
|
864
890
|
colors = clipped.colors;
|
|
865
891
|
idx = clipped.indices;
|
|
866
892
|
vertexCount = positions.length / 3;
|
|
893
|
+
const depthOnly = visibility?.depthOnly ?? false;
|
|
867
894
|
const vScreen = new Array(vertexCount);
|
|
868
895
|
const vInvW = new Float32Array(vertexCount);
|
|
869
896
|
const vNDCz = new Float32Array(vertexCount);
|
|
870
|
-
const vWorldN = new Array(
|
|
871
|
-
|
|
872
|
-
|
|
897
|
+
const vWorldN = new Array(
|
|
898
|
+
depthOnly ? 0 : vertexCount
|
|
899
|
+
);
|
|
900
|
+
const vWorldP = studio && !depthOnly ? new Array(vertexCount) : null;
|
|
901
|
+
const vColor = new Array(
|
|
902
|
+
depthOnly ? 0 : vertexCount
|
|
903
|
+
);
|
|
873
904
|
for (let i = 0; i < vertexCount; i++) {
|
|
874
905
|
const p = vec42.fromValues(
|
|
875
906
|
positions[i * 3 + 0],
|
|
@@ -892,6 +923,7 @@ var SoftwareRenderer = class {
|
|
|
892
923
|
vScreen[i] = [sx, sy];
|
|
893
924
|
vInvW[i] = invW;
|
|
894
925
|
vNDCz[i] = ndcZ;
|
|
926
|
+
if (depthOnly) continue;
|
|
895
927
|
const n = vec32.fromValues(
|
|
896
928
|
useNormals[i * 3 + 0],
|
|
897
929
|
useNormals[i * 3 + 1],
|
|
@@ -969,9 +1001,15 @@ var SoftwareRenderer = class {
|
|
|
969
1001
|
const z01 = zndc * 0.5 + 0.5;
|
|
970
1002
|
if (z01 < 0 || z01 > 1) continue;
|
|
971
1003
|
const di = y * this.width + x;
|
|
1004
|
+
if (visibility && !visibility.depthOnly && visibility.winners[di] !== visibility.triangleOffset + i / 3)
|
|
1005
|
+
continue;
|
|
972
1006
|
const depth = this.depth;
|
|
973
1007
|
if (z01 >= depth[di]) continue;
|
|
974
1008
|
depth[di] = z01;
|
|
1009
|
+
if (visibility?.depthOnly) {
|
|
1010
|
+
visibility.winners[di] = visibility.triangleOffset + i / 3;
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
975
1013
|
let baseColor = [
|
|
976
1014
|
material.baseColorFactor[0],
|
|
977
1015
|
material.baseColorFactor[1],
|
|
@@ -1006,24 +1044,7 @@ var SoftwareRenderer = class {
|
|
|
1006
1044
|
np1 / nlen,
|
|
1007
1045
|
np2 / nlen
|
|
1008
1046
|
];
|
|
1009
|
-
|
|
1010
|
-
const ambient = clamp(
|
|
1011
|
-
light.ambient ?? DEFAULT_RENDER_OPTIONS.ambient,
|
|
1012
|
-
0,
|
|
1013
|
-
1
|
|
1014
|
-
);
|
|
1015
|
-
const L = vec32.normalize(
|
|
1016
|
-
vec32.create(),
|
|
1017
|
-
vec32.fromValues(lightDir[0], lightDir[1], lightDir[2])
|
|
1018
|
-
);
|
|
1019
|
-
const ndotl = Math.max(
|
|
1020
|
-
0,
|
|
1021
|
-
nrm[0] * -L[0] + nrm[1] * -L[1] + nrm[2] * -L[2]
|
|
1022
|
-
);
|
|
1023
|
-
const lit = ambient + (1 - ambient) * ndotl;
|
|
1024
|
-
let r = baseColor[0] * lit;
|
|
1025
|
-
let g = baseColor[1] * lit;
|
|
1026
|
-
let b = baseColor[2] * lit;
|
|
1047
|
+
let r = 0, g = 0, b = 0;
|
|
1027
1048
|
if (studio && worldPositions) {
|
|
1028
1049
|
const position = this.perspInterp(worldPositions, invW, [
|
|
1029
1050
|
l0,
|
|
@@ -1035,8 +1056,18 @@ var SoftwareRenderer = class {
|
|
|
1035
1056
|
nrm,
|
|
1036
1057
|
[baseColor[0], baseColor[1], baseColor[2]],
|
|
1037
1058
|
material,
|
|
1038
|
-
|
|
1059
|
+
x,
|
|
1060
|
+
y
|
|
1061
|
+
);
|
|
1062
|
+
} else {
|
|
1063
|
+
const ndotl = Math.max(
|
|
1064
|
+
0,
|
|
1065
|
+
nrm[0] * -L[0] + nrm[1] * -L[1] + nrm[2] * -L[2]
|
|
1039
1066
|
);
|
|
1067
|
+
const lit = ambient + (1 - ambient) * ndotl;
|
|
1068
|
+
r = baseColor[0] * lit;
|
|
1069
|
+
g = baseColor[1] * lit;
|
|
1070
|
+
b = baseColor[2] * lit;
|
|
1040
1071
|
}
|
|
1041
1072
|
const emission = material.emissiveFactor ?? [0, 0, 0];
|
|
1042
1073
|
const strength = material.emissiveStrength ?? 1;
|
|
@@ -1117,6 +1148,7 @@ var SoftwareRenderer = class {
|
|
|
1117
1148
|
}
|
|
1118
1149
|
}
|
|
1119
1150
|
}
|
|
1151
|
+
return idx.length / 3;
|
|
1120
1152
|
}
|
|
1121
1153
|
};
|
|
1122
1154
|
function edge(a, b, p) {
|
|
@@ -1502,15 +1534,16 @@ var RayOcclusion = class {
|
|
|
1502
1534
|
if ((dc.mode ?? 4) !== 4 || dc.material.alphaMode === "BLEND") continue;
|
|
1503
1535
|
if (dc.material.alphaMode === "MASK") continue;
|
|
1504
1536
|
const count = dc.indices?.length ?? dc.positions.length / 3;
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1537
|
+
const worldVertices = [];
|
|
1538
|
+
for (let i = 0; i < dc.positions.length; i += 3) {
|
|
1507
1539
|
const p = vec33.transformMat4(
|
|
1508
1540
|
vec33.create(),
|
|
1509
1541
|
[dc.positions[i], dc.positions[i + 1], dc.positions[i + 2]],
|
|
1510
1542
|
dc.model
|
|
1511
1543
|
);
|
|
1512
|
-
|
|
1513
|
-
}
|
|
1544
|
+
worldVertices.push([p[0], p[1], p[2]]);
|
|
1545
|
+
}
|
|
1546
|
+
const vertex = (index) => worldVertices[dc.indices?.[index] ?? index];
|
|
1514
1547
|
for (let i = 0; i < count; i += 3) {
|
|
1515
1548
|
const p = vertex(i), q = vertex(i + 1), r = vertex(i + 2);
|
|
1516
1549
|
const min = [0, 1, 2].map(
|
|
@@ -1586,52 +1619,97 @@ var RayOcclusion = class {
|
|
|
1586
1619
|
}
|
|
1587
1620
|
}
|
|
1588
1621
|
build(triangles) {
|
|
1589
|
-
const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
|
|
1622
|
+
const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity], centerMin = [Infinity, Infinity, Infinity], centerMax = [-Infinity, -Infinity, -Infinity];
|
|
1590
1623
|
for (const t of triangles)
|
|
1591
1624
|
for (let a = 0; a < 3; a++) {
|
|
1592
1625
|
min[a] = Math.min(min[a], t.min[a]);
|
|
1593
1626
|
max[a] = Math.max(max[a], t.max[a]);
|
|
1627
|
+
centerMin[a] = Math.min(centerMin[a], t.center[a]);
|
|
1628
|
+
centerMax[a] = Math.max(centerMax[a], t.center[a]);
|
|
1594
1629
|
}
|
|
1595
1630
|
if (triangles.length <= 8) return { min, max, triangles };
|
|
1631
|
+
const binCount = 16;
|
|
1596
1632
|
const area = (lo, hi) => {
|
|
1597
1633
|
const x = hi[0] - lo[0], y = hi[1] - lo[1], z = hi[2] - lo[2];
|
|
1598
1634
|
return 2 * (x * y + y * z + z * x);
|
|
1599
1635
|
};
|
|
1600
|
-
let bestCost = Infinity,
|
|
1636
|
+
let bestCost = Infinity, bestAxis = -1, bestSplit = 0, bestMin = 0, bestScale = 0;
|
|
1637
|
+
let longestAxis = 0;
|
|
1638
|
+
for (let a = 1; a < 3; a++)
|
|
1639
|
+
if (centerMax[a] - centerMin[a] > centerMax[longestAxis] - centerMin[longestAxis])
|
|
1640
|
+
longestAxis = a;
|
|
1601
1641
|
for (let axis = 0; axis < 3; axis++) {
|
|
1602
|
-
|
|
1603
|
-
const
|
|
1604
|
-
|
|
1605
|
-
|
|
1642
|
+
if (triangles.length <= 4096 && axis !== longestAxis) continue;
|
|
1643
|
+
const loCenter = centerMin[axis], hiCenter = centerMax[axis];
|
|
1644
|
+
if (loCenter === hiCenter) continue;
|
|
1645
|
+
const scale = binCount / (hiCenter - loCenter);
|
|
1646
|
+
const counts = new Uint32Array(binCount);
|
|
1647
|
+
const bounds = new Float64Array(binCount * 6);
|
|
1648
|
+
for (let i = 0; i < binCount; i++) {
|
|
1649
|
+
bounds.fill(Infinity, i * 6, i * 6 + 3);
|
|
1650
|
+
bounds.fill(-Infinity, i * 6 + 3, i * 6 + 6);
|
|
1651
|
+
}
|
|
1652
|
+
for (const t of triangles) {
|
|
1653
|
+
const bin = Math.min(
|
|
1654
|
+
binCount - 1,
|
|
1655
|
+
Math.floor((t.center[axis] - loCenter) * scale)
|
|
1656
|
+
);
|
|
1657
|
+
counts[bin]++;
|
|
1658
|
+
const offset = bin * 6;
|
|
1606
1659
|
for (let a = 0; a < 3; a++) {
|
|
1607
|
-
|
|
1608
|
-
|
|
1660
|
+
bounds[offset + a] = Math.min(bounds[offset + a], t.min[a]);
|
|
1661
|
+
bounds[offset + a + 3] = Math.max(bounds[offset + a + 3], t.max[a]);
|
|
1609
1662
|
}
|
|
1610
|
-
|
|
1663
|
+
}
|
|
1664
|
+
const suffixCost = new Float64Array(binCount);
|
|
1665
|
+
let lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity], count = 0;
|
|
1666
|
+
for (let i = binCount - 1; i >= 0; i--) {
|
|
1667
|
+
count += counts[i];
|
|
1668
|
+
if (counts[i])
|
|
1669
|
+
for (let a = 0; a < 3; a++) {
|
|
1670
|
+
lo[a] = Math.min(lo[a], bounds[i * 6 + a]);
|
|
1671
|
+
hi[a] = Math.max(hi[a], bounds[i * 6 + a + 3]);
|
|
1672
|
+
}
|
|
1673
|
+
suffixCost[i] = count ? area(lo, hi) * count : 0;
|
|
1611
1674
|
}
|
|
1612
1675
|
lo = [Infinity, Infinity, Infinity];
|
|
1613
1676
|
hi = [-Infinity, -Infinity, -Infinity];
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1677
|
+
count = 0;
|
|
1678
|
+
for (let i = 0; i < binCount - 1; i++) {
|
|
1679
|
+
count += counts[i];
|
|
1680
|
+
if (counts[i])
|
|
1681
|
+
for (let a = 0; a < 3; a++) {
|
|
1682
|
+
lo[a] = Math.min(lo[a], bounds[i * 6 + a]);
|
|
1683
|
+
hi[a] = Math.max(hi[a], bounds[i * 6 + a + 3]);
|
|
1684
|
+
}
|
|
1685
|
+
if (!count || count === triangles.length) continue;
|
|
1686
|
+
const cost = area(lo, hi) * count + suffixCost[i + 1];
|
|
1622
1687
|
if (cost < bestCost) {
|
|
1623
1688
|
bestCost = cost;
|
|
1624
|
-
|
|
1625
|
-
|
|
1689
|
+
bestAxis = axis;
|
|
1690
|
+
bestSplit = i;
|
|
1691
|
+
bestMin = loCenter;
|
|
1692
|
+
bestScale = scale;
|
|
1626
1693
|
}
|
|
1627
1694
|
}
|
|
1628
1695
|
}
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
left
|
|
1633
|
-
right
|
|
1634
|
-
}
|
|
1696
|
+
let left, right;
|
|
1697
|
+
if (bestAxis < 0) {
|
|
1698
|
+
const middle = triangles.length >> 1;
|
|
1699
|
+
left = triangles.slice(0, middle);
|
|
1700
|
+
right = triangles.slice(middle);
|
|
1701
|
+
} else {
|
|
1702
|
+
left = [];
|
|
1703
|
+
right = [];
|
|
1704
|
+
for (const t of triangles) {
|
|
1705
|
+
const bin = Math.min(
|
|
1706
|
+
binCount - 1,
|
|
1707
|
+
Math.floor((t.center[bestAxis] - bestMin) * bestScale)
|
|
1708
|
+
);
|
|
1709
|
+
(bin <= bestSplit ? left : right).push(t);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
return { min, max, left: this.build(left), right: this.build(right) };
|
|
1635
1713
|
}
|
|
1636
1714
|
prepareRay(direction) {
|
|
1637
1715
|
this.parallelAxes = 0;
|
|
@@ -1766,6 +1844,9 @@ var W = 96;
|
|
|
1766
1844
|
var H = 48;
|
|
1767
1845
|
var SAMPLES = 128;
|
|
1768
1846
|
var ROUGHNESSES = [0.04, 0.1, 0.18, 0.28, 0.4, 0.55, 0.7, 0.85, 1];
|
|
1847
|
+
var SHADOW_SAMPLES = 4;
|
|
1848
|
+
var REFLECTION_SAMPLES = 4;
|
|
1849
|
+
var DIFFUSE_SAMPLES = 2;
|
|
1769
1850
|
var lobes = studio_environment_default.lobes.map((l) => ({
|
|
1770
1851
|
...l,
|
|
1771
1852
|
direction: normalize(l.direction),
|
|
@@ -1898,6 +1979,22 @@ var StudioLighting = class {
|
|
|
1898
1979
|
eye;
|
|
1899
1980
|
shadowDirections;
|
|
1900
1981
|
diffuseCache = /* @__PURE__ */ new WeakMap();
|
|
1982
|
+
reflectionSamples = /* @__PURE__ */ new Map();
|
|
1983
|
+
normalDiffuseCache = /* @__PURE__ */ new WeakMap();
|
|
1984
|
+
specularCache = /* @__PURE__ */ new Map();
|
|
1985
|
+
blendedSpecular(rough, level, blend) {
|
|
1986
|
+
const cached = this.specularCache.get(rough);
|
|
1987
|
+
if (cached) return cached;
|
|
1988
|
+
if (this.specularCache.size >= 16) return null;
|
|
1989
|
+
const tables = lobes.map((_, j) => {
|
|
1990
|
+
const lo = this.tables.specular[level][j], hi = this.tables.specular[level + 1][j], table = new Float64Array(W * H);
|
|
1991
|
+
for (let i = 0; i < table.length; i++)
|
|
1992
|
+
table[i] = lo[i] * (1 - blend) + hi[i] * blend;
|
|
1993
|
+
return table;
|
|
1994
|
+
});
|
|
1995
|
+
this.specularCache.set(rough, tables);
|
|
1996
|
+
return tables;
|
|
1997
|
+
}
|
|
1901
1998
|
constructor(drawCalls, camera) {
|
|
1902
1999
|
this.occlusion = new RayOcclusion(drawCalls);
|
|
1903
2000
|
const world = mat46.invert(mat46.create(), camera.view);
|
|
@@ -1906,8 +2003,8 @@ var StudioLighting = class {
|
|
|
1906
2003
|
this.eye = [world[12], world[13], world[14]];
|
|
1907
2004
|
this.shadowDirections = lobes.map((l) => {
|
|
1908
2005
|
const [t, b] = basis(l.direction);
|
|
1909
|
-
return Array.from({ length:
|
|
1910
|
-
const u = (i + 0.5) /
|
|
2006
|
+
return Array.from({ length: SHADOW_SAMPLES }, (_, i) => {
|
|
2007
|
+
const u = (i + 0.5) / SHADOW_SAMPLES, cos = 1 + Math.log(1 - u) / l.sharpness, sin = Math.sqrt(Math.max(0, 1 - cos * cos)), phi = 2 * Math.PI * radicalInverse(i);
|
|
1911
2008
|
const direction = orient(
|
|
1912
2009
|
sin * Math.cos(phi),
|
|
1913
2010
|
sin * Math.sin(phi),
|
|
@@ -1965,15 +2062,16 @@ var StudioLighting = class {
|
|
|
1965
2062
|
);
|
|
1966
2063
|
const pref = [...studio_environment_default.ambient];
|
|
1967
2064
|
const reflectedLookup = tableLookup(reflected);
|
|
2065
|
+
const specularTables = this.blendedSpecular(rough, level, blend);
|
|
1968
2066
|
for (let j = 0; j < lobes.length; j++) {
|
|
1969
|
-
const value = sampleTable(this.tables.specular[level][j], reflectedLookup) * (1 - blend) + sampleTable(this.tables.specular[level + 1][j], reflectedLookup) * blend;
|
|
2067
|
+
const value = specularTables ? sampleTable(specularTables[j], reflectedLookup) : sampleTable(this.tables.specular[level][j], reflectedLookup) * (1 - blend) + sampleTable(this.tables.specular[level + 1][j], reflectedLookup) * blend;
|
|
1970
2068
|
for (let k = 0; k < 3; k++) pref[k] += value * lobes[j].color[k];
|
|
1971
2069
|
}
|
|
1972
2070
|
for (let k = 0; k < 3; k++)
|
|
1973
2071
|
radiance[k] += pref[k] * ((0.04 * (1 - metal) + base[k] * metal) * A + B);
|
|
1974
2072
|
return radiance;
|
|
1975
2073
|
}
|
|
1976
|
-
shade(position, n, base, material,
|
|
2074
|
+
shade(position, n, base, material, pixelX = 0, pixelY = 0) {
|
|
1977
2075
|
const v = normalize([
|
|
1978
2076
|
this.eye[0] - position[0],
|
|
1979
2077
|
this.eye[1] - position[1],
|
|
@@ -2005,12 +2103,20 @@ var StudioLighting = class {
|
|
|
2005
2103
|
const specColor = f0.map(
|
|
2006
2104
|
(c, j) => studio_environment_default.ambient[j] * (c * A + B)
|
|
2007
2105
|
);
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
bits = Math.imul(bits ^ bits >>> 15, 2221713035);
|
|
2011
|
-
const hash = ((bits ^ bits >>> 16) >>> 0) / 4294967296;
|
|
2106
|
+
const gradient = (0.06711056 * pixelX + 583715e-8 * pixelY) % 1;
|
|
2107
|
+
const hash = 52.9829189 * gradient % 1;
|
|
2012
2108
|
const phase = hash * 2 * Math.PI, cosPhase = Math.cos(phase), sinPhase = Math.sin(phase);
|
|
2013
|
-
|
|
2109
|
+
let normalDiffuse = this.normalDiffuseCache.get(material);
|
|
2110
|
+
if (!normalDiffuse || normalDiffuse.normal.some((value, k) => !Object.is(value, n[k]))) {
|
|
2111
|
+
const lookup = tableLookup(n);
|
|
2112
|
+
normalDiffuse = {
|
|
2113
|
+
normal: [...n],
|
|
2114
|
+
values: this.tables.diffuse.map((table) => sampleTable(table, lookup))
|
|
2115
|
+
};
|
|
2116
|
+
this.normalDiffuseCache.set(material, normalDiffuse);
|
|
2117
|
+
}
|
|
2118
|
+
const reflectedLookup = tableLookup(reflected);
|
|
2119
|
+
const specularTables = this.blendedSpecular(rough, level, blend);
|
|
2014
2120
|
for (let j = 0; j < lobes.length; j++) {
|
|
2015
2121
|
const dirs = this.shadowDirections[j];
|
|
2016
2122
|
let visible = 0, total = 0;
|
|
@@ -2025,8 +2131,8 @@ var StudioLighting = class {
|
|
|
2025
2131
|
if (weight > 0 && !this.occlusion.occluded(origin, l)) visible += weight;
|
|
2026
2132
|
}
|
|
2027
2133
|
const visibility = total > 0 ? visible / total : 1;
|
|
2028
|
-
const diffuse =
|
|
2029
|
-
const specular = (sampleTable(this.tables.specular[level][j], reflectedLookup) * (1 - blend) + sampleTable(this.tables.specular[level + 1][j], reflectedLookup) * blend) * visibility;
|
|
2134
|
+
const diffuse = normalDiffuse.values[j] * visibility;
|
|
2135
|
+
const specular = (specularTables ? sampleTable(specularTables[j], reflectedLookup) : sampleTable(this.tables.specular[level][j], reflectedLookup) * (1 - blend) + sampleTable(this.tables.specular[level + 1][j], reflectedLookup) * blend) * visibility;
|
|
2030
2136
|
for (let k = 0; k < 3; k++) {
|
|
2031
2137
|
color[k] += lobes[j].color[k] * base[k] * (1 - metal) * 0.96 * diffuse;
|
|
2032
2138
|
specColor[k] += lobes[j].color[k] * (f0[k] * A + B) * specular;
|
|
@@ -2034,8 +2140,8 @@ var StudioLighting = class {
|
|
|
2034
2140
|
}
|
|
2035
2141
|
const [t, b] = basis(n);
|
|
2036
2142
|
let blocked = 0;
|
|
2037
|
-
for (let i = 0; metal < 1 && i <
|
|
2038
|
-
const phi = i * Math.PI /
|
|
2143
|
+
for (let i = 0; metal < 1 && i < DIFFUSE_SAMPLES; i++) {
|
|
2144
|
+
const phi = i * 2 * Math.PI / DIFFUSE_SAMPLES + phase, l = orient(
|
|
2039
2145
|
0.7 * Math.cos(phi),
|
|
2040
2146
|
0.7 * Math.sin(phi),
|
|
2041
2147
|
Math.sqrt(0.51),
|
|
@@ -2048,20 +2154,26 @@ var StudioLighting = class {
|
|
|
2048
2154
|
blocked++;
|
|
2049
2155
|
const bounce2 = this.bounceRadiance(hit, l);
|
|
2050
2156
|
for (let k = 0; k < 3; k++)
|
|
2051
|
-
color[k] += bounce2[k] * base[k] * (1 - metal) * 0.96 /
|
|
2157
|
+
color[k] += bounce2[k] * base[k] * (1 - metal) * 0.96 / DIFFUSE_SAMPLES;
|
|
2052
2158
|
}
|
|
2053
2159
|
}
|
|
2054
|
-
const ao = 1 - blocked /
|
|
2160
|
+
const ao = 1 - blocked / DIFFUSE_SAMPLES;
|
|
2055
2161
|
for (let k = 0; k < 3; k++)
|
|
2056
2162
|
color[k] -= studio_environment_default.ambient[k] * (1 - ao) * base[k] * (1 - metal) * 0.96;
|
|
2057
2163
|
const [rt, rb] = basis(reflected), bounce = [0, 0, 0];
|
|
2058
2164
|
let hitWeight = 0, totalWeight = 0;
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2165
|
+
let samples = this.reflectionSamples.get(rough);
|
|
2166
|
+
if (!samples) {
|
|
2167
|
+
samples = Array.from({ length: REFLECTION_SAMPLES }, (_, i) => {
|
|
2168
|
+
const u = (i + 0.5) / REFLECTION_SAMPLES, phi = 2 * Math.PI * radicalInverse(i), a = rough * rough, nh = Math.sqrt((1 - u) / (1 + (a * a - 1) * u)), sin = Math.sqrt(1 - nh * nh);
|
|
2169
|
+
return [sin * Math.cos(phi), sin * Math.sin(phi), nh];
|
|
2170
|
+
});
|
|
2171
|
+
this.reflectionSamples.set(rough, samples);
|
|
2172
|
+
}
|
|
2173
|
+
for (const [sx, sy, nh] of samples) {
|
|
2062
2174
|
const h = orient(
|
|
2063
|
-
|
|
2064
|
-
|
|
2175
|
+
sx * cosPhase - sy * sinPhase,
|
|
2176
|
+
sx * sinPhase + sy * cosPhase,
|
|
2065
2177
|
nh,
|
|
2066
2178
|
reflected,
|
|
2067
2179
|
rt,
|
|
@@ -2170,8 +2282,29 @@ function renderDrawCalls(drawCalls, optionsInput = {}, imageFactory = createUint
|
|
|
2170
2282
|
(dc) => (dc.material.alphaMode ?? "OPAQUE") === "BLEND"
|
|
2171
2283
|
);
|
|
2172
2284
|
const studio = options.realistic ? new StudioLighting(allDrawCalls, camera) : void 0;
|
|
2173
|
-
|
|
2174
|
-
|
|
2285
|
+
let winners;
|
|
2286
|
+
const triangleOffsets = [];
|
|
2287
|
+
if (studio && opaqueDraws.length > 1 && opaqueDraws.every((dc) => dc.mode !== 1)) {
|
|
2288
|
+
winners = new Int32Array(renderWidth * renderHeight).fill(-1);
|
|
2289
|
+
let triangleOffset = 0;
|
|
2290
|
+
for (const dc of opaqueDraws) {
|
|
2291
|
+
triangleOffsets.push(triangleOffset);
|
|
2292
|
+
triangleOffset += renderer.drawMesh(
|
|
2293
|
+
dc,
|
|
2294
|
+
camera,
|
|
2295
|
+
{ dir: options.lightDir, ambient: options.ambient },
|
|
2296
|
+
dc.material,
|
|
2297
|
+
options.cull,
|
|
2298
|
+
options.gamma,
|
|
2299
|
+
studio,
|
|
2300
|
+
{ winners, triangleOffset, depthOnly: true }
|
|
2301
|
+
);
|
|
2302
|
+
}
|
|
2303
|
+
renderer.depth.fill(Infinity);
|
|
2304
|
+
}
|
|
2305
|
+
const renderGroup = (dcs, opaque = false) => {
|
|
2306
|
+
for (let i = 0; i < dcs.length; i++) {
|
|
2307
|
+
const dc = dcs[i];
|
|
2175
2308
|
if (dc.mode === 1) {
|
|
2176
2309
|
renderer.drawLines(dc, camera, options.gamma);
|
|
2177
2310
|
} else {
|
|
@@ -2182,12 +2315,13 @@ function renderDrawCalls(drawCalls, optionsInput = {}, imageFactory = createUint
|
|
|
2182
2315
|
dc.material,
|
|
2183
2316
|
options.cull,
|
|
2184
2317
|
options.gamma,
|
|
2185
|
-
studio
|
|
2318
|
+
studio,
|
|
2319
|
+
opaque && winners ? { winners, triangleOffset: triangleOffsets[i], depthOnly: false } : void 0
|
|
2186
2320
|
);
|
|
2187
2321
|
}
|
|
2188
2322
|
}
|
|
2189
2323
|
};
|
|
2190
|
-
renderGroup(opaqueDraws);
|
|
2324
|
+
renderGroup(opaqueDraws, true);
|
|
2191
2325
|
renderGroup(maskDraws);
|
|
2192
2326
|
if (infiniteGridOptions) {
|
|
2193
2327
|
const aabb = computeWorldAABB(drawCalls);
|