@triforge/shader-core 0.1.2 → 0.2.1
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 +72 -35
- package/dist/core/CompileContext.d.ts.map +1 -1
- package/dist/core/CompileContext.js +26 -5
- package/dist/core/ShaderNode.d.ts.map +1 -1
- package/dist/core/ShaderNode.js +13 -4
- package/dist/nodes/process/ImageTexture.js +1 -1
- package/dist/nodes/process/Mapping.d.ts +0 -3
- package/dist/nodes/process/Mapping.d.ts.map +1 -1
- package/dist/nodes/process/Mapping.js +28 -25
- package/dist/nodes/process/NormalMap.d.ts +11 -7
- package/dist/nodes/process/NormalMap.d.ts.map +1 -1
- package/dist/nodes/process/NormalMap.js +34 -21
- package/dist/nodes/process/ShaderScript.d.ts.map +1 -1
- package/dist/nodes/process/ShaderScript.js +5 -0
- 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 [
|
|
@@ -178,14 +184,14 @@ var ShaderNode = class {
|
|
|
178
184
|
if (type === "float" && typeof literal === "number") {
|
|
179
185
|
socket.uniformName = `u_${this.id}_${name}`;
|
|
180
186
|
this._uniformMap.set(name, socket.uniformName);
|
|
181
|
-
this.parameters[name] = literal;
|
|
187
|
+
this.parameters[name] = finiteOr(literal, 0);
|
|
182
188
|
}
|
|
183
189
|
if (type === "color" && !(value instanceof OutputSocket)) {
|
|
184
190
|
if (typeof literal === "string" || Array.isArray(literal)) {
|
|
185
191
|
socket.uniformName = `u_${this.id}_${name}`;
|
|
186
192
|
this._uniformMap.set(name, socket.uniformName);
|
|
187
193
|
this._colorParams.add(name);
|
|
188
|
-
this.parameters[name] = typeof literal === "string" ? hexToRgb(literal) : literal;
|
|
194
|
+
this.parameters[name] = typeof literal === "string" ? hexToRgb(literal) : sanitizeVec3(literal);
|
|
189
195
|
}
|
|
190
196
|
}
|
|
191
197
|
sockets[name] = socket;
|
|
@@ -217,7 +223,7 @@ var ShaderNode = class {
|
|
|
217
223
|
Object.defineProperty(this.parameters, paramName, {
|
|
218
224
|
get: () => uniforms[uniformName].value,
|
|
219
225
|
set: (v) => {
|
|
220
|
-
uniforms[uniformName].value = v;
|
|
226
|
+
uniforms[uniformName].value = sanitizeVec3(v);
|
|
221
227
|
},
|
|
222
228
|
enumerable: true,
|
|
223
229
|
configurable: true
|
|
@@ -226,7 +232,7 @@ var ShaderNode = class {
|
|
|
226
232
|
Object.defineProperty(this.parameters, paramName, {
|
|
227
233
|
get: () => uniforms[uniformName].value,
|
|
228
234
|
set: (v) => {
|
|
229
|
-
uniforms[uniformName].value = v;
|
|
235
|
+
uniforms[uniformName].value = finiteOr(v, 0);
|
|
230
236
|
},
|
|
231
237
|
enumerable: true,
|
|
232
238
|
configurable: true
|
|
@@ -308,16 +314,24 @@ var CompileContext = class _CompileContext {
|
|
|
308
314
|
*/
|
|
309
315
|
resolveInput(socket) {
|
|
310
316
|
if (socket.connection) {
|
|
311
|
-
|
|
317
|
+
const varName = this.outputVar(socket.connection.node, socket.connection.name);
|
|
318
|
+
if (socket.type === "vector" && socket.connection.type !== "vector") {
|
|
319
|
+
return `(${varName}).xy`;
|
|
320
|
+
}
|
|
321
|
+
if (socket.type === "color" && socket.connection.type === "vector") {
|
|
322
|
+
return `vec3(${varName}, 0.0)`;
|
|
323
|
+
}
|
|
324
|
+
return varName;
|
|
312
325
|
}
|
|
313
326
|
if (socket.uniformName !== null) {
|
|
314
327
|
if (!(socket.uniformName in this._uniforms)) {
|
|
315
328
|
if (socket.type === "color") {
|
|
316
329
|
const raw = socket.defaultValue;
|
|
317
330
|
const rgb = typeof raw === "string" ? _CompileContext.hexToRgbArray(raw) : Array.isArray(raw) ? raw : [1, 1, 1];
|
|
318
|
-
this._uniforms[socket.uniformName] = { value: rgb };
|
|
331
|
+
this._uniforms[socket.uniformName] = { value: rgb.map((n) => Number.isFinite(n) ? n : 0) };
|
|
319
332
|
} else {
|
|
320
|
-
|
|
333
|
+
const n = socket.defaultValue;
|
|
334
|
+
this._uniforms[socket.uniformName] = { value: Number.isFinite(n) ? n : 0 };
|
|
321
335
|
}
|
|
322
336
|
}
|
|
323
337
|
return socket.uniformName;
|
|
@@ -421,6 +435,8 @@ var CompileContext = class _CompileContext {
|
|
|
421
435
|
typesCompatible(from, to) {
|
|
422
436
|
if (from === to) return true;
|
|
423
437
|
if ((from === "shader" || from === "color") && (to === "shader" || to === "color")) return true;
|
|
438
|
+
if ((from === "color" || from === "shader") && to === "vector") return true;
|
|
439
|
+
if (from === "vector" && to === "color") return true;
|
|
424
440
|
return false;
|
|
425
441
|
}
|
|
426
442
|
// ── GLSL builders ─────────────────────────────────────────────────
|
|
@@ -469,8 +485,10 @@ ${callsBlock}
|
|
|
469
485
|
// ── Literal conversion ────────────────────────────────────────────
|
|
470
486
|
toLiteral(value, type) {
|
|
471
487
|
switch (type) {
|
|
472
|
-
case "float":
|
|
473
|
-
|
|
488
|
+
case "float": {
|
|
489
|
+
const n = typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
490
|
+
return n.toFixed(4);
|
|
491
|
+
}
|
|
474
492
|
case "color":
|
|
475
493
|
return typeof value === "string" ? this.hexToVec3(value) : "vec3(1.0, 1.0, 1.0)";
|
|
476
494
|
case "vector":
|
|
@@ -2033,7 +2051,7 @@ var ImageTexture = class extends ProcessNode {
|
|
|
2033
2051
|
return `uniform sampler2D ${this.uniformName};`;
|
|
2034
2052
|
}
|
|
2035
2053
|
compileCall(ctx) {
|
|
2036
|
-
const uv = this._inputs.vector.isConnected() ? ctx.
|
|
2054
|
+
const uv = this._inputs.vector.isConnected() ? ctx.resolveInput(this._inputs.vector) : "vUv";
|
|
2037
2055
|
const cv = ctx.outputVar(this, "Color");
|
|
2038
2056
|
const av = ctx.outputVar(this, "Alpha");
|
|
2039
2057
|
return [
|
|
@@ -2644,7 +2662,7 @@ vec3 _st_wavelength(float w) {
|
|
|
2644
2662
|
};
|
|
2645
2663
|
|
|
2646
2664
|
// src/nodes/process/NormalMap.ts
|
|
2647
|
-
var NormalMap = class extends ProcessNode {
|
|
2665
|
+
var NormalMap = class _NormalMap extends ProcessNode {
|
|
2648
2666
|
get nodeType() {
|
|
2649
2667
|
return "NormalMap";
|
|
2650
2668
|
}
|
|
@@ -2657,19 +2675,35 @@ var NormalMap = class extends ProcessNode {
|
|
|
2657
2675
|
category: "Vector",
|
|
2658
2676
|
color: "#4a3a8a",
|
|
2659
2677
|
cost: "medium",
|
|
2660
|
-
costNote: "
|
|
2678
|
+
costNote: "Builds a derivative-based TBN frame per fragment."
|
|
2661
2679
|
};
|
|
2662
2680
|
}
|
|
2663
2681
|
static {
|
|
2664
2682
|
this.glslFunction = `
|
|
2665
|
-
vec3 _st_normalMap(
|
|
2666
|
-
|
|
2683
|
+
vec3 _st_normalMap(vec3 sampledColor, float strength, vec3 N, vec3 P, vec2 uv) {
|
|
2684
|
+
vec3 n = sampledColor * 2.0 - 1.0;
|
|
2685
|
+
n.xy *= strength;
|
|
2686
|
+
|
|
2687
|
+
// Derivative-based cotangent frame (Sch\xFCler) \u2014 no vertex tangent attribute needed.
|
|
2688
|
+
vec3 dp1 = dFdx(P);
|
|
2689
|
+
vec3 dp2 = dFdy(P);
|
|
2690
|
+
vec2 duv1 = dFdx(uv);
|
|
2691
|
+
vec2 duv2 = dFdy(uv);
|
|
2692
|
+
|
|
2693
|
+
vec3 dp2perp = cross(dp2, N);
|
|
2694
|
+
vec3 dp1perp = cross(N, dp1);
|
|
2695
|
+
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
|
|
2696
|
+
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
|
|
2697
|
+
|
|
2698
|
+
float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
|
|
2699
|
+
mat3 TBN = mat3(T * invmax, B * invmax, N);
|
|
2700
|
+
return normalize(TBN * n);
|
|
2667
2701
|
}`;
|
|
2668
2702
|
}
|
|
2669
2703
|
constructor(inputs = {}) {
|
|
2670
2704
|
super("NormalMap");
|
|
2671
2705
|
this._inputs = this.createInputs(inputs, {
|
|
2672
|
-
|
|
2706
|
+
color: ["color", [0.5, 0.5, 1]],
|
|
2673
2707
|
strength: ["float", inputs.strength ?? 1]
|
|
2674
2708
|
});
|
|
2675
2709
|
this._outputs = this.createOutputs({ Normal: "color" });
|
|
@@ -2681,17 +2715,13 @@ vec3 _st_normalMap(float fac, float strength) {
|
|
|
2681
2715
|
return this._outputs;
|
|
2682
2716
|
}
|
|
2683
2717
|
compileDefs() {
|
|
2684
|
-
return
|
|
2718
|
+
return _NormalMap.glslFunction;
|
|
2685
2719
|
}
|
|
2686
2720
|
compileCall(ctx) {
|
|
2687
|
-
const
|
|
2721
|
+
const color = ctx.resolveInput(this._inputs.color);
|
|
2688
2722
|
const strength = ctx.resolveInput(this._inputs.strength);
|
|
2689
2723
|
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 ");
|
|
2724
|
+
return `vec3 ${normalVar} = _st_normalMap(${color}, ${strength}, normalize(vNormal), vPosition, vUv);`;
|
|
2695
2725
|
}
|
|
2696
2726
|
};
|
|
2697
2727
|
|
|
@@ -2793,10 +2823,12 @@ vec3 _st_rotateZ(vec3 v, float a) {
|
|
|
2793
2823
|
constructor(inputs = {}) {
|
|
2794
2824
|
super("Mapping");
|
|
2795
2825
|
this.mode = inputs.mode ?? "TEXTURE";
|
|
2796
|
-
this.
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2826
|
+
this._inputs = this.createInputs(inputs, {
|
|
2827
|
+
vector: ["color", null],
|
|
2828
|
+
location: ["color", inputs.location ?? [0, 0, 0]],
|
|
2829
|
+
rotation: ["color", inputs.rotation ?? [0, 0, 0]],
|
|
2830
|
+
scale: ["color", inputs.scale ?? [1, 1, 1]]
|
|
2831
|
+
});
|
|
2800
2832
|
this._outputs = this.createOutputs({ Vector: "color" });
|
|
2801
2833
|
}
|
|
2802
2834
|
getInputSockets() {
|
|
@@ -2810,18 +2842,19 @@ vec3 _st_rotateZ(vec3 v, float a) {
|
|
|
2810
2842
|
}
|
|
2811
2843
|
compileCall(ctx) {
|
|
2812
2844
|
const v = ctx.resolveInput(this._inputs.vector);
|
|
2845
|
+
const loc = ctx.resolveInput(this._inputs.location);
|
|
2846
|
+
const rot = ctx.resolveInput(this._inputs.rotation);
|
|
2847
|
+
const scl = ctx.resolveInput(this._inputs.scale);
|
|
2813
2848
|
const out = ctx.outputVar(this, "Vector");
|
|
2814
|
-
const toRad = (
|
|
2815
|
-
const [lx, ly, lz] = this.loc;
|
|
2816
|
-
const [rx, ry, rz] = this.rot.map(Number);
|
|
2817
|
-
const [sx, sy, sz] = this.scl;
|
|
2849
|
+
const toRad = (Math.PI / 180).toFixed(6);
|
|
2818
2850
|
return [
|
|
2819
2851
|
`vec3 _mp_v_${this.id} = ${v};`,
|
|
2820
|
-
`
|
|
2821
|
-
`_mp_v_${this.id} =
|
|
2822
|
-
`_mp_v_${this.id} =
|
|
2823
|
-
`_mp_v_${this.id} =
|
|
2824
|
-
`_mp_v_${this.id} = _mp_v_${this.id}
|
|
2852
|
+
`vec3 _mp_r_${this.id} = ${rot} * ${toRad};`,
|
|
2853
|
+
`_mp_v_${this.id} = _mp_v_${this.id} - ${loc};`,
|
|
2854
|
+
`_mp_v_${this.id} = _st_rotateX(_mp_v_${this.id}, _mp_r_${this.id}.x);`,
|
|
2855
|
+
`_mp_v_${this.id} = _st_rotateY(_mp_v_${this.id}, _mp_r_${this.id}.y);`,
|
|
2856
|
+
`_mp_v_${this.id} = _st_rotateZ(_mp_v_${this.id}, _mp_r_${this.id}.z);`,
|
|
2857
|
+
`_mp_v_${this.id} = _mp_v_${this.id} * ${scl};`,
|
|
2825
2858
|
`vec3 ${out} = _mp_v_${this.id};`
|
|
2826
2859
|
].join("\n ");
|
|
2827
2860
|
}
|
|
@@ -4859,6 +4892,7 @@ var ShaderScript = class extends ProcessNode {
|
|
|
4859
4892
|
const defs = {};
|
|
4860
4893
|
const supplied = {};
|
|
4861
4894
|
for (const [name, spec] of Object.entries(config.inputs ?? {})) {
|
|
4895
|
+
assertGlslIdentifier(name, "ShaderScript input name");
|
|
4862
4896
|
if (Array.isArray(spec)) {
|
|
4863
4897
|
const [type, defaultVal] = spec;
|
|
4864
4898
|
defs[name] = [type, defaultVal];
|
|
@@ -4868,6 +4902,9 @@ var ShaderScript = class extends ProcessNode {
|
|
|
4868
4902
|
supplied[name] = sock;
|
|
4869
4903
|
}
|
|
4870
4904
|
}
|
|
4905
|
+
for (const name of Object.keys(this._outputDecl)) {
|
|
4906
|
+
assertGlslIdentifier(name, "ShaderScript output name");
|
|
4907
|
+
}
|
|
4871
4908
|
this._inputs = this.createInputs(supplied, defs);
|
|
4872
4909
|
this._outputs = this.createOutputs(this._outputDecl);
|
|
4873
4910
|
}
|
|
@@ -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;
|
|
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;IAkClD,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"}
|
|
@@ -53,7 +53,16 @@ export class CompileContext {
|
|
|
53
53
|
*/
|
|
54
54
|
resolveInput(socket) {
|
|
55
55
|
if (socket.connection) {
|
|
56
|
-
|
|
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)) {
|
|
@@ -62,10 +71,12 @@ export class CompileContext {
|
|
|
62
71
|
const rgb = typeof raw === 'string'
|
|
63
72
|
? CompileContext.hexToRgbArray(raw)
|
|
64
73
|
: (Array.isArray(raw) ? raw : [1, 1, 1]);
|
|
65
|
-
|
|
74
|
+
// Guard against NaN/Infinity reaching the GPU uniform.
|
|
75
|
+
this._uniforms[socket.uniformName] = { value: rgb.map(n => Number.isFinite(n) ? n : 0) };
|
|
66
76
|
}
|
|
67
77
|
else {
|
|
68
|
-
|
|
78
|
+
const n = socket.defaultValue;
|
|
79
|
+
this._uniforms[socket.uniformName] = { value: Number.isFinite(n) ? n : 0 };
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
82
|
return socket.uniformName;
|
|
@@ -192,6 +203,13 @@ export class CompileContext {
|
|
|
192
203
|
// shader and color are both vec3 — compatible
|
|
193
204
|
if ((from === 'shader' || from === 'color') && (to === 'shader' || to === 'color'))
|
|
194
205
|
return true;
|
|
206
|
+
// vec3 (color/shader) implicitly narrows to vec2 (vector) via .xy, mirroring Blender's
|
|
207
|
+
// Mapping (vector output) → Image Texture Vector panning graph
|
|
208
|
+
if ((from === 'color' || from === 'shader') && to === 'vector')
|
|
209
|
+
return true;
|
|
210
|
+
// vec2 (vector) implicitly widens to vec3 (color), e.g. Texture Coordinate UV → Mapping.vector
|
|
211
|
+
if (from === 'vector' && to === 'color')
|
|
212
|
+
return true;
|
|
195
213
|
return false;
|
|
196
214
|
}
|
|
197
215
|
// ── GLSL builders ─────────────────────────────────────────────────
|
|
@@ -247,8 +265,11 @@ ${callsBlock}
|
|
|
247
265
|
// ── Literal conversion ────────────────────────────────────────────
|
|
248
266
|
toLiteral(value, type) {
|
|
249
267
|
switch (type) {
|
|
250
|
-
case 'float':
|
|
251
|
-
|
|
268
|
+
case 'float': {
|
|
269
|
+
// Guard against NaN/Infinity reaching the shader source as an invalid GLSL literal.
|
|
270
|
+
const n = typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
271
|
+
return n.toFixed(4);
|
|
272
|
+
}
|
|
252
273
|
case 'color':
|
|
253
274
|
return typeof value === 'string'
|
|
254
275
|
? this.hexToVec3(value)
|
|
@@ -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;
|
|
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;;;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"}
|
package/dist/core/ShaderNode.js
CHANGED
|
@@ -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 [
|
|
@@ -92,7 +99,7 @@ export class ShaderNode {
|
|
|
92
99
|
if (type === 'float' && typeof literal === 'number') {
|
|
93
100
|
socket.uniformName = `u_${this.id}_${name}`;
|
|
94
101
|
this._uniformMap.set(name, socket.uniformName);
|
|
95
|
-
this.parameters[name] = literal;
|
|
102
|
+
this.parameters[name] = finiteOr(literal, 0);
|
|
96
103
|
}
|
|
97
104
|
// Auto-register unconnected color inputs as vec3 uniforms
|
|
98
105
|
if (type === 'color' && !(value instanceof OutputSocket)) {
|
|
@@ -100,7 +107,9 @@ export class ShaderNode {
|
|
|
100
107
|
socket.uniformName = `u_${this.id}_${name}`;
|
|
101
108
|
this._uniformMap.set(name, socket.uniformName);
|
|
102
109
|
this._colorParams.add(name);
|
|
103
|
-
this.parameters[name] = typeof literal === 'string'
|
|
110
|
+
this.parameters[name] = typeof literal === 'string'
|
|
111
|
+
? hexToRgb(literal)
|
|
112
|
+
: sanitizeVec3(literal);
|
|
104
113
|
}
|
|
105
114
|
}
|
|
106
115
|
sockets[name] = socket;
|
|
@@ -131,7 +140,7 @@ export class ShaderNode {
|
|
|
131
140
|
if (this._colorParams.has(paramName)) {
|
|
132
141
|
Object.defineProperty(this.parameters, paramName, {
|
|
133
142
|
get: () => uniforms[uniformName].value,
|
|
134
|
-
set: (v) => { uniforms[uniformName].value = v; },
|
|
143
|
+
set: (v) => { uniforms[uniformName].value = sanitizeVec3(v); },
|
|
135
144
|
enumerable: true,
|
|
136
145
|
configurable: true,
|
|
137
146
|
});
|
|
@@ -139,7 +148,7 @@ export class ShaderNode {
|
|
|
139
148
|
else {
|
|
140
149
|
Object.defineProperty(this.parameters, paramName, {
|
|
141
150
|
get: () => uniforms[uniformName].value,
|
|
142
|
-
set: (v) => { uniforms[uniformName].value = v; },
|
|
151
|
+
set: (v) => { uniforms[uniformName].value = finiteOr(v, 0); },
|
|
143
152
|
enumerable: true,
|
|
144
153
|
configurable: true,
|
|
145
154
|
});
|
|
@@ -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.
|
|
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,
|
|
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.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 = (
|
|
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
|
-
`
|
|
36
|
-
`_mp_v_${this.id} =
|
|
37
|
-
`_mp_v_${this.id} =
|
|
38
|
-
`_mp_v_${this.id} =
|
|
39
|
-
`_mp_v_${this.id} = _mp_v_${this.id}
|
|
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
|
-
|
|
6
|
+
color?: OutputSocket;
|
|
7
7
|
strength?: number | OutputSocket;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Normal Map —
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
16
|
-
* strength (float)
|
|
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 (
|
|
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,
|
|
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 —
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
9
|
-
* strength (float)
|
|
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 (
|
|
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: '
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
53
|
-
|
|
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;
|
|
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
|
}
|