@triforge/shader-core 0.3.0 → 0.4.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.
Files changed (33) hide show
  1. package/dist/bundle.js +148 -8
  2. package/dist/core/CompileContext.d.ts +17 -0
  3. package/dist/core/CompileContext.d.ts.map +1 -1
  4. package/dist/core/CompileContext.js +26 -2
  5. package/dist/core/ShaderNode.d.ts.map +1 -1
  6. package/dist/core/ShaderNode.js +16 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/nodes/process/AddShader.js +3 -3
  11. package/dist/nodes/process/Bump.d.ts +25 -2
  12. package/dist/nodes/process/Bump.d.ts.map +1 -1
  13. package/dist/nodes/process/Bump.js +114 -32
  14. package/dist/nodes/process/DiffuseBSDF.js +7 -7
  15. package/dist/nodes/process/Emission.js +3 -3
  16. package/dist/nodes/process/GlassBSDF.js +13 -13
  17. package/dist/nodes/process/GlossyBSDF.js +14 -14
  18. package/dist/nodes/process/MixShader.js +3 -3
  19. package/dist/nodes/process/PrincipledBSDF.js +39 -39
  20. package/dist/nodes/process/PrincipledHair.js +70 -70
  21. package/dist/nodes/process/PrincipledVolume.js +21 -21
  22. package/dist/nodes/process/RefractionBSDF.js +7 -7
  23. package/dist/nodes/process/SheenBSDF.js +12 -12
  24. package/dist/nodes/process/SpecularBSDF.js +15 -15
  25. package/dist/nodes/process/SubsurfaceScattering.js +7 -7
  26. package/dist/nodes/process/ToonBSDF.js +6 -6
  27. package/dist/nodes/process/TranslucentBSDF.js +5 -5
  28. package/dist/nodes/process/TransparentBSDF.d.ts +26 -0
  29. package/dist/nodes/process/TransparentBSDF.d.ts.map +1 -0
  30. package/dist/nodes/process/TransparentBSDF.js +29 -0
  31. package/dist/nodes/process/VolumeAbsorption.js +3 -3
  32. package/dist/nodes/process/VolumeScatter.js +7 -7
  33. package/package.json +1 -1
@@ -1,9 +1,14 @@
1
1
  import { ProcessNode } from '../../core/ProcessNode.js';
2
+ import { ShaderNodeError } from '../../core/ShaderNodeError.js';
3
+ import { ShaderConfig } from '../../core/ShaderConfig.js';
4
+ import { assertGlslIdentifier } from '../../core/glslIdentifier.js';
2
5
  /**
3
6
  * Bump — Blender "Bump" node equivalent.
4
- * Converts a height map into a perturbed surface normal using screen-space derivatives.
7
+ * Converts a height map into a perturbed surface normal.
5
8
  *
6
9
  * Inputs: strength [0-1], distance, height (float), normal (optional override)
10
+ * method ('derivative' | 'uv-offset'), uniformName/vector/texelSize/lod
11
+ * (uv-offset mode only)
7
12
  * Outputs: Normal (color/vec3)
8
13
  */
9
14
  export class Bump extends ProcessNode {
@@ -13,53 +18,130 @@ export class Bump extends ProcessNode {
13
18
  }
14
19
  constructor(inputs = {}) {
15
20
  super('Bump');
21
+ this._method = inputs.method ?? 'derivative';
22
+ if (this._method === 'uv-offset') {
23
+ if (ShaderConfig.errorLevel !== 'off' && !inputs.uniformName) {
24
+ ShaderNodeError.throw({
25
+ nodeType: 'Bump',
26
+ nodeId: this.id,
27
+ problem: 'uniformName is required when method:"uv-offset".',
28
+ fix: 'new Bump({ method: "uv-offset", uniformName: "uHeightTex" })\n After compile(): mat.material.uniforms.uHeightTex = { value: texture }',
29
+ });
30
+ }
31
+ if (inputs.uniformName)
32
+ assertGlslIdentifier(inputs.uniformName, 'Bump uniformName');
33
+ this._uniformName = inputs.uniformName ?? null;
34
+ }
35
+ else {
36
+ this._uniformName = null;
37
+ }
16
38
  this._inputs = this.createInputs(inputs, {
17
39
  strength: ['float', inputs.strength ?? 1.0],
18
40
  distance: ['float', inputs.distance ?? 1.0],
19
41
  height: ['float', 0.5],
20
42
  normal: ['color', null],
43
+ vector: ['vector', null],
44
+ texelSize: ['float', inputs.texelSize ?? 1 / 512],
45
+ lod: ['float', inputs.lod ?? 0.0],
21
46
  });
22
47
  this._outputs = this.createOutputs({ Normal: 'color' });
23
48
  }
24
49
  getInputSockets() { return this._inputs; }
25
50
  getOutputSockets() { return this._outputs; }
26
- compileDefs() { return Bump.glslFunction; }
51
+ compileDefs() {
52
+ if (this._method === 'uv-offset') {
53
+ // The #extension line is hoisted to the very top of the fragment shader
54
+ // (before `precision`) and deduplicated by CompileContext regardless of
55
+ // where/how many times it's emitted here — see
56
+ // CompileContext._extractExtensions. No local guard needed.
57
+ return [
58
+ '#extension GL_EXT_shader_texture_lod : enable',
59
+ `uniform sampler2D ${this._uniformName};`,
60
+ ].join('\n');
61
+ }
62
+ return Bump.derivativeGlslFunction;
63
+ }
27
64
  compileCall(ctx) {
28
- const height = ctx.resolveInput(this._inputs.height);
29
65
  const strength = ctx.resolveInput(this._inputs.strength);
30
66
  const distance = ctx.resolveInput(this._inputs.distance);
31
67
  const normal = this._inputs.normal.isConnected()
32
68
  ? ctx.outputVar(this._inputs.normal.connection.node, this._inputs.normal.connection.name)
33
69
  : 'normalize(vNormal)';
70
+ if (this._method === 'uv-offset') {
71
+ const uv = this._inputs.vector.isConnected() ? ctx.resolveInput(this._inputs.vector) : 'vUv';
72
+ const texelSize = ctx.resolveInput(this._inputs.texelSize);
73
+ const lod = ctx.resolveInput(this._inputs.lod);
74
+ const id = this.id;
75
+ const hL = `_bmp_${id}_hL`, hR = `_bmp_${id}_hR`;
76
+ const hD = `_bmp_${id}_hD`, hU = `_bmp_${id}_hU`;
77
+ const dPdx = `_bmp_${id}_dPdx`, dPdy = `_bmp_${id}_dPdy`;
78
+ const dUdx = `_bmp_${id}_dUdx`, dUdy = `_bmp_${id}_dUdy`;
79
+ const det = `_bmp_${id}_det`, sgn = `_bmp_${id}_sgn`, invDet = `_bmp_${id}_invDet`;
80
+ const T = `_bmp_${id}_T`, B = `_bmp_${id}_B`;
81
+ const dhdu = `_bmp_${id}_dhdu`, dhdv = `_bmp_${id}_dhdv`;
82
+ const scale = `_bmp_${id}_scale`;
83
+ const ts = `_bmp_${id}_ts`;
84
+ return [
85
+ `float ${ts} = ${texelSize};`,
86
+ `float ${hL} = texture2DLodEXT(${this._uniformName}, ${uv} - vec2(${ts}, 0.0), ${lod}).r;`,
87
+ `float ${hR} = texture2DLodEXT(${this._uniformName}, ${uv} + vec2(${ts}, 0.0), ${lod}).r;`,
88
+ `float ${hD} = texture2DLodEXT(${this._uniformName}, ${uv} - vec2(0.0, ${ts}), ${lod}).r;`,
89
+ `float ${hU} = texture2DLodEXT(${this._uniformName}, ${uv} + vec2(0.0, ${ts}), ${lod}).r;`,
90
+ // Cotangent frame from screen-space derivatives — this builds the tangent
91
+ // basis (surface-fixed), not the height sample, so it doesn't carry the
92
+ // camera-distance instability the uv-offset method exists to avoid.
93
+ `vec3 ${dPdx} = dFdx(vPosition);`,
94
+ `vec3 ${dPdy} = dFdy(vPosition);`,
95
+ `vec2 ${dUdx} = dFdx(vUv);`,
96
+ `vec2 ${dUdy} = dFdy(vUv);`,
97
+ `float ${det} = ${dUdx}.x * ${dUdy}.y - ${dUdx}.y * ${dUdy}.x;`,
98
+ `float ${sgn} = sign(${det});`,
99
+ `float ${invDet} = ${sgn} / max(abs(${det}), 1e-6);`,
100
+ `vec3 ${T} = normalize((${dUdy}.y * ${dPdx} - ${dUdx}.y * ${dPdy}) * ${invDet});`,
101
+ `vec3 ${B} = normalize(cross(${normal}, ${T})) * ${sgn};`,
102
+ `float ${dhdu} = (${hR} - ${hL}) / max(2.0 * ${ts}, 1e-6);`,
103
+ `float ${dhdv} = (${hU} - ${hD}) / max(2.0 * ${ts}, 1e-6);`,
104
+ `float ${scale} = ${strength} * ${distance} * 50.0;`,
105
+ `vec3 ${ctx.outputVar(this, 'Normal')} = normalize(${normal} - ${T} * ${dhdu} * ${scale} - ${B} * ${dhdv} * ${scale});`,
106
+ ].join('\n ');
107
+ }
108
+ const height = ctx.resolveInput(this._inputs.height);
34
109
  return `vec3 ${ctx.outputVar(this, 'Normal')} = _st_bump(${height}, ${strength}, ${distance}, ${normal});`;
35
110
  }
36
111
  }
37
- Bump.instanceSpecificDef = false;
38
- Bump.glslFunction = `
39
- vec3 _st_bump(float height, float strength, float distance, vec3 N) {
40
- // Cotangent frame bump — camera-independent.
41
- // We derive world-space surface tangent T and bitangent B from the UV
42
- // parameterisation (cotangent frame). This gives vectors that are fixed
43
- // to the surface regardless of camera orientation.
44
- // Then we express the height gradient in UV space (dh/du, dh/dv) which
45
- // is also surface-fixed, eliminating the screen-space dependency.
46
- vec3 dPdx = dFdx(vPosition);
47
- vec3 dPdy = dFdy(vPosition);
48
- vec2 dUdx = dFdx(vUv);
49
- vec2 dUdy = dFdy(vUv);
50
-
51
- float det = dUdx.x * dUdy.y - dUdx.y * dUdy.x;
52
- float sgn = sign(det);
53
- float invDet = sgn / max(abs(det), 1e-6);
54
-
55
- // World-space tangent and bitangent from UV Jacobian (camera-independent)
56
- vec3 T = normalize((dUdy.y * dPdx - dUdx.y * dPdy) * invDet);
57
- vec3 B = normalize(cross(N, T)) * sgn;
58
-
59
- // Height gradient in UV space (camera-independent)
60
- float dhdu = (dUdy.y * dFdx(height) - dUdx.y * dFdy(height)) * invDet;
61
- float dhdv = (dUdx.x * dFdy(height) - dUdy.x * dFdx(height)) * invDet;
62
-
63
- float scale = strength * distance * 50.0;
64
- return normalize(N - T * dhdu * scale - B * dhdv * scale);
65
- }`;
112
+ // Both modes emit their own per-instance defs (a #ifndef-guarded shared function
113
+ // for 'derivative', a per-instance uniform declaration for 'uv-offset' — see
114
+ // compileDefs). instanceSpecificDef:true is required so multiple 'uv-offset'
115
+ // instances each get their own uniform decl instead of being deduped by nodeType.
116
+ Bump.instanceSpecificDef = true;
117
+ Bump.derivativeGlslFunction = `
118
+ #ifndef _ST_BUMP_FN
119
+ #define _ST_BUMP_FN
120
+ vec3 _st_bump(float height, float strength, float distance, vec3 N) {
121
+ // Cotangent frame bump — camera-independent.
122
+ // We derive world-space surface tangent T and bitangent B from the UV
123
+ // parameterisation (cotangent frame). This gives vectors that are fixed
124
+ // to the surface regardless of camera orientation.
125
+ // Then we express the height gradient in UV space (dh/du, dh/dv) which
126
+ // is also surface-fixed, eliminating the screen-space dependency.
127
+ vec3 dPdx = dFdx(vPosition);
128
+ vec3 dPdy = dFdy(vPosition);
129
+ vec2 dUdx = dFdx(vUv);
130
+ vec2 dUdy = dFdy(vUv);
131
+
132
+ float det = dUdx.x * dUdy.y - dUdx.y * dUdy.x;
133
+ float sgn = sign(det);
134
+ float invDet = sgn / max(abs(det), 1e-6);
135
+
136
+ // World-space tangent and bitangent from UV Jacobian (camera-independent)
137
+ vec3 T = normalize((dUdy.y * dPdx - dUdx.y * dPdy) * invDet);
138
+ vec3 B = normalize(cross(N, T)) * sgn;
139
+
140
+ // Height gradient in UV space (camera-independent)
141
+ float dhdu = (dUdy.y * dFdx(height) - dUdx.y * dFdy(height)) * invDet;
142
+ float dhdv = (dUdx.x * dFdy(height) - dUdy.x * dFdx(height)) * invDet;
143
+
144
+ float scale = strength * distance * 50.0;
145
+ return normalize(N - T * dhdu * scale - B * dhdv * scale);
146
+ }
147
+ #endif`;
@@ -33,11 +33,11 @@ export class DiffuseBSDF extends ProcessNode {
33
33
  }
34
34
  }
35
35
  DiffuseBSDF.instanceSpecificDef = false;
36
- DiffuseBSDF.glslFunction = `
37
- vec3 _st_diffuseBSDF(vec3 color, float roughness, vec3 N) {
38
- vec3 L = normalize(vec3(1.0, 2.0, 1.0));
39
- float NdotL = max(dot(N, L), 0.0);
40
- float diffuse = NdotL / 3.14159265;
41
- vec3 ambient = color * 0.15;
42
- return ambient + color * (diffuse * (1.0 - roughness * 0.5));
36
+ DiffuseBSDF.glslFunction = `
37
+ vec3 _st_diffuseBSDF(vec3 color, float roughness, vec3 N) {
38
+ vec3 L = normalize(vec3(1.0, 2.0, 1.0));
39
+ float NdotL = max(dot(N, L), 0.0);
40
+ float diffuse = NdotL / 3.14159265;
41
+ vec3 ambient = color * 0.15;
42
+ return ambient + color * (diffuse * (1.0 - roughness * 0.5));
43
43
  }`;
@@ -30,7 +30,7 @@ export class Emission extends ProcessNode {
30
30
  }
31
31
  }
32
32
  Emission.instanceSpecificDef = false;
33
- Emission.glslFunction = `
34
- vec3 _st_emission(vec3 color, float strength) {
35
- return color * strength;
33
+ Emission.glslFunction = `
34
+ vec3 _st_emission(vec3 color, float strength) {
35
+ return color * strength;
36
36
  }`;
@@ -35,17 +35,17 @@ export class GlassBSDF extends ProcessNode {
35
35
  }
36
36
  }
37
37
  GlassBSDF.instanceSpecificDef = false;
38
- GlassBSDF.glslFunction = `
39
- vec3 _st_glassBSDF(vec3 color, float roughness, float ior, vec3 N) {
40
- vec3 V = normalize(cameraPosition - vPosition);
41
- vec3 L = normalize(vec3(1.0, 2.0, 1.0));
42
- vec3 H = normalize(L + V);
43
- float F0 = pow((1.0 - ior) / (1.0 + ior), 2.0);
44
- float fresnel = F0 + (1.0 - F0) * pow(1.0 - max(dot(V, N), 0.0), 5.0);
45
- float NdotH = max(dot(N, H), 0.0);
46
- float a2 = roughness * roughness * roughness * roughness;
47
- float D = a2 / max(3.14159 * pow(NdotH * NdotH * (a2 - 1.0) + 1.0, 2.0), 0.0001);
48
- vec3 spec = vec3(1.0) * D * fresnel;
49
- vec3 refr = color * (1.0 - fresnel) * (1.0 - roughness * 0.3);
50
- return spec + refr;
38
+ GlassBSDF.glslFunction = `
39
+ vec3 _st_glassBSDF(vec3 color, float roughness, float ior, vec3 N) {
40
+ vec3 V = normalize(cameraPosition - vPosition);
41
+ vec3 L = normalize(vec3(1.0, 2.0, 1.0));
42
+ vec3 H = normalize(L + V);
43
+ float F0 = pow((1.0 - ior) / (1.0 + ior), 2.0);
44
+ float fresnel = F0 + (1.0 - F0) * pow(1.0 - max(dot(V, N), 0.0), 5.0);
45
+ float NdotH = max(dot(N, H), 0.0);
46
+ float a2 = roughness * roughness * roughness * roughness;
47
+ float D = a2 / max(3.14159 * pow(NdotH * NdotH * (a2 - 1.0) + 1.0, 2.0), 0.0001);
48
+ vec3 spec = vec3(1.0) * D * fresnel;
49
+ vec3 refr = color * (1.0 - fresnel) * (1.0 - roughness * 0.3);
50
+ return spec + refr;
51
51
  }`;
@@ -33,18 +33,18 @@ export class GlossyBSDF extends ProcessNode {
33
33
  }
34
34
  }
35
35
  GlossyBSDF.instanceSpecificDef = false;
36
- GlossyBSDF.glslFunction = `
37
- vec3 _st_glossyBSDF(vec3 color, float roughness, vec3 N) {
38
- vec3 L = normalize(vec3(1.0, 2.0, 1.0));
39
- vec3 V = normalize(cameraPosition - vPosition);
40
- vec3 H = normalize(L + V);
41
- float a = roughness * roughness;
42
- float a2 = a * a;
43
- float NdotH = max(dot(N, H), 0.0);
44
- float D = a2 / max(3.14159265 * pow(NdotH * NdotH * (a2 - 1.0) + 1.0, 2.0), 0.0001);
45
- float NdotL = max(dot(N, L), 0.0);
46
- float NdotV = max(dot(N, V), 0.0001);
47
- float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
48
- float G = (NdotL / (NdotL * (1.0 - k) + k)) * (NdotV / (NdotV * (1.0 - k) + k));
49
- return color * D * G / max(4.0 * NdotL * NdotV, 0.0001) * NdotL;
36
+ GlossyBSDF.glslFunction = `
37
+ vec3 _st_glossyBSDF(vec3 color, float roughness, vec3 N) {
38
+ vec3 L = normalize(vec3(1.0, 2.0, 1.0));
39
+ vec3 V = normalize(cameraPosition - vPosition);
40
+ vec3 H = normalize(L + V);
41
+ float a = roughness * roughness;
42
+ float a2 = a * a;
43
+ float NdotH = max(dot(N, H), 0.0);
44
+ float D = a2 / max(3.14159265 * pow(NdotH * NdotH * (a2 - 1.0) + 1.0, 2.0), 0.0001);
45
+ float NdotL = max(dot(N, L), 0.0);
46
+ float NdotV = max(dot(N, V), 0.0001);
47
+ float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
48
+ float G = (NdotL / (NdotL * (1.0 - k) + k)) * (NdotV / (NdotV * (1.0 - k) + k));
49
+ return color * D * G / max(4.0 * NdotL * NdotV, 0.0001) * NdotL;
50
50
  }`;
@@ -41,7 +41,7 @@ export class MixShader extends ProcessNode {
41
41
  }
42
42
  }
43
43
  MixShader.instanceSpecificDef = false;
44
- MixShader.glslFunction = `
45
- vec4 _st_mixShader(float fac, vec4 a, vec4 b) {
46
- return mix(a, b, clamp(fac, 0.0, 1.0));
44
+ MixShader.glslFunction = `
45
+ vec4 _st_mixShader(float fac, vec4 a, vec4 b) {
46
+ return mix(a, b, clamp(fac, 0.0, 1.0));
47
47
  }`;
@@ -48,43 +48,43 @@ export class PrincipledBSDF extends ProcessNode {
48
48
  }
49
49
  }
50
50
  PrincipledBSDF.instanceSpecificDef = false;
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;
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;
90
90
  }`;
@@ -64,74 +64,74 @@ export class PrincipledHair extends ProcessNode {
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
  }`;
@@ -43,25 +43,25 @@ export class PrincipledVolume extends ProcessNode {
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
  }`;
@@ -36,11 +36,11 @@ export class RefractionBSDF extends ProcessNode {
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
  }`;
@@ -34,16 +34,16 @@ export class SheenBSDF extends ProcessNode {
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
  }`;
@@ -36,19 +36,19 @@ export class SpecularBSDF extends ProcessNode {
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
  }`;