@chromatic-coherence/generative-engine 1.3.0 → 1.5.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/README.md +17 -1
- package/dist/shaders.d.ts +5 -3
- package/dist/shaders.js +94 -8
- package/dist/world.d.ts +36 -0
- package/dist/world.js +154 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -119,9 +119,25 @@ individually in the vertex stage (shadow pass included): per-instance scattered
|
|
|
119
119
|
height-scaled displacement, a second harmonic so the motion breathes. `w.wind = { dir,
|
|
120
120
|
speed }` is the one wind that blows through every swaying batch. Zero CPU per frame.
|
|
121
121
|
|
|
122
|
+
## GPU flight (v1.4)
|
|
123
|
+
|
|
124
|
+
`meshInstanced(wingMesh, Array.from({ length: 10000 }, () => ({})), { flight: true })` —
|
|
125
|
+
the batch IS a murmuration: per-bird orbit parameters ride the instance buffer, and the
|
|
126
|
+
whole flock (a wandering heart, lobes that split and merge, a ripple phase, flapping
|
|
127
|
+
wings) is evaluated in the vertex stage from time alone. Ten thousand birds, zero CPU.
|
|
128
|
+
The instance matrices are the shared flock frame — place, scale and steer it whole.
|
|
129
|
+
|
|
130
|
+
## Real water & planar reflections (v1.5)
|
|
131
|
+
|
|
132
|
+
`w.water = { on, level, size, hue, amp, distort, alpha }` — one water table per world.
|
|
133
|
+
The scene renders a second time through the reflected camera (solids and every instanced
|
|
134
|
+
batch, GPU flocks included), clipped at the plane; the surface is a wave-displaced grid
|
|
135
|
+
sampling that reflection in screen space with a fresnel blend. The table fills wherever
|
|
136
|
+
the ground dips below `level` — carve a basin and it becomes a lake that reflects the sky.
|
|
137
|
+
|
|
122
138
|
## Roadmap
|
|
123
139
|
|
|
124
|
-
|
|
140
|
+
Skeletal figures. The chart editions stay lean.
|
|
125
141
|
|
|
126
142
|
## Licence
|
|
127
143
|
|
package/dist/shaders.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
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 FACE_INST_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;\nin vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3; in vec4 aIP;\nuniform mat4 uVP; uniform mat4 uSVP; uniform float uT; uniform vec3 uWindDir;\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 mat4 inst = mat4(aI0, aI1, aI2, aI3);\n vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);\n // W9 GPU WIND: per-instance (phase, freq, amp); displacement grows with height,\n
|
|
4
|
-
export declare const DEPTH_INST_VS = "#version 300 es\nin vec3 aPos; in float aGrp; in vec3 aPos2;\nin vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3; in vec4 aIP;\nuniform mat4 uVP; uniform float uT; uniform vec3 uWindDir;\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 float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);\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;\nuniform float uClipY;\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 if (vPos.y < uClipY) discard; // W11: the reflection pass clips at the water plane\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 FACE_INST_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;\nin vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3; in vec4 aIP;\nuniform mat4 uVP; uniform mat4 uSVP; uniform float uT; uniform vec3 uWindDir;\nuniform float uFlight;\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;\n// W10 FLIGHT: aIP = (a0, r0, ph, lobe). The flock's heart wanders, three lobes orbit\n// it breathing apart and back, each bird orbits its lobe with a ripple phase \u2014 the\n// murmuration, evaluated per vertex from uT alone. The instance matrix is the flock\n// frame (shared), so one batch = one flock you can place, scale and steer.\nvec3 flightPos(vec4 ip, float t){\n float a0 = ip.x, r0 = ip.y, ph = ip.z, lobe = ip.w;\n float cx = sin(t * 0.11) * 700.0, cz = -500.0 + cos(t * 0.07) * 380.0;\n float cy = 430.0 + sin(t * 0.16) * 110.0;\n float spread = 180.0 + 140.0 * sin(t * 0.21 + 1.3);\n float la = t * 0.34 + lobe * 2.0944;\n vec3 lc = vec3(cx + cos(la) * spread, cy + sin(la * 2.0 + lobe) * 60.0, cz + sin(la) * spread * 0.7);\n float aa = a0 + t * (0.5 + 0.25 * sin(ph));\n float wob = sin(t * 1.1 + ph) * 0.35;\n float rr = r0 * (0.75 + 0.25 * sin(t * 0.5 + ph + lobe));\n 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));\n}\nvec3 flightLocal(vec3 pl, vec4 ip, float t){\n float aa = ip.x + t * (0.5 + 0.25 * sin(ip.z));\n pl.y += sin(t * 11.0 + ip.z * 3.0) * abs(pl.z) * 0.9; // the flap, growing to the wingtips\n float ca = cos(aa + 1.5708), sa = sin(aa + 1.5708); // heading = the orbit tangent\n return vec3(pl.x * ca - pl.z * sa, pl.y, pl.x * sa + pl.z * ca);\n}\nvoid main(){\n int g = int(aGrp + 0.5);\n float w = uMW[g];\n mat4 inst = mat4(aI0, aI1, aI2, aI3);\n vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);\n if (uFlight > 0.5) {\n p = flightPos(aIP, uT) + flightLocal(p, aIP, uT);\n } else {\n // W9 GPU WIND: per-instance (phase, freq, amp); displacement grows with height,\n // plus a second harmonic so the sway breathes instead of ticking\n float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);\n p += uWindDir * (sw * aIP.z * max(p.y, 0.0));\n }\n vec4 wp = uGM[g] * (inst * vec4(p, 1.0));\n vPos = wp.xyz; vNorm = uNM[g] * (mat3(inst) * n); vCol = aCol; vGA = uGA[g]; vBlood = aBlood;\n vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;\n}";
|
|
4
|
+
export declare const DEPTH_INST_VS = "#version 300 es\nin vec3 aPos; in float aGrp; in vec3 aPos2;\nin vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3; in vec4 aIP;\nuniform mat4 uVP; uniform float uT; uniform vec3 uWindDir;\nuniform float uFlight;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\n// W10 FLIGHT: aIP = (a0, r0, ph, lobe). The flock's heart wanders, three lobes orbit\n// it breathing apart and back, each bird orbits its lobe with a ripple phase \u2014 the\n// murmuration, evaluated per vertex from uT alone. The instance matrix is the flock\n// frame (shared), so one batch = one flock you can place, scale and steer.\nvec3 flightPos(vec4 ip, float t){\n float a0 = ip.x, r0 = ip.y, ph = ip.z, lobe = ip.w;\n float cx = sin(t * 0.11) * 700.0, cz = -500.0 + cos(t * 0.07) * 380.0;\n float cy = 430.0 + sin(t * 0.16) * 110.0;\n float spread = 180.0 + 140.0 * sin(t * 0.21 + 1.3);\n float la = t * 0.34 + lobe * 2.0944;\n vec3 lc = vec3(cx + cos(la) * spread, cy + sin(la * 2.0 + lobe) * 60.0, cz + sin(la) * spread * 0.7);\n float aa = a0 + t * (0.5 + 0.25 * sin(ph));\n float wob = sin(t * 1.1 + ph) * 0.35;\n float rr = r0 * (0.75 + 0.25 * sin(t * 0.5 + ph + lobe));\n 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));\n}\nvec3 flightLocal(vec3 pl, vec4 ip, float t){\n float aa = ip.x + t * (0.5 + 0.25 * sin(ip.z));\n pl.y += sin(t * 11.0 + ip.z * 3.0) * abs(pl.z) * 0.9; // the flap, growing to the wingtips\n float ca = cos(aa + 1.5708), sa = sin(aa + 1.5708); // heading = the orbit tangent\n return vec3(pl.x * ca - pl.z * sa, pl.y, pl.x * sa + pl.z * ca);\n}\nvoid main(){\n int g = int(aGrp + 0.5);\n vec3 p = mix(aPos, aPos2, uMW[g]);\n if (uFlight > 0.5) {\n p = flightPos(aIP, uT) + flightLocal(p, aIP, uT);\n } else {\n float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);\n p += uWindDir * (sw * aIP.z * max(p.y, 0.0));\n }\n gl_Position = uVP * (uGM[g] * (mat4(aI0, aI1, aI2, aI3) * vec4(p, 1.0)));\n}";
|
|
5
5
|
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}";
|
|
6
6
|
export declare const DEPTH_FS = "#version 300 es\nprecision mediump float;\nvoid main(){}";
|
|
7
7
|
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}";
|
|
@@ -18,4 +18,6 @@ export declare const AOBLUR_FS = "#version 300 es\nprecision mediump float;\nin
|
|
|
18
18
|
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}";
|
|
19
19
|
export declare const DOF_FS = "#version 300 es\nprecision highp float;\nin vec2 vUv; uniform sampler2D uTex; uniform sampler2D uDepth;\nuniform float uNear; uniform float uFar; uniform float uFocus; uniform float uRange;\nuniform float uMaxBlur; uniform vec2 uPx;\nout vec4 fragColor;\nfloat lin(float d){ return uNear * uFar / (uFar - d * (uFar - uNear)); }\nconst vec2 DISC[12] = vec2[](\n vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621),\n vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893),\n vec2(0.507, 0.064), vec2(0.896, 0.412), vec2(-0.322, -0.933), vec2(-0.792, -0.598));\nvoid main(){\n float z = lin(texture(uDepth, vUv).r);\n float coc = clamp(abs(z - uFocus) / max(1.0, uRange), 0.0, 1.0) * uMaxBlur;\n vec3 acc = texture(uTex, vUv).rgb;\n float wsum = 1.0;\n for (int i = 0; i < 12; i++) {\n vec2 off = DISC[i] * coc * uPx;\n // a sharp (in-focus) neighbour must not bleed into a blurred one: weight each tap\n // by ITS OWN CoC so foreground focus stays crisp against a soft background\n float zt = lin(texture(uDepth, vUv + off).r);\n float ct = clamp(abs(zt - uFocus) / max(1.0, uRange), 0.0, 1.0);\n float w = 0.25 + 0.75 * ct;\n acc += texture(uTex, vUv + off).rgb * w;\n wsum += w;\n }\n fragColor = vec4(acc / wsum, 1.0);\n}";
|
|
20
20
|
export declare const MOBLUR_FS = "#version 300 es\nprecision highp float;\nin vec2 vUv; uniform sampler2D uTex; uniform sampler2D uDepth;\nuniform mat4 uInvVP; uniform mat4 uPrevVP; uniform float uStrength;\nout vec4 fragColor;\nvoid main(){\n float d = texture(uDepth, vUv).r;\n vec4 ndc = vec4(vUv * 2.0 - 1.0, d * 2.0 - 1.0, 1.0);\n vec4 wp = uInvVP * ndc; wp /= wp.w;\n vec4 pc = uPrevVP * wp;\n vec2 prevUv = (pc.xy / pc.w) * 0.5 + 0.5;\n vec2 vel = (vUv - prevUv) * uStrength;\n float vlen = length(vel);\n if (vlen < 1e-5 || pc.w <= 0.0) { fragColor = vec4(texture(uTex, vUv).rgb, 1.0); return; }\n vel = vlen > 0.04 ? vel * (0.04 / vlen) : vel; // clamp the streak to 4% of the screen\n vec3 acc = vec3(0.0);\n for (int i = 0; i < 8; i++) {\n float t = (float(i) / 7.0) - 0.5;\n acc += texture(uTex, clamp(vUv + vel * t, vec2(0.001), vec2(0.999))).rgb;\n }\n fragColor = vec4(acc / 8.0, 1.0);\n}";
|
|
21
|
+
export declare const WATER_VS = "#version 300 es\nin vec3 aPos;\nuniform mat4 uVP; uniform float uT; uniform float uLevel; uniform float uAmp;\nout vec3 vW; out vec3 vN; out float vZ;\nvoid main(){\n vec3 p = aPos;\n float y = sin(p.x * 0.020 + uT * 1.3) + sin(p.z * 0.017 + uT * 1.1)\n + 0.5 * sin((p.x + p.z) * 0.031 + uT * 2.2);\n float dx = 0.020 * cos(p.x * 0.020 + uT * 1.3) + 0.0155 * cos((p.x + p.z) * 0.031 + uT * 2.2);\n float dz = 0.017 * cos(p.z * 0.017 + uT * 1.1) + 0.0155 * cos((p.x + p.z) * 0.031 + uT * 2.2);\n p.y = uLevel + y * uAmp;\n vW = p; vN = normalize(vec3(-dx * uAmp, 1.0, -dz * uAmp));\n vec4 cp = uVP * vec4(p, 1.0); vZ = cp.w; gl_Position = cp;\n}";
|
|
22
|
+
export declare const WATER_FS = "#version 300 es\nprecision highp float;\nin vec3 vW; in vec3 vN; in float vZ;\nuniform sampler2D uRefl; uniform vec2 uRes; uniform vec3 uTint; uniform vec3 uCam;\nuniform float uFogK; uniform vec3 uBg; uniform float uDistort; uniform float uAlpha;\nout vec4 fragColor;\nvoid main(){\n vec2 uv = gl_FragCoord.xy / uRes;\n vec3 refl = texture(uRefl, clamp(uv + vN.xz * uDistort, 0.001, 0.999)).rgb;\n vec3 V = normalize(uCam - vW);\n float fres = 0.06 + 0.94 * pow(1.0 - max(dot(vN, V), 0.0), 3.0);\n vec3 c = mix(uTint, refl, clamp(0.35 + fres * 0.6, 0.0, 0.95));\n float fog = clamp(uFogK / vZ, 0.08, 1.0);\n fragColor = vec4(mix(uBg, c, fog), uAlpha);\n}";
|
|
21
23
|
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}";
|
package/dist/shaders.js
CHANGED
|
@@ -31,6 +31,7 @@ uniform vec3 uCam; uniform float uFogK; uniform float uSpec; uniform vec3 uBg;
|
|
|
31
31
|
uniform vec3 uSkyC; uniform vec3 uGroundC; uniform float uPulse;
|
|
32
32
|
uniform float uShadowOn; uniform float uShadowSoft; uniform float uShadowBias;
|
|
33
33
|
uniform lowp sampler2DShadow uShadow; uniform float uShadowPx;
|
|
34
|
+
uniform float uClipY;
|
|
34
35
|
out vec4 fragColor;
|
|
35
36
|
float h3(vec3 p){ return fract(sin(dot(p, vec3(12.9898, 78.233, 37.719))) * 43758.5453); }
|
|
36
37
|
float vnoise(vec3 p){ vec3 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f);
|
|
@@ -49,6 +50,7 @@ float shadowFactor(){
|
|
|
49
50
|
return 0.35 + 0.65 * s;
|
|
50
51
|
}
|
|
51
52
|
void main(){
|
|
53
|
+
if (vPos.y < uClipY) discard; // W11: the reflection pass clips at the water plane
|
|
52
54
|
vec3 n = normalize(vNorm);
|
|
53
55
|
vec3 V = normalize(uCam - vPos);
|
|
54
56
|
float skin = clamp(vBlood * 5.0, 0.0, 1.0);
|
|
@@ -105,18 +107,45 @@ export const FACE_INST_VS = `#version 300 es
|
|
|
105
107
|
in vec3 aPos; in vec3 aNorm; in vec4 aCol; in float aBlood; in float aGrp;
|
|
106
108
|
in vec3 aPos2; in vec3 aNorm2;
|
|
107
109
|
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
|
|
110
|
+
uniform mat4 uVP; uniform mat4 uSVP; uniform float uT; uniform vec3 uWindDir;
|
|
111
|
+
uniform float uFlight;${GROUPS}
|
|
109
112
|
out vec3 vPos; out vec3 vNorm; out vec4 vCol; out float vZ; out vec4 vSh;
|
|
110
113
|
out float vGA; out float vBlood;
|
|
114
|
+
// W10 FLIGHT: aIP = (a0, r0, ph, lobe). The flock's heart wanders, three lobes orbit
|
|
115
|
+
// it breathing apart and back, each bird orbits its lobe with a ripple phase — the
|
|
116
|
+
// murmuration, evaluated per vertex from uT alone. The instance matrix is the flock
|
|
117
|
+
// frame (shared), so one batch = one flock you can place, scale and steer.
|
|
118
|
+
vec3 flightPos(vec4 ip, float t){
|
|
119
|
+
float a0 = ip.x, r0 = ip.y, ph = ip.z, lobe = ip.w;
|
|
120
|
+
float cx = sin(t * 0.11) * 700.0, cz = -500.0 + cos(t * 0.07) * 380.0;
|
|
121
|
+
float cy = 430.0 + sin(t * 0.16) * 110.0;
|
|
122
|
+
float spread = 180.0 + 140.0 * sin(t * 0.21 + 1.3);
|
|
123
|
+
float la = t * 0.34 + lobe * 2.0944;
|
|
124
|
+
vec3 lc = vec3(cx + cos(la) * spread, cy + sin(la * 2.0 + lobe) * 60.0, cz + sin(la) * spread * 0.7);
|
|
125
|
+
float aa = a0 + t * (0.5 + 0.25 * sin(ph));
|
|
126
|
+
float wob = sin(t * 1.1 + ph) * 0.35;
|
|
127
|
+
float rr = r0 * (0.75 + 0.25 * sin(t * 0.5 + ph + lobe));
|
|
128
|
+
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));
|
|
129
|
+
}
|
|
130
|
+
vec3 flightLocal(vec3 pl, vec4 ip, float t){
|
|
131
|
+
float aa = ip.x + t * (0.5 + 0.25 * sin(ip.z));
|
|
132
|
+
pl.y += sin(t * 11.0 + ip.z * 3.0) * abs(pl.z) * 0.9; // the flap, growing to the wingtips
|
|
133
|
+
float ca = cos(aa + 1.5708), sa = sin(aa + 1.5708); // heading = the orbit tangent
|
|
134
|
+
return vec3(pl.x * ca - pl.z * sa, pl.y, pl.x * sa + pl.z * ca);
|
|
135
|
+
}
|
|
111
136
|
void main(){
|
|
112
137
|
int g = int(aGrp + 0.5);
|
|
113
138
|
float w = uMW[g];
|
|
114
139
|
mat4 inst = mat4(aI0, aI1, aI2, aI3);
|
|
115
140
|
vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
141
|
+
if (uFlight > 0.5) {
|
|
142
|
+
p = flightPos(aIP, uT) + flightLocal(p, aIP, uT);
|
|
143
|
+
} else {
|
|
144
|
+
// W9 GPU WIND: per-instance (phase, freq, amp); displacement grows with height,
|
|
145
|
+
// plus a second harmonic so the sway breathes instead of ticking
|
|
146
|
+
float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);
|
|
147
|
+
p += uWindDir * (sw * aIP.z * max(p.y, 0.0));
|
|
148
|
+
}
|
|
120
149
|
vec4 wp = uGM[g] * (inst * vec4(p, 1.0));
|
|
121
150
|
vPos = wp.xyz; vNorm = uNM[g] * (mat3(inst) * n); vCol = aCol; vGA = uGA[g]; vBlood = aBlood;
|
|
122
151
|
vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;
|
|
@@ -124,12 +153,39 @@ void main(){
|
|
|
124
153
|
export const DEPTH_INST_VS = `#version 300 es
|
|
125
154
|
in vec3 aPos; in float aGrp; in vec3 aPos2;
|
|
126
155
|
in vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3; in vec4 aIP;
|
|
127
|
-
uniform mat4 uVP; uniform float uT; uniform vec3 uWindDir
|
|
156
|
+
uniform mat4 uVP; uniform float uT; uniform vec3 uWindDir;
|
|
157
|
+
uniform float uFlight;${GROUPS}
|
|
158
|
+
// W10 FLIGHT: aIP = (a0, r0, ph, lobe). The flock's heart wanders, three lobes orbit
|
|
159
|
+
// it breathing apart and back, each bird orbits its lobe with a ripple phase — the
|
|
160
|
+
// murmuration, evaluated per vertex from uT alone. The instance matrix is the flock
|
|
161
|
+
// frame (shared), so one batch = one flock you can place, scale and steer.
|
|
162
|
+
vec3 flightPos(vec4 ip, float t){
|
|
163
|
+
float a0 = ip.x, r0 = ip.y, ph = ip.z, lobe = ip.w;
|
|
164
|
+
float cx = sin(t * 0.11) * 700.0, cz = -500.0 + cos(t * 0.07) * 380.0;
|
|
165
|
+
float cy = 430.0 + sin(t * 0.16) * 110.0;
|
|
166
|
+
float spread = 180.0 + 140.0 * sin(t * 0.21 + 1.3);
|
|
167
|
+
float la = t * 0.34 + lobe * 2.0944;
|
|
168
|
+
vec3 lc = vec3(cx + cos(la) * spread, cy + sin(la * 2.0 + lobe) * 60.0, cz + sin(la) * spread * 0.7);
|
|
169
|
+
float aa = a0 + t * (0.5 + 0.25 * sin(ph));
|
|
170
|
+
float wob = sin(t * 1.1 + ph) * 0.35;
|
|
171
|
+
float rr = r0 * (0.75 + 0.25 * sin(t * 0.5 + ph + lobe));
|
|
172
|
+
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));
|
|
173
|
+
}
|
|
174
|
+
vec3 flightLocal(vec3 pl, vec4 ip, float t){
|
|
175
|
+
float aa = ip.x + t * (0.5 + 0.25 * sin(ip.z));
|
|
176
|
+
pl.y += sin(t * 11.0 + ip.z * 3.0) * abs(pl.z) * 0.9; // the flap, growing to the wingtips
|
|
177
|
+
float ca = cos(aa + 1.5708), sa = sin(aa + 1.5708); // heading = the orbit tangent
|
|
178
|
+
return vec3(pl.x * ca - pl.z * sa, pl.y, pl.x * sa + pl.z * ca);
|
|
179
|
+
}
|
|
128
180
|
void main(){
|
|
129
181
|
int g = int(aGrp + 0.5);
|
|
130
182
|
vec3 p = mix(aPos, aPos2, uMW[g]);
|
|
131
|
-
|
|
132
|
-
|
|
183
|
+
if (uFlight > 0.5) {
|
|
184
|
+
p = flightPos(aIP, uT) + flightLocal(p, aIP, uT);
|
|
185
|
+
} else {
|
|
186
|
+
float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);
|
|
187
|
+
p += uWindDir * (sw * aIP.z * max(p.y, 0.0));
|
|
188
|
+
}
|
|
133
189
|
gl_Position = uVP * (uGM[g] * (mat4(aI0, aI1, aI2, aI3) * vec4(p, 1.0)));
|
|
134
190
|
}`;
|
|
135
191
|
export const DEPTH_VS = `#version 300 es
|
|
@@ -371,6 +427,36 @@ void main(){
|
|
|
371
427
|
}
|
|
372
428
|
fragColor = vec4(acc / 8.0, 1.0);
|
|
373
429
|
}`;
|
|
430
|
+
// W11 WATER - a wave-displaced surface sampling the reflection pass in screen space
|
|
431
|
+
export const WATER_VS = `#version 300 es
|
|
432
|
+
in vec3 aPos;
|
|
433
|
+
uniform mat4 uVP; uniform float uT; uniform float uLevel; uniform float uAmp;
|
|
434
|
+
out vec3 vW; out vec3 vN; out float vZ;
|
|
435
|
+
void main(){
|
|
436
|
+
vec3 p = aPos;
|
|
437
|
+
float y = sin(p.x * 0.020 + uT * 1.3) + sin(p.z * 0.017 + uT * 1.1)
|
|
438
|
+
+ 0.5 * sin((p.x + p.z) * 0.031 + uT * 2.2);
|
|
439
|
+
float dx = 0.020 * cos(p.x * 0.020 + uT * 1.3) + 0.0155 * cos((p.x + p.z) * 0.031 + uT * 2.2);
|
|
440
|
+
float dz = 0.017 * cos(p.z * 0.017 + uT * 1.1) + 0.0155 * cos((p.x + p.z) * 0.031 + uT * 2.2);
|
|
441
|
+
p.y = uLevel + y * uAmp;
|
|
442
|
+
vW = p; vN = normalize(vec3(-dx * uAmp, 1.0, -dz * uAmp));
|
|
443
|
+
vec4 cp = uVP * vec4(p, 1.0); vZ = cp.w; gl_Position = cp;
|
|
444
|
+
}`;
|
|
445
|
+
export const WATER_FS = `#version 300 es
|
|
446
|
+
precision highp float;
|
|
447
|
+
in vec3 vW; in vec3 vN; in float vZ;
|
|
448
|
+
uniform sampler2D uRefl; uniform vec2 uRes; uniform vec3 uTint; uniform vec3 uCam;
|
|
449
|
+
uniform float uFogK; uniform vec3 uBg; uniform float uDistort; uniform float uAlpha;
|
|
450
|
+
out vec4 fragColor;
|
|
451
|
+
void main(){
|
|
452
|
+
vec2 uv = gl_FragCoord.xy / uRes;
|
|
453
|
+
vec3 refl = texture(uRefl, clamp(uv + vN.xz * uDistort, 0.001, 0.999)).rgb;
|
|
454
|
+
vec3 V = normalize(uCam - vW);
|
|
455
|
+
float fres = 0.06 + 0.94 * pow(1.0 - max(dot(vN, V), 0.0), 3.0);
|
|
456
|
+
vec3 c = mix(uTint, refl, clamp(0.35 + fres * 0.6, 0.0, 0.95));
|
|
457
|
+
float fog = clamp(uFogK / vZ, 0.08, 1.0);
|
|
458
|
+
fragColor = vec4(mix(uBg, c, fog), uAlpha);
|
|
459
|
+
}`;
|
|
374
460
|
// compact FXAA (luma-based, 5-tap edge blend) — runs last, on the LDR composite
|
|
375
461
|
export const FXAA_FS = `#version 300 es
|
|
376
462
|
precision mediump float;
|
package/dist/world.d.ts
CHANGED
|
@@ -96,6 +96,30 @@ export declare class World {
|
|
|
96
96
|
dir: V3;
|
|
97
97
|
speed: number;
|
|
98
98
|
};
|
|
99
|
+
/** W11 — REAL WATER: one water table per world. The scene renders a second time
|
|
100
|
+
* through the reflected camera (solids + instanced, flocks included) into a
|
|
101
|
+
* reflection target; the surface is a wave-displaced grid sampling it in screen
|
|
102
|
+
* space with a fresnel blend. The table fills wherever the ground dips below level. */
|
|
103
|
+
water: {
|
|
104
|
+
on: boolean;
|
|
105
|
+
level: number;
|
|
106
|
+
size: number;
|
|
107
|
+
hue: number;
|
|
108
|
+
sat: number;
|
|
109
|
+
light: number;
|
|
110
|
+
amp: number;
|
|
111
|
+
distort: number;
|
|
112
|
+
alpha: number;
|
|
113
|
+
};
|
|
114
|
+
private pWater;
|
|
115
|
+
private bWater;
|
|
116
|
+
private waterVerts;
|
|
117
|
+
private reflFBO;
|
|
118
|
+
private reflTex;
|
|
119
|
+
private reflDepth;
|
|
120
|
+
private reflW;
|
|
121
|
+
private reflH;
|
|
122
|
+
private clipY;
|
|
99
123
|
private post;
|
|
100
124
|
private ink;
|
|
101
125
|
private bg;
|
|
@@ -255,6 +279,12 @@ export declare class World {
|
|
|
255
279
|
amp: number;
|
|
256
280
|
freq?: number;
|
|
257
281
|
};
|
|
282
|
+
/** W10 GPU flight: the batch IS a murmuration — per-bird orbit params generated
|
|
283
|
+
* here, positions evaluated in the vertex stage from time alone. The instance
|
|
284
|
+
* matrices become the shared flock frame (place / scale / steer the whole flock). */
|
|
285
|
+
flight?: boolean | {
|
|
286
|
+
lobes?: number;
|
|
287
|
+
};
|
|
258
288
|
}): void;
|
|
259
289
|
grow(fn: GrowFn): void;
|
|
260
290
|
drawLine(a: V3, b: V3, color: string, alpha: number, width?: number): void;
|
|
@@ -287,6 +317,12 @@ export declare class World {
|
|
|
287
317
|
* after each draw — attribute divisor is global-per-index state and would otherwise
|
|
288
318
|
* poison the non-instanced draws that share those indices. */
|
|
289
319
|
private drawInstancedAll;
|
|
320
|
+
private resizeReflection;
|
|
321
|
+
/** The reflection pass: solids + instanced re-rendered through the reflected camera,
|
|
322
|
+
* clipped at the water plane. Lines / ribbons / points are skipped - reflections of
|
|
323
|
+
* the light layer read as noise at half-res and cost more than they give. */
|
|
324
|
+
private renderReflection;
|
|
325
|
+
private drawWater;
|
|
290
326
|
private renderFrame;
|
|
291
327
|
private frameFn;
|
|
292
328
|
private pausedByUser;
|
package/dist/world.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// hardware-PCF depth-texture shadows, per-group transforms + morph targets in the
|
|
5
5
|
// vertex stage, mesh + ribbon primitives. Zero third-party imports, on purpose.
|
|
6
6
|
import { v3, add, sub, scale, cross, norm, clamp, hsl, mIdent, mPerspective, mLookAt, mMul, mCompose, mInvert, normalMat3, } from "./math.js";
|
|
7
|
-
import { FACE_VS, FACE_FS, DEPTH_VS, DEPTH_FS, LINE_VS, LINE_FS, PT_VS, PT_FS, RIBBON_VS, RIBBON_FS, FACE_INST_VS, DEPTH_INST_VS, } from "./shaders.js";
|
|
7
|
+
import { FACE_VS, FACE_FS, DEPTH_VS, DEPTH_FS, LINE_VS, LINE_FS, PT_VS, PT_FS, RIBBON_VS, RIBBON_FS, FACE_INST_VS, DEPTH_INST_VS, WATER_VS, WATER_FS, } from "./shaders.js";
|
|
8
8
|
import { PInfo, PostChain } from "./post.js";
|
|
9
9
|
const FACE_STRIDE = 18; // pos3 norm3 rgb3 gloss1 blood1 grp1 pos2:3 norm2:3
|
|
10
10
|
const LINE_STRIDE = 8; // pos3 col4 grp1
|
|
@@ -36,6 +36,20 @@ export class World {
|
|
|
36
36
|
this.instanced = [];
|
|
37
37
|
/** W9 — the one wind that blows through every swaying instanced batch */
|
|
38
38
|
this.wind = { dir: v3(1, 0, 0.35), speed: 1 };
|
|
39
|
+
/** W11 — REAL WATER: one water table per world. The scene renders a second time
|
|
40
|
+
* through the reflected camera (solids + instanced, flocks included) into a
|
|
41
|
+
* reflection target; the surface is a wave-displaced grid sampling it in screen
|
|
42
|
+
* space with a fresnel blend. The table fills wherever the ground dips below level. */
|
|
43
|
+
this.water = { on: false, level: 0, size: 4000, hue: 205, sat: 50, light: 30, amp: 3, distort: 0.03, alpha: 0.88 };
|
|
44
|
+
this.pWater = null;
|
|
45
|
+
this.bWater = null;
|
|
46
|
+
this.waterVerts = 0;
|
|
47
|
+
this.reflFBO = null;
|
|
48
|
+
this.reflTex = null;
|
|
49
|
+
this.reflDepth = null;
|
|
50
|
+
this.reflW = 0;
|
|
51
|
+
this.reflH = 0;
|
|
52
|
+
this.clipY = -1e9;
|
|
39
53
|
this.ink = false;
|
|
40
54
|
this.bg = [0.024, 0.016, 0.055];
|
|
41
55
|
this.transparent = false;
|
|
@@ -395,13 +409,22 @@ export class World {
|
|
|
395
409
|
this.pushFace(store, v3(P[i], P[i + 1], P[i + 2]), v3(P[i + 3], P[i + 4], P[i + 5]), v3(P[i + 6], P[i + 7], P[i + 8]), o, v3(N[i], N[i + 1], N[i + 2]), v3(N[i + 3], N[i + 4], N[i + 5]), v3(N[i + 6], N[i + 7], N[i + 8]));
|
|
396
410
|
}
|
|
397
411
|
const mats = new Float32Array(instances.length * 20); // mat4 + sway vec4, one stride
|
|
412
|
+
const lobes = typeof o.flight === "object" ? (o.flight.lobes ?? 3) : 3;
|
|
398
413
|
instances.forEach((m, i) => {
|
|
399
414
|
mats.set(m instanceof Float32Array ? m : mCompose(m), i * 20);
|
|
400
415
|
const ph = ((i * 2654435761) % 6283) / 1000; // scattered phase
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
416
|
+
if (o.flight) { // aIP = (a0, r0, ph, lobe)
|
|
417
|
+
mats[i * 20 + 16] = ((i * 40503) % 6283) / 1000;
|
|
418
|
+
mats[i * 20 + 17] = 40 + ((i * 9973) % 1000) / 5;
|
|
419
|
+
mats[i * 20 + 18] = ph;
|
|
420
|
+
mats[i * 20 + 19] = i % lobes;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
mats[i * 20 + 16] = ph;
|
|
424
|
+
mats[i * 20 + 17] = (o.sway?.freq ?? 1.3) * (0.85 + ((i * 97) % 30) / 100);
|
|
425
|
+
mats[i * 20 + 18] = (o.sway?.amp ?? 0) * 0.01;
|
|
426
|
+
mats[i * 20 + 19] = 0;
|
|
427
|
+
}
|
|
405
428
|
});
|
|
406
429
|
const vbuf = gl.createBuffer();
|
|
407
430
|
gl.bindBuffer(gl.ARRAY_BUFFER, vbuf);
|
|
@@ -409,7 +432,7 @@ export class World {
|
|
|
409
432
|
const ibuf = gl.createBuffer();
|
|
410
433
|
gl.bindBuffer(gl.ARRAY_BUFFER, ibuf);
|
|
411
434
|
gl.bufferData(gl.ARRAY_BUFFER, mats, gl.STATIC_DRAW);
|
|
412
|
-
this.instanced.push({ vbuf, ibuf, verts: store.length / FACE_STRIDE, count: instances.length });
|
|
435
|
+
this.instanced.push({ vbuf, ibuf, verts: store.length / FACE_STRIDE, count: instances.length, flight: !!o.flight });
|
|
413
436
|
}
|
|
414
437
|
grow(fn) { this.hooks.push(fn); }
|
|
415
438
|
// ── the living verbs (gathered per frame) ──
|
|
@@ -492,6 +515,7 @@ export class World {
|
|
|
492
515
|
}
|
|
493
516
|
setLights(p, fogK) {
|
|
494
517
|
const gl = this.gl, L = norm(this.light.dir);
|
|
518
|
+
gl.uniform1f(p.u("uClipY"), this.clipY);
|
|
495
519
|
gl.uniform3f(p.u("uLdir"), L.x, L.y, L.z);
|
|
496
520
|
gl.uniform1f(p.u("uAmb"), this.ink ? Math.min(1, this.light.ambient + 0.22) : this.light.ambient);
|
|
497
521
|
gl.uniform1f(p.u("uDif"), this.ink ? this.light.diffuse * 0.55 : this.light.diffuse);
|
|
@@ -611,6 +635,7 @@ export class World {
|
|
|
611
635
|
return;
|
|
612
636
|
const gl = this.gl;
|
|
613
637
|
for (const rec of this.instanced) {
|
|
638
|
+
gl.uniform1f(p.u("uFlight"), rec.flight ? 1 : 0);
|
|
614
639
|
gl.bindBuffer(gl.ARRAY_BUFFER, rec.vbuf);
|
|
615
640
|
this.attrib(p, "aPos", 3, FACE_STRIDE, 0);
|
|
616
641
|
if (!depthOnly) {
|
|
@@ -638,6 +663,114 @@ export class World {
|
|
|
638
663
|
gl.vertexAttribDivisor(loc, 0);
|
|
639
664
|
}
|
|
640
665
|
}
|
|
666
|
+
resizeReflection() {
|
|
667
|
+
const gl = this.gl;
|
|
668
|
+
const rw = Math.max(1, this.canvas.width >> 1), rh = Math.max(1, this.canvas.height >> 1);
|
|
669
|
+
if (rw === this.reflW && rh === this.reflH && this.reflFBO)
|
|
670
|
+
return;
|
|
671
|
+
this.reflW = rw;
|
|
672
|
+
this.reflH = rh;
|
|
673
|
+
if (this.reflTex)
|
|
674
|
+
gl.deleteTexture(this.reflTex);
|
|
675
|
+
if (this.reflDepth)
|
|
676
|
+
gl.deleteRenderbuffer(this.reflDepth);
|
|
677
|
+
if (this.reflFBO)
|
|
678
|
+
gl.deleteFramebuffer(this.reflFBO);
|
|
679
|
+
this.reflTex = gl.createTexture();
|
|
680
|
+
gl.bindTexture(gl.TEXTURE_2D, this.reflTex);
|
|
681
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, rw, rh, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
682
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
683
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
684
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
685
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
686
|
+
this.reflDepth = gl.createRenderbuffer();
|
|
687
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, this.reflDepth);
|
|
688
|
+
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT24, rw, rh);
|
|
689
|
+
this.reflFBO = gl.createFramebuffer();
|
|
690
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.reflFBO);
|
|
691
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.reflTex, 0);
|
|
692
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.reflDepth);
|
|
693
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
694
|
+
}
|
|
695
|
+
/** The reflection pass: solids + instanced re-rendered through the reflected camera,
|
|
696
|
+
* clipped at the water plane. Lines / ribbons / points are skipped - reflections of
|
|
697
|
+
* the light layer read as noise at half-res and cost more than they give. */
|
|
698
|
+
renderReflection(fogK) {
|
|
699
|
+
const gl = this.gl;
|
|
700
|
+
const h = this.water.level;
|
|
701
|
+
const R = new Float32Array([1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 2 * h, 0, 1]);
|
|
702
|
+
const vpSave = this.vp;
|
|
703
|
+
this.vp = mMul(vpSave, R);
|
|
704
|
+
this.clipY = h;
|
|
705
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.reflFBO);
|
|
706
|
+
gl.viewport(0, 0, this.reflW, this.reflH);
|
|
707
|
+
gl.clearColor(this.bg[0], this.bg[1], this.bg[2], 1);
|
|
708
|
+
gl.enable(gl.DEPTH_TEST);
|
|
709
|
+
gl.depthMask(true);
|
|
710
|
+
gl.disable(gl.BLEND);
|
|
711
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
712
|
+
this.pFace.use();
|
|
713
|
+
this.setVP(this.pFace);
|
|
714
|
+
this.setLights(this.pFace, fogK);
|
|
715
|
+
this.drawFaces(this.bFace, this.sFace.length / FACE_STRIDE, null);
|
|
716
|
+
if (this.dFaceA.length)
|
|
717
|
+
this.drawFaces(this.bFaceD, this.dFaceA.length / FACE_STRIDE, new Float32Array(this.dFaceA));
|
|
718
|
+
if (this.instanced.length) {
|
|
719
|
+
this.pFaceI.use();
|
|
720
|
+
this.setVP(this.pFaceI);
|
|
721
|
+
this.setLights(this.pFaceI, fogK);
|
|
722
|
+
gl.uniform1f(this.pFaceI.u("uT"), this.lastT * this.wind.speed);
|
|
723
|
+
gl.uniform3f(this.pFaceI.u("uWindDir"), this.wind.dir.x, this.wind.dir.y, this.wind.dir.z);
|
|
724
|
+
this.drawInstancedAll(this.pFaceI, false);
|
|
725
|
+
}
|
|
726
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
727
|
+
this.vp = vpSave;
|
|
728
|
+
this.clipY = -1e9;
|
|
729
|
+
}
|
|
730
|
+
drawWater(t, fogK) {
|
|
731
|
+
const gl = this.gl;
|
|
732
|
+
if (!this.pWater)
|
|
733
|
+
this.pWater = new PInfo(gl, WATER_VS, WATER_FS);
|
|
734
|
+
if (!this.bWater) {
|
|
735
|
+
const g = [];
|
|
736
|
+
const N = 64, S = this.water.size;
|
|
737
|
+
for (let i = 0; i < N; i++)
|
|
738
|
+
for (let j = 0; j < N; j++) {
|
|
739
|
+
const x0 = (i / N - 0.5) * S, x1 = ((i + 1) / N - 0.5) * S;
|
|
740
|
+
const z0 = (j / N - 0.5) * S, z1 = ((j + 1) / N - 0.5) * S;
|
|
741
|
+
g.push(x0, 0, z0, x1, 0, z0, x1, 0, z1, x0, 0, z0, x1, 0, z1, x0, 0, z1);
|
|
742
|
+
}
|
|
743
|
+
this.bWater = gl.createBuffer();
|
|
744
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.bWater);
|
|
745
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(g), gl.STATIC_DRAW);
|
|
746
|
+
this.waterVerts = g.length / 3;
|
|
747
|
+
}
|
|
748
|
+
const p = this.pWater;
|
|
749
|
+
p.use();
|
|
750
|
+
gl.uniformMatrix4fv(p.u("uVP"), false, this.vp);
|
|
751
|
+
gl.uniform1f(p.u("uT"), t);
|
|
752
|
+
gl.uniform1f(p.u("uLevel"), this.water.level);
|
|
753
|
+
gl.uniform1f(p.u("uAmp"), this.water.amp);
|
|
754
|
+
gl.uniform1f(p.u("uDistort"), this.water.distort);
|
|
755
|
+
gl.uniform1f(p.u("uAlpha"), this.water.alpha);
|
|
756
|
+
const [r, gg, b] = this.col(this.water.hue, this.water.sat, this.water.light);
|
|
757
|
+
gl.uniform3f(p.u("uTint"), r, gg, b);
|
|
758
|
+
gl.uniform3f(p.u("uCam"), this.cx.x, this.cx.y, this.cx.z);
|
|
759
|
+
gl.uniform1f(p.u("uFogK"), fogK);
|
|
760
|
+
gl.uniform3f(p.u("uBg"), this.bg[0], this.bg[1], this.bg[2]);
|
|
761
|
+
gl.uniform2f(p.u("uRes"), this.canvas.width, this.canvas.height);
|
|
762
|
+
gl.activeTexture(gl.TEXTURE3);
|
|
763
|
+
gl.bindTexture(gl.TEXTURE_2D, this.reflTex);
|
|
764
|
+
gl.uniform1i(p.u("uRefl"), 3);
|
|
765
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.bWater);
|
|
766
|
+
this.attrib(p, "aPos", 3, 3, 0);
|
|
767
|
+
gl.depthMask(false);
|
|
768
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
769
|
+
gl.drawArrays(gl.TRIANGLES, 0, this.waterVerts);
|
|
770
|
+
if (!this.ink)
|
|
771
|
+
gl.blendFunc(gl.ONE, gl.ONE);
|
|
772
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
773
|
+
}
|
|
641
774
|
// ── the frame ──
|
|
642
775
|
renderFrame(t, dt) {
|
|
643
776
|
const gl = this.gl;
|
|
@@ -732,6 +865,11 @@ export class World {
|
|
|
732
865
|
}
|
|
733
866
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
734
867
|
}
|
|
868
|
+
// W11: the reflection pass first — the world through the mirrored camera
|
|
869
|
+
if (this.water.on) {
|
|
870
|
+
this.resizeReflection();
|
|
871
|
+
this.renderReflection(fogK);
|
|
872
|
+
}
|
|
735
873
|
// the scene pass — into the HDR target when the post chain is on
|
|
736
874
|
if (this.post)
|
|
737
875
|
this.post.begin();
|
|
@@ -765,6 +903,8 @@ export class World {
|
|
|
765
903
|
// the light — additive, depth-TESTED but not written (ink: normal alpha blend)
|
|
766
904
|
gl.depthMask(false);
|
|
767
905
|
gl.enable(gl.BLEND);
|
|
906
|
+
if (this.water.on && this.reflTex)
|
|
907
|
+
this.drawWater(t, fogK);
|
|
768
908
|
if (this.ink)
|
|
769
909
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
770
910
|
else
|
|
@@ -890,6 +1030,14 @@ export class World {
|
|
|
890
1030
|
this.gl.deleteBuffer(rec.ibuf);
|
|
891
1031
|
}
|
|
892
1032
|
this.instanced = [];
|
|
1033
|
+
if (this.reflTex)
|
|
1034
|
+
this.gl.deleteTexture(this.reflTex);
|
|
1035
|
+
if (this.reflDepth)
|
|
1036
|
+
this.gl.deleteRenderbuffer(this.reflDepth);
|
|
1037
|
+
if (this.reflFBO)
|
|
1038
|
+
this.gl.deleteFramebuffer(this.reflFBO);
|
|
1039
|
+
if (this.bWater)
|
|
1040
|
+
this.gl.deleteBuffer(this.bWater);
|
|
893
1041
|
this.post?.dispose();
|
|
894
1042
|
if (releaseContext)
|
|
895
1043
|
this.gl.getExtension("WEBGL_lose_context")?.loseContext();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chromatic-coherence/generative-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "THE THIRD ENGINE — a zero-dependency WebGL2 graphics engine grown from generative-charts-3d: HDR + bloom + SSAO post chain, hardware-PCF shadow maps, a geometry stdlib, ribbon strokes, group transforms and morph targets. The charts API rides on top unchanged.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|