@triforge/shader-core 0.1.2 → 0.2.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.
package/dist/bundle.js CHANGED
@@ -308,7 +308,14 @@ var CompileContext = class _CompileContext {
308
308
  */
309
309
  resolveInput(socket) {
310
310
  if (socket.connection) {
311
- return this.outputVar(socket.connection.node, socket.connection.name);
311
+ const varName = this.outputVar(socket.connection.node, socket.connection.name);
312
+ if (socket.type === "vector" && socket.connection.type !== "vector") {
313
+ return `(${varName}).xy`;
314
+ }
315
+ if (socket.type === "color" && socket.connection.type === "vector") {
316
+ return `vec3(${varName}, 0.0)`;
317
+ }
318
+ return varName;
312
319
  }
313
320
  if (socket.uniformName !== null) {
314
321
  if (!(socket.uniformName in this._uniforms)) {
@@ -421,6 +428,8 @@ var CompileContext = class _CompileContext {
421
428
  typesCompatible(from, to) {
422
429
  if (from === to) return true;
423
430
  if ((from === "shader" || from === "color") && (to === "shader" || to === "color")) return true;
431
+ if ((from === "color" || from === "shader") && to === "vector") return true;
432
+ if (from === "vector" && to === "color") return true;
424
433
  return false;
425
434
  }
426
435
  // ── GLSL builders ─────────────────────────────────────────────────
@@ -2033,7 +2042,7 @@ var ImageTexture = class extends ProcessNode {
2033
2042
  return `uniform sampler2D ${this.uniformName};`;
2034
2043
  }
2035
2044
  compileCall(ctx) {
2036
- const uv = this._inputs.vector.isConnected() ? ctx.outputVar(this._inputs.vector.connection.node, this._inputs.vector.connection.name) : "vUv";
2045
+ const uv = this._inputs.vector.isConnected() ? ctx.resolveInput(this._inputs.vector) : "vUv";
2037
2046
  const cv = ctx.outputVar(this, "Color");
2038
2047
  const av = ctx.outputVar(this, "Alpha");
2039
2048
  return [
@@ -2644,7 +2653,7 @@ vec3 _st_wavelength(float w) {
2644
2653
  };
2645
2654
 
2646
2655
  // src/nodes/process/NormalMap.ts
2647
- var NormalMap = class extends ProcessNode {
2656
+ var NormalMap = class _NormalMap extends ProcessNode {
2648
2657
  get nodeType() {
2649
2658
  return "NormalMap";
2650
2659
  }
@@ -2657,19 +2666,35 @@ var NormalMap = class extends ProcessNode {
2657
2666
  category: "Vector",
2658
2667
  color: "#4a3a8a",
2659
2668
  cost: "medium",
2660
- costNote: "Samples input Fac four times for gradient computation."
2669
+ costNote: "Builds a derivative-based TBN frame per fragment."
2661
2670
  };
2662
2671
  }
2663
2672
  static {
2664
2673
  this.glslFunction = `
2665
- vec3 _st_normalMap(float fac, float strength) {
2666
- return vec3(0.0, 1.0, 0.0);
2674
+ vec3 _st_normalMap(vec3 sampledColor, float strength, vec3 N, vec3 P, vec2 uv) {
2675
+ vec3 n = sampledColor * 2.0 - 1.0;
2676
+ n.xy *= strength;
2677
+
2678
+ // Derivative-based cotangent frame (Sch\xFCler) \u2014 no vertex tangent attribute needed.
2679
+ vec3 dp1 = dFdx(P);
2680
+ vec3 dp2 = dFdy(P);
2681
+ vec2 duv1 = dFdx(uv);
2682
+ vec2 duv2 = dFdy(uv);
2683
+
2684
+ vec3 dp2perp = cross(dp2, N);
2685
+ vec3 dp1perp = cross(N, dp1);
2686
+ vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
2687
+ vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
2688
+
2689
+ float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
2690
+ mat3 TBN = mat3(T * invmax, B * invmax, N);
2691
+ return normalize(TBN * n);
2667
2692
  }`;
2668
2693
  }
2669
2694
  constructor(inputs = {}) {
2670
2695
  super("NormalMap");
2671
2696
  this._inputs = this.createInputs(inputs, {
2672
- fac: ["float", 0.5],
2697
+ color: ["color", [0.5, 0.5, 1]],
2673
2698
  strength: ["float", inputs.strength ?? 1]
2674
2699
  });
2675
2700
  this._outputs = this.createOutputs({ Normal: "color" });
@@ -2681,17 +2706,13 @@ vec3 _st_normalMap(float fac, float strength) {
2681
2706
  return this._outputs;
2682
2707
  }
2683
2708
  compileDefs() {
2684
- return "";
2709
+ return _NormalMap.glslFunction;
2685
2710
  }
2686
2711
  compileCall(ctx) {
2687
- const facVar = this._inputs.fac.isConnected() ? ctx.outputVar(this._inputs.fac.connection.node, this._inputs.fac.connection.name) : ctx.resolveInput(this._inputs.fac);
2712
+ const color = ctx.resolveInput(this._inputs.color);
2688
2713
  const strength = ctx.resolveInput(this._inputs.strength);
2689
2714
  const normalVar = ctx.outputVar(this, "Normal");
2690
- return [
2691
- `float _nm_nx_${this.id} = dFdx(${facVar});`,
2692
- `float _nm_ny_${this.id} = dFdy(${facVar});`,
2693
- `vec3 ${normalVar} = normalize(vec3(-_nm_nx_${this.id} * ${strength}, 1.0, -_nm_ny_${this.id} * ${strength}));`
2694
- ].join("\n ");
2715
+ return `vec3 ${normalVar} = _st_normalMap(${color}, ${strength}, normalize(vNormal), vPosition, vUv);`;
2695
2716
  }
2696
2717
  };
2697
2718
 
@@ -2793,10 +2814,12 @@ vec3 _st_rotateZ(vec3 v, float a) {
2793
2814
  constructor(inputs = {}) {
2794
2815
  super("Mapping");
2795
2816
  this.mode = inputs.mode ?? "TEXTURE";
2796
- this.loc = inputs.location ?? [0, 0, 0];
2797
- this.rot = inputs.rotation ?? [0, 0, 0];
2798
- this.scl = inputs.scale ?? [1, 1, 1];
2799
- this._inputs = this.createInputs(inputs, { vector: ["color", null] });
2817
+ this._inputs = this.createInputs(inputs, {
2818
+ vector: ["color", null],
2819
+ location: ["color", inputs.location ?? [0, 0, 0]],
2820
+ rotation: ["color", inputs.rotation ?? [0, 0, 0]],
2821
+ scale: ["color", inputs.scale ?? [1, 1, 1]]
2822
+ });
2800
2823
  this._outputs = this.createOutputs({ Vector: "color" });
2801
2824
  }
2802
2825
  getInputSockets() {
@@ -2810,18 +2833,19 @@ vec3 _st_rotateZ(vec3 v, float a) {
2810
2833
  }
2811
2834
  compileCall(ctx) {
2812
2835
  const v = ctx.resolveInput(this._inputs.vector);
2836
+ const loc = ctx.resolveInput(this._inputs.location);
2837
+ const rot = ctx.resolveInput(this._inputs.rotation);
2838
+ const scl = ctx.resolveInput(this._inputs.scale);
2813
2839
  const out = ctx.outputVar(this, "Vector");
2814
- const toRad = (d) => (d * Math.PI / 180).toFixed(6);
2815
- const [lx, ly, lz] = this.loc;
2816
- const [rx, ry, rz] = this.rot.map(Number);
2817
- const [sx, sy, sz] = this.scl;
2840
+ const toRad = (Math.PI / 180).toFixed(6);
2818
2841
  return [
2819
2842
  `vec3 _mp_v_${this.id} = ${v};`,
2820
- `_mp_v_${this.id} = _mp_v_${this.id} - vec3(${lx.toFixed(4)}, ${ly.toFixed(4)}, ${lz.toFixed(4)});`,
2821
- `_mp_v_${this.id} = _st_rotateX(_mp_v_${this.id}, ${toRad(rx)});`,
2822
- `_mp_v_${this.id} = _st_rotateY(_mp_v_${this.id}, ${toRad(ry)});`,
2823
- `_mp_v_${this.id} = _st_rotateZ(_mp_v_${this.id}, ${toRad(rz)});`,
2824
- `_mp_v_${this.id} = _mp_v_${this.id} * vec3(${sx.toFixed(4)}, ${sy.toFixed(4)}, ${sz.toFixed(4)});`,
2843
+ `vec3 _mp_r_${this.id} = ${rot} * ${toRad};`,
2844
+ `_mp_v_${this.id} = _mp_v_${this.id} - ${loc};`,
2845
+ `_mp_v_${this.id} = _st_rotateX(_mp_v_${this.id}, _mp_r_${this.id}.x);`,
2846
+ `_mp_v_${this.id} = _st_rotateY(_mp_v_${this.id}, _mp_r_${this.id}.y);`,
2847
+ `_mp_v_${this.id} = _st_rotateZ(_mp_v_${this.id}, _mp_r_${this.id}.z);`,
2848
+ `_mp_v_${this.id} = _mp_v_${this.id} * ${scl};`,
2825
2849
  `vec3 ${out} = _mp_v_${this.id};`
2826
2850
  ].join("\n ");
2827
2851
  }
@@ -4859,6 +4883,7 @@ var ShaderScript = class extends ProcessNode {
4859
4883
  const defs = {};
4860
4884
  const supplied = {};
4861
4885
  for (const [name, spec] of Object.entries(config.inputs ?? {})) {
4886
+ assertGlslIdentifier(name, "ShaderScript input name");
4862
4887
  if (Array.isArray(spec)) {
4863
4888
  const [type, defaultVal] = spec;
4864
4889
  defs[name] = [type, defaultVal];
@@ -4868,6 +4893,9 @@ var ShaderScript = class extends ProcessNode {
4868
4893
  supplied[name] = sock;
4869
4894
  }
4870
4895
  }
4896
+ for (const name of Object.keys(this._outputDecl)) {
4897
+ assertGlslIdentifier(name, "ShaderScript output name");
4898
+ }
4871
4899
  this._inputs = this.createInputs(supplied, defs);
4872
4900
  this._outputs = this.createOutputs(this._outputDecl);
4873
4901
  }
@@ -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;IAuBlD,OAAO,CAAC,KAAK;IA8Bb,OAAO,CAAC,IAAI;IAyBZ,OAAO,CAAC,QAAQ;IA0BhB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,YAAY;IAsCpB,OAAO,CAAC,eAAe;IASvB,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,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"}
@@ -53,7 +53,16 @@ export class CompileContext {
53
53
  */
54
54
  resolveInput(socket) {
55
55
  if (socket.connection) {
56
- return this.outputVar(socket.connection.node, socket.connection.name);
56
+ 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') {
59
+ return `(${varName}).xy`;
60
+ }
61
+ // vec2 (vector) → vec3 (color) implicit conversion, e.g. TextureCoordinate.UV → Mapping.vector
62
+ if (socket.type === 'color' && socket.connection.type === 'vector') {
63
+ return `vec3(${varName}, 0.0)`;
64
+ }
65
+ return varName;
57
66
  }
58
67
  if (socket.uniformName !== null) {
59
68
  if (!(socket.uniformName in this._uniforms)) {
@@ -192,6 +201,13 @@ export class CompileContext {
192
201
  // shader and color are both vec3 — compatible
193
202
  if ((from === 'shader' || from === 'color') && (to === 'shader' || to === 'color'))
194
203
  return true;
204
+ // vec3 (color/shader) implicitly narrows to vec2 (vector) via .xy, mirroring Blender's
205
+ // Mapping (vector output) → Image Texture Vector panning graph
206
+ if ((from === 'color' || from === 'shader') && to === 'vector')
207
+ return true;
208
+ // vec2 (vector) implicitly widens to vec3 (color), e.g. Texture Coordinate UV → Mapping.vector
209
+ if (from === 'vector' && to === 'color')
210
+ return true;
195
211
  return false;
196
212
  }
197
213
  // ── GLSL builders ─────────────────────────────────────────────────
@@ -41,7 +41,7 @@ export class ImageTexture extends ProcessNode {
41
41
  }
42
42
  compileCall(ctx) {
43
43
  const uv = this._inputs.vector.isConnected()
44
- ? ctx.outputVar(this._inputs.vector.connection.node, this._inputs.vector.connection.name)
44
+ ? ctx.resolveInput(this._inputs.vector)
45
45
  : 'vUv';
46
46
  const cv = ctx.outputVar(this, 'Color');
47
47
  const av = ctx.outputVar(this, 'Alpha');
@@ -22,9 +22,6 @@ export declare class Mapping extends ProcessNode {
22
22
  static instanceSpecificDef: boolean;
23
23
  get metadata(): NodeMetadata;
24
24
  static glslFunction: string;
25
- private readonly loc;
26
- private readonly rot;
27
- private readonly scl;
28
25
  private readonly mode;
29
26
  private readonly _inputs;
30
27
  private readonly _outputs;
@@ -1 +1 @@
1
- {"version":3,"file":"Mapping.d.ts","sourceRoot":"","sources":["../../../src/nodes/process/Mapping.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,MAAM,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEnE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAM,WAAW,CAAA;IACtB,MAAM,CAAC,EAAI,YAAY,CAAA;IACvB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,KAAK,CAAC,EAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AAED;;;;;;GAMG;AACH,qBAAa,OAAQ,SAAQ,WAAW;IACtC,IAAI,QAAQ,WAAuB;IACnC,MAAM,CAAC,mBAAmB,UAAQ;IAElC,IAAI,QAAQ,IAAI,YAAY,CAE3B;IAED,MAAM,CAAC,YAAY,SAYnB;IAEA,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA0B;IAC9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA0B;IAC9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA0B;IAC9C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2E;IACnG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmE;gBAEhF,MAAM,GAAE,aAAkB;IAUtC,eAAe;IACf,gBAAgB;IAChB,WAAW;IAEX,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM;CAkBzC"}
1
+ {"version":3,"file":"Mapping.d.ts","sourceRoot":"","sources":["../../../src/nodes/process/Mapping.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,MAAM,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEnE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAM,WAAW,CAAA;IACtB,MAAM,CAAC,EAAI,YAAY,CAAA;IACvB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,KAAK,CAAC,EAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AAED;;;;;;GAMG;AACH,qBAAa,OAAQ,SAAQ,WAAW;IACtC,IAAI,QAAQ,WAAuB;IACnC,MAAM,CAAC,mBAAmB,UAAQ;IAElC,IAAI,QAAQ,IAAI,YAAY,CAE3B;IAED,MAAM,CAAC,YAAY,SAYnB;IAEA,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2E;IACnG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmE;gBAEhF,MAAM,GAAE,aAAkB;IAYtC,eAAe;IACf,gBAAgB;IAChB,WAAW;IAEX,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM;CAmBzC"}
@@ -14,10 +14,12 @@ export class Mapping extends ProcessNode {
14
14
  constructor(inputs = {}) {
15
15
  super('Mapping');
16
16
  this.mode = inputs.mode ?? 'TEXTURE';
17
- this.loc = inputs.location ?? [0, 0, 0];
18
- this.rot = inputs.rotation ?? [0, 0, 0];
19
- this.scl = inputs.scale ?? [1, 1, 1];
20
- this._inputs = this.createInputs(inputs, { vector: ['color', null] });
17
+ this._inputs = this.createInputs(inputs, {
18
+ vector: ['color', null],
19
+ location: ['color', inputs.location ?? [0, 0, 0]],
20
+ rotation: ['color', inputs.rotation ?? [0, 0, 0]],
21
+ scale: ['color', inputs.scale ?? [1, 1, 1]],
22
+ });
21
23
  this._outputs = this.createOutputs({ Vector: 'color' });
22
24
  }
23
25
  getInputSockets() { return this._inputs; }
@@ -25,33 +27,34 @@ export class Mapping extends ProcessNode {
25
27
  compileDefs() { return Mapping.glslFunction; }
26
28
  compileCall(ctx) {
27
29
  const v = ctx.resolveInput(this._inputs.vector);
30
+ const loc = ctx.resolveInput(this._inputs.location);
31
+ const rot = ctx.resolveInput(this._inputs.rotation);
32
+ const scl = ctx.resolveInput(this._inputs.scale);
28
33
  const out = ctx.outputVar(this, 'Vector');
29
- const toRad = (d) => (d * Math.PI / 180).toFixed(6);
30
- const [lx, ly, lz] = this.loc;
31
- const [rx, ry, rz] = this.rot.map(Number);
32
- const [sx, sy, sz] = this.scl;
34
+ const toRad = (Math.PI / 180).toFixed(6);
33
35
  return [
34
36
  `vec3 _mp_v_${this.id} = ${v};`,
35
- `_mp_v_${this.id} = _mp_v_${this.id} - vec3(${lx.toFixed(4)}, ${ly.toFixed(4)}, ${lz.toFixed(4)});`,
36
- `_mp_v_${this.id} = _st_rotateX(_mp_v_${this.id}, ${toRad(rx)});`,
37
- `_mp_v_${this.id} = _st_rotateY(_mp_v_${this.id}, ${toRad(ry)});`,
38
- `_mp_v_${this.id} = _st_rotateZ(_mp_v_${this.id}, ${toRad(rz)});`,
39
- `_mp_v_${this.id} = _mp_v_${this.id} * vec3(${sx.toFixed(4)}, ${sy.toFixed(4)}, ${sz.toFixed(4)});`,
37
+ `vec3 _mp_r_${this.id} = ${rot} * ${toRad};`,
38
+ `_mp_v_${this.id} = _mp_v_${this.id} - ${loc};`,
39
+ `_mp_v_${this.id} = _st_rotateX(_mp_v_${this.id}, _mp_r_${this.id}.x);`,
40
+ `_mp_v_${this.id} = _st_rotateY(_mp_v_${this.id}, _mp_r_${this.id}.y);`,
41
+ `_mp_v_${this.id} = _st_rotateZ(_mp_v_${this.id}, _mp_r_${this.id}.z);`,
42
+ `_mp_v_${this.id} = _mp_v_${this.id} * ${scl};`,
40
43
  `vec3 ${out} = _mp_v_${this.id};`,
41
44
  ].join('\n ');
42
45
  }
43
46
  }
44
47
  Mapping.instanceSpecificDef = false;
45
- Mapping.glslFunction = `
46
- vec3 _st_rotateX(vec3 v, float a) {
47
- float c = cos(a), s = sin(a);
48
- return vec3(v.x, c * v.y - s * v.z, s * v.y + c * v.z);
49
- }
50
- vec3 _st_rotateY(vec3 v, float a) {
51
- float c = cos(a), s = sin(a);
52
- return vec3(c * v.x + s * v.z, v.y, -s * v.x + c * v.z);
53
- }
54
- vec3 _st_rotateZ(vec3 v, float a) {
55
- float c = cos(a), s = sin(a);
56
- return vec3(c * v.x - s * v.y, s * v.x + c * v.y, v.z);
48
+ Mapping.glslFunction = `
49
+ vec3 _st_rotateX(vec3 v, float a) {
50
+ float c = cos(a), s = sin(a);
51
+ return vec3(v.x, c * v.y - s * v.z, s * v.y + c * v.z);
52
+ }
53
+ vec3 _st_rotateY(vec3 v, float a) {
54
+ float c = cos(a), s = sin(a);
55
+ return vec3(c * v.x + s * v.z, v.y, -s * v.x + c * v.z);
56
+ }
57
+ vec3 _st_rotateZ(vec3 v, float a) {
58
+ float c = cos(a), s = sin(a);
59
+ return vec3(c * v.x - s * v.y, s * v.x + c * v.y, v.z);
57
60
  }`;
@@ -3,20 +3,24 @@ import type { NodeMetadata } from '../../core/ShaderNode.js';
3
3
  import type { CompileContext } from '../../core/CompileContext.js';
4
4
  import type { OutputSocket } from '../../core/OutputSocket.js';
5
5
  export interface NormalMapInputs {
6
- fac?: OutputSocket;
6
+ color?: OutputSocket;
7
7
  strength?: number | OutputSocket;
8
8
  }
9
9
  /**
10
- * Normal Map — converts a float noise value into a surface normal.
11
- * Derives normals via numerical gradient of the input Fac value.
12
- * Connect to PrincipledBSDF.normal input for surface detail lighting.
10
+ * Normal Map — Blender "Normal Map" node equivalent.
11
+ * Decodes a tangent-space normal texture (RGB → vector, [0,1] → [-1,1]) and
12
+ * rotates it into world space using a derivative-based cotangent frame, so no
13
+ * explicit vertex tangent attribute is required.
14
+ *
15
+ * Connect ImageTexture.Color (sampling a non-color / linear normal map texture)
16
+ * to `color`. Connect the result to PrincipledBSDF.normal.
13
17
  *
14
18
  * Inputs:
15
- * fac (float) — greyscale height value, typically from NoiseTexture.Fac
16
- * strength (float) — normal intensity [0-10] default: 1.0
19
+ * color (color) — sampled RGB normal texture, still in [0,1] range
20
+ * strength (float) — normal intensity, default: 1.0
17
21
  *
18
22
  * Outputs:
19
- * Normal (vector) — perturbed surface normal, ready for PrincipledBSDF.normal
23
+ * Normal (color) — world-space perturbed normal, ready for PrincipledBSDF.normal
20
24
  */
21
25
  export declare class NormalMap extends ProcessNode {
22
26
  get nodeType(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"NormalMap.d.ts","sourceRoot":"","sources":["../../../src/nodes/process/NormalMap.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,eAAe;IAC9B,GAAG,CAAC,EAAO,YAAY,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAAA;CACjC;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,SAAU,SAAQ,WAAW;IACxC,IAAI,QAAQ,WAAyB;IACrC,MAAM,CAAC,mBAAmB,UAAQ;IAElC,IAAI,QAAQ,IAAI,YAAY,CAQ3B;IAED,MAAM,CAAC,YAAY,SAGnB;IAEA,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2E;IACnG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmE;gBAEhF,MAAM,GAAE,eAAoB;IASxC,eAAe;IACf,gBAAgB;IAChB,WAAW;IAEX,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM;CAezC"}
1
+ {"version":3,"file":"NormalMap.d.ts","sourceRoot":"","sources":["../../../src/nodes/process/NormalMap.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,eAAe;IAC9B,KAAK,CAAC,EAAK,YAAY,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAAA;CACjC;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,SAAU,SAAQ,WAAW;IACxC,IAAI,QAAQ,WAAyB;IACrC,MAAM,CAAC,mBAAmB,UAAQ;IAElC,IAAI,QAAQ,IAAI,YAAY,CAQ3B;IAED,MAAM,CAAC,YAAY,SAmBnB;IAEA,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2E;IACnG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmE;gBAEhF,MAAM,GAAE,eAAoB;IASxC,eAAe;IACf,gBAAgB;IAChB,WAAW;IAEX,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM;CAOzC"}
@@ -1,15 +1,19 @@
1
1
  import { ProcessNode } from '../../core/ProcessNode.js';
2
2
  /**
3
- * Normal Map — converts a float noise value into a surface normal.
4
- * Derives normals via numerical gradient of the input Fac value.
5
- * Connect to PrincipledBSDF.normal input for surface detail lighting.
3
+ * Normal Map — Blender "Normal Map" node equivalent.
4
+ * Decodes a tangent-space normal texture (RGB → vector, [0,1] → [-1,1]) and
5
+ * rotates it into world space using a derivative-based cotangent frame, so no
6
+ * explicit vertex tangent attribute is required.
7
+ *
8
+ * Connect ImageTexture.Color (sampling a non-color / linear normal map texture)
9
+ * to `color`. Connect the result to PrincipledBSDF.normal.
6
10
  *
7
11
  * Inputs:
8
- * fac (float) — greyscale height value, typically from NoiseTexture.Fac
9
- * strength (float) — normal intensity [0-10] default: 1.0
12
+ * color (color) — sampled RGB normal texture, still in [0,1] range
13
+ * strength (float) — normal intensity, default: 1.0
10
14
  *
11
15
  * Outputs:
12
- * Normal (vector) — perturbed surface normal, ready for PrincipledBSDF.normal
16
+ * Normal (color) — world-space perturbed normal, ready for PrincipledBSDF.normal
13
17
  */
14
18
  export class NormalMap extends ProcessNode {
15
19
  get nodeType() { return 'NormalMap'; }
@@ -19,36 +23,45 @@ export class NormalMap extends ProcessNode {
19
23
  category: 'Vector',
20
24
  color: '#4a3a8a',
21
25
  cost: 'medium',
22
- costNote: 'Samples input Fac four times for gradient computation.',
26
+ costNote: 'Builds a derivative-based TBN frame per fragment.',
23
27
  };
24
28
  }
25
29
  constructor(inputs = {}) {
26
30
  super('NormalMap');
27
31
  this._inputs = this.createInputs(inputs, {
28
- fac: ['float', 0.5],
32
+ color: ['color', [0.5, 0.5, 1.0]],
29
33
  strength: ['float', inputs.strength ?? 1.0],
30
34
  });
31
35
  this._outputs = this.createOutputs({ Normal: 'color' });
32
36
  }
33
37
  getInputSockets() { return this._inputs; }
34
38
  getOutputSockets() { return this._outputs; }
35
- compileDefs() { return ''; }
39
+ compileDefs() { return NormalMap.glslFunction; }
36
40
  compileCall(ctx) {
37
- const facVar = this._inputs.fac.isConnected()
38
- ? ctx.outputVar(this._inputs.fac.connection.node, this._inputs.fac.connection.name)
39
- : ctx.resolveInput(this._inputs.fac);
41
+ const color = ctx.resolveInput(this._inputs.color);
40
42
  const strength = ctx.resolveInput(this._inputs.strength);
41
43
  const normalVar = ctx.outputVar(this, 'Normal');
42
- // Numerical gradient — samples fac variable neighbour delta via world position
43
- return [
44
- `float _nm_nx_${this.id} = dFdx(${facVar});`,
45
- `float _nm_ny_${this.id} = dFdy(${facVar});`,
46
- `vec3 ${normalVar} = normalize(vec3(-_nm_nx_${this.id} * ${strength}, 1.0, -_nm_ny_${this.id} * ${strength}));`,
47
- ].join('\n ');
44
+ return `vec3 ${normalVar} = _st_normalMap(${color}, ${strength}, normalize(vNormal), vPosition, vUv);`;
48
45
  }
49
46
  }
50
47
  NormalMap.instanceSpecificDef = false;
51
- NormalMap.glslFunction = `
52
- vec3 _st_normalMap(float fac, float strength) {
53
- return vec3(0.0, 1.0, 0.0);
48
+ NormalMap.glslFunction = `
49
+ vec3 _st_normalMap(vec3 sampledColor, float strength, vec3 N, vec3 P, vec2 uv) {
50
+ vec3 n = sampledColor * 2.0 - 1.0;
51
+ n.xy *= strength;
52
+
53
+ // Derivative-based cotangent frame (Schüler) — no vertex tangent attribute needed.
54
+ vec3 dp1 = dFdx(P);
55
+ vec3 dp2 = dFdy(P);
56
+ vec2 duv1 = dFdx(uv);
57
+ vec2 duv2 = dFdy(uv);
58
+
59
+ vec3 dp2perp = cross(dp2, N);
60
+ vec3 dp1perp = cross(N, dp1);
61
+ vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
62
+ vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
63
+
64
+ float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
65
+ mat3 TBN = mat3(T * invmax, B * invmax, N);
66
+ return normalize(TBN * n);
54
67
  }`;
@@ -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;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;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;IA8BtC,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;IAMvB,OAAO,CAAC,aAAa;CAKtB"}
@@ -1,4 +1,5 @@
1
1
  import { ProcessNode } from '../../core/ProcessNode.js';
2
+ import { assertGlslIdentifier } from '../../core/glslIdentifier.js';
2
3
  // ── ShaderScript node ─────────────────────────────────────────────────────────
3
4
  /**
4
5
  * ShaderScript — Blender "Script Node (OSL)" equivalent for st-shader-core.
@@ -47,6 +48,7 @@ export class ShaderScript extends ProcessNode {
47
48
  const defs = {};
48
49
  const supplied = {};
49
50
  for (const [name, spec] of Object.entries(config.inputs ?? {})) {
51
+ assertGlslIdentifier(name, 'ShaderScript input name');
50
52
  if (Array.isArray(spec)) {
51
53
  // ['float'|'color'|'vector'|'shader', defaultValue]
52
54
  const [type, defaultVal] = spec;
@@ -60,6 +62,9 @@ export class ShaderScript extends ProcessNode {
60
62
  supplied[name] = sock;
61
63
  }
62
64
  }
65
+ for (const name of Object.keys(this._outputDecl)) {
66
+ assertGlslIdentifier(name, 'ShaderScript output name');
67
+ }
63
68
  this._inputs = this.createInputs(supplied, defs);
64
69
  this._outputs = this.createOutputs(this._outputDecl);
65
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triforge/shader-core",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "GLSL shader library for Three.js with Blender workflow",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",