@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/gpu-renderer",
3
- "version": "0.2.54",
3
+ "version": "0.2.56",
4
4
  "description": "Framework-agnostic WebGPU renderer runtime for Plasius projects.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -84,14 +84,6 @@ function textureSample(binding, textures, label, extra = {}) {
84
84
 
85
85
  function cloneMaterialExtensions(material, textures) {
86
86
  const extensions = { ...(material.extensions ?? {}) };
87
- if (material.workflow === "unlit") {
88
- const unlitKey = "KHR_materials_unlit";
89
- const unlitValue = extensions[unlitKey] ?? extensions.unlit ?? {};
90
- if (typeof unlitValue !== "object" || unlitValue === null || Array.isArray(unlitValue)) {
91
- throw new TypeError(`Canonical material extension ${unlitKey} must be an object.`);
92
- }
93
- extensions[unlitKey] = { ...unlitValue, enabled: true };
94
- }
95
87
  for (const [bindingName, extensionName, textureKey] of EXTENSION_TEXTURE_BINDINGS) {
96
88
  const binding = material.textures?.[bindingName];
97
89
  if (!binding) {
@@ -118,12 +110,18 @@ function mapMaterial(material, materialRefId, textures) {
118
110
  if (material.alphaMode === "mask") {
119
111
  throw new Error("Canonical alphaMode mask is unsupported by WavefrontMeshInput.");
120
112
  }
121
- if (material.workflow !== "metallic-roughness" && material.workflow !== "unlit") {
113
+ if (material.workflow === "unlit"
114
+ || Object.hasOwn(material.extensions ?? {}, "KHR_materials_unlit")
115
+ || Object.hasOwn(material.extensions ?? {}, "unlit")) {
116
+ throw new Error("Canonical unlit materials are unsupported by WavefrontMeshInput; retain an unlit-capable rendering path.");
117
+ }
118
+ if (material.workflow !== "metallic-roughness") {
122
119
  throw new Error(`Unsupported canonical material workflow: ${material.workflow}.`);
123
120
  }
124
121
 
125
122
  const mapped = {
126
123
  materialRefId,
124
+ alphaMode: material.alphaMode,
127
125
  ...(material.alphaMode === "blend" ? { materialKind: "transparent" } : {}),
128
126
  baseColor: material.baseColorFactor,
129
127
  emission: material.emissiveFactor
@@ -131,7 +129,8 @@ function mapMaterial(material, materialRefId, textures) {
131
129
  : [0, 0, 0, 1],
132
130
  roughness: material.roughnessFactor,
133
131
  metallic: material.metallicFactor,
134
- opacity: material.alphaMode === "opaque" ? 1 : material.baseColorFactor?.[3],
132
+ // The authored factor remains in baseColor.a; do not multiply it twice.
133
+ opacity: 1,
135
134
  clearcoat: material.clearcoatFactor,
136
135
  clearcoatRoughness: material.clearcoatRoughnessFactor,
137
136
  transmission: material.transmissionFactor,
@@ -158,6 +157,8 @@ function mapMaterial(material, materialRefId, textures) {
158
157
  /**
159
158
  * Maps decoded canonical mesh primitives into the renderer's existing Wavefront
160
159
  * input contract. Geometry and image decoding remain upstream responsibilities.
160
+ * Referenced unlit workflows or extension markers are rejected until an explicit
161
+ * unlit shading path exists; callers must retain an unlit-capable rendering path.
161
162
  */
162
163
  export function createCanonicalWavefrontMeshInputs({ document, geometry, textures = new Map() } = {}) {
163
164
  if (!document || typeof document !== "object") {
package/src/index.d.ts CHANGED
@@ -522,6 +522,9 @@ export interface WavefrontSceneObject {
522
522
  }
523
523
 
524
524
  export interface WavefrontMeshInput {
525
+ /** Opaque ignores factor/texture alpha. Blend defaults opacity to one.
526
+ * Omitted preserves legacy factor × opacity × texture alpha semantics. */
527
+ readonly alphaMode?: "opaque" | "blend";
525
528
  readonly id?: number;
526
529
  readonly positions: readonly number[] | Float32Array;
527
530
  readonly indices?: readonly number[] | Uint16Array | Uint32Array;
@@ -749,6 +752,7 @@ export interface WavefrontGpuMeshSource {
749
752
  readonly roughness: number;
750
753
  readonly metallic: number;
751
754
  readonly opacity: number;
755
+ readonly alphaMode?: "opaque" | "blend";
752
756
  readonly ior: number;
753
757
  readonly sheen: number;
754
758
  readonly sheenTint: number;
@@ -1416,6 +1420,7 @@ export function normalizeWavefrontMesh(
1416
1420
  roughness: number;
1417
1421
  metallic: number;
1418
1422
  opacity: number;
1423
+ alphaMode?: "opaque" | "blend";
1419
1424
  ior: number;
1420
1425
  sheen: number;
1421
1426
  sheenTint: number;
@@ -121,7 +121,7 @@ function createMeshTriangleRecords(meshes, gpuMaterialSource = null) {
121
121
  clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
122
122
  clampUnit(mesh.occlusionTexture?.strength ?? 1),
123
123
  clampUnit(mesh.emissiveTexture?.strength ?? 1),
124
- 0,
124
+ mesh.alphaMode === "opaque" ? 1 : 0,
125
125
  ]),
126
126
  extensionTextures: Object.freeze(
127
127
  Object.fromEntries(
@@ -499,7 +499,7 @@ export function createWavefrontGpuMaterialSource(meshes = []) {
499
499
  clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
500
500
  clampUnit(mesh.occlusionTexture?.strength ?? 1),
501
501
  clampUnit(mesh.emissiveTexture?.strength ?? 1),
502
- 0,
502
+ mesh.alphaMode === "opaque" ? 1 : 0,
503
503
  ]);
504
504
  });
505
505
 
@@ -705,7 +705,7 @@ export function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput
705
705
  clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
706
706
  clampUnit(mesh.occlusionTexture?.strength ?? 1),
707
707
  clampUnit(mesh.emissiveTexture?.strength ?? 1),
708
- 0,
708
+ mesh.alphaMode === "opaque" ? 1 : 0,
709
709
  ]);
710
710
 
711
711
  vertexCursor += meshVertexCount;
@@ -193,6 +193,10 @@ export function createDefaultWavefrontSceneObjects() {
193
193
  }
194
194
 
195
195
  export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
196
+ const alphaMode = input.alphaMode;
197
+ if (alphaMode !== undefined && alphaMode !== "opaque" && alphaMode !== "blend") {
198
+ throw new TypeError("mesh alphaMode must be opaque or blend.");
199
+ }
196
200
  const positions = input.positions;
197
201
  const positionLength = getArrayLikeLength(positions);
198
202
  if (positionLength < 9 || positionLength % 3 !== 0) {
@@ -236,7 +240,11 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
236
240
  input.emission ?? input.emissive ?? input.material?.emission ?? input.material?.emissive,
237
241
  [0, 0, 0, 1]
238
242
  );
239
- const opacity = clamp(readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, color[3] ?? 1), 0, 1);
243
+ const opacity = alphaMode === "opaque" ? 1 : clamp(
244
+ readFiniteNumber("opacity", input.opacity ?? input.material?.opacity, alphaMode === "blend" ? 1 : color[3] ?? 1),
245
+ 0,
246
+ 1
247
+ );
240
248
  const transmission = clamp(
241
249
  readFiniteNumber("transmission", input.transmission ?? input.material?.transmission, materialExtensions.transmission),
242
250
  0,
@@ -280,6 +288,7 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
280
288
  0
281
289
  ),
282
290
  medium,
291
+ alphaMode,
283
292
  color: Object.freeze(color),
284
293
  emission: Object.freeze(emission),
285
294
  roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
@@ -67,7 +67,7 @@ fn sample_surface_material(
67
67
  let baseColorTexel = sample_atlas(baseColorAtlasTexture, triangle.baseColorAtlas, uv);
68
68
  let baseColor = vec4<f32>(
69
69
  clamp(triangle.color.rgb * srgb_to_linear_vec3(baseColorTexel.rgb), vec3<f32>(0.0), vec3<f32>(1.0)),
70
- clamp(triangle.color.a * baseColorTexel.a, 0.0, 1.0)
70
+ select(clamp(triangle.color.a * baseColorTexel.a, 0.0, 1.0), 1.0, triangle.textureSettings.w > 0.5)
71
71
  );
72
72
  let metallicRoughnessTexel = sample_atlas(
73
73
  metallicRoughnessAtlasTexture,