@plasius/gpu-renderer 0.2.54 → 0.2.56

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 CHANGED
@@ -23,6 +23,34 @@ All notable changes to this project will be documented in this file.
23
23
  - **Security**
24
24
  - (placeholder)
25
25
 
26
+ ## [0.2.56] - 2026-10-24
27
+
28
+ - **Added**
29
+ - (placeholder)
30
+
31
+ - **Changed**
32
+ - (placeholder)
33
+
34
+ - **Fixed**
35
+ - Reject referenced canonical unlit workflows and declared unlit extension markers until an explicit unlit shading path exists, with a bounded diagnostic that preserves the caller's previous supported rendering path.
36
+
37
+ - **Security**
38
+ - (placeholder)
39
+
40
+ ## [0.2.55] - 2026-10-24
41
+
42
+ - **Added**
43
+ - (placeholder)
44
+
45
+ - **Changed**
46
+ - (placeholder)
47
+
48
+ - **Fixed**
49
+ - Preserve canonical blend alpha exactly once and ignore factor/texture alpha for opaque materials through CPU/GPU packing and material sampling; retain legacy Wavefront alpha composition when mode is omitted.
50
+
51
+ - **Security**
52
+ - (placeholder)
53
+
26
54
  ## [0.2.54] - 2026-10-21
27
55
 
28
56
  - **Added**
@@ -1300,3 +1328,5 @@ All notable changes to this project will be documented in this file.
1300
1328
  [0.2.52]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.52
1301
1329
  [0.2.53]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.53
1302
1330
  [0.2.54]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.54
1331
+ [0.2.55]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.55
1332
+ [0.2.56]: https://github.com/Plasius-LTD/gpu-renderer/releases/tag/v0.2.56
package/README.md CHANGED
@@ -74,6 +74,13 @@ The canonical adapter maps the core 0.5 material texture slots; it currently
74
74
  accepts UV0 and identity texture transforms. Other selections/transforms fail
75
75
  explicitly so authored appearance is not silently changed. See the
76
76
  [canonical integration contract](docs/guides/integration.md#canonical-model-ingestion).
77
+ Canonical blend materials multiply authored factor alpha by texture alpha once;
78
+ opaque materials ignore both alpha channels. Direct `WavefrontMeshInput` callers
79
+ can select `alphaMode: "opaque" | "blend"`; omitting it preserves legacy opacity
80
+ composition. Alpha masks remain unsupported by the canonical adapter.
81
+ Referenced canonical unlit workflows and declared `KHR_materials_unlit`/`unlit`
82
+ extension markers are unsupported and fail explicitly. Keep those assets on an
83
+ unlit-capable rendering path; do not drop their markers or reinterpret them as lit.
77
84
  For an interactive model viewer with loading, integrity checks, camera gestures
78
85
  and quality controls, use the shared package's `model-inspection` entry.
79
86
 
package/dist/index.cjs CHANGED
@@ -1654,14 +1654,6 @@ function textureSample(binding, textures, label, extra = {}) {
1654
1654
  }
1655
1655
  function cloneMaterialExtensions(material, textures) {
1656
1656
  const extensions = { ...material.extensions ?? {} };
1657
- if (material.workflow === "unlit") {
1658
- const unlitKey = "KHR_materials_unlit";
1659
- const unlitValue = extensions[unlitKey] ?? extensions.unlit ?? {};
1660
- if (typeof unlitValue !== "object" || unlitValue === null || Array.isArray(unlitValue)) {
1661
- throw new TypeError(`Canonical material extension ${unlitKey} must be an object.`);
1662
- }
1663
- extensions[unlitKey] = { ...unlitValue, enabled: true };
1664
- }
1665
1657
  for (const [bindingName, extensionName, textureKey] of EXTENSION_TEXTURE_BINDINGS) {
1666
1658
  const binding = material.textures?.[bindingName];
1667
1659
  if (!binding) {
@@ -1686,17 +1678,22 @@ function mapMaterial(material, materialRefId, textures) {
1686
1678
  if (material.alphaMode === "mask") {
1687
1679
  throw new Error("Canonical alphaMode mask is unsupported by WavefrontMeshInput.");
1688
1680
  }
1689
- if (material.workflow !== "metallic-roughness" && material.workflow !== "unlit") {
1681
+ if (material.workflow === "unlit" || Object.hasOwn(material.extensions ?? {}, "KHR_materials_unlit") || Object.hasOwn(material.extensions ?? {}, "unlit")) {
1682
+ throw new Error("Canonical unlit materials are unsupported by WavefrontMeshInput; retain an unlit-capable rendering path.");
1683
+ }
1684
+ if (material.workflow !== "metallic-roughness") {
1690
1685
  throw new Error(`Unsupported canonical material workflow: ${material.workflow}.`);
1691
1686
  }
1692
1687
  const mapped = {
1693
1688
  materialRefId,
1689
+ alphaMode: material.alphaMode,
1694
1690
  ...material.alphaMode === "blend" ? { materialKind: "transparent" } : {},
1695
1691
  baseColor: material.baseColorFactor,
1696
1692
  emission: material.emissiveFactor ? [...material.emissiveFactor, 1] : [0, 0, 0, 1],
1697
1693
  roughness: material.roughnessFactor,
1698
1694
  metallic: material.metallicFactor,
1699
- opacity: material.alphaMode === "opaque" ? 1 : material.baseColorFactor?.[3],
1695
+ // The authored factor remains in baseColor.a; do not multiply it twice.
1696
+ opacity: 1,
1700
1697
  clearcoat: material.clearcoatFactor,
1701
1698
  clearcoatRoughness: material.clearcoatRoughnessFactor,
1702
1699
  transmission: material.transmissionFactor,
@@ -8377,7 +8374,7 @@ fn sample_surface_material(
8377
8374
  let baseColorTexel = sample_atlas(baseColorAtlasTexture, triangle.baseColorAtlas, uv);
8378
8375
  let baseColor = vec4<f32>(
8379
8376
  clamp(triangle.color.rgb * srgb_to_linear_vec3(baseColorTexel.rgb), vec3<f32>(0.0), vec3<f32>(1.0)),
8380
- clamp(triangle.color.a * baseColorTexel.a, 0.0, 1.0)
8377
+ select(clamp(triangle.color.a * baseColorTexel.a, 0.0, 1.0), 1.0, triangle.textureSettings.w > 0.5)
8381
8378
  );
8382
8379
  let metallicRoughnessTexel = sample_atlas(
8383
8380
  metallicRoughnessAtlasTexture,
@@ -9361,6 +9358,10 @@ function createDefaultWavefrontSceneObjects() {
9361
9358
  ]);
9362
9359
  }
9363
9360
  function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
9361
+ const alphaMode = input.alphaMode;
9362
+ if (alphaMode !== void 0 && alphaMode !== "opaque" && alphaMode !== "blend") {
9363
+ throw new TypeError("mesh alphaMode must be opaque or blend.");
9364
+ }
9364
9365
  const positions = input.positions;
9365
9366
  const positionLength = getArrayLikeLength(positions);
9366
9367
  if (positionLength < 9 || positionLength % 3 !== 0) {
@@ -9390,7 +9391,11 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
9390
9391
  input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
9391
9392
  [0, 0, 0, 1]
9392
9393
  );
9393
- const opacity = clamp2(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
9394
+ const opacity = alphaMode === "opaque" ? 1 : clamp2(
9395
+ readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, alphaMode === "blend" ? 1 : color[3] ?? 1),
9396
+ 0,
9397
+ 1
9398
+ );
9394
9399
  const transmission = clamp2(
9395
9400
  readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, materialExtensions.transmission),
9396
9401
  0,
@@ -9422,6 +9427,7 @@ function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
9422
9427
  0
9423
9428
  ),
9424
9429
  medium,
9430
+ alphaMode,
9425
9431
  color: Object.freeze(color),
9426
9432
  emission: Object.freeze(emission),
9427
9433
  roughness: clamp2(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
@@ -9573,7 +9579,7 @@ function createMeshTriangleRecords(meshes, gpuMaterialSource = null) {
9573
9579
  clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
9574
9580
  clampUnit(mesh.occlusionTexture?.strength ?? 1),
9575
9581
  clampUnit(mesh.emissiveTexture?.strength ?? 1),
9576
- 0
9582
+ mesh.alphaMode === "opaque" ? 1 : 0
9577
9583
  ]),
9578
9584
  extensionTextures: Object.freeze(
9579
9585
  Object.fromEntries(
@@ -9916,7 +9922,7 @@ function createWavefrontGpuMaterialSource(meshes = []) {
9916
9922
  clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
9917
9923
  clampUnit(mesh.occlusionTexture?.strength ?? 1),
9918
9924
  clampUnit(mesh.emissiveTexture?.strength ?? 1),
9919
- 0
9925
+ mesh.alphaMode === "opaque" ? 1 : 0
9920
9926
  ]);
9921
9927
  });
9922
9928
  return Object.freeze({
@@ -10101,7 +10107,7 @@ function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput = null
10101
10107
  clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
10102
10108
  clampUnit(mesh.occlusionTexture?.strength ?? 1),
10103
10109
  clampUnit(mesh.emissiveTexture?.strength ?? 1),
10104
- 0
10110
+ mesh.alphaMode === "opaque" ? 1 : 0
10105
10111
  ]);
10106
10112
  vertexCursor += meshVertexCount;
10107
10113
  indexCursor += mesh.indices.length;