@chromatic-coherence/generative-engine 1.0.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/post.js ADDED
@@ -0,0 +1,213 @@
1
+ // generative-engine — post.ts: GL program helpers + the HDR post chain.
2
+ // The chain: scene renders into an offscreen HDR target (RGBA16F when
3
+ // EXT_color_buffer_float is available, RGBA8 otherwise) with a real depth
4
+ // texture; then bright-pass -> half-res separable gaussian (ping-pong) for
5
+ // bloom, depth-based half-res SSAO + blur, one composite pass (bloom add,
6
+ // ACES filmic, grade, vignette), and an optional FXAA pass last.
7
+ import { FST_VS, BRIGHT_FS, BLUR_FS, SSAO_FS, AOBLUR_FS, COMPOSITE_FS, FXAA_FS, } from "./shaders.js";
8
+ export function makeProg(gl, vs, fs) {
9
+ const sh = (type, src) => {
10
+ const s = gl.createShader(type);
11
+ gl.shaderSource(s, src);
12
+ gl.compileShader(s);
13
+ if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
14
+ throw new Error("generative-engine shader: " + gl.getShaderInfoLog(s));
15
+ return s;
16
+ };
17
+ const p = gl.createProgram();
18
+ gl.attachShader(p, sh(gl.VERTEX_SHADER, vs));
19
+ gl.attachShader(p, sh(gl.FRAGMENT_SHADER, fs));
20
+ gl.linkProgram(p);
21
+ if (!gl.getProgramParameter(p, gl.LINK_STATUS))
22
+ throw new Error("generative-engine link: " + gl.getProgramInfoLog(p));
23
+ return p;
24
+ }
25
+ /** A program plus a uniform-location cache — getUniformLocation once, ever. */
26
+ export class PInfo {
27
+ constructor(gl, vs, fs) {
28
+ this.gl = gl;
29
+ this.locs = new Map();
30
+ this.prog = makeProg(gl, vs, fs);
31
+ }
32
+ u(name) {
33
+ let l = this.locs.get(name);
34
+ if (l === undefined) {
35
+ l = this.gl.getUniformLocation(this.prog, name);
36
+ this.locs.set(name, l);
37
+ }
38
+ return l;
39
+ }
40
+ use() { this.gl.useProgram(this.prog); }
41
+ }
42
+ export class PostChain {
43
+ constructor(gl) {
44
+ this.scene = null;
45
+ this.sceneDepth = null;
46
+ this.bloomA = null;
47
+ this.bloomB = null;
48
+ this.aoA = null;
49
+ this.aoB = null;
50
+ this.ldr = null;
51
+ this.W = 0;
52
+ this.H = 0;
53
+ this.gl = gl;
54
+ this.hdr = !!gl.getExtension("EXT_color_buffer_float");
55
+ this.pBright = new PInfo(gl, FST_VS, BRIGHT_FS);
56
+ this.pBlur = new PInfo(gl, FST_VS, BLUR_FS);
57
+ this.pSsao = new PInfo(gl, FST_VS, SSAO_FS);
58
+ this.pAoBlur = new PInfo(gl, FST_VS, AOBLUR_FS);
59
+ this.pComposite = new PInfo(gl, FST_VS, COMPOSITE_FS);
60
+ this.pFxaa = new PInfo(gl, FST_VS, FXAA_FS);
61
+ }
62
+ makeTarget(w, h, hdrFmt, filter) {
63
+ const gl = this.gl;
64
+ const tex = gl.createTexture();
65
+ gl.bindTexture(gl.TEXTURE_2D, tex);
66
+ if (hdrFmt)
67
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, w, h, 0, gl.RGBA, gl.HALF_FLOAT, null);
68
+ else
69
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
70
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
71
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
72
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
73
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
74
+ const fbo = gl.createFramebuffer();
75
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
76
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
77
+ return { fbo, tex, w, h };
78
+ }
79
+ drop(t) {
80
+ if (!t)
81
+ return;
82
+ this.gl.deleteFramebuffer(t.fbo);
83
+ this.gl.deleteTexture(t.tex);
84
+ }
85
+ /** (Re)allocate every target at the given DEVICE-pixel size. */
86
+ resize(w, h) {
87
+ if (w === this.W && h === this.H)
88
+ return;
89
+ const gl = this.gl;
90
+ this.W = w;
91
+ this.H = h;
92
+ this.drop(this.scene);
93
+ this.drop(this.bloomA);
94
+ this.drop(this.bloomB);
95
+ this.drop(this.aoA);
96
+ this.drop(this.aoB);
97
+ this.drop(this.ldr);
98
+ if (this.sceneDepth)
99
+ gl.deleteTexture(this.sceneDepth);
100
+ this.scene = this.makeTarget(w, h, this.hdr, gl.LINEAR);
101
+ // real depth texture on the scene FBO — SSAO reads it directly
102
+ this.sceneDepth = gl.createTexture();
103
+ gl.bindTexture(gl.TEXTURE_2D, this.sceneDepth);
104
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, w, h, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
105
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
106
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
107
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
108
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
109
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.scene.fbo);
110
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this.sceneDepth, 0);
111
+ const hw = Math.max(1, w >> 1), hh = Math.max(1, h >> 1);
112
+ this.bloomA = this.makeTarget(hw, hh, this.hdr, gl.LINEAR);
113
+ this.bloomB = this.makeTarget(hw, hh, this.hdr, gl.LINEAR);
114
+ this.aoA = this.makeTarget(hw, hh, false, gl.LINEAR);
115
+ this.aoB = this.makeTarget(hw, hh, false, gl.LINEAR);
116
+ this.ldr = this.makeTarget(w, h, false, gl.LINEAR);
117
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
118
+ }
119
+ /** Bind the scene target — the world's render pass draws into this. */
120
+ begin() {
121
+ const gl = this.gl;
122
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.scene.fbo);
123
+ gl.viewport(0, 0, this.W, this.H);
124
+ }
125
+ fst(p, to) {
126
+ const gl = this.gl;
127
+ gl.bindFramebuffer(gl.FRAMEBUFFER, to ? to.fbo : null);
128
+ gl.viewport(0, 0, to ? to.w : this.W, to ? to.h : this.H);
129
+ p.use();
130
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
131
+ }
132
+ bindTex(unit, tex) {
133
+ const gl = this.gl;
134
+ gl.activeTexture(gl.TEXTURE0 + unit);
135
+ gl.bindTexture(gl.TEXTURE_2D, tex);
136
+ }
137
+ /** Run the chain: bloom + ssao + composite (+ fxaa) to the canvas. */
138
+ end(o) {
139
+ const gl = this.gl;
140
+ gl.disable(gl.DEPTH_TEST);
141
+ gl.disable(gl.BLEND);
142
+ gl.depthMask(true);
143
+ if (o.bloomOn) {
144
+ this.pBright.use();
145
+ this.bindTex(0, this.scene.tex);
146
+ gl.uniform1i(this.pBright.u("uTex"), 0);
147
+ gl.uniform1f(this.pBright.u("uThresh"), o.bloomThreshold);
148
+ gl.uniform1f(this.pBright.u("uKnee"), o.bloomKnee);
149
+ this.fst(this.pBright, this.bloomA);
150
+ for (let i = 0; i < 2; i++) { // two separable blur rounds: wide, soft
151
+ this.pBlur.use();
152
+ this.bindTex(0, this.bloomA.tex);
153
+ gl.uniform1i(this.pBlur.u("uTex"), 0);
154
+ gl.uniform2f(this.pBlur.u("uDir"), (1 + i) / this.bloomA.w, 0);
155
+ this.fst(this.pBlur, this.bloomB);
156
+ this.bindTex(0, this.bloomB.tex);
157
+ gl.uniform2f(this.pBlur.u("uDir"), 0, (1 + i) / this.bloomA.h);
158
+ this.fst(this.pBlur, this.bloomA);
159
+ }
160
+ }
161
+ if (o.ssaoOn) {
162
+ this.pSsao.use();
163
+ this.bindTex(0, this.sceneDepth);
164
+ gl.uniform1i(this.pSsao.u("uDepth"), 0);
165
+ gl.uniform1f(this.pSsao.u("uNear"), o.near);
166
+ gl.uniform1f(this.pSsao.u("uFar"), o.far);
167
+ gl.uniform1f(this.pSsao.u("uRadius"), o.ssaoRadius);
168
+ gl.uniform2f(this.pSsao.u("uRes"), this.aoA.w, this.aoA.h);
169
+ this.fst(this.pSsao, this.aoA);
170
+ this.pAoBlur.use();
171
+ this.bindTex(0, this.aoA.tex);
172
+ gl.uniform1i(this.pAoBlur.u("uTex"), 0);
173
+ gl.uniform2f(this.pAoBlur.u("uPx"), 1 / this.aoA.w, 1 / this.aoA.h);
174
+ this.fst(this.pAoBlur, this.aoB);
175
+ }
176
+ this.pComposite.use();
177
+ this.bindTex(0, this.scene.tex);
178
+ this.bindTex(1, o.bloomOn ? this.bloomA.tex : null);
179
+ this.bindTex(2, o.ssaoOn ? this.aoB.tex : null);
180
+ gl.uniform1i(this.pComposite.u("uScene"), 0);
181
+ gl.uniform1i(this.pComposite.u("uBloom"), 1);
182
+ gl.uniform1i(this.pComposite.u("uAO"), 2);
183
+ gl.uniform1f(this.pComposite.u("uBloomStr"), o.bloomOn ? o.bloomStrength : 0);
184
+ gl.uniform1f(this.pComposite.u("uAOStr"), o.ssaoOn ? o.ssaoStrength : 0);
185
+ gl.uniform1f(this.pComposite.u("uFilm"), o.filmic ? 1 : 0);
186
+ gl.uniform1f(this.pComposite.u("uGrade"), o.grade ? 1 : 0);
187
+ gl.uniform1f(this.pComposite.u("uVig"), o.vignette);
188
+ gl.uniform1f(this.pComposite.u("uInk"), o.ink ? 1 : 0);
189
+ this.fst(this.pComposite, o.fxaa ? this.ldr : null);
190
+ if (o.fxaa) {
191
+ this.pFxaa.use();
192
+ this.bindTex(0, this.ldr.tex);
193
+ gl.uniform1i(this.pFxaa.u("uTex"), 0);
194
+ gl.uniform2f(this.pFxaa.u("uPx"), 1 / this.W, 1 / this.H);
195
+ this.fst(this.pFxaa, null);
196
+ }
197
+ // leave texture unit 0 active and the default framebuffer bound
198
+ gl.activeTexture(gl.TEXTURE0);
199
+ gl.enable(gl.DEPTH_TEST);
200
+ }
201
+ dispose() {
202
+ this.drop(this.scene);
203
+ this.drop(this.bloomA);
204
+ this.drop(this.bloomB);
205
+ this.drop(this.aoA);
206
+ this.drop(this.aoB);
207
+ this.drop(this.ldr);
208
+ if (this.sceneDepth)
209
+ this.gl.deleteTexture(this.sceneDepth);
210
+ this.scene = this.bloomA = this.bloomB = this.aoA = this.aoB = this.ldr = null;
211
+ this.sceneDepth = null;
212
+ }
213
+ }
@@ -0,0 +1,17 @@
1
+ export declare const FACE_VS = "#version 300 es\nin vec3 aPos; in vec3 aNorm; in vec4 aCol; in float aBlood; in float aGrp;\nin vec3 aPos2; in vec3 aNorm2;\nuniform mat4 uVP; uniform mat4 uSVP;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nout vec3 vPos; out vec3 vNorm; out vec4 vCol; out float vZ; out vec4 vSh;\nout float vGA; out float vBlood;\nvoid main(){\n int g = int(aGrp + 0.5);\n float w = uMW[g];\n vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);\n vec4 wp = uGM[g] * vec4(p, 1.0);\n vPos = wp.xyz; vNorm = uNM[g] * n; vCol = aCol; vGA = uGA[g]; vBlood = aBlood;\n vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;\n}";
2
+ export declare const FACE_FS = "#version 300 es\nprecision highp float;\nin vec3 vPos; in vec3 vNorm; in vec4 vCol; in float vZ; in vec4 vSh;\nin float vGA; in float vBlood;\nuniform vec3 uLdir; uniform float uAmb; uniform float uDif; uniform float uSigned;\nuniform vec3 uLp[8]; uniform float uLi[8]; uniform float uLf[8]; uniform int uLn;\nuniform vec3 uCam; uniform float uFogK; uniform float uSpec; uniform vec3 uBg;\nuniform vec3 uSkyC; uniform vec3 uGroundC; uniform float uPulse;\nuniform float uShadowOn; uniform float uShadowSoft; uniform float uShadowBias;\nuniform lowp sampler2DShadow uShadow; uniform float uShadowPx;\nout vec4 fragColor;\nfloat h3(vec3 p){ return fract(sin(dot(p, vec3(12.9898, 78.233, 37.719))) * 43758.5453); }\nfloat vnoise(vec3 p){ vec3 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f);\n 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),\n 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); }\nfloat shadowFactor(){\n vec3 pr = vSh.xyz / vSh.w * 0.5 + 0.5;\n if (pr.x < 0.0 || pr.x > 1.0 || pr.y < 0.0 || pr.y > 1.0 || pr.z > 1.0) return 1.0;\n float ref = pr.z - uShadowBias;\n // 3x3 taps over a hardware-compared (LINEAR sampler2DShadow => free 2x2 PCF each) map\n float s = 0.0;\n for (int sy = -1; sy <= 1; sy++)\n for (int sx = -1; sx <= 1; sx++)\n s += texture(uShadow, vec3(pr.xy + vec2(float(sx), float(sy)) * uShadowPx * uShadowSoft, ref));\n s /= 9.0;\n return 0.35 + 0.65 * s;\n}\nvoid main(){\n vec3 n = normalize(vNorm);\n vec3 V = normalize(uCam - vPos);\n float skin = clamp(vBlood * 5.0, 0.0, 1.0);\n { // procedural micro-relief: perturb the shading normal by a fine noise gradient\n float bs = 5.5, e = 0.12, n0 = vnoise(vPos * bs);\n vec3 g = vec3(vnoise(vPos * bs + vec3(e, 0.0, 0.0)) - n0,\n vnoise(vPos * bs + vec3(0.0, e, 0.0)) - n0,\n vnoise(vPos * bs + vec3(0.0, 0.0, e)) - n0) / e;\n n = normalize(n - g * (0.10 + 0.30 * skin));\n }\n float d = dot(n, normalize(uLdir));\n float shade = uAmb + uDif * mix(abs(d), max(d, 0.0), uSigned);\n float spec = 0.0;\n float sh = uShadowOn > 0.5 ? shadowFactor() : 1.0;\n for (int i = 0; i < 8; i++) { if (i >= uLn) break;\n vec3 v = uLp[i] - vPos; float d2 = dot(v, v); vec3 L = v * inversesqrt(d2 + 1e-4);\n float lam = dot(n, L); lam = mix(abs(lam), max(lam, 0.0), uSigned);\n float fall = 1.0 / (1.0 + d2 / (uLf[i] * uLf[i]));\n float k = (i == 0) ? sh : 1.0;\n shade += uLi[i] * lam * fall * k;\n // GGX microfacet + Fresnel \u2014 roughness from the face's gloss (colour alpha slot)\n vec3 H = normalize(L + V);\n float nh = max(dot(n, H), 0.0);\n float rough = clamp(1.0 - vCol.a, 0.08, 1.0), a2 = rough * rough; a2 = a2 * a2;\n float dnm = nh * nh * (a2 - 1.0) + 1.0;\n float ggx = a2 / (3.14159 * dnm * dnm);\n float fres = 0.04 + 0.96 * pow(1.0 - max(dot(H, V), 0.0), 5.0);\n spec += uLi[i] * min(ggx, 6.0) * fres * fall * k * 0.16;\n }\n // coloured hemispheric ambient: cool from above, warm from below\n vec3 tint = mix(uGroundC, uSkyC, n.y * 0.5 + 0.5);\n vec3 lit = vCol.rgb * (uAmb * tint + vec3(shade - uAmb));\n // the perfusion term: blood near the surface changes how it meets the light\n float b = clamp(vBlood + uPulse * vBlood * 6.0, 0.0, 1.0);\n lit *= mix(vec3(1.0), vec3(1.07, 0.95, 0.94), b);\n lit += vec3(0.62, 0.06, 0.08) * (b * 0.20 * (1.0 - abs(d)));\n // pore noise, only where there is blood (skin, not cloth)\n float pore = vnoise(vPos * 2.4) * 0.6 + vnoise(vPos * 6.0) * 0.4;\n lit *= 1.0 + (pore - 0.5) * 0.09 * skin;\n // subsurface rim: grazing light bleeds through thin tissue, warm\n float fres = pow(1.0 - clamp(dot(n, V), 0.0, 1.0), 3.0);\n lit += vec3(0.55, 0.09, 0.10) * fres * skin * (0.28 + 0.5 * b);\n float fog = clamp(uFogK / vZ, 0.08, 1.0);\n vec3 outC = lit + vec3(1.0) * spec * uSpec * vCol.a;\n // fog and fades resolve toward the REAL background colour (the engine owns its ground)\n fragColor = vec4(mix(uBg, outC, fog * vGA), 1.0);\n}";
3
+ export declare const DEPTH_VS = "#version 300 es\nin vec3 aPos; in float aGrp; in vec3 aPos2;\nuniform mat4 uVP;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nvoid main(){\n int g = int(aGrp + 0.5);\n vec3 p = mix(aPos, aPos2, uMW[g]);\n gl_Position = uVP * (uGM[g] * vec4(p, 1.0));\n}";
4
+ export declare const DEPTH_FS = "#version 300 es\nprecision mediump float;\nvoid main(){}";
5
+ export declare const LINE_VS = "#version 300 es\nin vec3 aPos; in vec4 aCol; in float aGrp;\nuniform mat4 uVP;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nout vec4 vCol; out float vZ; out float vGA;\nvoid main(){\n int g = int(aGrp + 0.5);\n vCol = aCol; vGA = uGA[g];\n vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w; gl_Position = p;\n}";
6
+ export declare const LINE_FS = "#version 300 es\nprecision mediump float;\nin vec4 vCol; in float vZ; in float vGA;\nuniform float uFogK; uniform float uInk;\nout vec4 fragColor;\nvoid main(){\n float fog = clamp(uFogK / vZ, 0.08, 1.0);\n if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, vCol.a * 1.6) * fog * vGA);\n else fragColor = vec4(vCol.rgb * fog, vCol.a * fog * vGA);\n}";
7
+ export declare const PT_VS = "#version 300 es\nin vec3 aPos; in float aSize; in vec4 aCol; in float aGrp;\nuniform mat4 uVP; uniform float uFocal;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nout vec4 vCol; out float vZ; out float vGA;\nvoid main(){\n int g = int(aGrp + 0.5);\n vCol = aCol; vGA = uGA[g];\n vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w;\n gl_PointSize = clamp(aSize * uFocal / p.w, 1.0, 256.0); gl_Position = p;\n}";
8
+ export declare const PT_FS = "#version 300 es\nprecision mediump float;\nin vec4 vCol; in float vZ; in float vGA;\nuniform float uFogK; uniform float uInk;\nout vec4 fragColor;\nvoid main(){\n float r = length(gl_PointCoord - 0.5) * 2.0;\n float a = exp(-3.2 * r * r) * (1.0 - smoothstep(0.85, 1.0, r));\n float fog = clamp(uFogK / vZ, 0.08, 1.0);\n if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, vCol.a * 1.6) * a * fog * vGA);\n else fragColor = vec4(vCol.rgb, 1.0) * (vCol.a * a * fog * vGA);\n}";
9
+ export declare const RIBBON_VS = "#version 300 es\nin vec3 aPos; in vec3 aPrev; in vec3 aNext; in float aSide; in float aWidth;\nin vec4 aCol; in float aGrp;\nuniform mat4 uVP; uniform vec2 uRes;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nout vec4 vCol; out float vZ; out float vGA; out float vSide;\nvoid main(){\n int g = int(aGrp + 0.5);\n mat4 M = uGM[g];\n vec4 cp = uVP * (M * vec4(aPos, 1.0));\n vec4 cq = uVP * (M * vec4(aPrev, 1.0));\n vec4 cr = uVP * (M * vec4(aNext, 1.0));\n vec2 sp = cp.xy / max(cp.w, 1e-4) * uRes;\n vec2 sq = cq.xy / max(cq.w, 1e-4) * uRes;\n vec2 sr = cr.xy / max(cr.w, 1e-4) * uRes;\n vec2 dir = sr - sq;\n float dl = length(dir);\n dir = dl > 1e-3 ? dir / dl : vec2(1.0, 0.0);\n vec2 perp = vec2(-dir.y, dir.x);\n vec2 off = perp * aWidth * 0.5 * aSide;\n cp.xy += off / uRes * cp.w * 2.0;\n vCol = aCol; vZ = cp.w; vGA = uGA[g]; vSide = aSide;\n gl_Position = cp;\n}";
10
+ export declare const RIBBON_FS = "#version 300 es\nprecision mediump float;\nin vec4 vCol; in float vZ; in float vGA; in float vSide;\nuniform float uFogK; uniform float uInk;\nout vec4 fragColor;\nvoid main(){\n float edge = 1.0 - smoothstep(0.55, 1.0, abs(vSide));\n float fog = clamp(uFogK / vZ, 0.08, 1.0);\n float a = vCol.a * edge * fog * vGA;\n if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, a * 1.6));\n else fragColor = vec4(vCol.rgb * a, a);\n}";
11
+ export declare const FST_VS = "#version 300 es\nout vec2 vUv;\nvoid main(){\n vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n vUv = p; gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n}";
12
+ export declare const BRIGHT_FS = "#version 300 es\nprecision mediump float;\nin vec2 vUv; uniform sampler2D uTex; uniform float uThresh; uniform float uKnee;\nout vec4 fragColor;\nvoid main(){\n vec3 c = texture(uTex, vUv).rgb;\n float luma = max(c.r, max(c.g, c.b));\n float w = smoothstep(uThresh, uThresh + uKnee, luma);\n fragColor = vec4(c * w, 1.0);\n}";
13
+ export declare const BLUR_FS = "#version 300 es\nprecision mediump float;\nin vec2 vUv; uniform sampler2D uTex; uniform vec2 uDir;\nout vec4 fragColor;\nvoid main(){\n vec3 s = texture(uTex, vUv).rgb * 0.227027;\n vec2 o1 = uDir * 1.3846153846, o2 = uDir * 3.2307692308;\n s += (texture(uTex, vUv + o1).rgb + texture(uTex, vUv - o1).rgb) * 0.3162162162;\n s += (texture(uTex, vUv + o2).rgb + texture(uTex, vUv - o2).rgb) * 0.0702702703;\n fragColor = vec4(s, 1.0);\n}";
14
+ export declare const SSAO_FS = "#version 300 es\nprecision highp float;\nin vec2 vUv; uniform sampler2D uDepth;\nuniform float uNear; uniform float uFar; uniform float uRadius; uniform vec2 uRes;\nout vec4 fragColor;\nfloat viewZ(vec2 uv){\n float d = texture(uDepth, uv).r * 2.0 - 1.0;\n return 2.0 * uNear * uFar / (uFar + uNear - d * (uFar - uNear));\n}\nfloat hash(vec2 p){ return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }\nvoid main(){\n float z0 = viewZ(vUv);\n // screen-space sample radius shrinks with distance (a world-ish radius)\n float rad = uRadius / z0;\n float ang = hash(vUv * uRes) * 6.2831853;\n float occ = 0.0;\n for (int i = 0; i < 12; i++) {\n float fi = float(i);\n float a = ang + fi * 2.3999632; // golden-angle spiral\n float r = rad * (0.25 + 0.75 * fract(fi * 0.618034));\n vec2 uv = vUv + vec2(cos(a), sin(a)) * r;\n float dz = z0 - viewZ(uv); // positive => sample nearer camera => occluder\n occ += smoothstep(z0 * 0.002, z0 * 0.02, dz) * smoothstep(z0 * 0.35, 0.0, dz);\n }\n float ao = 1.0 - occ / 12.0;\n fragColor = vec4(vec3(ao), 1.0);\n}";
15
+ export declare const AOBLUR_FS = "#version 300 es\nprecision mediump float;\nin vec2 vUv; uniform sampler2D uTex; uniform vec2 uPx;\nout vec4 fragColor;\nvoid main(){\n float s = 0.0;\n for (int y = -1; y <= 2; y++)\n for (int x = -1; x <= 2; x++)\n s += texture(uTex, vUv + vec2(float(x) - 0.5, float(y) - 0.5) * uPx).r;\n fragColor = vec4(vec3(s / 16.0), 1.0);\n}";
16
+ export declare const COMPOSITE_FS = "#version 300 es\nprecision mediump float;\nin vec2 vUv;\nuniform sampler2D uScene; uniform sampler2D uBloom; uniform sampler2D uAO;\nuniform float uBloomStr; uniform float uAOStr;\nuniform float uFilm; uniform float uGrade; uniform float uVig; uniform float uInk;\nout vec4 fragColor;\nvoid main(){\n vec3 c = texture(uScene, vUv).rgb;\n c *= mix(1.0, texture(uAO, vUv).r, uAOStr);\n c += texture(uBloom, vUv).rgb * uBloomStr;\n if (uFilm > 0.5) { // ACES-ish filmic: highlights roll off instead of clipping\n c = (c * (2.51 * c + 0.03)) / (c * (2.43 * c + 0.59) + 0.14);\n }\n c = clamp(c, 0.0, 1.0);\n if (uGrade > 0.5) { // violet-cool shadows, warm highlights\n float luma = dot(c, vec3(0.299, 0.587, 0.114));\n c *= mix(vec3(0.85, 0.83, 1.07), vec3(1.07, 0.99, 0.92), smoothstep(0.12, 0.72, luma));\n c = clamp(c, 0.0, 1.0);\n }\n if (uVig > 0.0) { // cinematic vignette, in the composite now (off the overlay)\n vec2 q = vUv - vec2(0.5, 0.54);\n float r = smoothstep(0.35, 0.9, length(q * vec2(1.15, 1.0)));\n vec3 dk = uInk > 0.5 ? vec3(0.20, 0.19, 0.30) : vec3(0.024, 0.016, 0.055);\n c = mix(c, dk, uVig * r * (uInk > 0.5 ? 0.3 : 1.0));\n }\n fragColor = vec4(c, 1.0);\n}";
17
+ export declare const FXAA_FS = "#version 300 es\nprecision mediump float;\nin vec2 vUv; uniform sampler2D uTex; uniform vec2 uPx;\nout vec4 fragColor;\nfloat luma(vec3 c){ return dot(c, vec3(0.299, 0.587, 0.114)); }\nvoid main(){\n vec3 cM = texture(uTex, vUv).rgb;\n float lM = luma(cM);\n float lN = luma(texture(uTex, vUv + vec2(0.0, -uPx.y)).rgb);\n float lS = luma(texture(uTex, vUv + vec2(0.0, uPx.y)).rgb);\n float lW = luma(texture(uTex, vUv + vec2(-uPx.x, 0.0)).rgb);\n float lE = luma(texture(uTex, vUv + vec2(uPx.x, 0.0)).rgb);\n float lMin = min(lM, min(min(lN, lS), min(lW, lE)));\n float lMax = max(lM, max(max(lN, lS), max(lW, lE)));\n float range = lMax - lMin;\n if (range < max(0.0312, lMax * 0.125)) { fragColor = vec4(cM, 1.0); return; }\n vec2 dir = normalize(vec2((lE + lS) - (lW + lN), (lN + lE) - (lS + lW)) + 1e-6);\n vec3 a = texture(uTex, vUv + dir * uPx * 0.5).rgb;\n vec3 b = texture(uTex, vUv - dir * uPx * 0.5).rgb;\n fragColor = vec4(mix(cM, (a + b) * 0.5, 0.75), 1.0);\n}";
@@ -0,0 +1,307 @@
1
+ // generative-engine — shaders.ts: every GLSL ES 3.00 source, scene + post, as plain strings.
2
+ // The scene shaders carry dark-math-3d's whole light model forward (Lambert + point
3
+ // lights, GGX + Fresnel, procedural micro-relief, the perfusion term, pore noise,
4
+ // subsurface rim) and add: group transforms, morph targets, hardware-PCF shadow
5
+ // maps, and HDR output for the post chain. No clamping in the scene pass — light
6
+ // is allowed to exceed 1.0 and the composite tone-maps it.
7
+ // ── shared vertex preamble: 8 groups, each with a mat4, normal mat3, morph weight, alpha ──
8
+ const GROUPS = `
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;
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);
98
+ }`;
99
+ // depth-only pass (shadow map) — same morph + group transforms, no colour output
100
+ export const DEPTH_VS = `#version 300 es
101
+ in vec3 aPos; in float aGrp; in vec3 aPos2;
102
+ uniform mat4 uVP;${GROUPS}
103
+ void main(){
104
+ int g = int(aGrp + 0.5);
105
+ vec3 p = mix(aPos, aPos2, uMW[g]);
106
+ gl_Position = uVP * (uGM[g] * vec4(p, 1.0));
107
+ }`;
108
+ export const DEPTH_FS = `#version 300 es
109
+ precision mediump float;
110
+ void main(){}`;
111
+ export const LINE_VS = `#version 300 es
112
+ in vec3 aPos; in vec4 aCol; in float aGrp;
113
+ uniform mat4 uVP;${GROUPS}
114
+ out vec4 vCol; out float vZ; out float vGA;
115
+ void main(){
116
+ int g = int(aGrp + 0.5);
117
+ vCol = aCol; vGA = uGA[g];
118
+ vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w; gl_Position = p;
119
+ }`;
120
+ export const LINE_FS = `#version 300 es
121
+ precision mediump float;
122
+ in vec4 vCol; in float vZ; in float vGA;
123
+ uniform float uFogK; uniform float uInk;
124
+ out vec4 fragColor;
125
+ void main(){
126
+ float fog = clamp(uFogK / vZ, 0.08, 1.0);
127
+ if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, vCol.a * 1.6) * fog * vGA);
128
+ else fragColor = vec4(vCol.rgb * fog, vCol.a * fog * vGA);
129
+ }`;
130
+ export const PT_VS = `#version 300 es
131
+ in vec3 aPos; in float aSize; in vec4 aCol; in float aGrp;
132
+ uniform mat4 uVP; uniform float uFocal;${GROUPS}
133
+ out vec4 vCol; out float vZ; out float vGA;
134
+ void main(){
135
+ int g = int(aGrp + 0.5);
136
+ vCol = aCol; vGA = uGA[g];
137
+ vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w;
138
+ gl_PointSize = clamp(aSize * uFocal / p.w, 1.0, 256.0); gl_Position = p;
139
+ }`;
140
+ export const PT_FS = `#version 300 es
141
+ precision mediump float;
142
+ in vec4 vCol; in float vZ; in float vGA;
143
+ uniform float uFogK; uniform float uInk;
144
+ out vec4 fragColor;
145
+ void main(){
146
+ float r = length(gl_PointCoord - 0.5) * 2.0;
147
+ float a = exp(-3.2 * r * r) * (1.0 - smoothstep(0.85, 1.0, r));
148
+ float fog = clamp(uFogK / vZ, 0.08, 1.0);
149
+ if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, vCol.a * 1.6) * a * fog * vGA);
150
+ else fragColor = vec4(vCol.rgb, 1.0) * (vCol.a * a * fog * vGA);
151
+ }`;
152
+ // ── ribbons: screen-space-expanded strokes with real pixel width ──
153
+ // Each vertex carries its neighbours; the direction is the central difference in
154
+ // screen space, so joins round themselves. aSide is -1/+1; the fragment feathers
155
+ // the edge for free antialiasing.
156
+ export const RIBBON_VS = `#version 300 es
157
+ in vec3 aPos; in vec3 aPrev; in vec3 aNext; in float aSide; in float aWidth;
158
+ in vec4 aCol; in float aGrp;
159
+ uniform mat4 uVP; uniform vec2 uRes;${GROUPS}
160
+ out vec4 vCol; out float vZ; out float vGA; out float vSide;
161
+ void main(){
162
+ int g = int(aGrp + 0.5);
163
+ mat4 M = uGM[g];
164
+ vec4 cp = uVP * (M * vec4(aPos, 1.0));
165
+ vec4 cq = uVP * (M * vec4(aPrev, 1.0));
166
+ vec4 cr = uVP * (M * vec4(aNext, 1.0));
167
+ vec2 sp = cp.xy / max(cp.w, 1e-4) * uRes;
168
+ vec2 sq = cq.xy / max(cq.w, 1e-4) * uRes;
169
+ vec2 sr = cr.xy / max(cr.w, 1e-4) * uRes;
170
+ vec2 dir = sr - sq;
171
+ float dl = length(dir);
172
+ dir = dl > 1e-3 ? dir / dl : vec2(1.0, 0.0);
173
+ vec2 perp = vec2(-dir.y, dir.x);
174
+ vec2 off = perp * aWidth * 0.5 * aSide;
175
+ cp.xy += off / uRes * cp.w * 2.0;
176
+ vCol = aCol; vZ = cp.w; vGA = uGA[g]; vSide = aSide;
177
+ gl_Position = cp;
178
+ }`;
179
+ export const RIBBON_FS = `#version 300 es
180
+ precision mediump float;
181
+ in vec4 vCol; in float vZ; in float vGA; in float vSide;
182
+ uniform float uFogK; uniform float uInk;
183
+ out vec4 fragColor;
184
+ void main(){
185
+ float edge = 1.0 - smoothstep(0.55, 1.0, abs(vSide));
186
+ float fog = clamp(uFogK / vZ, 0.08, 1.0);
187
+ float a = vCol.a * edge * fog * vGA;
188
+ if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, a * 1.6));
189
+ else fragColor = vec4(vCol.rgb * a, a);
190
+ }`;
191
+ // ── post chain ──
192
+ // fullscreen triangle, attribute-less (gl_VertexID)
193
+ export const FST_VS = `#version 300 es
194
+ out vec2 vUv;
195
+ void main(){
196
+ vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
197
+ vUv = p; gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
198
+ }`;
199
+ export const BRIGHT_FS = `#version 300 es
200
+ precision mediump float;
201
+ in vec2 vUv; uniform sampler2D uTex; uniform float uThresh; uniform float uKnee;
202
+ out vec4 fragColor;
203
+ void main(){
204
+ vec3 c = texture(uTex, vUv).rgb;
205
+ float luma = max(c.r, max(c.g, c.b));
206
+ float w = smoothstep(uThresh, uThresh + uKnee, luma);
207
+ fragColor = vec4(c * w, 1.0);
208
+ }`;
209
+ export const BLUR_FS = `#version 300 es
210
+ precision mediump float;
211
+ in vec2 vUv; uniform sampler2D uTex; uniform vec2 uDir;
212
+ out vec4 fragColor;
213
+ void main(){
214
+ vec3 s = texture(uTex, vUv).rgb * 0.227027;
215
+ vec2 o1 = uDir * 1.3846153846, o2 = uDir * 3.2307692308;
216
+ s += (texture(uTex, vUv + o1).rgb + texture(uTex, vUv - o1).rgb) * 0.3162162162;
217
+ s += (texture(uTex, vUv + o2).rgb + texture(uTex, vUv - o2).rgb) * 0.0702702703;
218
+ fragColor = vec4(s, 1.0);
219
+ }`;
220
+ export const SSAO_FS = `#version 300 es
221
+ precision highp float;
222
+ in vec2 vUv; uniform sampler2D uDepth;
223
+ uniform float uNear; uniform float uFar; uniform float uRadius; uniform vec2 uRes;
224
+ out vec4 fragColor;
225
+ float viewZ(vec2 uv){
226
+ float d = texture(uDepth, uv).r * 2.0 - 1.0;
227
+ return 2.0 * uNear * uFar / (uFar + uNear - d * (uFar - uNear));
228
+ }
229
+ float hash(vec2 p){ return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
230
+ void main(){
231
+ float z0 = viewZ(vUv);
232
+ // screen-space sample radius shrinks with distance (a world-ish radius)
233
+ float rad = uRadius / z0;
234
+ float ang = hash(vUv * uRes) * 6.2831853;
235
+ float occ = 0.0;
236
+ for (int i = 0; i < 12; i++) {
237
+ float fi = float(i);
238
+ float a = ang + fi * 2.3999632; // golden-angle spiral
239
+ float r = rad * (0.25 + 0.75 * fract(fi * 0.618034));
240
+ vec2 uv = vUv + vec2(cos(a), sin(a)) * r;
241
+ float dz = z0 - viewZ(uv); // positive => sample nearer camera => occluder
242
+ occ += smoothstep(z0 * 0.002, z0 * 0.02, dz) * smoothstep(z0 * 0.35, 0.0, dz);
243
+ }
244
+ float ao = 1.0 - occ / 12.0;
245
+ fragColor = vec4(vec3(ao), 1.0);
246
+ }`;
247
+ export const AOBLUR_FS = `#version 300 es
248
+ precision mediump float;
249
+ in vec2 vUv; uniform sampler2D uTex; uniform vec2 uPx;
250
+ out vec4 fragColor;
251
+ void main(){
252
+ float s = 0.0;
253
+ for (int y = -1; y <= 2; y++)
254
+ for (int x = -1; x <= 2; x++)
255
+ s += texture(uTex, vUv + vec2(float(x) - 0.5, float(y) - 0.5) * uPx).r;
256
+ fragColor = vec4(vec3(s / 16.0), 1.0);
257
+ }`;
258
+ export const COMPOSITE_FS = `#version 300 es
259
+ precision mediump float;
260
+ in vec2 vUv;
261
+ uniform sampler2D uScene; uniform sampler2D uBloom; uniform sampler2D uAO;
262
+ uniform float uBloomStr; uniform float uAOStr;
263
+ uniform float uFilm; uniform float uGrade; uniform float uVig; uniform float uInk;
264
+ out vec4 fragColor;
265
+ void main(){
266
+ vec3 c = texture(uScene, vUv).rgb;
267
+ c *= mix(1.0, texture(uAO, vUv).r, uAOStr);
268
+ c += texture(uBloom, vUv).rgb * uBloomStr;
269
+ if (uFilm > 0.5) { // ACES-ish filmic: highlights roll off instead of clipping
270
+ c = (c * (2.51 * c + 0.03)) / (c * (2.43 * c + 0.59) + 0.14);
271
+ }
272
+ c = clamp(c, 0.0, 1.0);
273
+ if (uGrade > 0.5) { // violet-cool shadows, warm highlights
274
+ float luma = dot(c, vec3(0.299, 0.587, 0.114));
275
+ c *= mix(vec3(0.85, 0.83, 1.07), vec3(1.07, 0.99, 0.92), smoothstep(0.12, 0.72, luma));
276
+ c = clamp(c, 0.0, 1.0);
277
+ }
278
+ if (uVig > 0.0) { // cinematic vignette, in the composite now (off the overlay)
279
+ vec2 q = vUv - vec2(0.5, 0.54);
280
+ float r = smoothstep(0.35, 0.9, length(q * vec2(1.15, 1.0)));
281
+ vec3 dk = uInk > 0.5 ? vec3(0.20, 0.19, 0.30) : vec3(0.024, 0.016, 0.055);
282
+ c = mix(c, dk, uVig * r * (uInk > 0.5 ? 0.3 : 1.0));
283
+ }
284
+ fragColor = vec4(c, 1.0);
285
+ }`;
286
+ // compact FXAA (luma-based, 5-tap edge blend) — runs last, on the LDR composite
287
+ export const FXAA_FS = `#version 300 es
288
+ precision mediump float;
289
+ in vec2 vUv; uniform sampler2D uTex; uniform vec2 uPx;
290
+ out vec4 fragColor;
291
+ float luma(vec3 c){ return dot(c, vec3(0.299, 0.587, 0.114)); }
292
+ void main(){
293
+ vec3 cM = texture(uTex, vUv).rgb;
294
+ float lM = luma(cM);
295
+ float lN = luma(texture(uTex, vUv + vec2(0.0, -uPx.y)).rgb);
296
+ float lS = luma(texture(uTex, vUv + vec2(0.0, uPx.y)).rgb);
297
+ float lW = luma(texture(uTex, vUv + vec2(-uPx.x, 0.0)).rgb);
298
+ float lE = luma(texture(uTex, vUv + vec2(uPx.x, 0.0)).rgb);
299
+ float lMin = min(lM, min(min(lN, lS), min(lW, lE)));
300
+ float lMax = max(lM, max(max(lN, lS), max(lW, lE)));
301
+ float range = lMax - lMin;
302
+ if (range < max(0.0312, lMax * 0.125)) { fragColor = vec4(cM, 1.0); return; }
303
+ vec2 dir = normalize(vec2((lE + lS) - (lW + lN), (lN + lE) - (lS + lW)) + 1e-6);
304
+ vec3 a = texture(uTex, vUv + dir * uPx * 0.5).rgb;
305
+ vec3 b = texture(uTex, vUv - dir * uPx * 0.5).rgb;
306
+ fragColor = vec4(mix(cM, (a + b) * 0.5, 0.75), 1.0);
307
+ }`;