@triforge/shader-core 0.2.1 → 0.4.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 (46) hide show
  1. package/dist/bundle.js +196 -39
  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 +24 -9
  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 +23 -0
  10. package/dist/core/SocketType.d.ts +1 -1
  11. package/dist/core/SocketType.js +1 -1
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -0
  15. package/dist/nodes/output/MaterialOutput.js +2 -2
  16. package/dist/nodes/process/AddShader.js +4 -4
  17. package/dist/nodes/process/Bump.d.ts +25 -2
  18. package/dist/nodes/process/Bump.d.ts.map +1 -1
  19. package/dist/nodes/process/Bump.js +113 -32
  20. package/dist/nodes/process/DiffuseBSDF.js +8 -8
  21. package/dist/nodes/process/Emission.js +4 -4
  22. package/dist/nodes/process/GlassBSDF.js +14 -14
  23. package/dist/nodes/process/GlossyBSDF.js +15 -15
  24. package/dist/nodes/process/MixShader.js +4 -4
  25. package/dist/nodes/process/PrincipledBSDF.d.ts +7 -0
  26. package/dist/nodes/process/PrincipledBSDF.d.ts.map +1 -1
  27. package/dist/nodes/process/PrincipledBSDF.js +55 -40
  28. package/dist/nodes/process/PrincipledHair.js +71 -71
  29. package/dist/nodes/process/PrincipledVolume.js +22 -22
  30. package/dist/nodes/process/RefractionBSDF.js +8 -8
  31. package/dist/nodes/process/ShaderScript.d.ts +2 -2
  32. package/dist/nodes/process/ShaderScript.d.ts.map +1 -1
  33. package/dist/nodes/process/ShaderScript.js +7 -3
  34. package/dist/nodes/process/ShaderToRGB.js +2 -2
  35. package/dist/nodes/process/SheenBSDF.js +13 -13
  36. package/dist/nodes/process/SpecularBSDF.js +16 -16
  37. package/dist/nodes/process/SubsurfaceScattering.js +8 -8
  38. package/dist/nodes/process/ToonBSDF.js +7 -7
  39. package/dist/nodes/process/TranslucentBSDF.js +6 -6
  40. package/dist/nodes/process/TransparentBSDF.d.ts +26 -0
  41. package/dist/nodes/process/TransparentBSDF.d.ts.map +1 -0
  42. package/dist/nodes/process/TransparentBSDF.js +29 -0
  43. package/dist/nodes/process/VolumeAbsorption.js +4 -4
  44. package/dist/nodes/process/VolumeScatter.js +8 -8
  45. package/dist/nodes/shader/RayPortal.js +1 -1
  46. package/package.json +1 -1
@@ -25,51 +25,66 @@ 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;
36
- PrincipledBSDF.glslFunction = `
37
- // Set these on material.uniforms after compile()
38
- #ifndef _ST_HAIR_LIGHT_UNIFORMS
39
- #define _ST_HAIR_LIGHT_UNIFORMS
40
- uniform vec3 uSunDirection;
41
- uniform vec3 uSunColor;
42
- uniform vec3 uAmbientColor;
43
- #endif
44
-
45
- vec3 _st_schlick(vec3 F0, float c) {
46
- return F0 + (1.0 - F0) * pow(clamp(1.0 - c, 0.0, 1.0), 5.0);
47
- }
48
- float _st_ggx(float NdotH, float r) {
49
- float a = r * r, a2 = a * a;
50
- float d = (NdotH * NdotH) * (a2 - 1.0) + 1.0;
51
- return a2 / max(3.14159 * d * d, 1e-7);
52
- }
53
- float _st_geo(float NdotX, float r) {
54
- float k = (r + 1.0) * (r + 1.0) / 8.0;
55
- return NdotX / max(NdotX * (1.0 - k) + k, 1e-7);
56
- }
57
- vec3 _st_principledBSDF(vec3 base, float metallic, float roughness, float ior, float alpha, vec3 N) {
58
- vec3 V = normalize(cameraPosition - vPosition);
59
- vec3 L = normalize(uSunDirection);
60
- vec3 H = normalize(L + V);
61
- float r = max(roughness, 0.04);
62
- float NdotL = max(dot(N, L), 0.0);
63
- float NdotV = max(dot(N, V), 1e-4);
64
- float NdotH = max(dot(N, H), 0.0);
65
- float HdotV = max(dot(H, V), 0.0);
66
- float f0s = pow((1.0 - ior) / (1.0 + ior), 2.0);
67
- vec3 F0 = mix(vec3(f0s), base, metallic);
68
- vec3 F = _st_schlick(F0, HdotV);
69
- float D = _st_ggx(NdotH, r);
70
- float G = _st_geo(NdotL, r) * _st_geo(NdotV, r);
71
- vec3 spec = (D * G * F) / max(4.0 * NdotV * NdotL, 1e-7);
72
- vec3 kD = (1.0 - F) * (1.0 - metallic);
73
- vec3 ambient = uAmbientColor * base;
74
- return ambient + (kD * base / 3.14159 + spec * uSunColor) * NdotL;
51
+ PrincipledBSDF.glslFunction = `
52
+ // Set these on material.uniforms after compile()
53
+ #ifndef _ST_HAIR_LIGHT_UNIFORMS
54
+ #define _ST_HAIR_LIGHT_UNIFORMS
55
+ uniform vec3 uSunDirection;
56
+ uniform vec3 uSunColor;
57
+ uniform vec3 uAmbientColor;
58
+ #endif
59
+
60
+ vec3 _st_schlick(vec3 F0, float c) {
61
+ return F0 + (1.0 - F0) * pow(clamp(1.0 - c, 0.0, 1.0), 5.0);
62
+ }
63
+ float _st_ggx(float NdotH, float r) {
64
+ float a = r * r, a2 = a * a;
65
+ float d = (NdotH * NdotH) * (a2 - 1.0) + 1.0;
66
+ return a2 / max(3.14159 * d * d, 1e-7);
67
+ }
68
+ float _st_geo(float NdotX, float r) {
69
+ float k = (r + 1.0) * (r + 1.0) / 8.0;
70
+ return NdotX / max(NdotX * (1.0 - k) + k, 1e-7);
71
+ }
72
+ vec3 _st_principledBSDF(vec3 base, float metallic, float roughness, float ior, vec3 N) {
73
+ vec3 V = normalize(cameraPosition - vPosition);
74
+ vec3 L = normalize(uSunDirection);
75
+ vec3 H = normalize(L + V);
76
+ float r = max(roughness, 0.04);
77
+ float NdotL = max(dot(N, L), 0.0);
78
+ float NdotV = max(dot(N, V), 1e-4);
79
+ float NdotH = max(dot(N, H), 0.0);
80
+ float HdotV = max(dot(H, V), 0.0);
81
+ float f0s = pow((1.0 - ior) / (1.0 + ior), 2.0);
82
+ vec3 F0 = mix(vec3(f0s), base, metallic);
83
+ vec3 F = _st_schlick(F0, HdotV);
84
+ float D = _st_ggx(NdotH, r);
85
+ float G = _st_geo(NdotL, r) * _st_geo(NdotV, r);
86
+ vec3 spec = (D * G * F) / max(4.0 * NdotV * NdotL, 1e-7);
87
+ vec3 kD = (1.0 - F) * (1.0 - metallic);
88
+ vec3 ambient = uAmbientColor * base;
89
+ return ambient + (kD * base / 3.14159 + spec * uSunColor) * NdotL;
75
90
  }`;
@@ -59,79 +59,79 @@ 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;
66
66
  // Light uniforms guarded by a #define so they survive alongside PrincipledBSDF in the same graph.
67
- PrincipledHair.glslFunction = `
68
- #ifndef _ST_HAIR_LIGHT_UNIFORMS
69
- #define _ST_HAIR_LIGHT_UNIFORMS
70
- uniform vec3 uSunDirection;
71
- uniform vec3 uSunColor;
72
- uniform vec3 uAmbientColor;
73
- #endif
74
-
75
- float _st_hairHash(float n) {
76
- return fract(sin(n * 127.1 + 311.7) * 43758.5453);
77
- }
78
-
79
- // Kajiya-Kay dual-lobe hair BSDF.
80
- // T = strand tangent (unit, world space)
81
- // R = primary specular exponent = 1 / (roughness^2)
82
- // R2 = secondary specular exponent = 1 / (radialRoughness^2)
83
- vec3 _st_principledHair(
84
- vec3 color,
85
- float roughness,
86
- float radialRoughness,
87
- float coat,
88
- float ior,
89
- float offset,
90
- float randomColor,
91
- float randomRoughness,
92
- float rnd,
93
- vec3 T
94
- ) {
95
- // Per-strand variation
96
- float rc = _st_hairHash(rnd * 1.3731 + 0.1);
97
- float rr = _st_hairHash(rnd * 2.7183 + 0.2);
98
- color = mix(color, color * (0.6 + 0.8 * rc), randomColor);
99
- roughness = clamp(roughness + (rr - 0.5) * randomRoughness * 0.5, 0.02, 1.0);
100
-
101
- vec3 V = normalize(cameraPosition - vPosition);
102
- vec3 L = normalize(uSunDirection);
103
- vec3 H = normalize(L + V);
104
- vec3 N = normalize(vNormal);
105
-
106
- // Kajiya-Kay diffuse: sin(angle T, L)
107
- float cosT = dot(T, L);
108
- float sinT = sqrt(max(1.0 - cosT * cosT, 0.0));
109
- float diff = sinT * max(dot(N, L) * 0.5 + 0.5, 0.0); // wrap diffuse
110
-
111
- // R lobe — primary specular, shifted by +offset along normal
112
- float f0 = pow(clamp((1.0 - ior) / (1.0 + ior), 0.0, 1.0), 2.0);
113
- vec3 T1 = normalize(T + offset * N);
114
- float cosTH1 = dot(T1, H);
115
- float sinTH1 = sqrt(max(1.0 - cosTH1 * cosTH1, 0.0));
116
- float exp1 = max(1.0 / max(roughness * roughness, 0.001), 1.0);
117
- float spec1 = pow(sinTH1, exp1);
118
- vec3 F1 = vec3(f0) + (vec3(1.0) - vec3(f0)) * pow(1.0 - max(dot(H, V), 0.0), 5.0);
119
-
120
- // TRT lobe — secondary specular, shifted by -2*offset, colored
121
- vec3 T2 = normalize(T - 2.0 * offset * N);
122
- float cosTH2 = dot(T2, H);
123
- float sinTH2 = sqrt(max(1.0 - cosTH2 * cosTH2, 0.0));
124
- float rr2 = clamp(radialRoughness, 0.02, 1.0);
125
- float exp2 = max(1.0 / max(rr2 * rr2, 0.001), 1.0);
126
- float spec2 = pow(sinTH2, exp2) * 0.25;
127
-
128
- // Coat gloss
129
- float cosTHc = dot(T, H);
130
- float sinTHc = sqrt(max(1.0 - cosTHc * cosTHc, 0.0));
131
- float coatSpec = pow(sinTHc, 128.0) * coat;
132
-
133
- vec3 diffuse = color * (uSunColor * diff + uAmbientColor);
134
- vec3 specular = uSunColor * (F1 * spec1 + vec3(coatSpec)) + color * uSunColor * spec2;
135
-
136
- return diffuse + specular;
67
+ PrincipledHair.glslFunction = `
68
+ #ifndef _ST_HAIR_LIGHT_UNIFORMS
69
+ #define _ST_HAIR_LIGHT_UNIFORMS
70
+ uniform vec3 uSunDirection;
71
+ uniform vec3 uSunColor;
72
+ uniform vec3 uAmbientColor;
73
+ #endif
74
+
75
+ float _st_hairHash(float n) {
76
+ return fract(sin(n * 127.1 + 311.7) * 43758.5453);
77
+ }
78
+
79
+ // Kajiya-Kay dual-lobe hair BSDF.
80
+ // T = strand tangent (unit, world space)
81
+ // R = primary specular exponent = 1 / (roughness^2)
82
+ // R2 = secondary specular exponent = 1 / (radialRoughness^2)
83
+ vec3 _st_principledHair(
84
+ vec3 color,
85
+ float roughness,
86
+ float radialRoughness,
87
+ float coat,
88
+ float ior,
89
+ float offset,
90
+ float randomColor,
91
+ float randomRoughness,
92
+ float rnd,
93
+ vec3 T
94
+ ) {
95
+ // Per-strand variation
96
+ float rc = _st_hairHash(rnd * 1.3731 + 0.1);
97
+ float rr = _st_hairHash(rnd * 2.7183 + 0.2);
98
+ color = mix(color, color * (0.6 + 0.8 * rc), randomColor);
99
+ roughness = clamp(roughness + (rr - 0.5) * randomRoughness * 0.5, 0.02, 1.0);
100
+
101
+ vec3 V = normalize(cameraPosition - vPosition);
102
+ vec3 L = normalize(uSunDirection);
103
+ vec3 H = normalize(L + V);
104
+ vec3 N = normalize(vNormal);
105
+
106
+ // Kajiya-Kay diffuse: sin(angle T, L)
107
+ float cosT = dot(T, L);
108
+ float sinT = sqrt(max(1.0 - cosT * cosT, 0.0));
109
+ float diff = sinT * max(dot(N, L) * 0.5 + 0.5, 0.0); // wrap diffuse
110
+
111
+ // R lobe — primary specular, shifted by +offset along normal
112
+ float f0 = pow(clamp((1.0 - ior) / (1.0 + ior), 0.0, 1.0), 2.0);
113
+ vec3 T1 = normalize(T + offset * N);
114
+ float cosTH1 = dot(T1, H);
115
+ float sinTH1 = sqrt(max(1.0 - cosTH1 * cosTH1, 0.0));
116
+ float exp1 = max(1.0 / max(roughness * roughness, 0.001), 1.0);
117
+ float spec1 = pow(sinTH1, exp1);
118
+ vec3 F1 = vec3(f0) + (vec3(1.0) - vec3(f0)) * pow(1.0 - max(dot(H, V), 0.0), 5.0);
119
+
120
+ // TRT lobe — secondary specular, shifted by -2*offset, colored
121
+ vec3 T2 = normalize(T - 2.0 * offset * N);
122
+ float cosTH2 = dot(T2, H);
123
+ float sinTH2 = sqrt(max(1.0 - cosTH2 * cosTH2, 0.0));
124
+ float rr2 = clamp(radialRoughness, 0.02, 1.0);
125
+ float exp2 = max(1.0 / max(rr2 * rr2, 0.001), 1.0);
126
+ float spec2 = pow(sinTH2, exp2) * 0.25;
127
+
128
+ // Coat gloss
129
+ float cosTHc = dot(T, H);
130
+ float sinTHc = sqrt(max(1.0 - cosTHc * cosTHc, 0.0));
131
+ float coatSpec = pow(sinTHc, 128.0) * coat;
132
+
133
+ vec3 diffuse = color * (uSunColor * diff + uAmbientColor);
134
+ vec3 specular = uSunColor * (F1 * spec1 + vec3(coatSpec)) + color * uSunColor * spec2;
135
+
136
+ return diffuse + specular;
137
137
  }`;
@@ -39,29 +39,29 @@ 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;
46
- PrincipledVolume.glslFunction = `
47
- vec3 _st_principledVolume(
48
- vec3 scatterColor, float density,
49
- float anisotropy, vec3 absorptionColor,
50
- vec3 emissionColor, float emissionStrength,
51
- float blackbodyIntensity, float temperature
52
- ) {
53
- float g = clamp(anisotropy, -0.9999, 0.9999);
54
- float g2 = g * g;
55
- float phase = (1.0 - g2) / pow(1.0 + g2, 1.5);
56
-
57
- vec3 scatter = scatterColor * density * phase;
58
- vec3 absorption = absorptionColor * density;
59
- vec3 emission = emissionColor * emissionStrength;
60
-
61
- // Simplified blackbody tint: blend toward hot white at high temperature
62
- float tNorm = clamp((temperature - 800.0) / 4200.0, 0.0, 1.0);
63
- vec3 bb = mix(vec3(1.0, 0.2, 0.0), vec3(1.0, 0.95, 0.8), tNorm);
64
- emission += bb * blackbodyIntensity * density;
65
-
66
- return scatter + emission - absorption * 0.5;
46
+ PrincipledVolume.glslFunction = `
47
+ vec3 _st_principledVolume(
48
+ vec3 scatterColor, float density,
49
+ float anisotropy, vec3 absorptionColor,
50
+ vec3 emissionColor, float emissionStrength,
51
+ float blackbodyIntensity, float temperature
52
+ ) {
53
+ float g = clamp(anisotropy, -0.9999, 0.9999);
54
+ float g2 = g * g;
55
+ float phase = (1.0 - g2) / pow(1.0 + g2, 1.5);
56
+
57
+ vec3 scatter = scatterColor * density * phase;
58
+ vec3 absorption = absorptionColor * density;
59
+ vec3 emission = emissionColor * emissionStrength;
60
+
61
+ // Simplified blackbody tint: blend toward hot white at high temperature
62
+ float tNorm = clamp((temperature - 800.0) / 4200.0, 0.0, 1.0);
63
+ vec3 bb = mix(vec3(1.0, 0.2, 0.0), vec3(1.0, 0.95, 0.8), tNorm);
64
+ emission += bb * blackbodyIntensity * density;
65
+
66
+ return scatter + emission - absorption * 0.5;
67
67
  }`;
@@ -32,15 +32,15 @@ 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;
39
- RefractionBSDF.glslFunction = `
40
- vec3 _st_refractionBSDF(vec3 color, float roughness, float ior, vec3 N) {
41
- vec3 V = normalize(cameraPosition - vPosition);
42
- vec3 refDir = refract(-V, N, 1.0 / ior);
43
- float fresnel = pow(1.0 - max(dot(V, N), 0.0), 5.0);
44
- vec3 refColor = color * (1.0 - fresnel) * (1.0 - roughness * 0.5);
45
- return refColor;
39
+ RefractionBSDF.glslFunction = `
40
+ vec3 _st_refractionBSDF(vec3 color, float roughness, float ior, vec3 N) {
41
+ vec3 V = normalize(cameraPosition - vPosition);
42
+ vec3 refDir = refract(-V, N, 1.0 / ior);
43
+ float fresnel = pow(1.0 - max(dot(V, N), 0.0), 5.0);
44
+ vec3 refColor = color * (1.0 - fresnel) * (1.0 - roughness * 0.5);
45
+ return refColor;
46
46
  }`;
@@ -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,20 +30,20 @@ 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;
37
- SheenBSDF.glslFunction = `
38
- vec3 _st_sheenBSDF(vec3 color, float roughness, vec3 N) {
39
- vec3 V = normalize(cameraPosition - vPosition);
40
- vec3 L = normalize(vec3(1.0, 2.0, 1.0));
41
- vec3 H = normalize(L + V);
42
- float NdotL = max(dot(N, L), 0.0);
43
- float NdotV = max(dot(N, V), 0.0001);
44
- float NdotH = max(dot(N, H), 0.0);
45
- float sinThetaH = sqrt(max(1.0 - NdotH * NdotH, 0.0));
46
- float r2 = roughness * roughness;
47
- float sheen = (2.0 + 1.0 / r2) * pow(sinThetaH, 1.0 / r2) / (2.0 * 3.14159);
48
- return color * sheen * NdotL / max(NdotL + NdotV - NdotL * NdotV, 0.0001) + color * 0.03;
37
+ SheenBSDF.glslFunction = `
38
+ vec3 _st_sheenBSDF(vec3 color, float roughness, vec3 N) {
39
+ vec3 V = normalize(cameraPosition - vPosition);
40
+ vec3 L = normalize(vec3(1.0, 2.0, 1.0));
41
+ vec3 H = normalize(L + V);
42
+ float NdotL = max(dot(N, L), 0.0);
43
+ float NdotV = max(dot(N, V), 0.0001);
44
+ float NdotH = max(dot(N, H), 0.0);
45
+ float sinThetaH = sqrt(max(1.0 - NdotH * NdotH, 0.0));
46
+ float r2 = roughness * roughness;
47
+ float sheen = (2.0 + 1.0 / r2) * pow(sinThetaH, 1.0 / r2) / (2.0 * 3.14159);
48
+ return color * sheen * NdotL / max(NdotL + NdotV - NdotL * NdotV, 0.0001) + color * 0.03;
49
49
  }`;
@@ -32,23 +32,23 @@ 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;
39
- SpecularBSDF.glslFunction = `
40
- vec3 _st_specularBSDF(vec3 base, vec3 spec, float roughness, vec3 N) {
41
- vec3 L = normalize(vec3(1.0, 2.0, 1.0));
42
- vec3 V = normalize(cameraPosition - vPosition);
43
- vec3 H = normalize(L + V);
44
- float NdotL = max(dot(N, L), 0.0);
45
- float NdotH = max(dot(N, H), 0.0);
46
- float NdotV = max(dot(N, V), 0.0001);
47
- float a2 = pow(roughness, 4.0);
48
- float D = a2 / max(3.14159 * pow(NdotH * NdotH * (a2 - 1.0) + 1.0, 2.0), 0.0001);
49
- float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
50
- float G = (NdotL / (NdotL * (1.0 - k) + k)) * (NdotV / (NdotV * (1.0 - k) + k));
51
- vec3 F = spec + (1.0 - spec) * pow(1.0 - max(dot(H, V), 0.0), 5.0);
52
- vec3 specColor = D * G * F / max(4.0 * NdotL * NdotV, 0.0001);
53
- return (base / 3.14159 + specColor) * NdotL + base * 0.04;
39
+ SpecularBSDF.glslFunction = `
40
+ vec3 _st_specularBSDF(vec3 base, vec3 spec, float roughness, vec3 N) {
41
+ vec3 L = normalize(vec3(1.0, 2.0, 1.0));
42
+ vec3 V = normalize(cameraPosition - vPosition);
43
+ vec3 H = normalize(L + V);
44
+ float NdotL = max(dot(N, L), 0.0);
45
+ float NdotH = max(dot(N, H), 0.0);
46
+ float NdotV = max(dot(N, V), 0.0001);
47
+ float a2 = pow(roughness, 4.0);
48
+ float D = a2 / max(3.14159 * pow(NdotH * NdotH * (a2 - 1.0) + 1.0, 2.0), 0.0001);
49
+ float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
50
+ float G = (NdotL / (NdotL * (1.0 - k) + k)) * (NdotV / (NdotV * (1.0 - k) + k));
51
+ vec3 F = spec + (1.0 - spec) * pow(1.0 - max(dot(H, V), 0.0), 5.0);
52
+ vec3 specColor = D * G * F / max(4.0 * NdotL * NdotV, 0.0001);
53
+ return (base / 3.14159 + specColor) * NdotL + base * 0.04;
54
54
  }`;
@@ -32,15 +32,15 @@ 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;
39
- SubsurfaceScattering.glslFunction = `
40
- vec3 _st_sss(vec3 color, float scale, vec3 radius, vec3 N) {
41
- vec3 L = normalize(vec3(1.0, 2.0, 1.0));
42
- float wrap = max(dot(N, L) * 0.5 + 0.5, 0.0);
43
- vec3 scatter = color * radius * wrap * scale;
44
- float back = max(-dot(N, L), 0.0) * scale * 0.3;
45
- return color * (0.04 + wrap * 0.5) + scatter + color * back;
39
+ SubsurfaceScattering.glslFunction = `
40
+ vec3 _st_sss(vec3 color, float scale, vec3 radius, vec3 N) {
41
+ vec3 L = normalize(vec3(1.0, 2.0, 1.0));
42
+ float wrap = max(dot(N, L) * 0.5 + 0.5, 0.0);
43
+ vec3 scatter = color * radius * wrap * scale;
44
+ float back = max(-dot(N, L), 0.0) * scale * 0.3;
45
+ return color * (0.04 + wrap * 0.5) + scatter + color * back;
46
46
  }`;
@@ -31,14 +31,14 @@ 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;
38
- ToonBSDF.glslFunction = `
39
- vec3 _st_toonBSDF(vec3 color, float size, float smoothWidth, vec3 N) {
40
- vec3 L = normalize(vec3(1.0, 2.0, 1.0));
41
- float NdotL = dot(N, L) * 0.5 + 0.5;
42
- float band = smoothstep(size - smoothWidth, size + smoothWidth, NdotL);
43
- return color * band + color * 0.1;
38
+ ToonBSDF.glslFunction = `
39
+ vec3 _st_toonBSDF(vec3 color, float size, float smoothWidth, vec3 N) {
40
+ vec3 L = normalize(vec3(1.0, 2.0, 1.0));
41
+ float NdotL = dot(N, L) * 0.5 + 0.5;
42
+ float band = smoothstep(size - smoothWidth, size + smoothWidth, NdotL);
43
+ return color * band + color * 0.1;
44
44
  }`;
@@ -27,13 +27,13 @@ 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;
34
- TranslucentBSDF.glslFunction = `
35
- vec3 _st_translucentBSDF(vec3 color, vec3 N) {
36
- vec3 L = normalize(vec3(1.0, 2.0, 1.0));
37
- float backDot = max(dot(-N, L), 0.0);
38
- return color * backDot / 3.14159265 + color * 0.02;
34
+ TranslucentBSDF.glslFunction = `
35
+ vec3 _st_translucentBSDF(vec3 color, vec3 N) {
36
+ vec3 L = normalize(vec3(1.0, 2.0, 1.0));
37
+ float backDot = max(dot(-N, L), 0.0);
38
+ return color * backDot / 3.14159265 + color * 0.02;
39
39
  }`;
@@ -0,0 +1,26 @@
1
+ import { ProcessNode } from '../../core/ProcessNode.js';
2
+ import type { NodeMetadata } from '../../core/ShaderNode.js';
3
+ import type { CompileContext } from '../../core/CompileContext.js';
4
+ /**
5
+ * Transparent BSDF — Blender "Transparent BSDF" node equivalent.
6
+ * Passes light through unfiltered — full transparency, colorless, no inputs.
7
+ * Combine with MixShader/AddShader to build cutout, fresnel-fade, or
8
+ * rim-light materials without a hand-written GLSL output node.
9
+ *
10
+ * Outputs: BSDF (shader) — always vec4(0.0, 0.0, 0.0, 0.0)
11
+ */
12
+ export declare class TransparentBSDF extends ProcessNode {
13
+ get nodeType(): string;
14
+ static instanceSpecificDef: boolean;
15
+ get metadata(): NodeMetadata;
16
+ private readonly _inputs;
17
+ private readonly _outputs;
18
+ constructor();
19
+ getInputSockets(): Record<string, import("../../index.js").InputSocket<unknown>>;
20
+ getOutputSockets(): Record<string, import("../../index.js").OutputSocket>;
21
+ compileDefs(): string;
22
+ /** Always fully transparent — a constant, not a dynamic value, so this always requests transparent:true. */
23
+ wantsTransparency(): boolean;
24
+ compileCall(ctx: CompileContext): string;
25
+ }
26
+ //# sourceMappingURL=TransparentBSDF.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransparentBSDF.d.ts","sourceRoot":"","sources":["../../../src/nodes/process/TransparentBSDF.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;AAElE;;;;;;;GAOG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,IAAI,QAAQ,WAA+B;IAC3C,MAAM,CAAC,mBAAmB,UAAQ;IAElC,IAAI,QAAQ,IAAI,YAAY,CAE3B;IAED,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2E;IACnG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmE;;IAQ5F,eAAe;IACf,gBAAgB;IAChB,WAAW;IAEX,4GAA4G;IAC5G,iBAAiB,IAAI,OAAO;IAE5B,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM;CAGzC"}
@@ -0,0 +1,29 @@
1
+ import { ProcessNode } from '../../core/ProcessNode.js';
2
+ /**
3
+ * Transparent BSDF — Blender "Transparent BSDF" node equivalent.
4
+ * Passes light through unfiltered — full transparency, colorless, no inputs.
5
+ * Combine with MixShader/AddShader to build cutout, fresnel-fade, or
6
+ * rim-light materials without a hand-written GLSL output node.
7
+ *
8
+ * Outputs: BSDF (shader) — always vec4(0.0, 0.0, 0.0, 0.0)
9
+ */
10
+ export class TransparentBSDF extends ProcessNode {
11
+ get nodeType() { return 'TransparentBSDF'; }
12
+ get metadata() {
13
+ return { label: 'Transparent BSDF', category: 'Shader', color: '#336699', cost: 'low' };
14
+ }
15
+ constructor() {
16
+ super('TransparentBSDF');
17
+ this._inputs = this.createInputs({}, {});
18
+ this._outputs = this.createOutputs({ BSDF: 'shader' });
19
+ }
20
+ getInputSockets() { return this._inputs; }
21
+ getOutputSockets() { return this._outputs; }
22
+ compileDefs() { return ''; }
23
+ /** Always fully transparent — a constant, not a dynamic value, so this always requests transparent:true. */
24
+ wantsTransparency() { return true; }
25
+ compileCall(ctx) {
26
+ return `vec4 ${ctx.outputVar(this, 'BSDF')} = vec4(0.0, 0.0, 0.0, 0.0);`;
27
+ }
28
+ }
29
+ TransparentBSDF.instanceSpecificDef = false;
@@ -26,11 +26,11 @@ 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;
33
- VolumeAbsorption.glslFunction = `
34
- vec3 _st_volumeAbsorption(vec3 color, float density) {
35
- return color * density;
33
+ VolumeAbsorption.glslFunction = `
34
+ vec3 _st_volumeAbsorption(vec3 color, float density) {
35
+ return color * density;
36
36
  }`;