@triforge/shader-core 0.2.0 → 0.3.0

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.
Files changed (37) hide show
  1. package/dist/bundle.js +88 -41
  2. package/dist/core/CompileContext.d.ts +2 -0
  3. package/dist/core/CompileContext.d.ts.map +1 -1
  4. package/dist/core/CompileContext.js +33 -13
  5. package/dist/core/OutputNode.d.ts.map +1 -1
  6. package/dist/core/OutputNode.js +1 -0
  7. package/dist/core/ShaderNode.d.ts +7 -0
  8. package/dist/core/ShaderNode.d.ts.map +1 -1
  9. package/dist/core/ShaderNode.js +20 -4
  10. package/dist/core/SocketType.d.ts +1 -1
  11. package/dist/core/SocketType.js +1 -1
  12. package/dist/nodes/output/MaterialOutput.js +2 -2
  13. package/dist/nodes/process/AddShader.js +3 -3
  14. package/dist/nodes/process/DiffuseBSDF.js +1 -1
  15. package/dist/nodes/process/Emission.js +1 -1
  16. package/dist/nodes/process/GlassBSDF.js +1 -1
  17. package/dist/nodes/process/GlossyBSDF.js +1 -1
  18. package/dist/nodes/process/MixShader.js +2 -2
  19. package/dist/nodes/process/PrincipledBSDF.d.ts +7 -0
  20. package/dist/nodes/process/PrincipledBSDF.d.ts.map +1 -1
  21. package/dist/nodes/process/PrincipledBSDF.js +17 -2
  22. package/dist/nodes/process/PrincipledHair.js +1 -1
  23. package/dist/nodes/process/PrincipledVolume.js +1 -1
  24. package/dist/nodes/process/RefractionBSDF.js +1 -1
  25. package/dist/nodes/process/ShaderScript.d.ts +2 -2
  26. package/dist/nodes/process/ShaderScript.d.ts.map +1 -1
  27. package/dist/nodes/process/ShaderScript.js +7 -3
  28. package/dist/nodes/process/ShaderToRGB.js +2 -2
  29. package/dist/nodes/process/SheenBSDF.js +1 -1
  30. package/dist/nodes/process/SpecularBSDF.js +1 -1
  31. package/dist/nodes/process/SubsurfaceScattering.js +1 -1
  32. package/dist/nodes/process/ToonBSDF.js +1 -1
  33. package/dist/nodes/process/TranslucentBSDF.js +1 -1
  34. package/dist/nodes/process/VolumeAbsorption.js +1 -1
  35. package/dist/nodes/process/VolumeScatter.js +1 -1
  36. package/dist/nodes/shader/RayPortal.js +1 -1
  37. package/package.json +1 -1
package/dist/bundle.js CHANGED
@@ -84,6 +84,12 @@ var OutputSocket = class {
84
84
  };
85
85
 
86
86
  // src/core/ShaderNode.ts
87
+ function finiteOr(value, fallback) {
88
+ return Number.isFinite(value) ? value : fallback;
89
+ }
90
+ function sanitizeVec3(v) {
91
+ return [finiteOr(v[0], 0), finiteOr(v[1], 0), finiteOr(v[2], 0)];
92
+ }
87
93
  function hexToRgb(hex) {
88
94
  const h = hex.replace("#", "");
89
95
  return [
@@ -133,6 +139,15 @@ var ShaderNode = class {
133
139
  vertexInjections() {
134
140
  return [];
135
141
  }
142
+ /**
143
+ * Whether this node's contribution to the compiled material requires
144
+ * `transparent: true` on the resulting THREE.ShaderMaterial (e.g. a
145
+ * `shader`-socket alpha channel that can read below 1.0).
146
+ * Override in nodes that carry alpha semantics — see PrincipledBSDF.
147
+ */
148
+ wantsTransparency() {
149
+ return false;
150
+ }
136
151
  /**
137
152
  * Returns the named output socket.
138
153
  * Throws ShaderNodeError if the socket does not exist.
@@ -178,14 +193,14 @@ var ShaderNode = class {
178
193
  if (type === "float" && typeof literal === "number") {
179
194
  socket.uniformName = `u_${this.id}_${name}`;
180
195
  this._uniformMap.set(name, socket.uniformName);
181
- this.parameters[name] = literal;
196
+ this.parameters[name] = finiteOr(literal, 0);
182
197
  }
183
198
  if (type === "color" && !(value instanceof OutputSocket)) {
184
199
  if (typeof literal === "string" || Array.isArray(literal)) {
185
200
  socket.uniformName = `u_${this.id}_${name}`;
186
201
  this._uniformMap.set(name, socket.uniformName);
187
202
  this._colorParams.add(name);
188
- this.parameters[name] = typeof literal === "string" ? hexToRgb(literal) : literal;
203
+ this.parameters[name] = typeof literal === "string" ? hexToRgb(literal) : sanitizeVec3(literal);
189
204
  }
190
205
  }
191
206
  sockets[name] = socket;
@@ -217,7 +232,7 @@ var ShaderNode = class {
217
232
  Object.defineProperty(this.parameters, paramName, {
218
233
  get: () => uniforms[uniformName].value,
219
234
  set: (v) => {
220
- uniforms[uniformName].value = v;
235
+ uniforms[uniformName].value = sanitizeVec3(v);
221
236
  },
222
237
  enumerable: true,
223
238
  configurable: true
@@ -226,7 +241,7 @@ var ShaderNode = class {
226
241
  Object.defineProperty(this.parameters, paramName, {
227
242
  get: () => uniforms[uniformName].value,
228
243
  set: (v) => {
229
- uniforms[uniformName].value = v;
244
+ uniforms[uniformName].value = finiteOr(v, 0);
230
245
  },
231
246
  enumerable: true,
232
247
  configurable: true
@@ -289,7 +304,8 @@ var CompileContext = class _CompileContext {
289
304
  vertexShader: this.buildVertexShader(),
290
305
  fragmentShader: this.buildFragmentShader(),
291
306
  uniforms: this._uniforms,
292
- nodes: this.order
307
+ nodes: this.order,
308
+ needsTransparency: this.order.some((node) => node.wantsTransparency())
293
309
  };
294
310
  }
295
311
  /**
@@ -309,12 +325,23 @@ var CompileContext = class _CompileContext {
309
325
  resolveInput(socket) {
310
326
  if (socket.connection) {
311
327
  const varName = this.outputVar(socket.connection.node, socket.connection.name);
312
- if (socket.type === "vector" && socket.connection.type !== "vector") {
328
+ const fromType = socket.connection.type;
329
+ const toType = socket.type;
330
+ if (toType === "vector" && fromType !== "vector") {
313
331
  return `(${varName}).xy`;
314
332
  }
315
- if (socket.type === "color" && socket.connection.type === "vector") {
333
+ if (toType === "color" && fromType === "vector") {
316
334
  return `vec3(${varName}, 0.0)`;
317
335
  }
336
+ if (toType === "color" && fromType === "shader") {
337
+ return `(${varName}).rgb`;
338
+ }
339
+ if (toType === "shader" && fromType === "color") {
340
+ return `vec4(${varName}, 1.0)`;
341
+ }
342
+ if (toType === "shader" && fromType === "vector") {
343
+ return `vec4(${varName}, 0.0, 1.0)`;
344
+ }
318
345
  return varName;
319
346
  }
320
347
  if (socket.uniformName !== null) {
@@ -322,9 +349,10 @@ var CompileContext = class _CompileContext {
322
349
  if (socket.type === "color") {
323
350
  const raw = socket.defaultValue;
324
351
  const rgb = typeof raw === "string" ? _CompileContext.hexToRgbArray(raw) : Array.isArray(raw) ? raw : [1, 1, 1];
325
- this._uniforms[socket.uniformName] = { value: rgb };
352
+ this._uniforms[socket.uniformName] = { value: rgb.map((n) => Number.isFinite(n) ? n : 0) };
326
353
  } else {
327
- this._uniforms[socket.uniformName] = { value: socket.defaultValue };
354
+ const n = socket.defaultValue;
355
+ this._uniforms[socket.uniformName] = { value: Number.isFinite(n) ? n : 0 };
328
356
  }
329
357
  }
330
358
  return socket.uniformName;
@@ -429,7 +457,7 @@ var CompileContext = class _CompileContext {
429
457
  if (from === to) return true;
430
458
  if ((from === "shader" || from === "color") && (to === "shader" || to === "color")) return true;
431
459
  if ((from === "color" || from === "shader") && to === "vector") return true;
432
- if (from === "vector" && to === "color") return true;
460
+ if (from === "vector" && (to === "color" || to === "shader")) return true;
433
461
  return false;
434
462
  }
435
463
  // ── GLSL builders ─────────────────────────────────────────────────
@@ -478,14 +506,16 @@ ${callsBlock}
478
506
  // ── Literal conversion ────────────────────────────────────────────
479
507
  toLiteral(value, type) {
480
508
  switch (type) {
481
- case "float":
482
- return (typeof value === "number" ? value : 0).toFixed(4);
509
+ case "float": {
510
+ const n = typeof value === "number" && Number.isFinite(value) ? value : 0;
511
+ return n.toFixed(4);
512
+ }
483
513
  case "color":
484
514
  return typeof value === "string" ? this.hexToVec3(value) : "vec3(1.0, 1.0, 1.0)";
485
515
  case "vector":
486
516
  return "vec2(vUv)";
487
517
  case "shader":
488
- return "vec3(0.0)";
518
+ return "vec4(0.0, 0.0, 0.0, 1.0)";
489
519
  default:
490
520
  return "vec3(1.0)";
491
521
  }
@@ -544,7 +574,8 @@ var OutputNode = class extends ShaderNode {
544
574
  this.material = new ShaderMaterial({
545
575
  vertexShader: compiled.vertexShader,
546
576
  fragmentShader: compiled.fragmentShader,
547
- uniforms: compiled.uniforms
577
+ uniforms: compiled.uniforms,
578
+ transparent: compiled.needsTransparency
548
579
  });
549
580
  if (!this.material.uniforms.uSunDirection) {
550
581
  this.material.uniforms.uSunDirection = { value: new Vector3(1, 2, 1).normalize() };
@@ -568,7 +599,7 @@ var ProcessNode = class extends ShaderNode {
568
599
 
569
600
  // src/core/SocketType.ts
570
601
  var SOCKET_GLSL_TYPE = {
571
- shader: "vec3",
602
+ shader: "vec4",
572
603
  color: "vec3",
573
604
  vector: "vec2",
574
605
  float: "float"
@@ -1222,8 +1253,8 @@ var MaterialOutput = class extends OutputNode {
1222
1253
  return mat;
1223
1254
  }
1224
1255
  compileCall(ctx) {
1225
- const sv = this._inputs.surface.isConnected() ? ctx.outputVar(this._inputs.surface.connection.node, this._inputs.surface.connection.name) : "vec3(0.8)";
1226
- return `gl_FragColor = vec4(${sv}, 1.0);`;
1256
+ const sv = this._inputs.surface.isConnected() ? ctx.outputVar(this._inputs.surface.connection.node, this._inputs.surface.connection.name) : "vec4(0.8, 0.8, 0.8, 1.0)";
1257
+ return `gl_FragColor = ${sv};`;
1227
1258
  }
1228
1259
  };
1229
1260
 
@@ -3372,7 +3403,7 @@ float _st_geo(float NdotX, float r) {
3372
3403
  float k = (r + 1.0) * (r + 1.0) / 8.0;
3373
3404
  return NdotX / max(NdotX * (1.0 - k) + k, 1e-7);
3374
3405
  }
3375
- vec3 _st_principledBSDF(vec3 base, float metallic, float roughness, float ior, float alpha, vec3 N) {
3406
+ vec3 _st_principledBSDF(vec3 base, float metallic, float roughness, float ior, vec3 N) {
3376
3407
  vec3 V = normalize(cameraPosition - vPosition);
3377
3408
  vec3 L = normalize(uSunDirection);
3378
3409
  vec3 H = normalize(L + V);
@@ -3413,9 +3444,23 @@ vec3 _st_principledBSDF(vec3 base, float metallic, float roughness, float ior, f
3413
3444
  compileDefs() {
3414
3445
  return this.glslFunction ?? _PrincipledBSDF.glslFunction;
3415
3446
  }
3447
+ /**
3448
+ * Alpha has no channel of its own in the underlying vec3 lighting function —
3449
+ * it's applied as the vec4 alpha component at the call site instead (see
3450
+ * compileCall). transparent:true is needed whenever alpha can read below 1.0:
3451
+ * a non-default literal, or any live/connected value (conservatively true).
3452
+ */
3453
+ wantsTransparency() {
3454
+ const alphaSocket = this._inputs.alpha;
3455
+ if (alphaSocket.isConnected()) return true;
3456
+ const v = alphaSocket.defaultValue;
3457
+ return typeof v === "number" && v < 1;
3458
+ }
3416
3459
  compileCall(ctx) {
3417
3460
  const normal = this._inputs.normal.isConnected() ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name) : "normalize(vNormal)";
3418
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_principledBSDF(${ctx.resolveInput(this._inputs.baseColor)}, ${ctx.resolveInput(this._inputs.metallic)}, ${ctx.resolveInput(this._inputs.roughness)}, ${ctx.resolveInput(this._inputs.ior)}, ${ctx.resolveInput(this._inputs.alpha)}, ${normal});`;
3461
+ const rgb = `_st_principledBSDF(${ctx.resolveInput(this._inputs.baseColor)}, ${ctx.resolveInput(this._inputs.metallic)}, ${ctx.resolveInput(this._inputs.roughness)}, ${ctx.resolveInput(this._inputs.ior)}, ${normal})`;
3462
+ const alpha = ctx.resolveInput(this._inputs.alpha);
3463
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(${rgb}, ${alpha});`;
3419
3464
  }
3420
3465
  };
3421
3466
 
@@ -3456,7 +3501,7 @@ vec3 _st_emission(vec3 color, float strength) {
3456
3501
  compileCall(ctx) {
3457
3502
  const color = ctx.resolveInput(this._inputs.color);
3458
3503
  const strength = ctx.resolveInput(this._inputs.strength);
3459
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_emission(${color}, ${strength});`;
3504
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_emission(${color}, ${strength}), 1.0);`;
3460
3505
  }
3461
3506
  };
3462
3507
 
@@ -3497,7 +3542,7 @@ vec3 _st_volumeAbsorption(vec3 color, float density) {
3497
3542
  compileCall(ctx) {
3498
3543
  const color = ctx.resolveInput(this._inputs.color);
3499
3544
  const density = ctx.resolveInput(this._inputs.density);
3500
- return `vec3 ${ctx.outputVar(this, "Volume")} = _st_volumeAbsorption(${color}, ${density});`;
3545
+ return `vec4 ${ctx.outputVar(this, "Volume")} = vec4(_st_volumeAbsorption(${color}, ${density}), 1.0);`;
3501
3546
  }
3502
3547
  };
3503
3548
 
@@ -3544,7 +3589,7 @@ vec3 _st_volumeScatter(vec3 color, float density, float anisotropy) {
3544
3589
  const color = ctx.resolveInput(this._inputs.color);
3545
3590
  const density = ctx.resolveInput(this._inputs.density);
3546
3591
  const anisotropy = ctx.resolveInput(this._inputs.anisotropy);
3547
- return `vec3 ${ctx.outputVar(this, "Volume")} = _st_volumeScatter(${color}, ${density}, ${anisotropy});`;
3592
+ return `vec4 ${ctx.outputVar(this, "Volume")} = vec4(_st_volumeScatter(${color}, ${density}, ${anisotropy}), 1.0);`;
3548
3593
  }
3549
3594
  };
3550
3595
 
@@ -3615,7 +3660,7 @@ vec3 _st_principledVolume(
3615
3660
  const emissionStrength = ctx.resolveInput(this._inputs.emissionStrength);
3616
3661
  const blackbodyIntensity = ctx.resolveInput(this._inputs.blackbodyIntensity);
3617
3662
  const temperature = ctx.resolveInput(this._inputs.temperature);
3618
- return `vec3 ${ctx.outputVar(this, "Volume")} = _st_principledVolume(${color}, ${density}, ${anisotropy}, ${absorptionColor}, ${emissionColor}, ${emissionStrength}, ${blackbodyIntensity}, ${temperature});`;
3663
+ return `vec4 ${ctx.outputVar(this, "Volume")} = vec4(_st_principledVolume(${color}, ${density}, ${anisotropy}, ${absorptionColor}, ${emissionColor}, ${emissionStrength}, ${blackbodyIntensity}, ${temperature}), 1.0);`;
3619
3664
  }
3620
3665
  };
3621
3666
 
@@ -3637,7 +3682,7 @@ var MixShader = class _MixShader extends ProcessNode {
3637
3682
  }
3638
3683
  static {
3639
3684
  this.glslFunction = `
3640
- vec3 _st_mixShader(float fac, vec3 a, vec3 b) {
3685
+ vec4 _st_mixShader(float fac, vec4 a, vec4 b) {
3641
3686
  return mix(a, b, clamp(fac, 0.0, 1.0));
3642
3687
  }`;
3643
3688
  }
@@ -3663,7 +3708,7 @@ vec3 _st_mixShader(float fac, vec3 a, vec3 b) {
3663
3708
  const fac = ctx.resolveInput(this._inputs.fac);
3664
3709
  const shader1 = ctx.resolveInput(this._inputs.shader1);
3665
3710
  const shader2 = ctx.resolveInput(this._inputs.shader2);
3666
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_mixShader(${fac}, ${shader1}, ${shader2});`;
3711
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = _st_mixShader(${fac}, ${shader1}, ${shader2});`;
3667
3712
  }
3668
3713
  };
3669
3714
 
@@ -3680,8 +3725,8 @@ var AddShader = class _AddShader extends ProcessNode {
3680
3725
  }
3681
3726
  static {
3682
3727
  this.glslFunction = `
3683
- vec3 _st_addShader(vec3 a, vec3 b) {
3684
- return a + b;
3728
+ vec4 _st_addShader(vec4 a, vec4 b) {
3729
+ return vec4(a.rgb + b.rgb, max(a.a, b.a));
3685
3730
  }`;
3686
3731
  }
3687
3732
  constructor(inputs = {}) {
@@ -3704,7 +3749,7 @@ vec3 _st_addShader(vec3 a, vec3 b) {
3704
3749
  compileCall(ctx) {
3705
3750
  const s1 = ctx.resolveInput(this._inputs.shader1);
3706
3751
  const s2 = ctx.resolveInput(this._inputs.shader2);
3707
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_addShader(${s1}, ${s2});`;
3752
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = _st_addShader(${s1}, ${s2});`;
3708
3753
  }
3709
3754
  };
3710
3755
 
@@ -3801,7 +3846,7 @@ vec3 _st_diffuseBSDF(vec3 color, float roughness, vec3 N) {
3801
3846
  const color = ctx.resolveInput(this._inputs.color);
3802
3847
  const roughness = ctx.resolveInput(this._inputs.roughness);
3803
3848
  const normal = this._inputs.normal.isConnected() ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name) : "normalize(vNormal)";
3804
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_diffuseBSDF(${color}, ${roughness}, ${normal});`;
3849
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_diffuseBSDF(${color}, ${roughness}, ${normal}), 1.0);`;
3805
3850
  }
3806
3851
  };
3807
3852
 
@@ -3855,7 +3900,7 @@ vec3 _st_glossyBSDF(vec3 color, float roughness, vec3 N) {
3855
3900
  const color = ctx.resolveInput(this._inputs.color);
3856
3901
  const roughness = ctx.resolveInput(this._inputs.roughness);
3857
3902
  const normal = this._inputs.normal.isConnected() ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name) : "normalize(vNormal)";
3858
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_glossyBSDF(${color}, ${roughness}, ${normal});`;
3903
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_glossyBSDF(${color}, ${roughness}, ${normal}), 1.0);`;
3859
3904
  }
3860
3905
  };
3861
3906
 
@@ -3904,7 +3949,7 @@ vec3 _st_refractionBSDF(vec3 color, float roughness, float ior, vec3 N) {
3904
3949
  const roughness = ctx.resolveInput(this._inputs.roughness);
3905
3950
  const ior = ctx.resolveInput(this._inputs.ior);
3906
3951
  const normal = this._inputs.normal.isConnected() ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name) : "normalize(vNormal)";
3907
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_refractionBSDF(${color}, ${roughness}, ${ior}, ${normal});`;
3952
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_refractionBSDF(${color}, ${roughness}, ${ior}, ${normal}), 1.0);`;
3908
3953
  }
3909
3954
  };
3910
3955
 
@@ -3959,7 +4004,7 @@ vec3 _st_glassBSDF(vec3 color, float roughness, float ior, vec3 N) {
3959
4004
  const roughness = ctx.resolveInput(this._inputs.roughness);
3960
4005
  const ior = ctx.resolveInput(this._inputs.ior);
3961
4006
  const normal = this._inputs.normal.isConnected() ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name) : "normalize(vNormal)";
3962
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_glassBSDF(${color}, ${roughness}, ${ior}, ${normal});`;
4007
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_glassBSDF(${color}, ${roughness}, ${ior}, ${normal}), 1.0);`;
3963
4008
  }
3964
4009
  };
3965
4010
 
@@ -4011,7 +4056,7 @@ vec3 _st_sheenBSDF(vec3 color, float roughness, vec3 N) {
4011
4056
  const color = ctx.resolveInput(this._inputs.color);
4012
4057
  const roughness = ctx.resolveInput(this._inputs.roughness);
4013
4058
  const normal = this._inputs.normal.isConnected() ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name) : "normalize(vNormal)";
4014
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_sheenBSDF(${color}, ${roughness}, ${normal});`;
4059
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_sheenBSDF(${color}, ${roughness}, ${normal}), 1.0);`;
4015
4060
  }
4016
4061
  };
4017
4062
 
@@ -4060,7 +4105,7 @@ vec3 _st_sss(vec3 color, float scale, vec3 radius, vec3 N) {
4060
4105
  const scale = ctx.resolveInput(this._inputs.scale);
4061
4106
  const [r, g, b] = this.radius;
4062
4107
  const normal = this._inputs.normal.isConnected() ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name) : "normalize(vNormal)";
4063
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_sss(${color}, ${scale}, vec3(${r.toFixed(4)}, ${g.toFixed(4)}, ${b.toFixed(4)}), ${normal});`;
4108
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_sss(${color}, ${scale}, vec3(${r.toFixed(4)}, ${g.toFixed(4)}, ${b.toFixed(4)}), ${normal}), 1.0);`;
4064
4109
  }
4065
4110
  };
4066
4111
 
@@ -4108,7 +4153,7 @@ vec3 _st_toonBSDF(vec3 color, float size, float smoothWidth, vec3 N) {
4108
4153
  const size = ctx.resolveInput(this._inputs.size);
4109
4154
  const smoothWidth = ctx.resolveInput(this._inputs.smooth);
4110
4155
  const normal = this._inputs.normal.isConnected() ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name) : "normalize(vNormal)";
4111
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_toonBSDF(${color}, ${size}, ${smoothWidth}, ${normal});`;
4156
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_toonBSDF(${color}, ${size}, ${smoothWidth}, ${normal}), 1.0);`;
4112
4157
  }
4113
4158
  };
4114
4159
 
@@ -4165,7 +4210,7 @@ vec3 _st_specularBSDF(vec3 base, vec3 spec, float roughness, vec3 N) {
4165
4210
  const spec = ctx.resolveInput(this._inputs.specular);
4166
4211
  const roughness = ctx.resolveInput(this._inputs.roughness);
4167
4212
  const normal = this._inputs.normal.isConnected() ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name) : "normalize(vNormal)";
4168
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_specularBSDF(${base}, ${spec}, ${roughness}, ${normal});`;
4213
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_specularBSDF(${base}, ${spec}, ${roughness}, ${normal}), 1.0);`;
4169
4214
  }
4170
4215
  };
4171
4216
 
@@ -4208,7 +4253,7 @@ vec3 _st_translucentBSDF(vec3 color, vec3 N) {
4208
4253
  compileCall(ctx) {
4209
4254
  const color = ctx.resolveInput(this._inputs.color);
4210
4255
  const normal = this._inputs.normal.isConnected() ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name) : "normalize(vNormal)";
4211
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_translucentBSDF(${color}, ${normal});`;
4256
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_translucentBSDF(${color}, ${normal}), 1.0);`;
4212
4257
  }
4213
4258
  };
4214
4259
 
@@ -4340,7 +4385,7 @@ vec3 _st_principledHair(
4340
4385
  const randomRoughness = ctx.resolveInput(this._inputs.randomRoughness);
4341
4386
  const random = ctx.resolveInput(this._inputs.random);
4342
4387
  const tangent = this._inputs.tangent.isConnected() ? ctx.outputVar(this._inputs.tangent.connection.node, this._inputs.tangent.connection.name) : "normalize(vStrandTangent)";
4343
- return `vec3 ${ctx.outputVar(this, "BSDF")} = _st_principledHair(${color}, ${roughness}, ${radialRoughness}, ${coat}, ${ior}, ${offset}, ${randomColor}, ${randomRoughness}, ${random}, ${tangent});`;
4388
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(_st_principledHair(${color}, ${roughness}, ${radialRoughness}, ${coat}, ${ior}, ${offset}, ${randomColor}, ${randomRoughness}, ${random}, ${tangent}), 1.0);`;
4344
4389
  }
4345
4390
  };
4346
4391
 
@@ -4655,8 +4700,8 @@ var ShaderToRGB = class extends ProcessNode {
4655
4700
  const cv = ctx.outputVar(this, "Color");
4656
4701
  const av = ctx.outputVar(this, "Alpha");
4657
4702
  return [
4658
- `vec3 ${cv} = ${shader};`,
4659
- `float ${av} = 1.0;`
4703
+ `vec3 ${cv} = (${shader}).rgb;`,
4704
+ `float ${av} = (${shader}).a;`
4660
4705
  ].join("\n ");
4661
4706
  }
4662
4707
  };
@@ -4942,11 +4987,13 @@ var ShaderScript = class extends ProcessNode {
4942
4987
  _socketGlslType(type) {
4943
4988
  if (type === "float") return "float";
4944
4989
  if (type === "vector") return "vec2";
4990
+ if (type === "shader") return "vec4";
4945
4991
  return "vec3";
4946
4992
  }
4947
4993
  _declGlslType(type) {
4948
4994
  if (type === "float") return "float";
4949
4995
  if (type === "vector") return "vec2";
4996
+ if (type === "shader") return "vec4";
4950
4997
  return "vec3";
4951
4998
  }
4952
4999
  };
@@ -5528,7 +5575,7 @@ var RayPortal = class extends ProcessNode {
5528
5575
  const color = ctx.resolveInput(this._inputs.color);
5529
5576
  const rawBlend = this.parameters.blend;
5530
5577
  const blend = Number.isFinite(rawBlend) ? Math.min(Math.max(rawBlend, 0), 1).toFixed(4) : "1.0000";
5531
- return `vec3 ${ctx.outputVar(this, "BSDF")} = ${color} * ${blend};`;
5578
+ return `vec4 ${ctx.outputVar(this, "BSDF")} = vec4(${color} * ${blend}, 1.0);`;
5532
5579
  }
5533
5580
  };
5534
5581
  export {
@@ -8,6 +8,8 @@ export interface CompiledMaterial {
8
8
  value: unknown;
9
9
  }>;
10
10
  nodes: ShaderNode[];
11
+ /** True when any node in the graph carries alpha that can read below 1.0 (see ShaderNode.wantsTransparency). */
12
+ needsTransparency: boolean;
11
13
  }
12
14
  /**
13
15
  * Walks the node graph from an OutputNode, validates all connections,
@@ -1 +1 @@
1
- {"version":3,"file":"CompileContext.d.ts","sourceRoot":"","sources":["../../src/core/CompileContext.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAI,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAQ,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAClD,KAAK,EAAW,UAAU,EAAE,CAAA;CAC7B;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAqD;IACpE,OAAO,CAAC,QAAQ,CAAoD;IACpE,OAAO,CAAC,KAAK,CAAgD;IAC7D,OAAO,CAAC,WAAW,CAAiD;IACpE,OAAO,CAAC,IAAI,CAAiD;IAC7D,OAAO,CAAC,KAAK,CAAgD;IAC7D,OAAO,CAAC,SAAS,CAA4C;IAC7D,OAAO,CAAC,SAAS,CAAuD;IACxE,0FAA0F;IAC1F,OAAO,CAAC,iBAAiB,CAAiE;IAI1F,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,gBAAgB;IAcjD;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAKvD;;;;;OAKG;IACH,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,MAAM;IAgClD,OAAO,CAAC,KAAK;IA8Bb,OAAO,CAAC,IAAI;IAyBZ,OAAO,CAAC,QAAQ;IA0BhB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,YAAY;IAsCpB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,iBAAiB;IAuBzB,OAAO,CAAC,wBAAwB;IAMhC,OAAO,CAAC,mBAAmB;IA2B3B,OAAO,CAAC,SAAS;IAqBjB,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;IAS3D,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAQrC,OAAO,CAAC,SAAS;CAGlB"}
1
+ {"version":3,"file":"CompileContext.d.ts","sourceRoot":"","sources":["../../src/core/CompileContext.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAO,MAAM,CAAA;IACzB,cAAc,EAAK,MAAM,CAAA;IACzB,QAAQ,EAAW,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACrD,KAAK,EAAc,UAAU,EAAE,CAAA;IAC/B,gHAAgH;IAChH,iBAAiB,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAqD;IACpE,OAAO,CAAC,QAAQ,CAAoD;IACpE,OAAO,CAAC,KAAK,CAAgD;IAC7D,OAAO,CAAC,WAAW,CAAiD;IACpE,OAAO,CAAC,IAAI,CAAiD;IAC7D,OAAO,CAAC,KAAK,CAAgD;IAC7D,OAAO,CAAC,SAAS,CAA4C;IAC7D,OAAO,CAAC,SAAS,CAAuD;IACxE,0FAA0F;IAC1F,OAAO,CAAC,iBAAiB,CAAiE;IAI1F,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,gBAAgB;IAejD;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAKvD;;;;;OAKG;IACH,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,MAAM;IAgDlD,OAAO,CAAC,KAAK;IA8Bb,OAAO,CAAC,IAAI;IAyBZ,OAAO,CAAC,QAAQ;IA0BhB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,YAAY;IAsCpB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,iBAAiB;IAuBzB,OAAO,CAAC,wBAAwB;IAMhC,OAAO,CAAC,mBAAmB;IA2B3B,OAAO,CAAC,SAAS;IAwBjB,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;IAS3D,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAQrC,OAAO,CAAC,SAAS;CAGlB"}
@@ -35,6 +35,7 @@ export class CompileContext {
35
35
  fragmentShader: this.buildFragmentShader(),
36
36
  uniforms: this._uniforms,
37
37
  nodes: this.order,
38
+ needsTransparency: this.order.some(node => node.wantsTransparency()),
38
39
  };
39
40
  }
40
41
  /**
@@ -54,14 +55,28 @@ export class CompileContext {
54
55
  resolveInput(socket) {
55
56
  if (socket.connection) {
56
57
  const varName = this.outputVar(socket.connection.node, socket.connection.name);
57
- // vec3 (color/shader) → vec2 (vector) implicit conversion, e.g. Mapping.Vector → ImageTexture.vector
58
- if (socket.type === 'vector' && socket.connection.type !== 'vector') {
58
+ const fromType = socket.connection.type;
59
+ const toType = socket.type;
60
+ // vec3/vec4 (color/shader) → vec2 (vector) implicit narrowing, e.g. Mapping.Vector → ImageTexture.vector
61
+ if (toType === 'vector' && fromType !== 'vector') {
59
62
  return `(${varName}).xy`;
60
63
  }
61
- // vec2 (vector) → vec3 (color) implicit conversion, e.g. TextureCoordinate.UV → Mapping.vector
62
- if (socket.type === 'color' && socket.connection.type === 'vector') {
64
+ // vec2 (vector) → vec3 (color) implicit widening, e.g. TextureCoordinate.UV → Mapping.vector
65
+ if (toType === 'color' && fromType === 'vector') {
63
66
  return `vec3(${varName}, 0.0)`;
64
67
  }
68
+ // vec4 (shader) → vec3 (color) implicit narrowing — drop alpha, e.g. ShaderToRGB-style consumption
69
+ if (toType === 'color' && fromType === 'shader') {
70
+ return `(${varName}).rgb`;
71
+ }
72
+ // vec3 (color) → vec4 (shader) implicit widening — opaque alpha
73
+ if (toType === 'shader' && fromType === 'color') {
74
+ return `vec4(${varName}, 1.0)`;
75
+ }
76
+ // vec2 (vector) → vec4 (shader) implicit widening — opaque alpha
77
+ if (toType === 'shader' && fromType === 'vector') {
78
+ return `vec4(${varName}, 0.0, 1.0)`;
79
+ }
65
80
  return varName;
66
81
  }
67
82
  if (socket.uniformName !== null) {
@@ -71,10 +86,12 @@ export class CompileContext {
71
86
  const rgb = typeof raw === 'string'
72
87
  ? CompileContext.hexToRgbArray(raw)
73
88
  : (Array.isArray(raw) ? raw : [1, 1, 1]);
74
- this._uniforms[socket.uniformName] = { value: rgb };
89
+ // Guard against NaN/Infinity reaching the GPU uniform.
90
+ this._uniforms[socket.uniformName] = { value: rgb.map(n => Number.isFinite(n) ? n : 0) };
75
91
  }
76
92
  else {
77
- this._uniforms[socket.uniformName] = { value: socket.defaultValue };
93
+ const n = socket.defaultValue;
94
+ this._uniforms[socket.uniformName] = { value: Number.isFinite(n) ? n : 0 };
78
95
  }
79
96
  }
80
97
  return socket.uniformName;
@@ -198,15 +215,15 @@ export class CompileContext {
198
215
  typesCompatible(from, to) {
199
216
  if (from === to)
200
217
  return true;
201
- // shader and color are both vec3 — compatible
218
+ // shader (vec4) and color (vec3) freely convert — narrow via .rgb, widen via vec4(v, 1.0)
202
219
  if ((from === 'shader' || from === 'color') && (to === 'shader' || to === 'color'))
203
220
  return true;
204
- // vec3 (color/shader) implicitly narrows to vec2 (vector) via .xy, mirroring Blender's
221
+ // vec3/vec4 (color/shader) implicitly narrows to vec2 (vector) via .xy, mirroring Blender's
205
222
  // Mapping (vector output) → Image Texture Vector panning graph
206
223
  if ((from === 'color' || from === 'shader') && to === 'vector')
207
224
  return true;
208
- // vec2 (vector) implicitly widens to vec3 (color), e.g. Texture Coordinate UV → Mapping.vector
209
- if (from === 'vector' && to === 'color')
225
+ // vec2 (vector) implicitly widens to vec3 (color) or vec4 (shader)
226
+ if (from === 'vector' && (to === 'color' || to === 'shader'))
210
227
  return true;
211
228
  return false;
212
229
  }
@@ -263,8 +280,11 @@ ${callsBlock}
263
280
  // ── Literal conversion ────────────────────────────────────────────
264
281
  toLiteral(value, type) {
265
282
  switch (type) {
266
- case 'float':
267
- return (typeof value === 'number' ? value : 0).toFixed(4);
283
+ case 'float': {
284
+ // Guard against NaN/Infinity reaching the shader source as an invalid GLSL literal.
285
+ const n = typeof value === 'number' && Number.isFinite(value) ? value : 0;
286
+ return n.toFixed(4);
287
+ }
268
288
  case 'color':
269
289
  return typeof value === 'string'
270
290
  ? this.hexToVec3(value)
@@ -272,7 +292,7 @@ ${callsBlock}
272
292
  case 'vector':
273
293
  return 'vec2(vUv)';
274
294
  case 'shader':
275
- return 'vec3(0.0)';
295
+ return 'vec4(0.0, 0.0, 0.0, 1.0)';
276
296
  default:
277
297
  return 'vec3(1.0)';
278
298
  }
@@ -1 +1 @@
1
- {"version":3,"file":"OutputNode.d.ts","sourceRoot":"","sources":["../../src/core/OutputNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C,OAAO,EAAE,cAAc,EAAW,MAAM,OAAO,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD;;;;GAIG;AACH,8BAAsB,UAAW,SAAQ,UAAU;IACjD,+EAA+E;IAC/E,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAO;IAEtC,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;IAIhD,WAAW,IAAI,MAAM;IAIrB;;;;;;;;;;;;;OAaG;IACH,OAAO,IAAI,cAAc;CAyB1B"}
1
+ {"version":3,"file":"OutputNode.d.ts","sourceRoot":"","sources":["../../src/core/OutputNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C,OAAO,EAAE,cAAc,EAAW,MAAM,OAAO,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD;;;;GAIG;AACH,8BAAsB,UAAW,SAAQ,UAAU;IACjD,+EAA+E;IAC/E,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAO;IAEtC,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;IAIhD,WAAW,IAAI,MAAM;IAIrB;;;;;;;;;;;;;OAaG;IACH,OAAO,IAAI,cAAc;CA0B1B"}
@@ -39,6 +39,7 @@ export class OutputNode extends ShaderNode {
39
39
  vertexShader: compiled.vertexShader,
40
40
  fragmentShader: compiled.fragmentShader,
41
41
  uniforms: compiled.uniforms,
42
+ transparent: compiled.needsTransparency,
42
43
  });
43
44
  // Inject default scene-lighting uniforms used by PrincipledBSDF.
44
45
  // Users can override these after compile() to match their scene lights.
@@ -61,6 +61,13 @@ export declare abstract class ShaderNode {
61
61
  attrType: 'float' | 'vec2' | 'vec3';
62
62
  varyingName: string;
63
63
  }>;
64
+ /**
65
+ * Whether this node's contribution to the compiled material requires
66
+ * `transparent: true` on the resulting THREE.ShaderMaterial (e.g. a
67
+ * `shader`-socket alpha channel that can read below 1.0).
68
+ * Override in nodes that carry alpha semantics — see PrincipledBSDF.
69
+ */
70
+ wantsTransparency(): boolean;
64
71
  /**
65
72
  * Returns the named output socket.
66
73
  * Throws ShaderNodeError if the socket does not exist.
@@ -1 +1 @@
1
- {"version":3,"file":"ShaderNode.d.ts","sourceRoot":"","sources":["../../src/core/ShaderNode.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAajD,6DAA6D;AAC7D,KAAK,QAAQ,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;AAE/C,8CAA8C;AAC9C,KAAK,SAAS,GAAG,UAAU,CAAA;AAE3B,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAK,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAK,MAAM,CAAA;IAChB,IAAI,EAAM,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAA;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,8BAAsB,UAAU;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IAEnB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB;;;;;OAKG;IACH,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAEvC;;;;;;;;OAQG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAK;IAElE,2EAA2E;IAC3E,OAAO,CAAC,WAAW,CAAmC;IACtD,yDAAyD;IACzD,OAAO,CAAC,YAAY,CAAkC;gBAE1C,QAAQ,EAAE,MAAM;IAI5B,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAA;IAC/B,QAAQ,KAAK,QAAQ,IAAI,YAAY,CAAA;IAErC,QAAQ,CAAC,eAAe,IAAK,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IACjE,QAAQ,CAAC,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;IAEzD;;;;;;;;;OASG;IACH,gBAAgB,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAIzG;;;OAGG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAelC;;;OAGG;IACH,QAAQ,CAAC,WAAW,IAAI,MAAM;IAE9B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,OAAO,qBAAqB,EAAE,cAAc,GAAG,MAAM;IAE/E;;;;OAIG;IACH,MAAM,CAAC,mBAAmB,UAAQ;IAElC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,YAAY,CACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAgCvC;;;;;OAKG;IACH,SAAS,CAAC,aAAa,CACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;IAQ/B;;;;OAIG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI;CAqBpE"}
1
+ {"version":3,"file":"ShaderNode.d.ts","sourceRoot":"","sources":["../../src/core/ShaderNode.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAsBjD,6DAA6D;AAC7D,KAAK,QAAQ,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;AAE/C,8CAA8C;AAC9C,KAAK,SAAS,GAAG,UAAU,CAAA;AAE3B,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAK,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAK,MAAM,CAAA;IAChB,IAAI,EAAM,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAA;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,8BAAsB,UAAU;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IAEnB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB;;;;;OAKG;IACH,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAEvC;;;;;;;;OAQG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAK;IAElE,2EAA2E;IAC3E,OAAO,CAAC,WAAW,CAAmC;IACtD,yDAAyD;IACzD,OAAO,CAAC,YAAY,CAAkC;gBAE1C,QAAQ,EAAE,MAAM;IAI5B,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAA;IAC/B,QAAQ,KAAK,QAAQ,IAAI,YAAY,CAAA;IAErC,QAAQ,CAAC,eAAe,IAAK,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IACjE,QAAQ,CAAC,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;IAEzD;;;;;;;;;OASG;IACH,gBAAgB,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAIzG;;;;;OAKG;IACH,iBAAiB,IAAI,OAAO;IAE5B;;;OAGG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAelC;;;OAGG;IACH,QAAQ,CAAC,WAAW,IAAI,MAAM;IAE9B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,OAAO,qBAAqB,EAAE,cAAc,GAAG,MAAM;IAE/E;;;;OAIG;IACH,MAAM,CAAC,mBAAmB,UAAQ;IAElC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,YAAY,CACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAkCvC;;;;;OAKG;IACH,SAAS,CAAC,aAAa,CACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;IAQ/B;;;;OAIG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI;CAqBpE"}
@@ -2,6 +2,13 @@ import { ShaderNodeError } from './ShaderNodeError.js';
2
2
  import { ShaderConfig } from './ShaderConfig.js';
3
3
  import { InputSocket } from './InputSocket.js';
4
4
  import { OutputSocket } from './OutputSocket.js';
5
+ /** Guards against NaN/Infinity reaching a GLSL uniform (would emit invalid GLSL literals like "NaN"). */
6
+ function finiteOr(value, fallback) {
7
+ return Number.isFinite(value) ? value : fallback;
8
+ }
9
+ function sanitizeVec3(v) {
10
+ return [finiteOr(v[0], 0), finiteOr(v[1], 0), finiteOr(v[2], 0)];
11
+ }
5
12
  function hexToRgb(hex) {
6
13
  const h = hex.replace('#', '');
7
14
  return [
@@ -53,6 +60,13 @@ export class ShaderNode {
53
60
  vertexInjections() {
54
61
  return [];
55
62
  }
63
+ /**
64
+ * Whether this node's contribution to the compiled material requires
65
+ * `transparent: true` on the resulting THREE.ShaderMaterial (e.g. a
66
+ * `shader`-socket alpha channel that can read below 1.0).
67
+ * Override in nodes that carry alpha semantics — see PrincipledBSDF.
68
+ */
69
+ wantsTransparency() { return false; }
56
70
  /**
57
71
  * Returns the named output socket.
58
72
  * Throws ShaderNodeError if the socket does not exist.
@@ -92,7 +106,7 @@ export class ShaderNode {
92
106
  if (type === 'float' && typeof literal === 'number') {
93
107
  socket.uniformName = `u_${this.id}_${name}`;
94
108
  this._uniformMap.set(name, socket.uniformName);
95
- this.parameters[name] = literal;
109
+ this.parameters[name] = finiteOr(literal, 0);
96
110
  }
97
111
  // Auto-register unconnected color inputs as vec3 uniforms
98
112
  if (type === 'color' && !(value instanceof OutputSocket)) {
@@ -100,7 +114,9 @@ export class ShaderNode {
100
114
  socket.uniformName = `u_${this.id}_${name}`;
101
115
  this._uniformMap.set(name, socket.uniformName);
102
116
  this._colorParams.add(name);
103
- this.parameters[name] = typeof literal === 'string' ? hexToRgb(literal) : literal;
117
+ this.parameters[name] = typeof literal === 'string'
118
+ ? hexToRgb(literal)
119
+ : sanitizeVec3(literal);
104
120
  }
105
121
  }
106
122
  sockets[name] = socket;
@@ -131,7 +147,7 @@ export class ShaderNode {
131
147
  if (this._colorParams.has(paramName)) {
132
148
  Object.defineProperty(this.parameters, paramName, {
133
149
  get: () => uniforms[uniformName].value,
134
- set: (v) => { uniforms[uniformName].value = v; },
150
+ set: (v) => { uniforms[uniformName].value = sanitizeVec3(v); },
135
151
  enumerable: true,
136
152
  configurable: true,
137
153
  });
@@ -139,7 +155,7 @@ export class ShaderNode {
139
155
  else {
140
156
  Object.defineProperty(this.parameters, paramName, {
141
157
  get: () => uniforms[uniformName].value,
142
- set: (v) => { uniforms[uniformName].value = v; },
158
+ set: (v) => { uniforms[uniformName].value = finiteOr(v, 0); },
143
159
  enumerable: true,
144
160
  configurable: true,
145
161
  });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Socket types mirror Blender's socket colours.
3
3
  *
4
- * shader — yellow — BSDF / material output
4
+ * shader — yellow — BSDF / material output — vec4 RGB + alpha
5
5
  * color — yellow-green — vec3 RGB
6
6
  * vector — purple — vec2 / vec3 spatial
7
7
  * float — grey — single scalar
@@ -1,6 +1,6 @@
1
1
  /** Maps SocketType to its GLSL primitive. */
2
2
  export const SOCKET_GLSL_TYPE = {
3
- shader: 'vec3',
3
+ shader: 'vec4',
4
4
  color: 'vec3',
5
5
  vector: 'vec2',
6
6
  float: 'float',
@@ -112,7 +112,7 @@ export class MaterialOutput extends OutputNode {
112
112
  compileCall(ctx) {
113
113
  const sv = this._inputs.surface.isConnected()
114
114
  ? ctx.outputVar(this._inputs.surface.connection.node, this._inputs.surface.connection.name)
115
- : 'vec3(0.8)';
116
- return `gl_FragColor = vec4(${sv}, 1.0);`;
115
+ : 'vec4(0.8, 0.8, 0.8, 1.0)';
116
+ return `gl_FragColor = ${sv};`;
117
117
  }
118
118
  }
@@ -26,11 +26,11 @@ export class AddShader extends ProcessNode {
26
26
  compileCall(ctx) {
27
27
  const s1 = ctx.resolveInput(this._inputs.shader1);
28
28
  const s2 = ctx.resolveInput(this._inputs.shader2);
29
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_addShader(${s1}, ${s2});`;
29
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = _st_addShader(${s1}, ${s2});`;
30
30
  }
31
31
  }
32
32
  AddShader.instanceSpecificDef = false;
33
33
  AddShader.glslFunction = `
34
- vec3 _st_addShader(vec3 a, vec3 b) {
35
- return a + b;
34
+ vec4 _st_addShader(vec4 a, vec4 b) {
35
+ return vec4(a.rgb + b.rgb, max(a.a, b.a));
36
36
  }`;
@@ -29,7 +29,7 @@ export class DiffuseBSDF extends ProcessNode {
29
29
  const normal = this._inputs.normal.isConnected()
30
30
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
31
31
  : 'normalize(vNormal)';
32
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_diffuseBSDF(${color}, ${roughness}, ${normal});`;
32
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_diffuseBSDF(${color}, ${roughness}, ${normal}), 1.0);`;
33
33
  }
34
34
  }
35
35
  DiffuseBSDF.instanceSpecificDef = false;
@@ -26,7 +26,7 @@ export class Emission extends ProcessNode {
26
26
  compileCall(ctx) {
27
27
  const color = ctx.resolveInput(this._inputs.color);
28
28
  const strength = ctx.resolveInput(this._inputs.strength);
29
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_emission(${color}, ${strength});`;
29
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_emission(${color}, ${strength}), 1.0);`;
30
30
  }
31
31
  }
32
32
  Emission.instanceSpecificDef = false;
@@ -31,7 +31,7 @@ export class GlassBSDF extends ProcessNode {
31
31
  const normal = this._inputs.normal.isConnected()
32
32
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
33
33
  : 'normalize(vNormal)';
34
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_glassBSDF(${color}, ${roughness}, ${ior}, ${normal});`;
34
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_glassBSDF(${color}, ${roughness}, ${ior}, ${normal}), 1.0);`;
35
35
  }
36
36
  }
37
37
  GlassBSDF.instanceSpecificDef = false;
@@ -29,7 +29,7 @@ export class GlossyBSDF extends ProcessNode {
29
29
  const normal = this._inputs.normal.isConnected()
30
30
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
31
31
  : 'normalize(vNormal)';
32
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_glossyBSDF(${color}, ${roughness}, ${normal});`;
32
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_glossyBSDF(${color}, ${roughness}, ${normal}), 1.0);`;
33
33
  }
34
34
  }
35
35
  GlossyBSDF.instanceSpecificDef = false;
@@ -37,11 +37,11 @@ export class MixShader extends ProcessNode {
37
37
  const fac = ctx.resolveInput(this._inputs.fac);
38
38
  const shader1 = ctx.resolveInput(this._inputs.shader1);
39
39
  const shader2 = ctx.resolveInput(this._inputs.shader2);
40
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_mixShader(${fac}, ${shader1}, ${shader2});`;
40
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = _st_mixShader(${fac}, ${shader1}, ${shader2});`;
41
41
  }
42
42
  }
43
43
  MixShader.instanceSpecificDef = false;
44
44
  MixShader.glslFunction = `
45
- vec3 _st_mixShader(float fac, vec3 a, vec3 b) {
45
+ vec4 _st_mixShader(float fac, vec4 a, vec4 b) {
46
46
  return mix(a, b, clamp(fac, 0.0, 1.0));
47
47
  }`;
@@ -27,6 +27,13 @@ export declare class PrincipledBSDF extends ProcessNode {
27
27
  getInputSockets(): Record<string, import("../../index.js").InputSocket<unknown>>;
28
28
  getOutputSockets(): Record<string, OutputSocket>;
29
29
  compileDefs(): string;
30
+ /**
31
+ * Alpha has no channel of its own in the underlying vec3 lighting function —
32
+ * it's applied as the vec4 alpha component at the call site instead (see
33
+ * compileCall). transparent:true is needed whenever alpha can read below 1.0:
34
+ * a non-default literal, or any live/connected value (conservatively true).
35
+ */
36
+ wantsTransparency(): boolean;
30
37
  compileCall(ctx: CompileContext): string;
31
38
  }
32
39
  //# sourceMappingURL=PrincipledBSDF.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PrincipledBSDF.d.ts","sourceRoot":"","sources":["../../../src/nodes/process/PrincipledBSDF.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,YAAY,GAAG,MAAM,CAAA;IACjC,QAAQ,CAAC,EAAG,YAAY,GAAG,MAAM,CAAA;IACjC,SAAS,CAAC,EAAE,YAAY,GAAG,MAAM,CAAA;IACjC,GAAG,CAAC,EAAQ,YAAY,GAAG,MAAM,CAAA;IACjC,KAAK,CAAC,EAAM,YAAY,GAAG,MAAM,CAAA;IACjC,MAAM,CAAC,EAAK,YAAY,CAAA;CACzB;AAED;;;;;GAKG;AACH,qBAAa,cAAe,SAAQ,WAAW;IAC7C,IAAI,QAAQ,WAA8B;IAC1C,MAAM,CAAC,mBAAmB,UAAQ;IAElC,IAAI,QAAQ,IAAI,YAAY,CAE3B;IAED,MAAM,CAAC,YAAY,SAuCnB;IAEA,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2E;IACnG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmE;gBAEhF,MAAM,GAAE,oBAAyB;IAa7C,eAAe;IACf,gBAAgB;IAChB,WAAW;IAEX,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM;CAMzC"}
1
+ {"version":3,"file":"PrincipledBSDF.d.ts","sourceRoot":"","sources":["../../../src/nodes/process/PrincipledBSDF.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,YAAY,GAAG,MAAM,CAAA;IACjC,QAAQ,CAAC,EAAG,YAAY,GAAG,MAAM,CAAA;IACjC,SAAS,CAAC,EAAE,YAAY,GAAG,MAAM,CAAA;IACjC,GAAG,CAAC,EAAQ,YAAY,GAAG,MAAM,CAAA;IACjC,KAAK,CAAC,EAAM,YAAY,GAAG,MAAM,CAAA;IACjC,MAAM,CAAC,EAAK,YAAY,CAAA;CACzB;AAED;;;;;GAKG;AACH,qBAAa,cAAe,SAAQ,WAAW;IAC7C,IAAI,QAAQ,WAA8B;IAC1C,MAAM,CAAC,mBAAmB,UAAQ;IAElC,IAAI,QAAQ,IAAI,YAAY,CAE3B;IAED,MAAM,CAAC,YAAY,SAuCnB;IAEA,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2E;IACnG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmE;gBAEhF,MAAM,GAAE,oBAAyB;IAa7C,eAAe;IACf,gBAAgB;IAChB,WAAW;IAEX;;;;;OAKG;IACH,iBAAiB,IAAI,OAAO;IAO5B,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM;CAQzC"}
@@ -25,11 +25,26 @@ export class PrincipledBSDF extends ProcessNode {
25
25
  getInputSockets() { return this._inputs; }
26
26
  getOutputSockets() { return this._outputs; }
27
27
  compileDefs() { return this.glslFunction ?? PrincipledBSDF.glslFunction; }
28
+ /**
29
+ * Alpha has no channel of its own in the underlying vec3 lighting function —
30
+ * it's applied as the vec4 alpha component at the call site instead (see
31
+ * compileCall). transparent:true is needed whenever alpha can read below 1.0:
32
+ * a non-default literal, or any live/connected value (conservatively true).
33
+ */
34
+ wantsTransparency() {
35
+ const alphaSocket = this._inputs.alpha;
36
+ if (alphaSocket.isConnected())
37
+ return true;
38
+ const v = alphaSocket.defaultValue;
39
+ return typeof v === 'number' && v < 1.0;
40
+ }
28
41
  compileCall(ctx) {
29
42
  const normal = this._inputs.normal.isConnected()
30
43
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
31
44
  : 'normalize(vNormal)';
32
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_principledBSDF(${ctx.resolveInput(this._inputs.baseColor)}, ${ctx.resolveInput(this._inputs.metallic)}, ${ctx.resolveInput(this._inputs.roughness)}, ${ctx.resolveInput(this._inputs.ior)}, ${ctx.resolveInput(this._inputs.alpha)}, ${normal});`;
45
+ const rgb = `_st_principledBSDF(${ctx.resolveInput(this._inputs.baseColor)}, ${ctx.resolveInput(this._inputs.metallic)}, ${ctx.resolveInput(this._inputs.roughness)}, ${ctx.resolveInput(this._inputs.ior)}, ${normal})`;
46
+ const alpha = ctx.resolveInput(this._inputs.alpha);
47
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(${rgb}, ${alpha});`;
33
48
  }
34
49
  }
35
50
  PrincipledBSDF.instanceSpecificDef = false;
@@ -54,7 +69,7 @@ float _st_geo(float NdotX, float r) {
54
69
  float k = (r + 1.0) * (r + 1.0) / 8.0;
55
70
  return NdotX / max(NdotX * (1.0 - k) + k, 1e-7);
56
71
  }
57
- vec3 _st_principledBSDF(vec3 base, float metallic, float roughness, float ior, float alpha, vec3 N) {
72
+ vec3 _st_principledBSDF(vec3 base, float metallic, float roughness, float ior, vec3 N) {
58
73
  vec3 V = normalize(cameraPosition - vPosition);
59
74
  vec3 L = normalize(uSunDirection);
60
75
  vec3 H = normalize(L + V);
@@ -59,7 +59,7 @@ export class PrincipledHair extends ProcessNode {
59
59
  const tangent = this._inputs.tangent.isConnected()
60
60
  ? ctx.outputVar(this._inputs.tangent.connection.node, this._inputs.tangent.connection.name)
61
61
  : 'normalize(vStrandTangent)'; // fallback: read attribute directly if not connected
62
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_principledHair(${color}, ${roughness}, ${radialRoughness}, ${coat}, ${ior}, ${offset}, ${randomColor}, ${randomRoughness}, ${random}, ${tangent});`;
62
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_principledHair(${color}, ${roughness}, ${radialRoughness}, ${coat}, ${ior}, ${offset}, ${randomColor}, ${randomRoughness}, ${random}, ${tangent}), 1.0);`;
63
63
  }
64
64
  }
65
65
  PrincipledHair.instanceSpecificDef = false;
@@ -39,7 +39,7 @@ export class PrincipledVolume extends ProcessNode {
39
39
  const emissionStrength = ctx.resolveInput(this._inputs.emissionStrength);
40
40
  const blackbodyIntensity = ctx.resolveInput(this._inputs.blackbodyIntensity);
41
41
  const temperature = ctx.resolveInput(this._inputs.temperature);
42
- return `vec3 ${ctx.outputVar(this, 'Volume')} = _st_principledVolume(${color}, ${density}, ${anisotropy}, ${absorptionColor}, ${emissionColor}, ${emissionStrength}, ${blackbodyIntensity}, ${temperature});`;
42
+ return `vec4 ${ctx.outputVar(this, 'Volume')} = vec4(_st_principledVolume(${color}, ${density}, ${anisotropy}, ${absorptionColor}, ${emissionColor}, ${emissionStrength}, ${blackbodyIntensity}, ${temperature}), 1.0);`;
43
43
  }
44
44
  }
45
45
  PrincipledVolume.instanceSpecificDef = false;
@@ -32,7 +32,7 @@ export class RefractionBSDF extends ProcessNode {
32
32
  const normal = this._inputs.normal.isConnected()
33
33
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
34
34
  : 'normalize(vNormal)';
35
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_refractionBSDF(${color}, ${roughness}, ${ior}, ${normal});`;
35
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_refractionBSDF(${color}, ${roughness}, ${ior}, ${normal}), 1.0);`;
36
36
  }
37
37
  }
38
38
  RefractionBSDF.instanceSpecificDef = false;
@@ -7,7 +7,7 @@ import type { OutputSocket } from '../../core/OutputSocket.js';
7
7
  * 'float' → float (uniform or literal)
8
8
  * 'color' → vec3 (uniform or literal hex / [r,g,b])
9
9
  * 'vector' → vec2 (UV coordinates)
10
- * 'shader' → vec3 (BSDF connection)
10
+ * 'shader' → vec4 (BSDF connection, rgb + alpha)
11
11
  */
12
12
  export type ScriptInputDecl = ['float' | 'color' | 'vector' | 'shader', unknown];
13
13
  /** Output socket type map: socketName → glslType */
@@ -49,7 +49,7 @@ export interface ShaderScriptConfig {
49
49
  * Inside the GLSL body:
50
50
  * • Input sockets are available as local variables with the exact name you declared.
51
51
  * • Output sockets must be assigned by name before the block ends.
52
- * • GLSL types: 'float' → float, 'color'/'shader' → vec3, 'vector' → vec2
52
+ * • GLSL types: 'float' → float, 'color' → vec3, 'shader' → vec4, 'vector' → vec2
53
53
  *
54
54
  * @example
55
55
  * const ripple = new ShaderScript({
@@ -1 +1 @@
1
- {"version":3,"file":"ShaderScript.d.ts","sourceRoot":"","sources":["../../../src/nodes/process/ShaderScript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAK9D;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,EAAE,OAAO,CAAC,CAAA;AAEhF,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAA;AAEtF,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAG,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,YAAY,CAAC,CAAA;IAExD,2CAA2C;IAC3C,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAE1B;;;;;;;;;OASG;IACH,IAAI,EAAE,MAAM,CAAA;IAEZ;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,YAAa,SAAQ,WAAW;IAC3C,IAAI,QAAQ,WAA4B;IACxC,MAAM,CAAC,mBAAmB,UAAO;IAEjC,IAAI,QAAQ,IAAI,YAAY,CAQ3B;IAED,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+E;IACvG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;gBAE/C,MAAM,EAAE,kBAAkB;IAmCtC,eAAe;IACf,gBAAgB;IAEhB,mDAAmD;IACnD,WAAW,IAAI,MAAM;IAErB;;;;;;OAMG;IACH,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM;IAkCxC,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,aAAa;CAKtB"}
1
+ {"version":3,"file":"ShaderScript.d.ts","sourceRoot":"","sources":["../../../src/nodes/process/ShaderScript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAK9D;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,EAAE,OAAO,CAAC,CAAA;AAEhF,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAA;AAEtF,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAG,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,YAAY,CAAC,CAAA;IAExD,2CAA2C;IAC3C,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAE1B;;;;;;;;;OASG;IACH,IAAI,EAAE,MAAM,CAAA;IAEZ;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,YAAa,SAAQ,WAAW;IAC3C,IAAI,QAAQ,WAA4B;IACxC,MAAM,CAAC,mBAAmB,UAAO;IAEjC,IAAI,QAAQ,IAAI,YAAY,CAQ3B;IAED,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+E;IACvG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;gBAE/C,MAAM,EAAE,kBAAkB;IAmCtC,eAAe;IACf,gBAAgB;IAEhB,mDAAmD;IACnD,WAAW,IAAI,MAAM;IAErB;;;;;;OAMG;IACH,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM;IAkCxC,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,aAAa;CAMtB"}
@@ -10,7 +10,7 @@ import { assertGlslIdentifier } from '../../core/glslIdentifier.js';
10
10
  * Inside the GLSL body:
11
11
  * • Input sockets are available as local variables with the exact name you declared.
12
12
  * • Output sockets must be assigned by name before the block ends.
13
- * • GLSL types: 'float' → float, 'color'/'shader' → vec3, 'vector' → vec2
13
+ * • GLSL types: 'float' → float, 'color' → vec3, 'shader' → vec4, 'vector' → vec2
14
14
  *
15
15
  * @example
16
16
  * const ripple = new ShaderScript({
@@ -111,14 +111,18 @@ export class ShaderScript extends ProcessNode {
111
111
  return 'float';
112
112
  if (type === 'vector')
113
113
  return 'vec2';
114
- return 'vec3'; // 'color' | 'shader'
114
+ if (type === 'shader')
115
+ return 'vec4';
116
+ return 'vec3'; // 'color'
115
117
  }
116
118
  _declGlslType(type) {
117
119
  if (type === 'float')
118
120
  return 'float';
119
121
  if (type === 'vector')
120
122
  return 'vec2';
121
- return 'vec3'; // 'color' | 'shader'
123
+ if (type === 'shader')
124
+ return 'vec4';
125
+ return 'vec3'; // 'color'
122
126
  }
123
127
  }
124
128
  ShaderScript.instanceSpecificDef = true; // every script is unique — no dedup
@@ -25,8 +25,8 @@ export class ShaderToRGB extends ProcessNode {
25
25
  const cv = ctx.outputVar(this, 'Color');
26
26
  const av = ctx.outputVar(this, 'Alpha');
27
27
  return [
28
- `vec3 ${cv} = ${shader};`,
29
- `float ${av} = 1.0;`,
28
+ `vec3 ${cv} = (${shader}).rgb;`,
29
+ `float ${av} = (${shader}).a;`,
30
30
  ].join('\n ');
31
31
  }
32
32
  }
@@ -30,7 +30,7 @@ export class SheenBSDF extends ProcessNode {
30
30
  const normal = this._inputs.normal.isConnected()
31
31
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
32
32
  : 'normalize(vNormal)';
33
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_sheenBSDF(${color}, ${roughness}, ${normal});`;
33
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_sheenBSDF(${color}, ${roughness}, ${normal}), 1.0);`;
34
34
  }
35
35
  }
36
36
  SheenBSDF.instanceSpecificDef = false;
@@ -32,7 +32,7 @@ export class SpecularBSDF extends ProcessNode {
32
32
  const normal = this._inputs.normal.isConnected()
33
33
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
34
34
  : 'normalize(vNormal)';
35
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_specularBSDF(${base}, ${spec}, ${roughness}, ${normal});`;
35
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_specularBSDF(${base}, ${spec}, ${roughness}, ${normal}), 1.0);`;
36
36
  }
37
37
  }
38
38
  SpecularBSDF.instanceSpecificDef = false;
@@ -32,7 +32,7 @@ export class SubsurfaceScattering extends ProcessNode {
32
32
  const normal = this._inputs.normal.isConnected()
33
33
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
34
34
  : 'normalize(vNormal)';
35
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_sss(${color}, ${scale}, vec3(${r.toFixed(4)}, ${g.toFixed(4)}, ${b.toFixed(4)}), ${normal});`;
35
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_sss(${color}, ${scale}, vec3(${r.toFixed(4)}, ${g.toFixed(4)}, ${b.toFixed(4)}), ${normal}), 1.0);`;
36
36
  }
37
37
  }
38
38
  SubsurfaceScattering.instanceSpecificDef = false;
@@ -31,7 +31,7 @@ export class ToonBSDF extends ProcessNode {
31
31
  const normal = this._inputs.normal.isConnected()
32
32
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
33
33
  : 'normalize(vNormal)';
34
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_toonBSDF(${color}, ${size}, ${smoothWidth}, ${normal});`;
34
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_toonBSDF(${color}, ${size}, ${smoothWidth}, ${normal}), 1.0);`;
35
35
  }
36
36
  }
37
37
  ToonBSDF.instanceSpecificDef = false;
@@ -27,7 +27,7 @@ export class TranslucentBSDF extends ProcessNode {
27
27
  const normal = this._inputs.normal.isConnected()
28
28
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
29
29
  : 'normalize(vNormal)';
30
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = _st_translucentBSDF(${color}, ${normal});`;
30
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(_st_translucentBSDF(${color}, ${normal}), 1.0);`;
31
31
  }
32
32
  }
33
33
  TranslucentBSDF.instanceSpecificDef = false;
@@ -26,7 +26,7 @@ export class VolumeAbsorption extends ProcessNode {
26
26
  compileCall(ctx) {
27
27
  const color = ctx.resolveInput(this._inputs.color);
28
28
  const density = ctx.resolveInput(this._inputs.density);
29
- return `vec3 ${ctx.outputVar(this, 'Volume')} = _st_volumeAbsorption(${color}, ${density});`;
29
+ return `vec4 ${ctx.outputVar(this, 'Volume')} = vec4(_st_volumeAbsorption(${color}, ${density}), 1.0);`;
30
30
  }
31
31
  }
32
32
  VolumeAbsorption.instanceSpecificDef = false;
@@ -28,7 +28,7 @@ export class VolumeScatter extends ProcessNode {
28
28
  const color = ctx.resolveInput(this._inputs.color);
29
29
  const density = ctx.resolveInput(this._inputs.density);
30
30
  const anisotropy = ctx.resolveInput(this._inputs.anisotropy);
31
- return `vec3 ${ctx.outputVar(this, 'Volume')} = _st_volumeScatter(${color}, ${density}, ${anisotropy});`;
31
+ return `vec4 ${ctx.outputVar(this, 'Volume')} = vec4(_st_volumeScatter(${color}, ${density}, ${anisotropy}), 1.0);`;
32
32
  }
33
33
  }
34
34
  VolumeScatter.instanceSpecificDef = false;
@@ -50,7 +50,7 @@ export class RayPortal extends ProcessNode {
50
50
  const blend = Number.isFinite(rawBlend) ? Math.min(Math.max(rawBlend, 0.0), 1.0).toFixed(4) : '1.0000';
51
51
  // Rasterizer passthrough: BSDF = color * blend
52
52
  // Portal Transform is intentionally unused in this mode.
53
- return `vec3 ${ctx.outputVar(this, 'BSDF')} = ${color} * ${blend};`;
53
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(${color} * ${blend}, 1.0);`;
54
54
  }
55
55
  }
56
56
  RayPortal.instanceSpecificDef = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triforge/shader-core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "GLSL shader library for Three.js with Blender workflow",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",