@chromatic-coherence/generative-engine 1.2.0 → 1.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.
package/dist/shaders.js CHANGED
@@ -5,385 +5,445 @@
5
5
  // maps, and HDR output for the post chain. No clamping in the scene pass — light
6
6
  // is allowed to exceed 1.0 and the composite tone-maps it.
7
7
  // ── shared vertex preamble: 8 groups, each with a mat4, normal mat3, morph weight, alpha ──
8
- const GROUPS = `
8
+ const GROUPS = `
9
9
  uniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];`;
10
- export const FACE_VS = `#version 300 es
11
- in vec3 aPos; in vec3 aNorm; in vec4 aCol; in float aBlood; in float aGrp;
12
- in vec3 aPos2; in vec3 aNorm2;
13
- uniform mat4 uVP; uniform mat4 uSVP;${GROUPS}
14
- out vec3 vPos; out vec3 vNorm; out vec4 vCol; out float vZ; out vec4 vSh;
15
- out float vGA; out float vBlood;
16
- void main(){
17
- int g = int(aGrp + 0.5);
18
- float w = uMW[g];
19
- vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);
20
- vec4 wp = uGM[g] * vec4(p, 1.0);
21
- vPos = wp.xyz; vNorm = uNM[g] * n; vCol = aCol; vGA = uGA[g]; vBlood = aBlood;
22
- vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;
10
+ export const FACE_VS = `#version 300 es
11
+ in vec3 aPos; in vec3 aNorm; in vec4 aCol; in float aBlood; in float aGrp;
12
+ in vec3 aPos2; in vec3 aNorm2;
13
+ uniform mat4 uVP; uniform mat4 uSVP;${GROUPS}
14
+ out vec3 vPos; out vec3 vNorm; out vec4 vCol; out float vZ; out vec4 vSh;
15
+ out float vGA; out float vBlood;
16
+ void main(){
17
+ int g = int(aGrp + 0.5);
18
+ float w = uMW[g];
19
+ vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);
20
+ vec4 wp = uGM[g] * vec4(p, 1.0);
21
+ vPos = wp.xyz; vNorm = uNM[g] * n; vCol = aCol; vGA = uGA[g]; vBlood = aBlood;
22
+ vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;
23
23
  }`;
24
- export const FACE_FS = `#version 300 es
25
- precision highp float;
26
- in vec3 vPos; in vec3 vNorm; in vec4 vCol; in float vZ; in vec4 vSh;
27
- in float vGA; in float vBlood;
28
- uniform vec3 uLdir; uniform float uAmb; uniform float uDif; uniform float uSigned;
29
- uniform vec3 uLp[8]; uniform float uLi[8]; uniform float uLf[8]; uniform int uLn;
30
- uniform vec3 uCam; uniform float uFogK; uniform float uSpec; uniform vec3 uBg;
31
- uniform vec3 uSkyC; uniform vec3 uGroundC; uniform float uPulse;
32
- uniform float uShadowOn; uniform float uShadowSoft; uniform float uShadowBias;
33
- uniform lowp sampler2DShadow uShadow; uniform float uShadowPx;
34
- out vec4 fragColor;
35
- float h3(vec3 p){ return fract(sin(dot(p, vec3(12.9898, 78.233, 37.719))) * 43758.5453); }
36
- float vnoise(vec3 p){ vec3 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f);
37
- return mix(mix(mix(h3(i), h3(i + vec3(1,0,0)), f.x), mix(h3(i + vec3(0,1,0)), h3(i + vec3(1,1,0)), f.x), f.y),
38
- mix(mix(h3(i + vec3(0,0,1)), h3(i + vec3(1,0,1)), f.x), mix(h3(i + vec3(0,1,1)), h3(i + vec3(1,1,1)), f.x), f.y), f.z); }
39
- float shadowFactor(){
40
- vec3 pr = vSh.xyz / vSh.w * 0.5 + 0.5;
41
- if (pr.x < 0.0 || pr.x > 1.0 || pr.y < 0.0 || pr.y > 1.0 || pr.z > 1.0) return 1.0;
42
- float ref = pr.z - uShadowBias;
43
- // 3x3 taps over a hardware-compared (LINEAR sampler2DShadow => free 2x2 PCF each) map
44
- float s = 0.0;
45
- for (int sy = -1; sy <= 1; sy++)
46
- for (int sx = -1; sx <= 1; sx++)
47
- s += texture(uShadow, vec3(pr.xy + vec2(float(sx), float(sy)) * uShadowPx * uShadowSoft, ref));
48
- s /= 9.0;
49
- return 0.35 + 0.65 * s;
50
- }
51
- void main(){
52
- vec3 n = normalize(vNorm);
53
- vec3 V = normalize(uCam - vPos);
54
- float skin = clamp(vBlood * 5.0, 0.0, 1.0);
55
- { // procedural micro-relief: perturb the shading normal by a fine noise gradient
56
- float bs = 5.5, e = 0.12, n0 = vnoise(vPos * bs);
57
- vec3 g = vec3(vnoise(vPos * bs + vec3(e, 0.0, 0.0)) - n0,
58
- vnoise(vPos * bs + vec3(0.0, e, 0.0)) - n0,
59
- vnoise(vPos * bs + vec3(0.0, 0.0, e)) - n0) / e;
60
- n = normalize(n - g * (0.10 + 0.30 * skin));
61
- }
62
- float d = dot(n, normalize(uLdir));
63
- float shade = uAmb + uDif * mix(abs(d), max(d, 0.0), uSigned);
64
- float spec = 0.0;
65
- float sh = uShadowOn > 0.5 ? shadowFactor() : 1.0;
66
- for (int i = 0; i < 8; i++) { if (i >= uLn) break;
67
- vec3 v = uLp[i] - vPos; float d2 = dot(v, v); vec3 L = v * inversesqrt(d2 + 1e-4);
68
- float lam = dot(n, L); lam = mix(abs(lam), max(lam, 0.0), uSigned);
69
- float fall = 1.0 / (1.0 + d2 / (uLf[i] * uLf[i]));
70
- float k = (i == 0) ? sh : 1.0;
71
- shade += uLi[i] * lam * fall * k;
72
- // GGX microfacet + Fresnel — roughness from the face's gloss (colour alpha slot)
73
- vec3 H = normalize(L + V);
74
- float nh = max(dot(n, H), 0.0);
75
- float rough = clamp(1.0 - vCol.a, 0.08, 1.0), a2 = rough * rough; a2 = a2 * a2;
76
- float dnm = nh * nh * (a2 - 1.0) + 1.0;
77
- float ggx = a2 / (3.14159 * dnm * dnm);
78
- float fres = 0.04 + 0.96 * pow(1.0 - max(dot(H, V), 0.0), 5.0);
79
- spec += uLi[i] * min(ggx, 6.0) * fres * fall * k * 0.16;
80
- }
81
- // coloured hemispheric ambient: cool from above, warm from below
82
- vec3 tint = mix(uGroundC, uSkyC, n.y * 0.5 + 0.5);
83
- vec3 lit = vCol.rgb * (uAmb * tint + vec3(shade - uAmb));
84
- // the perfusion term: blood near the surface changes how it meets the light
85
- float b = clamp(vBlood + uPulse * vBlood * 6.0, 0.0, 1.0);
86
- lit *= mix(vec3(1.0), vec3(1.07, 0.95, 0.94), b);
87
- lit += vec3(0.62, 0.06, 0.08) * (b * 0.20 * (1.0 - abs(d)));
88
- // pore noise, only where there is blood (skin, not cloth)
89
- float pore = vnoise(vPos * 2.4) * 0.6 + vnoise(vPos * 6.0) * 0.4;
90
- lit *= 1.0 + (pore - 0.5) * 0.09 * skin;
91
- // subsurface rim: grazing light bleeds through thin tissue, warm
92
- float fres = pow(1.0 - clamp(dot(n, V), 0.0, 1.0), 3.0);
93
- lit += vec3(0.55, 0.09, 0.10) * fres * skin * (0.28 + 0.5 * b);
94
- float fog = clamp(uFogK / vZ, 0.08, 1.0);
95
- vec3 outC = lit + vec3(1.0) * spec * uSpec * vCol.a;
96
- // fog and fades resolve toward the REAL background colour (the engine owns its ground)
97
- fragColor = vec4(mix(uBg, outC, fog * vGA), 1.0);
24
+ export const FACE_FS = `#version 300 es
25
+ precision highp float;
26
+ in vec3 vPos; in vec3 vNorm; in vec4 vCol; in float vZ; in vec4 vSh;
27
+ in float vGA; in float vBlood;
28
+ uniform vec3 uLdir; uniform float uAmb; uniform float uDif; uniform float uSigned;
29
+ uniform vec3 uLp[8]; uniform float uLi[8]; uniform float uLf[8]; uniform int uLn;
30
+ uniform vec3 uCam; uniform float uFogK; uniform float uSpec; uniform vec3 uBg;
31
+ uniform vec3 uSkyC; uniform vec3 uGroundC; uniform float uPulse;
32
+ uniform float uShadowOn; uniform float uShadowSoft; uniform float uShadowBias;
33
+ uniform lowp sampler2DShadow uShadow; uniform float uShadowPx;
34
+ out vec4 fragColor;
35
+ float h3(vec3 p){ return fract(sin(dot(p, vec3(12.9898, 78.233, 37.719))) * 43758.5453); }
36
+ float vnoise(vec3 p){ vec3 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f);
37
+ return mix(mix(mix(h3(i), h3(i + vec3(1,0,0)), f.x), mix(h3(i + vec3(0,1,0)), h3(i + vec3(1,1,0)), f.x), f.y),
38
+ mix(mix(h3(i + vec3(0,0,1)), h3(i + vec3(1,0,1)), f.x), mix(h3(i + vec3(0,1,1)), h3(i + vec3(1,1,1)), f.x), f.y), f.z); }
39
+ float shadowFactor(){
40
+ vec3 pr = vSh.xyz / vSh.w * 0.5 + 0.5;
41
+ if (pr.x < 0.0 || pr.x > 1.0 || pr.y < 0.0 || pr.y > 1.0 || pr.z > 1.0) return 1.0;
42
+ float ref = pr.z - uShadowBias;
43
+ // 3x3 taps over a hardware-compared (LINEAR sampler2DShadow => free 2x2 PCF each) map
44
+ float s = 0.0;
45
+ for (int sy = -1; sy <= 1; sy++)
46
+ for (int sx = -1; sx <= 1; sx++)
47
+ s += texture(uShadow, vec3(pr.xy + vec2(float(sx), float(sy)) * uShadowPx * uShadowSoft, ref));
48
+ s /= 9.0;
49
+ return 0.35 + 0.65 * s;
50
+ }
51
+ void main(){
52
+ vec3 n = normalize(vNorm);
53
+ vec3 V = normalize(uCam - vPos);
54
+ float skin = clamp(vBlood * 5.0, 0.0, 1.0);
55
+ { // procedural micro-relief: perturb the shading normal by a fine noise gradient
56
+ float bs = 5.5, e = 0.12, n0 = vnoise(vPos * bs);
57
+ vec3 g = vec3(vnoise(vPos * bs + vec3(e, 0.0, 0.0)) - n0,
58
+ vnoise(vPos * bs + vec3(0.0, e, 0.0)) - n0,
59
+ vnoise(vPos * bs + vec3(0.0, 0.0, e)) - n0) / e;
60
+ n = normalize(n - g * (0.10 + 0.30 * skin));
61
+ }
62
+ float d = dot(n, normalize(uLdir));
63
+ float shade = uAmb + uDif * mix(abs(d), max(d, 0.0), uSigned);
64
+ float spec = 0.0;
65
+ float sh = uShadowOn > 0.5 ? shadowFactor() : 1.0;
66
+ for (int i = 0; i < 8; i++) { if (i >= uLn) break;
67
+ vec3 v = uLp[i] - vPos; float d2 = dot(v, v); vec3 L = v * inversesqrt(d2 + 1e-4);
68
+ float lam = dot(n, L); lam = mix(abs(lam), max(lam, 0.0), uSigned);
69
+ float fall = 1.0 / (1.0 + d2 / (uLf[i] * uLf[i]));
70
+ float k = (i == 0) ? sh : 1.0;
71
+ shade += uLi[i] * lam * fall * k;
72
+ // GGX microfacet + Fresnel — roughness from the face's gloss (colour alpha slot)
73
+ vec3 H = normalize(L + V);
74
+ float nh = max(dot(n, H), 0.0);
75
+ float rough = clamp(1.0 - vCol.a, 0.08, 1.0), a2 = rough * rough; a2 = a2 * a2;
76
+ float dnm = nh * nh * (a2 - 1.0) + 1.0;
77
+ float ggx = a2 / (3.14159 * dnm * dnm);
78
+ float fres = 0.04 + 0.96 * pow(1.0 - max(dot(H, V), 0.0), 5.0);
79
+ spec += uLi[i] * min(ggx, 6.0) * fres * fall * k * 0.16;
80
+ }
81
+ // coloured hemispheric ambient: cool from above, warm from below
82
+ vec3 tint = mix(uGroundC, uSkyC, n.y * 0.5 + 0.5);
83
+ vec3 lit = vCol.rgb * (uAmb * tint + vec3(shade - uAmb));
84
+ // the perfusion term: blood near the surface changes how it meets the light
85
+ float b = clamp(vBlood + uPulse * vBlood * 6.0, 0.0, 1.0);
86
+ lit *= mix(vec3(1.0), vec3(1.07, 0.95, 0.94), b);
87
+ lit += vec3(0.62, 0.06, 0.08) * (b * 0.20 * (1.0 - abs(d)));
88
+ // pore noise, only where there is blood (skin, not cloth)
89
+ float pore = vnoise(vPos * 2.4) * 0.6 + vnoise(vPos * 6.0) * 0.4;
90
+ lit *= 1.0 + (pore - 0.5) * 0.09 * skin;
91
+ // subsurface rim: grazing light bleeds through thin tissue, warm
92
+ float fres = pow(1.0 - clamp(dot(n, V), 0.0, 1.0), 3.0);
93
+ lit += vec3(0.55, 0.09, 0.10) * fres * skin * (0.28 + 0.5 * b);
94
+ float fog = clamp(uFogK / vZ, 0.08, 1.0);
95
+ vec3 outC = lit + vec3(1.0) * spec * uSpec * vCol.a;
96
+ // fog and fades resolve toward the REAL background colour (the engine owns its ground)
97
+ fragColor = vec4(mix(uBg, outC, fog * vGA), 1.0);
98
98
  }`;
99
99
  // depth-only pass (shadow map) — same morph + group transforms, no colour output
100
100
  // instanced face VS (W8): the per-instance model matrix arrives as 4 vec4 attributes
101
101
  // (divisor 1) and composes INSIDE the group transform — instances ride their group's
102
102
  // fade / morph / transform like any other geometry. Normals use mat3(instance); with
103
103
  // rotation + uniform scale that is exact (the normalize() in the FS absorbs scale).
104
- export const FACE_INST_VS = `#version 300 es
105
- in vec3 aPos; in vec3 aNorm; in vec4 aCol; in float aBlood; in float aGrp;
106
- in vec3 aPos2; in vec3 aNorm2;
107
- in vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3;
108
- uniform mat4 uVP; uniform mat4 uSVP;${GROUPS}
109
- out vec3 vPos; out vec3 vNorm; out vec4 vCol; out float vZ; out vec4 vSh;
110
- out float vGA; out float vBlood;
111
- void main(){
112
- int g = int(aGrp + 0.5);
113
- float w = uMW[g];
114
- mat4 inst = mat4(aI0, aI1, aI2, aI3);
115
- vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);
116
- vec4 wp = uGM[g] * (inst * vec4(p, 1.0));
117
- vPos = wp.xyz; vNorm = uNM[g] * (mat3(inst) * n); vCol = aCol; vGA = uGA[g]; vBlood = aBlood;
118
- vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;
104
+ export const FACE_INST_VS = `#version 300 es
105
+ in vec3 aPos; in vec3 aNorm; in vec4 aCol; in float aBlood; in float aGrp;
106
+ in vec3 aPos2; in vec3 aNorm2;
107
+ in vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3; in vec4 aIP;
108
+ uniform mat4 uVP; uniform mat4 uSVP; uniform float uT; uniform vec3 uWindDir;
109
+ uniform float uFlight;${GROUPS}
110
+ out vec3 vPos; out vec3 vNorm; out vec4 vCol; out float vZ; out vec4 vSh;
111
+ out float vGA; out float vBlood;
112
+ // W10 FLIGHT: aIP = (a0, r0, ph, lobe). The flock's heart wanders, three lobes orbit
113
+ // it breathing apart and back, each bird orbits its lobe with a ripple phase — the
114
+ // murmuration, evaluated per vertex from uT alone. The instance matrix is the flock
115
+ // frame (shared), so one batch = one flock you can place, scale and steer.
116
+ vec3 flightPos(vec4 ip, float t){
117
+ float a0 = ip.x, r0 = ip.y, ph = ip.z, lobe = ip.w;
118
+ float cx = sin(t * 0.11) * 700.0, cz = -500.0 + cos(t * 0.07) * 380.0;
119
+ float cy = 430.0 + sin(t * 0.16) * 110.0;
120
+ float spread = 180.0 + 140.0 * sin(t * 0.21 + 1.3);
121
+ float la = t * 0.34 + lobe * 2.0944;
122
+ vec3 lc = vec3(cx + cos(la) * spread, cy + sin(la * 2.0 + lobe) * 60.0, cz + sin(la) * spread * 0.7);
123
+ float aa = a0 + t * (0.5 + 0.25 * sin(ph));
124
+ float wob = sin(t * 1.1 + ph) * 0.35;
125
+ float rr = r0 * (0.75 + 0.25 * sin(t * 0.5 + ph + lobe));
126
+ return lc + vec3(cos(aa) * rr, sin(aa * 1.7 + ph) * rr * 0.32 + wob * 24.0, sin(aa) * rr * (0.55 + wob * 0.2));
127
+ }
128
+ vec3 flightLocal(vec3 pl, vec4 ip, float t){
129
+ float aa = ip.x + t * (0.5 + 0.25 * sin(ip.z));
130
+ pl.y += sin(t * 11.0 + ip.z * 3.0) * abs(pl.z) * 0.9; // the flap, growing to the wingtips
131
+ float ca = cos(aa + 1.5708), sa = sin(aa + 1.5708); // heading = the orbit tangent
132
+ return vec3(pl.x * ca - pl.z * sa, pl.y, pl.x * sa + pl.z * ca);
133
+ }
134
+ void main(){
135
+ int g = int(aGrp + 0.5);
136
+ float w = uMW[g];
137
+ mat4 inst = mat4(aI0, aI1, aI2, aI3);
138
+ vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);
139
+ if (uFlight > 0.5) {
140
+ p = flightPos(aIP, uT) + flightLocal(p, aIP, uT);
141
+ } else {
142
+ // W9 GPU WIND: per-instance (phase, freq, amp); displacement grows with height,
143
+ // plus a second harmonic so the sway breathes instead of ticking
144
+ float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);
145
+ p += uWindDir * (sw * aIP.z * max(p.y, 0.0));
146
+ }
147
+ vec4 wp = uGM[g] * (inst * vec4(p, 1.0));
148
+ vPos = wp.xyz; vNorm = uNM[g] * (mat3(inst) * n); vCol = aCol; vGA = uGA[g]; vBlood = aBlood;
149
+ vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;
119
150
  }`;
120
- export const DEPTH_INST_VS = `#version 300 es
121
- in vec3 aPos; in float aGrp; in vec3 aPos2;
122
- in vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3;
123
- uniform mat4 uVP;${GROUPS}
124
- void main(){
125
- int g = int(aGrp + 0.5);
126
- vec3 p = mix(aPos, aPos2, uMW[g]);
127
- gl_Position = uVP * (uGM[g] * (mat4(aI0, aI1, aI2, aI3) * vec4(p, 1.0)));
151
+ export const DEPTH_INST_VS = `#version 300 es
152
+ in vec3 aPos; in float aGrp; in vec3 aPos2;
153
+ in vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3; in vec4 aIP;
154
+ uniform mat4 uVP; uniform float uT; uniform vec3 uWindDir;
155
+ uniform float uFlight;${GROUPS}
156
+ // W10 FLIGHT: aIP = (a0, r0, ph, lobe). The flock's heart wanders, three lobes orbit
157
+ // it breathing apart and back, each bird orbits its lobe with a ripple phase — the
158
+ // murmuration, evaluated per vertex from uT alone. The instance matrix is the flock
159
+ // frame (shared), so one batch = one flock you can place, scale and steer.
160
+ vec3 flightPos(vec4 ip, float t){
161
+ float a0 = ip.x, r0 = ip.y, ph = ip.z, lobe = ip.w;
162
+ float cx = sin(t * 0.11) * 700.0, cz = -500.0 + cos(t * 0.07) * 380.0;
163
+ float cy = 430.0 + sin(t * 0.16) * 110.0;
164
+ float spread = 180.0 + 140.0 * sin(t * 0.21 + 1.3);
165
+ float la = t * 0.34 + lobe * 2.0944;
166
+ vec3 lc = vec3(cx + cos(la) * spread, cy + sin(la * 2.0 + lobe) * 60.0, cz + sin(la) * spread * 0.7);
167
+ float aa = a0 + t * (0.5 + 0.25 * sin(ph));
168
+ float wob = sin(t * 1.1 + ph) * 0.35;
169
+ float rr = r0 * (0.75 + 0.25 * sin(t * 0.5 + ph + lobe));
170
+ return lc + vec3(cos(aa) * rr, sin(aa * 1.7 + ph) * rr * 0.32 + wob * 24.0, sin(aa) * rr * (0.55 + wob * 0.2));
171
+ }
172
+ vec3 flightLocal(vec3 pl, vec4 ip, float t){
173
+ float aa = ip.x + t * (0.5 + 0.25 * sin(ip.z));
174
+ pl.y += sin(t * 11.0 + ip.z * 3.0) * abs(pl.z) * 0.9; // the flap, growing to the wingtips
175
+ float ca = cos(aa + 1.5708), sa = sin(aa + 1.5708); // heading = the orbit tangent
176
+ return vec3(pl.x * ca - pl.z * sa, pl.y, pl.x * sa + pl.z * ca);
177
+ }
178
+ void main(){
179
+ int g = int(aGrp + 0.5);
180
+ vec3 p = mix(aPos, aPos2, uMW[g]);
181
+ if (uFlight > 0.5) {
182
+ p = flightPos(aIP, uT) + flightLocal(p, aIP, uT);
183
+ } else {
184
+ float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);
185
+ p += uWindDir * (sw * aIP.z * max(p.y, 0.0));
186
+ }
187
+ gl_Position = uVP * (uGM[g] * (mat4(aI0, aI1, aI2, aI3) * vec4(p, 1.0)));
128
188
  }`;
129
- export const DEPTH_VS = `#version 300 es
130
- in vec3 aPos; in float aGrp; in vec3 aPos2;
131
- uniform mat4 uVP;${GROUPS}
132
- void main(){
133
- int g = int(aGrp + 0.5);
134
- vec3 p = mix(aPos, aPos2, uMW[g]);
135
- gl_Position = uVP * (uGM[g] * vec4(p, 1.0));
189
+ export const DEPTH_VS = `#version 300 es
190
+ in vec3 aPos; in float aGrp; in vec3 aPos2;
191
+ uniform mat4 uVP;${GROUPS}
192
+ void main(){
193
+ int g = int(aGrp + 0.5);
194
+ vec3 p = mix(aPos, aPos2, uMW[g]);
195
+ gl_Position = uVP * (uGM[g] * vec4(p, 1.0));
136
196
  }`;
137
- export const DEPTH_FS = `#version 300 es
138
- precision mediump float;
197
+ export const DEPTH_FS = `#version 300 es
198
+ precision mediump float;
139
199
  void main(){}`;
140
- export const LINE_VS = `#version 300 es
141
- in vec3 aPos; in vec4 aCol; in float aGrp;
142
- uniform mat4 uVP;${GROUPS}
143
- out vec4 vCol; out float vZ; out float vGA;
144
- void main(){
145
- int g = int(aGrp + 0.5);
146
- vCol = aCol; vGA = uGA[g];
147
- vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w; gl_Position = p;
200
+ export const LINE_VS = `#version 300 es
201
+ in vec3 aPos; in vec4 aCol; in float aGrp;
202
+ uniform mat4 uVP;${GROUPS}
203
+ out vec4 vCol; out float vZ; out float vGA;
204
+ void main(){
205
+ int g = int(aGrp + 0.5);
206
+ vCol = aCol; vGA = uGA[g];
207
+ vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w; gl_Position = p;
148
208
  }`;
149
- export const LINE_FS = `#version 300 es
150
- precision mediump float;
151
- in vec4 vCol; in float vZ; in float vGA;
152
- uniform float uFogK; uniform float uInk;
153
- out vec4 fragColor;
154
- void main(){
155
- float fog = clamp(uFogK / vZ, 0.08, 1.0);
156
- if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, vCol.a * 1.6) * fog * vGA);
157
- else fragColor = vec4(vCol.rgb * fog, vCol.a * fog * vGA);
209
+ export const LINE_FS = `#version 300 es
210
+ precision mediump float;
211
+ in vec4 vCol; in float vZ; in float vGA;
212
+ uniform float uFogK; uniform float uInk;
213
+ out vec4 fragColor;
214
+ void main(){
215
+ float fog = clamp(uFogK / vZ, 0.08, 1.0);
216
+ if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, vCol.a * 1.6) * fog * vGA);
217
+ else fragColor = vec4(vCol.rgb * fog, vCol.a * fog * vGA);
158
218
  }`;
159
- export const PT_VS = `#version 300 es
160
- in vec3 aPos; in float aSize; in vec4 aCol; in float aGrp;
161
- uniform mat4 uVP; uniform float uFocal;${GROUPS}
162
- out vec4 vCol; out float vZ; out float vGA;
163
- void main(){
164
- int g = int(aGrp + 0.5);
165
- vCol = aCol; vGA = uGA[g];
166
- vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w;
167
- gl_PointSize = clamp(aSize * uFocal / p.w, 1.0, 256.0); gl_Position = p;
219
+ export const PT_VS = `#version 300 es
220
+ in vec3 aPos; in float aSize; in vec4 aCol; in float aGrp;
221
+ uniform mat4 uVP; uniform float uFocal;${GROUPS}
222
+ out vec4 vCol; out float vZ; out float vGA;
223
+ void main(){
224
+ int g = int(aGrp + 0.5);
225
+ vCol = aCol; vGA = uGA[g];
226
+ vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w;
227
+ gl_PointSize = clamp(aSize * uFocal / p.w, 1.0, 256.0); gl_Position = p;
168
228
  }`;
169
- export const PT_FS = `#version 300 es
170
- precision mediump float;
171
- in vec4 vCol; in float vZ; in float vGA;
172
- uniform float uFogK; uniform float uInk;
173
- out vec4 fragColor;
174
- void main(){
175
- float r = length(gl_PointCoord - 0.5) * 2.0;
176
- float a = exp(-3.2 * r * r) * (1.0 - smoothstep(0.85, 1.0, r));
177
- float fog = clamp(uFogK / vZ, 0.08, 1.0);
178
- if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, vCol.a * 1.6) * a * fog * vGA);
179
- else fragColor = vec4(vCol.rgb, 1.0) * (vCol.a * a * fog * vGA);
229
+ export const PT_FS = `#version 300 es
230
+ precision mediump float;
231
+ in vec4 vCol; in float vZ; in float vGA;
232
+ uniform float uFogK; uniform float uInk;
233
+ out vec4 fragColor;
234
+ void main(){
235
+ float r = length(gl_PointCoord - 0.5) * 2.0;
236
+ float a = exp(-3.2 * r * r) * (1.0 - smoothstep(0.85, 1.0, r));
237
+ float fog = clamp(uFogK / vZ, 0.08, 1.0);
238
+ if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, vCol.a * 1.6) * a * fog * vGA);
239
+ else fragColor = vec4(vCol.rgb, 1.0) * (vCol.a * a * fog * vGA);
180
240
  }`;
181
241
  // ── ribbons: screen-space-expanded strokes with real pixel width ──
182
242
  // Each vertex carries its neighbours; the direction is the central difference in
183
243
  // screen space, so joins round themselves. aSide is -1/+1; the fragment feathers
184
244
  // the edge for free antialiasing.
185
- export const RIBBON_VS = `#version 300 es
186
- in vec3 aPos; in vec3 aPrev; in vec3 aNext; in float aSide; in float aWidth;
187
- in vec4 aCol; in float aGrp;
188
- uniform mat4 uVP; uniform vec2 uRes;${GROUPS}
189
- out vec4 vCol; out float vZ; out float vGA; out float vSide;
190
- void main(){
191
- int g = int(aGrp + 0.5);
192
- mat4 M = uGM[g];
193
- vec4 cp = uVP * (M * vec4(aPos, 1.0));
194
- vec4 cq = uVP * (M * vec4(aPrev, 1.0));
195
- vec4 cr = uVP * (M * vec4(aNext, 1.0));
196
- vec2 sp = cp.xy / max(cp.w, 1e-4) * uRes;
197
- vec2 sq = cq.xy / max(cq.w, 1e-4) * uRes;
198
- vec2 sr = cr.xy / max(cr.w, 1e-4) * uRes;
199
- vec2 dir = sr - sq;
200
- float dl = length(dir);
201
- dir = dl > 1e-3 ? dir / dl : vec2(1.0, 0.0);
202
- vec2 perp = vec2(-dir.y, dir.x);
203
- vec2 off = perp * aWidth * 0.5 * aSide;
204
- cp.xy += off / uRes * cp.w * 2.0;
205
- vCol = aCol; vZ = cp.w; vGA = uGA[g]; vSide = aSide;
206
- gl_Position = cp;
245
+ export const RIBBON_VS = `#version 300 es
246
+ in vec3 aPos; in vec3 aPrev; in vec3 aNext; in float aSide; in float aWidth;
247
+ in vec4 aCol; in float aGrp;
248
+ uniform mat4 uVP; uniform vec2 uRes;${GROUPS}
249
+ out vec4 vCol; out float vZ; out float vGA; out float vSide;
250
+ void main(){
251
+ int g = int(aGrp + 0.5);
252
+ mat4 M = uGM[g];
253
+ vec4 cp = uVP * (M * vec4(aPos, 1.0));
254
+ vec4 cq = uVP * (M * vec4(aPrev, 1.0));
255
+ vec4 cr = uVP * (M * vec4(aNext, 1.0));
256
+ vec2 sp = cp.xy / max(cp.w, 1e-4) * uRes;
257
+ vec2 sq = cq.xy / max(cq.w, 1e-4) * uRes;
258
+ vec2 sr = cr.xy / max(cr.w, 1e-4) * uRes;
259
+ vec2 dir = sr - sq;
260
+ float dl = length(dir);
261
+ dir = dl > 1e-3 ? dir / dl : vec2(1.0, 0.0);
262
+ vec2 perp = vec2(-dir.y, dir.x);
263
+ vec2 off = perp * aWidth * 0.5 * aSide;
264
+ cp.xy += off / uRes * cp.w * 2.0;
265
+ vCol = aCol; vZ = cp.w; vGA = uGA[g]; vSide = aSide;
266
+ gl_Position = cp;
207
267
  }`;
208
- export const RIBBON_FS = `#version 300 es
209
- precision mediump float;
210
- in vec4 vCol; in float vZ; in float vGA; in float vSide;
211
- uniform float uFogK; uniform float uInk;
212
- out vec4 fragColor;
213
- void main(){
214
- float edge = 1.0 - smoothstep(0.55, 1.0, abs(vSide));
215
- float fog = clamp(uFogK / vZ, 0.08, 1.0);
216
- float a = vCol.a * edge * fog * vGA;
217
- if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, a * 1.6));
218
- else fragColor = vec4(vCol.rgb * a, a);
268
+ export const RIBBON_FS = `#version 300 es
269
+ precision mediump float;
270
+ in vec4 vCol; in float vZ; in float vGA; in float vSide;
271
+ uniform float uFogK; uniform float uInk;
272
+ out vec4 fragColor;
273
+ void main(){
274
+ float edge = 1.0 - smoothstep(0.55, 1.0, abs(vSide));
275
+ float fog = clamp(uFogK / vZ, 0.08, 1.0);
276
+ float a = vCol.a * edge * fog * vGA;
277
+ if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, a * 1.6));
278
+ else fragColor = vec4(vCol.rgb * a, a);
219
279
  }`;
220
280
  // ── post chain ──
221
281
  // fullscreen triangle, attribute-less (gl_VertexID)
222
- export const FST_VS = `#version 300 es
223
- out vec2 vUv;
224
- void main(){
225
- vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
226
- vUv = p; gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
282
+ export const FST_VS = `#version 300 es
283
+ out vec2 vUv;
284
+ void main(){
285
+ vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
286
+ vUv = p; gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
227
287
  }`;
228
- export const BRIGHT_FS = `#version 300 es
229
- precision mediump float;
230
- in vec2 vUv; uniform sampler2D uTex; uniform float uThresh; uniform float uKnee;
231
- out vec4 fragColor;
232
- void main(){
233
- vec3 c = texture(uTex, vUv).rgb;
234
- float luma = max(c.r, max(c.g, c.b));
235
- float w = smoothstep(uThresh, uThresh + uKnee, luma);
236
- fragColor = vec4(c * w, 1.0);
288
+ export const BRIGHT_FS = `#version 300 es
289
+ precision mediump float;
290
+ in vec2 vUv; uniform sampler2D uTex; uniform float uThresh; uniform float uKnee;
291
+ out vec4 fragColor;
292
+ void main(){
293
+ vec3 c = texture(uTex, vUv).rgb;
294
+ float luma = max(c.r, max(c.g, c.b));
295
+ float w = smoothstep(uThresh, uThresh + uKnee, luma);
296
+ fragColor = vec4(c * w, 1.0);
237
297
  }`;
238
- export const BLUR_FS = `#version 300 es
239
- precision mediump float;
240
- in vec2 vUv; uniform sampler2D uTex; uniform vec2 uDir;
241
- out vec4 fragColor;
242
- void main(){
243
- vec3 s = texture(uTex, vUv).rgb * 0.227027;
244
- vec2 o1 = uDir * 1.3846153846, o2 = uDir * 3.2307692308;
245
- s += (texture(uTex, vUv + o1).rgb + texture(uTex, vUv - o1).rgb) * 0.3162162162;
246
- s += (texture(uTex, vUv + o2).rgb + texture(uTex, vUv - o2).rgb) * 0.0702702703;
247
- fragColor = vec4(s, 1.0);
298
+ export const BLUR_FS = `#version 300 es
299
+ precision mediump float;
300
+ in vec2 vUv; uniform sampler2D uTex; uniform vec2 uDir;
301
+ out vec4 fragColor;
302
+ void main(){
303
+ vec3 s = texture(uTex, vUv).rgb * 0.227027;
304
+ vec2 o1 = uDir * 1.3846153846, o2 = uDir * 3.2307692308;
305
+ s += (texture(uTex, vUv + o1).rgb + texture(uTex, vUv - o1).rgb) * 0.3162162162;
306
+ s += (texture(uTex, vUv + o2).rgb + texture(uTex, vUv - o2).rgb) * 0.0702702703;
307
+ fragColor = vec4(s, 1.0);
248
308
  }`;
249
- export const SSAO_FS = `#version 300 es
250
- precision highp float;
251
- in vec2 vUv; uniform sampler2D uDepth;
252
- uniform float uNear; uniform float uFar; uniform float uRadius; uniform vec2 uRes;
253
- out vec4 fragColor;
254
- float viewZ(vec2 uv){
255
- float d = texture(uDepth, uv).r * 2.0 - 1.0;
256
- return 2.0 * uNear * uFar / (uFar + uNear - d * (uFar - uNear));
257
- }
258
- float hash(vec2 p){ return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
259
- void main(){
260
- float z0 = viewZ(vUv);
261
- // screen-space sample radius shrinks with distance (a world-ish radius)
262
- float rad = uRadius / z0;
263
- float ang = hash(vUv * uRes) * 6.2831853;
264
- float occ = 0.0;
265
- for (int i = 0; i < 12; i++) {
266
- float fi = float(i);
267
- float a = ang + fi * 2.3999632; // golden-angle spiral
268
- float r = rad * (0.25 + 0.75 * fract(fi * 0.618034));
269
- vec2 uv = vUv + vec2(cos(a), sin(a)) * r;
270
- float dz = z0 - viewZ(uv); // positive => sample nearer camera => occluder
271
- occ += smoothstep(z0 * 0.002, z0 * 0.02, dz) * smoothstep(z0 * 0.35, 0.0, dz);
272
- }
273
- float ao = 1.0 - occ / 12.0;
274
- fragColor = vec4(vec3(ao), 1.0);
309
+ export const SSAO_FS = `#version 300 es
310
+ precision highp float;
311
+ in vec2 vUv; uniform sampler2D uDepth;
312
+ uniform float uNear; uniform float uFar; uniform float uRadius; uniform vec2 uRes;
313
+ out vec4 fragColor;
314
+ float viewZ(vec2 uv){
315
+ float d = texture(uDepth, uv).r * 2.0 - 1.0;
316
+ return 2.0 * uNear * uFar / (uFar + uNear - d * (uFar - uNear));
317
+ }
318
+ float hash(vec2 p){ return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
319
+ void main(){
320
+ float z0 = viewZ(vUv);
321
+ // screen-space sample radius shrinks with distance (a world-ish radius)
322
+ float rad = uRadius / z0;
323
+ float ang = hash(vUv * uRes) * 6.2831853;
324
+ float occ = 0.0;
325
+ for (int i = 0; i < 12; i++) {
326
+ float fi = float(i);
327
+ float a = ang + fi * 2.3999632; // golden-angle spiral
328
+ float r = rad * (0.25 + 0.75 * fract(fi * 0.618034));
329
+ vec2 uv = vUv + vec2(cos(a), sin(a)) * r;
330
+ float dz = z0 - viewZ(uv); // positive => sample nearer camera => occluder
331
+ occ += smoothstep(z0 * 0.002, z0 * 0.02, dz) * smoothstep(z0 * 0.35, 0.0, dz);
332
+ }
333
+ float ao = 1.0 - occ / 12.0;
334
+ fragColor = vec4(vec3(ao), 1.0);
275
335
  }`;
276
- export const AOBLUR_FS = `#version 300 es
277
- precision mediump float;
278
- in vec2 vUv; uniform sampler2D uTex; uniform vec2 uPx;
279
- out vec4 fragColor;
280
- void main(){
281
- float s = 0.0;
282
- for (int y = -1; y <= 2; y++)
283
- for (int x = -1; x <= 2; x++)
284
- s += texture(uTex, vUv + vec2(float(x) - 0.5, float(y) - 0.5) * uPx).r;
285
- fragColor = vec4(vec3(s / 16.0), 1.0);
336
+ export const AOBLUR_FS = `#version 300 es
337
+ precision mediump float;
338
+ in vec2 vUv; uniform sampler2D uTex; uniform vec2 uPx;
339
+ out vec4 fragColor;
340
+ void main(){
341
+ float s = 0.0;
342
+ for (int y = -1; y <= 2; y++)
343
+ for (int x = -1; x <= 2; x++)
344
+ s += texture(uTex, vUv + vec2(float(x) - 0.5, float(y) - 0.5) * uPx).r;
345
+ fragColor = vec4(vec3(s / 16.0), 1.0);
286
346
  }`;
287
- export const COMPOSITE_FS = `#version 300 es
288
- precision mediump float;
289
- in vec2 vUv;
290
- uniform sampler2D uScene; uniform sampler2D uBloom; uniform sampler2D uAO;
291
- uniform float uBloomStr; uniform float uAOStr;
292
- uniform float uFilm; uniform float uGrade; uniform float uVig; uniform float uInk;
293
- out vec4 fragColor;
294
- void main(){
295
- vec3 c = texture(uScene, vUv).rgb;
296
- c *= mix(1.0, texture(uAO, vUv).r, uAOStr);
297
- c += texture(uBloom, vUv).rgb * uBloomStr;
298
- if (uFilm > 0.5) { // ACES-ish filmic: highlights roll off instead of clipping
299
- c = (c * (2.51 * c + 0.03)) / (c * (2.43 * c + 0.59) + 0.14);
300
- }
301
- c = clamp(c, 0.0, 1.0);
302
- if (uGrade > 0.5) { // violet-cool shadows, warm highlights
303
- float luma = dot(c, vec3(0.299, 0.587, 0.114));
304
- c *= mix(vec3(0.85, 0.83, 1.07), vec3(1.07, 0.99, 0.92), smoothstep(0.12, 0.72, luma));
305
- c = clamp(c, 0.0, 1.0);
306
- }
307
- if (uVig > 0.0) { // cinematic vignette, in the composite now (off the overlay)
308
- vec2 q = vUv - vec2(0.5, 0.54);
309
- float r = smoothstep(0.35, 0.9, length(q * vec2(1.15, 1.0)));
310
- vec3 dk = uInk > 0.5 ? vec3(0.20, 0.19, 0.30) : vec3(0.024, 0.016, 0.055);
311
- c = mix(c, dk, uVig * r * (uInk > 0.5 ? 0.3 : 1.0));
312
- }
313
- fragColor = vec4(c, 1.0);
347
+ export const COMPOSITE_FS = `#version 300 es
348
+ precision mediump float;
349
+ in vec2 vUv;
350
+ uniform sampler2D uScene; uniform sampler2D uBloom; uniform sampler2D uAO;
351
+ uniform float uBloomStr; uniform float uAOStr;
352
+ uniform float uFilm; uniform float uGrade; uniform float uVig; uniform float uInk;
353
+ out vec4 fragColor;
354
+ void main(){
355
+ vec3 c = texture(uScene, vUv).rgb;
356
+ c *= mix(1.0, texture(uAO, vUv).r, uAOStr);
357
+ c += texture(uBloom, vUv).rgb * uBloomStr;
358
+ if (uFilm > 0.5) { // ACES-ish filmic: highlights roll off instead of clipping
359
+ c = (c * (2.51 * c + 0.03)) / (c * (2.43 * c + 0.59) + 0.14);
360
+ }
361
+ c = clamp(c, 0.0, 1.0);
362
+ if (uGrade > 0.5) { // violet-cool shadows, warm highlights
363
+ float luma = dot(c, vec3(0.299, 0.587, 0.114));
364
+ c *= mix(vec3(0.85, 0.83, 1.07), vec3(1.07, 0.99, 0.92), smoothstep(0.12, 0.72, luma));
365
+ c = clamp(c, 0.0, 1.0);
366
+ }
367
+ if (uVig > 0.0) { // cinematic vignette, in the composite now (off the overlay)
368
+ vec2 q = vUv - vec2(0.5, 0.54);
369
+ float r = smoothstep(0.35, 0.9, length(q * vec2(1.15, 1.0)));
370
+ vec3 dk = uInk > 0.5 ? vec3(0.20, 0.19, 0.30) : vec3(0.024, 0.016, 0.055);
371
+ c = mix(c, dk, uVig * r * (uInk > 0.5 ? 0.3 : 1.0));
372
+ }
373
+ fragColor = vec4(c, 1.0);
314
374
  }`;
315
375
  // depth of field — CoC from linearized depth, 12-tap poisson disc gather on the LDR
316
- export const DOF_FS = `#version 300 es
317
- precision highp float;
318
- in vec2 vUv; uniform sampler2D uTex; uniform sampler2D uDepth;
319
- uniform float uNear; uniform float uFar; uniform float uFocus; uniform float uRange;
320
- uniform float uMaxBlur; uniform vec2 uPx;
321
- out vec4 fragColor;
322
- float lin(float d){ return uNear * uFar / (uFar - d * (uFar - uNear)); }
323
- const vec2 DISC[12] = vec2[](
324
- vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621),
325
- vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893),
326
- vec2(0.507, 0.064), vec2(0.896, 0.412), vec2(-0.322, -0.933), vec2(-0.792, -0.598));
327
- void main(){
328
- float z = lin(texture(uDepth, vUv).r);
329
- float coc = clamp(abs(z - uFocus) / max(1.0, uRange), 0.0, 1.0) * uMaxBlur;
330
- vec3 acc = texture(uTex, vUv).rgb;
331
- float wsum = 1.0;
332
- for (int i = 0; i < 12; i++) {
333
- vec2 off = DISC[i] * coc * uPx;
334
- // a sharp (in-focus) neighbour must not bleed into a blurred one: weight each tap
335
- // by ITS OWN CoC so foreground focus stays crisp against a soft background
336
- float zt = lin(texture(uDepth, vUv + off).r);
337
- float ct = clamp(abs(zt - uFocus) / max(1.0, uRange), 0.0, 1.0);
338
- float w = 0.25 + 0.75 * ct;
339
- acc += texture(uTex, vUv + off).rgb * w;
340
- wsum += w;
341
- }
342
- fragColor = vec4(acc / wsum, 1.0);
376
+ export const DOF_FS = `#version 300 es
377
+ precision highp float;
378
+ in vec2 vUv; uniform sampler2D uTex; uniform sampler2D uDepth;
379
+ uniform float uNear; uniform float uFar; uniform float uFocus; uniform float uRange;
380
+ uniform float uMaxBlur; uniform vec2 uPx;
381
+ out vec4 fragColor;
382
+ float lin(float d){ return uNear * uFar / (uFar - d * (uFar - uNear)); }
383
+ const vec2 DISC[12] = vec2[](
384
+ vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621),
385
+ vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893),
386
+ vec2(0.507, 0.064), vec2(0.896, 0.412), vec2(-0.322, -0.933), vec2(-0.792, -0.598));
387
+ void main(){
388
+ float z = lin(texture(uDepth, vUv).r);
389
+ float coc = clamp(abs(z - uFocus) / max(1.0, uRange), 0.0, 1.0) * uMaxBlur;
390
+ vec3 acc = texture(uTex, vUv).rgb;
391
+ float wsum = 1.0;
392
+ for (int i = 0; i < 12; i++) {
393
+ vec2 off = DISC[i] * coc * uPx;
394
+ // a sharp (in-focus) neighbour must not bleed into a blurred one: weight each tap
395
+ // by ITS OWN CoC so foreground focus stays crisp against a soft background
396
+ float zt = lin(texture(uDepth, vUv + off).r);
397
+ float ct = clamp(abs(zt - uFocus) / max(1.0, uRange), 0.0, 1.0);
398
+ float w = 0.25 + 0.75 * ct;
399
+ acc += texture(uTex, vUv + off).rgb * w;
400
+ wsum += w;
401
+ }
402
+ fragColor = vec4(acc / wsum, 1.0);
343
403
  }`;
344
404
  // camera motion blur — reconstruct world position from depth, reproject into the
345
405
  // previous frame's clip space, sample along the screen-space velocity
346
- export const MOBLUR_FS = `#version 300 es
347
- precision highp float;
348
- in vec2 vUv; uniform sampler2D uTex; uniform sampler2D uDepth;
349
- uniform mat4 uInvVP; uniform mat4 uPrevVP; uniform float uStrength;
350
- out vec4 fragColor;
351
- void main(){
352
- float d = texture(uDepth, vUv).r;
353
- vec4 ndc = vec4(vUv * 2.0 - 1.0, d * 2.0 - 1.0, 1.0);
354
- vec4 wp = uInvVP * ndc; wp /= wp.w;
355
- vec4 pc = uPrevVP * wp;
356
- vec2 prevUv = (pc.xy / pc.w) * 0.5 + 0.5;
357
- vec2 vel = (vUv - prevUv) * uStrength;
358
- float vlen = length(vel);
359
- if (vlen < 1e-5 || pc.w <= 0.0) { fragColor = vec4(texture(uTex, vUv).rgb, 1.0); return; }
360
- vel = vlen > 0.04 ? vel * (0.04 / vlen) : vel; // clamp the streak to 4% of the screen
361
- vec3 acc = vec3(0.0);
362
- for (int i = 0; i < 8; i++) {
363
- float t = (float(i) / 7.0) - 0.5;
364
- acc += texture(uTex, clamp(vUv + vel * t, vec2(0.001), vec2(0.999))).rgb;
365
- }
366
- fragColor = vec4(acc / 8.0, 1.0);
406
+ export const MOBLUR_FS = `#version 300 es
407
+ precision highp float;
408
+ in vec2 vUv; uniform sampler2D uTex; uniform sampler2D uDepth;
409
+ uniform mat4 uInvVP; uniform mat4 uPrevVP; uniform float uStrength;
410
+ out vec4 fragColor;
411
+ void main(){
412
+ float d = texture(uDepth, vUv).r;
413
+ vec4 ndc = vec4(vUv * 2.0 - 1.0, d * 2.0 - 1.0, 1.0);
414
+ vec4 wp = uInvVP * ndc; wp /= wp.w;
415
+ vec4 pc = uPrevVP * wp;
416
+ vec2 prevUv = (pc.xy / pc.w) * 0.5 + 0.5;
417
+ vec2 vel = (vUv - prevUv) * uStrength;
418
+ float vlen = length(vel);
419
+ if (vlen < 1e-5 || pc.w <= 0.0) { fragColor = vec4(texture(uTex, vUv).rgb, 1.0); return; }
420
+ vel = vlen > 0.04 ? vel * (0.04 / vlen) : vel; // clamp the streak to 4% of the screen
421
+ vec3 acc = vec3(0.0);
422
+ for (int i = 0; i < 8; i++) {
423
+ float t = (float(i) / 7.0) - 0.5;
424
+ acc += texture(uTex, clamp(vUv + vel * t, vec2(0.001), vec2(0.999))).rgb;
425
+ }
426
+ fragColor = vec4(acc / 8.0, 1.0);
367
427
  }`;
368
428
  // compact FXAA (luma-based, 5-tap edge blend) — runs last, on the LDR composite
369
- export const FXAA_FS = `#version 300 es
370
- precision mediump float;
371
- in vec2 vUv; uniform sampler2D uTex; uniform vec2 uPx;
372
- out vec4 fragColor;
373
- float luma(vec3 c){ return dot(c, vec3(0.299, 0.587, 0.114)); }
374
- void main(){
375
- vec3 cM = texture(uTex, vUv).rgb;
376
- float lM = luma(cM);
377
- float lN = luma(texture(uTex, vUv + vec2(0.0, -uPx.y)).rgb);
378
- float lS = luma(texture(uTex, vUv + vec2(0.0, uPx.y)).rgb);
379
- float lW = luma(texture(uTex, vUv + vec2(-uPx.x, 0.0)).rgb);
380
- float lE = luma(texture(uTex, vUv + vec2(uPx.x, 0.0)).rgb);
381
- float lMin = min(lM, min(min(lN, lS), min(lW, lE)));
382
- float lMax = max(lM, max(max(lN, lS), max(lW, lE)));
383
- float range = lMax - lMin;
384
- if (range < max(0.0312, lMax * 0.125)) { fragColor = vec4(cM, 1.0); return; }
385
- vec2 dir = normalize(vec2((lE + lS) - (lW + lN), (lN + lE) - (lS + lW)) + 1e-6);
386
- vec3 a = texture(uTex, vUv + dir * uPx * 0.5).rgb;
387
- vec3 b = texture(uTex, vUv - dir * uPx * 0.5).rgb;
388
- fragColor = vec4(mix(cM, (a + b) * 0.5, 0.75), 1.0);
429
+ export const FXAA_FS = `#version 300 es
430
+ precision mediump float;
431
+ in vec2 vUv; uniform sampler2D uTex; uniform vec2 uPx;
432
+ out vec4 fragColor;
433
+ float luma(vec3 c){ return dot(c, vec3(0.299, 0.587, 0.114)); }
434
+ void main(){
435
+ vec3 cM = texture(uTex, vUv).rgb;
436
+ float lM = luma(cM);
437
+ float lN = luma(texture(uTex, vUv + vec2(0.0, -uPx.y)).rgb);
438
+ float lS = luma(texture(uTex, vUv + vec2(0.0, uPx.y)).rgb);
439
+ float lW = luma(texture(uTex, vUv + vec2(-uPx.x, 0.0)).rgb);
440
+ float lE = luma(texture(uTex, vUv + vec2(uPx.x, 0.0)).rgb);
441
+ float lMin = min(lM, min(min(lN, lS), min(lW, lE)));
442
+ float lMax = max(lM, max(max(lN, lS), max(lW, lE)));
443
+ float range = lMax - lMin;
444
+ if (range < max(0.0312, lMax * 0.125)) { fragColor = vec4(cM, 1.0); return; }
445
+ vec2 dir = normalize(vec2((lE + lS) - (lW + lN), (lN + lE) - (lS + lW)) + 1e-6);
446
+ vec3 a = texture(uTex, vUv + dir * uPx * 0.5).rgb;
447
+ vec3 b = texture(uTex, vUv - dir * uPx * 0.5).rgb;
448
+ fragColor = vec4(mix(cM, (a + b) * 0.5, 0.75), 1.0);
389
449
  }`;