@chromatic-coherence/generative-engine 1.2.0 → 1.3.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 +137 -130
- package/dist/shaders.d.ts +2 -2
- package/dist/shaders.js +351 -345
- package/dist/world.d.ts +13 -1
- package/dist/world.js +18 -5
- package/package.json +44 -44
package/README.md
CHANGED
|
@@ -1,130 +1,137 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
# generative-engine
|
|
4
|
-
|
|
5
|
-
### **THE THIRD ENGINE** — the [Generative Charts](https://chromaticcoherence.ai/generative-charts) family grows into a full **WebGL2 graphics engine**.
|
|
6
|
-
|
|
7
|
-
[](./LICENSE)
|
|
8
|
-
[](#)
|
|
9
|
-
|
|
10
|
-
**[Live scenes →](https://chromaticcoherence.ai/generative-engine)** · **[The charts sibling →](https://www.npmjs.com/package/@chromatic-coherence/generative-charts)**
|
|
11
|
-
|
|
12
|
-
</div>
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
The charts library stays what it is — small, stable, chart-first. **generative-engine** is where
|
|
17
|
-
the family expands: the same API (`createWorld` · `face` · `line` · `glow` · `grow` · `draw*` ·
|
|
18
|
-
`fade`), so chart recipes port forward by changing one import — and underneath it, a real
|
|
19
|
-
graphics engine. Zero dependencies; the browser is the only runtime.
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npm install @chromatic-coherence/generative-engine
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
```js
|
|
26
|
-
import { createWorld, v3, icosphere, heightfield } from "@chromatic-coherence/generative-engine";
|
|
27
|
-
|
|
28
|
-
const w = createWorld({ canvas, controls: true, shadows: true });
|
|
29
|
-
w.lights.push({ p: v3(-160, 320, 120), intensity: 1.2, falloff: 500 });
|
|
30
|
-
w.mesh(heightfield((x, z) => Math.sin(x * 0.01) * 20, 800, 800), { hue: 262, sat: 38, light: 26 });
|
|
31
|
-
w.meshMorph(icosphere(70, 3), myBlob, { hue: 350, sat: 30, light: 42, gloss: 0.5, group: "blob" });
|
|
32
|
-
w.grow((t) => { w.morph("blob", 0.5 + 0.5 * Math.sin(t)); w.setTransform("blob", { rotateY: t * 0.4 }); });
|
|
33
|
-
w.start();
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## What the engine adds over the chart editions
|
|
37
|
-
|
|
38
|
-
| | |
|
|
39
|
-
|---|---|
|
|
40
|
-
| **WebGL2 + HDR post chain** | The scene renders to a floating-point target, then: **bloom** (soft-knee bright pass, half-res gaussian), **SSAO** (depth-based contact shading), one composite pass (ACES filmic, colour grade, vignette), and **FXAA**. All art-directable (`w.bloom`, `w.ssao`, `w.fxaa`), all on by default. `post: false` opts out. |
|
|
41
|
-
| **Real shadow maps** | A depth **texture** with hardware-comparison PCF (`sampler2DShadow`) — soft, exact, 2048px by default. `shadows: { size, softness, bias }`. |
|
|
42
|
-
| **Geometry stdlib** | `plane` · `box` · `icosphere` · `tube` (parallel-transport sweep, taperable) · `lathe` · `extrude` · `heightfield` · `transformMesh` · `mergeMesh` — all pure math, all unit-tested, uploaded in one `w.mesh(geo, opts)` call with smooth normals. |
|
|
43
|
-
| **Ribbons** | `w.ribbon(points, { width })` / `w.drawRibbon(...)` — screen-space-expanded strokes with **real pixel width**, feathered edges, depth-tested. The stroke aesthetic `gl.LINES` never allowed. |
|
|
44
|
-
| **Group transforms** | `w.setTransform(group, { translate, rotateY, scale })` — geometry moves rigidly in the vertex stage, zero CPU re-tessellation, shadow pass included. |
|
|
45
|
-
| **Morph targets** | `w.meshMorph(geoA, geoB, opts)` + `w.morph(group, t)` — blend between two vertex sets in the shader (breathing, becoming). Eased like `fade`. |
|
|
46
|
-
| **8 point lights** | Up from 4; `lights[0]` casts the shadows. HDR intensities welcome — bloom catches what exceeds 1.0. |
|
|
47
|
-
| **The carried light model** | Everything the chart edition learned rides along: GGX + Fresnel specular, procedural micro-relief, the perfusion term (`blood`, `bloodPulse`), pore noise, subsurface rim, coloured hemispheric ambient, fog — now resolving toward a real background colour the engine owns. |
|
|
48
|
-
|
|
49
|
-
**The ink edition** (`theme: "light"`) still renders any recipe on paper instead of night —
|
|
50
|
-
lightness inverts, strokes blend normally, SSAO becomes pencil shading. Bloom stays dark-only.
|
|
51
|
-
|
|
52
|
-
## Objectives — scenes that know where they're going
|
|
53
|
-
|
|
54
|
-
A scene gets a **goal** and the engine drives motion toward it; a live **movement meter**
|
|
55
|
-
tells you whether the scene is moving or settled — and names what is moving.
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
import { createWorld, attachObjectives } from "@chromatic-coherence/generative-engine";
|
|
59
|
-
|
|
60
|
-
const w = createWorld({ canvas });
|
|
61
|
-
const obj = attachObjectives(w);
|
|
62
|
-
|
|
63
|
-
obj.define("reveal", {
|
|
64
|
-
morph: { group: "terrain", to: 1 }, // blend the terrain to its B shape
|
|
65
|
-
camera: { dist: 420, pitch: 0.62 }, // settle the camera in close
|
|
66
|
-
fade: { group: "veil", to: 0 }, // dissolve the veil
|
|
67
|
-
pace: 3, // seconds to ~95% of the way there
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
obj.onReading(r => {
|
|
71
|
-
// { name, progress, level, floor, moving, settled, driving: ["morph:terrain","camera",...] }
|
|
72
|
-
if (r.settled) console.log(`${r.name}: settled`);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// later — the goal itself moves; motion continues from the current state, no snap,
|
|
76
|
-
// and the meter lights up again:
|
|
77
|
-
obj.retarget("reveal", { camera: { dist: 300 } });
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Channels: `transform` (group rigid-body spec) · `morph` · `fade` · `camera` · `custom`
|
|
81
|
-
(an `apply(progress, dt)` escape hatch). `readings()` is the pull form of `onReading`.
|
|
82
|
-
|
|
83
|
-
**The meter's semantics** come from the objective-movement measurement work this engine's
|
|
84
|
-
org ran first as science: movement is a **level sustained above a settled floor**, not a
|
|
85
|
-
discrete event. `driving` names the elevated channels — movement you can name; `settled`
|
|
86
|
-
is the sustained fall to the floor. Options: `attachObjectives(w, { floor, settleAfter })`.
|
|
87
|
-
|
|
88
|
-
The layer rides the frame loop as a `grow()` hook and drives the World through its public
|
|
89
|
-
verbs only — the renderer is untouched, and everything composes with hand-written `grow()`
|
|
90
|
-
animation.
|
|
91
|
-
|
|
92
|
-
## Demo
|
|
93
|
-
|
|
94
|
-
`demo/index.html` — serve the package folder (`python -m http.server`) and open
|
|
95
|
-
`/demo/`. Drag to orbit, ctrl+wheel to zoom; `?theme=light`, `?post=0`, `?still`.
|
|
96
|
-
|
|
97
|
-
## Falling back
|
|
98
|
-
|
|
99
|
-
`createWorld` **throws** (never a blank page) when WebGL2 is missing. Catch it and build the
|
|
100
|
-
same recipe through the chart editions — the API is a subset, so the fallback chain
|
|
101
|
-
engine → charts-3d → charts-2d is three imports of the same code. Browsers cap live GL
|
|
102
|
-
contexts per page: on teardown call `dispose(true)` to release the context immediately, and
|
|
103
|
-
mount a **fresh** canvas if you rebuild (a lost context is dead on its canvas).
|
|
104
|
-
|
|
105
|
-
## Instancing, depth of field, motion blur (v1.2)
|
|
106
|
-
|
|
107
|
-
- **`w.meshInstanced(geo, transforms, opts)`** — upload a mesh once, draw it at every
|
|
108
|
-
transform in a single instanced call, shadow pass included. A forest is one draw.
|
|
109
|
-
Instances ride their group's fade / morph / transform like any other geometry.
|
|
110
|
-
- **`w.dof = { on, focus, range, maxBlur }`** — depth of field: blur grows with distance
|
|
111
|
-
from the focus plane (world units), sharp subjects protected per-tap.
|
|
112
|
-
- **`w.motionBlur = { on, strength }`** — camera motion blur by depth reprojection into
|
|
113
|
-
the previous frame; streaks clamp to 4 % of the screen.
|
|
114
|
-
|
|
115
|
-
##
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# generative-engine
|
|
4
|
+
|
|
5
|
+
### **THE THIRD ENGINE** — the [Generative Charts](https://chromaticcoherence.ai/generative-charts) family grows into a full **WebGL2 graphics engine**.
|
|
6
|
+
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](#)
|
|
9
|
+
|
|
10
|
+
**[Live scenes →](https://chromaticcoherence.ai/generative-engine)** · **[The charts sibling →](https://www.npmjs.com/package/@chromatic-coherence/generative-charts)**
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
The charts library stays what it is — small, stable, chart-first. **generative-engine** is where
|
|
17
|
+
the family expands: the same API (`createWorld` · `face` · `line` · `glow` · `grow` · `draw*` ·
|
|
18
|
+
`fade`), so chart recipes port forward by changing one import — and underneath it, a real
|
|
19
|
+
graphics engine. Zero dependencies; the browser is the only runtime.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @chromatic-coherence/generative-engine
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { createWorld, v3, icosphere, heightfield } from "@chromatic-coherence/generative-engine";
|
|
27
|
+
|
|
28
|
+
const w = createWorld({ canvas, controls: true, shadows: true });
|
|
29
|
+
w.lights.push({ p: v3(-160, 320, 120), intensity: 1.2, falloff: 500 });
|
|
30
|
+
w.mesh(heightfield((x, z) => Math.sin(x * 0.01) * 20, 800, 800), { hue: 262, sat: 38, light: 26 });
|
|
31
|
+
w.meshMorph(icosphere(70, 3), myBlob, { hue: 350, sat: 30, light: 42, gloss: 0.5, group: "blob" });
|
|
32
|
+
w.grow((t) => { w.morph("blob", 0.5 + 0.5 * Math.sin(t)); w.setTransform("blob", { rotateY: t * 0.4 }); });
|
|
33
|
+
w.start();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## What the engine adds over the chart editions
|
|
37
|
+
|
|
38
|
+
| | |
|
|
39
|
+
|---|---|
|
|
40
|
+
| **WebGL2 + HDR post chain** | The scene renders to a floating-point target, then: **bloom** (soft-knee bright pass, half-res gaussian), **SSAO** (depth-based contact shading), one composite pass (ACES filmic, colour grade, vignette), and **FXAA**. All art-directable (`w.bloom`, `w.ssao`, `w.fxaa`), all on by default. `post: false` opts out. |
|
|
41
|
+
| **Real shadow maps** | A depth **texture** with hardware-comparison PCF (`sampler2DShadow`) — soft, exact, 2048px by default. `shadows: { size, softness, bias }`. |
|
|
42
|
+
| **Geometry stdlib** | `plane` · `box` · `icosphere` · `tube` (parallel-transport sweep, taperable) · `lathe` · `extrude` · `heightfield` · `transformMesh` · `mergeMesh` — all pure math, all unit-tested, uploaded in one `w.mesh(geo, opts)` call with smooth normals. |
|
|
43
|
+
| **Ribbons** | `w.ribbon(points, { width })` / `w.drawRibbon(...)` — screen-space-expanded strokes with **real pixel width**, feathered edges, depth-tested. The stroke aesthetic `gl.LINES` never allowed. |
|
|
44
|
+
| **Group transforms** | `w.setTransform(group, { translate, rotateY, scale })` — geometry moves rigidly in the vertex stage, zero CPU re-tessellation, shadow pass included. |
|
|
45
|
+
| **Morph targets** | `w.meshMorph(geoA, geoB, opts)` + `w.morph(group, t)` — blend between two vertex sets in the shader (breathing, becoming). Eased like `fade`. |
|
|
46
|
+
| **8 point lights** | Up from 4; `lights[0]` casts the shadows. HDR intensities welcome — bloom catches what exceeds 1.0. |
|
|
47
|
+
| **The carried light model** | Everything the chart edition learned rides along: GGX + Fresnel specular, procedural micro-relief, the perfusion term (`blood`, `bloodPulse`), pore noise, subsurface rim, coloured hemispheric ambient, fog — now resolving toward a real background colour the engine owns. |
|
|
48
|
+
|
|
49
|
+
**The ink edition** (`theme: "light"`) still renders any recipe on paper instead of night —
|
|
50
|
+
lightness inverts, strokes blend normally, SSAO becomes pencil shading. Bloom stays dark-only.
|
|
51
|
+
|
|
52
|
+
## Objectives — scenes that know where they're going
|
|
53
|
+
|
|
54
|
+
A scene gets a **goal** and the engine drives motion toward it; a live **movement meter**
|
|
55
|
+
tells you whether the scene is moving or settled — and names what is moving.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { createWorld, attachObjectives } from "@chromatic-coherence/generative-engine";
|
|
59
|
+
|
|
60
|
+
const w = createWorld({ canvas });
|
|
61
|
+
const obj = attachObjectives(w);
|
|
62
|
+
|
|
63
|
+
obj.define("reveal", {
|
|
64
|
+
morph: { group: "terrain", to: 1 }, // blend the terrain to its B shape
|
|
65
|
+
camera: { dist: 420, pitch: 0.62 }, // settle the camera in close
|
|
66
|
+
fade: { group: "veil", to: 0 }, // dissolve the veil
|
|
67
|
+
pace: 3, // seconds to ~95% of the way there
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
obj.onReading(r => {
|
|
71
|
+
// { name, progress, level, floor, moving, settled, driving: ["morph:terrain","camera",...] }
|
|
72
|
+
if (r.settled) console.log(`${r.name}: settled`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// later — the goal itself moves; motion continues from the current state, no snap,
|
|
76
|
+
// and the meter lights up again:
|
|
77
|
+
obj.retarget("reveal", { camera: { dist: 300 } });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Channels: `transform` (group rigid-body spec) · `morph` · `fade` · `camera` · `custom`
|
|
81
|
+
(an `apply(progress, dt)` escape hatch). `readings()` is the pull form of `onReading`.
|
|
82
|
+
|
|
83
|
+
**The meter's semantics** come from the objective-movement measurement work this engine's
|
|
84
|
+
org ran first as science: movement is a **level sustained above a settled floor**, not a
|
|
85
|
+
discrete event. `driving` names the elevated channels — movement you can name; `settled`
|
|
86
|
+
is the sustained fall to the floor. Options: `attachObjectives(w, { floor, settleAfter })`.
|
|
87
|
+
|
|
88
|
+
The layer rides the frame loop as a `grow()` hook and drives the World through its public
|
|
89
|
+
verbs only — the renderer is untouched, and everything composes with hand-written `grow()`
|
|
90
|
+
animation.
|
|
91
|
+
|
|
92
|
+
## Demo
|
|
93
|
+
|
|
94
|
+
`demo/index.html` — serve the package folder (`python -m http.server`) and open
|
|
95
|
+
`/demo/`. Drag to orbit, ctrl+wheel to zoom; `?theme=light`, `?post=0`, `?still`.
|
|
96
|
+
|
|
97
|
+
## Falling back
|
|
98
|
+
|
|
99
|
+
`createWorld` **throws** (never a blank page) when WebGL2 is missing. Catch it and build the
|
|
100
|
+
same recipe through the chart editions — the API is a subset, so the fallback chain
|
|
101
|
+
engine → charts-3d → charts-2d is three imports of the same code. Browsers cap live GL
|
|
102
|
+
contexts per page: on teardown call `dispose(true)` to release the context immediately, and
|
|
103
|
+
mount a **fresh** canvas if you rebuild (a lost context is dead on its canvas).
|
|
104
|
+
|
|
105
|
+
## Instancing, depth of field, motion blur (v1.2)
|
|
106
|
+
|
|
107
|
+
- **`w.meshInstanced(geo, transforms, opts)`** — upload a mesh once, draw it at every
|
|
108
|
+
transform in a single instanced call, shadow pass included. A forest is one draw.
|
|
109
|
+
Instances ride their group's fade / morph / transform like any other geometry.
|
|
110
|
+
- **`w.dof = { on, focus, range, maxBlur }`** — depth of field: blur grows with distance
|
|
111
|
+
from the focus plane (world units), sharp subjects protected per-tap.
|
|
112
|
+
- **`w.motionBlur = { on, strength }`** — camera motion blur by depth reprojection into
|
|
113
|
+
the previous frame; streaks clamp to 4 % of the screen.
|
|
114
|
+
|
|
115
|
+
## GPU wind (v1.3)
|
|
116
|
+
|
|
117
|
+
`meshInstanced(geo, transforms, { ..., sway: { amp, freq? } })` — every instance sways
|
|
118
|
+
individually in the vertex stage (shadow pass included): per-instance scattered phase,
|
|
119
|
+
height-scaled displacement, a second harmonic so the motion breathes. `w.wind = { dir,
|
|
120
|
+
speed }` is the one wind that blows through every swaying batch. Zero CPU per frame.
|
|
121
|
+
|
|
122
|
+
## Roadmap
|
|
123
|
+
|
|
124
|
+
Planar mirrors · real water · skeletal figures. The chart editions stay lean.
|
|
125
|
+
|
|
126
|
+
## Licence
|
|
127
|
+
|
|
128
|
+
**Source-available, not open source.** Free for individuals, charities and small businesses
|
|
129
|
+
(fewer than 25 people and under £1M annual revenue); a one-off **£100** covers any larger
|
|
130
|
+
organisation. Full terms in [LICENSE](./LICENSE); commercial licensing:
|
|
131
|
+
[hello@chromaticcoherence.ai](mailto:hello@chromaticcoherence.ai).
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
<div align="center">
|
|
136
|
+
<sub>© 2026 Chromatic Coherence. All rights reserved.</sub>
|
|
137
|
+
</div>
|
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
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;\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 mat4 inst = mat4(aI0, aI1, aI2, aI3);\n vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);\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;\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] * (mat4(aI0, aI1, aI2, aI3) * vec4(p, 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 // 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 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 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 p += uWindDir * (sw * aIP.z * max(p.y, 0.0));\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}";
|
package/dist/shaders.js
CHANGED
|
@@ -5,385 +5,391 @@
|
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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;${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
|
+
// W9 GPU WIND: per-instance (phase, freq, amp); displacement grows with height,
|
|
117
|
+
// plus a second harmonic so the sway breathes instead of ticking
|
|
118
|
+
float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);
|
|
119
|
+
p += uWindDir * (sw * aIP.z * max(p.y, 0.0));
|
|
120
|
+
vec4 wp = uGM[g] * (inst * vec4(p, 1.0));
|
|
121
|
+
vPos = wp.xyz; vNorm = uNM[g] * (mat3(inst) * n); vCol = aCol; vGA = uGA[g]; vBlood = aBlood;
|
|
122
|
+
vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;
|
|
119
123
|
}`;
|
|
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
|
-
|
|
124
|
+
export const DEPTH_INST_VS = `#version 300 es
|
|
125
|
+
in vec3 aPos; in float aGrp; in vec3 aPos2;
|
|
126
|
+
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;${GROUPS}
|
|
128
|
+
void main(){
|
|
129
|
+
int g = int(aGrp + 0.5);
|
|
130
|
+
vec3 p = mix(aPos, aPos2, uMW[g]);
|
|
131
|
+
float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);
|
|
132
|
+
p += uWindDir * (sw * aIP.z * max(p.y, 0.0));
|
|
133
|
+
gl_Position = uVP * (uGM[g] * (mat4(aI0, aI1, aI2, aI3) * vec4(p, 1.0)));
|
|
128
134
|
}`;
|
|
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));
|
|
135
|
+
export const DEPTH_VS = `#version 300 es
|
|
136
|
+
in vec3 aPos; in float aGrp; in vec3 aPos2;
|
|
137
|
+
uniform mat4 uVP;${GROUPS}
|
|
138
|
+
void main(){
|
|
139
|
+
int g = int(aGrp + 0.5);
|
|
140
|
+
vec3 p = mix(aPos, aPos2, uMW[g]);
|
|
141
|
+
gl_Position = uVP * (uGM[g] * vec4(p, 1.0));
|
|
136
142
|
}`;
|
|
137
|
-
export const DEPTH_FS = `#version 300 es
|
|
138
|
-
precision mediump float;
|
|
143
|
+
export const DEPTH_FS = `#version 300 es
|
|
144
|
+
precision mediump float;
|
|
139
145
|
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;
|
|
146
|
+
export const LINE_VS = `#version 300 es
|
|
147
|
+
in vec3 aPos; in vec4 aCol; in float aGrp;
|
|
148
|
+
uniform mat4 uVP;${GROUPS}
|
|
149
|
+
out vec4 vCol; out float vZ; out float vGA;
|
|
150
|
+
void main(){
|
|
151
|
+
int g = int(aGrp + 0.5);
|
|
152
|
+
vCol = aCol; vGA = uGA[g];
|
|
153
|
+
vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w; gl_Position = p;
|
|
148
154
|
}`;
|
|
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);
|
|
155
|
+
export const LINE_FS = `#version 300 es
|
|
156
|
+
precision mediump float;
|
|
157
|
+
in vec4 vCol; in float vZ; in float vGA;
|
|
158
|
+
uniform float uFogK; uniform float uInk;
|
|
159
|
+
out vec4 fragColor;
|
|
160
|
+
void main(){
|
|
161
|
+
float fog = clamp(uFogK / vZ, 0.08, 1.0);
|
|
162
|
+
if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, vCol.a * 1.6) * fog * vGA);
|
|
163
|
+
else fragColor = vec4(vCol.rgb * fog, vCol.a * fog * vGA);
|
|
158
164
|
}`;
|
|
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;
|
|
165
|
+
export const PT_VS = `#version 300 es
|
|
166
|
+
in vec3 aPos; in float aSize; in vec4 aCol; in float aGrp;
|
|
167
|
+
uniform mat4 uVP; uniform float uFocal;${GROUPS}
|
|
168
|
+
out vec4 vCol; out float vZ; out float vGA;
|
|
169
|
+
void main(){
|
|
170
|
+
int g = int(aGrp + 0.5);
|
|
171
|
+
vCol = aCol; vGA = uGA[g];
|
|
172
|
+
vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w;
|
|
173
|
+
gl_PointSize = clamp(aSize * uFocal / p.w, 1.0, 256.0); gl_Position = p;
|
|
168
174
|
}`;
|
|
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);
|
|
175
|
+
export const PT_FS = `#version 300 es
|
|
176
|
+
precision mediump float;
|
|
177
|
+
in vec4 vCol; in float vZ; in float vGA;
|
|
178
|
+
uniform float uFogK; uniform float uInk;
|
|
179
|
+
out vec4 fragColor;
|
|
180
|
+
void main(){
|
|
181
|
+
float r = length(gl_PointCoord - 0.5) * 2.0;
|
|
182
|
+
float a = exp(-3.2 * r * r) * (1.0 - smoothstep(0.85, 1.0, r));
|
|
183
|
+
float fog = clamp(uFogK / vZ, 0.08, 1.0);
|
|
184
|
+
if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, vCol.a * 1.6) * a * fog * vGA);
|
|
185
|
+
else fragColor = vec4(vCol.rgb, 1.0) * (vCol.a * a * fog * vGA);
|
|
180
186
|
}`;
|
|
181
187
|
// ── ribbons: screen-space-expanded strokes with real pixel width ──
|
|
182
188
|
// Each vertex carries its neighbours; the direction is the central difference in
|
|
183
189
|
// screen space, so joins round themselves. aSide is -1/+1; the fragment feathers
|
|
184
190
|
// 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;
|
|
191
|
+
export const RIBBON_VS = `#version 300 es
|
|
192
|
+
in vec3 aPos; in vec3 aPrev; in vec3 aNext; in float aSide; in float aWidth;
|
|
193
|
+
in vec4 aCol; in float aGrp;
|
|
194
|
+
uniform mat4 uVP; uniform vec2 uRes;${GROUPS}
|
|
195
|
+
out vec4 vCol; out float vZ; out float vGA; out float vSide;
|
|
196
|
+
void main(){
|
|
197
|
+
int g = int(aGrp + 0.5);
|
|
198
|
+
mat4 M = uGM[g];
|
|
199
|
+
vec4 cp = uVP * (M * vec4(aPos, 1.0));
|
|
200
|
+
vec4 cq = uVP * (M * vec4(aPrev, 1.0));
|
|
201
|
+
vec4 cr = uVP * (M * vec4(aNext, 1.0));
|
|
202
|
+
vec2 sp = cp.xy / max(cp.w, 1e-4) * uRes;
|
|
203
|
+
vec2 sq = cq.xy / max(cq.w, 1e-4) * uRes;
|
|
204
|
+
vec2 sr = cr.xy / max(cr.w, 1e-4) * uRes;
|
|
205
|
+
vec2 dir = sr - sq;
|
|
206
|
+
float dl = length(dir);
|
|
207
|
+
dir = dl > 1e-3 ? dir / dl : vec2(1.0, 0.0);
|
|
208
|
+
vec2 perp = vec2(-dir.y, dir.x);
|
|
209
|
+
vec2 off = perp * aWidth * 0.5 * aSide;
|
|
210
|
+
cp.xy += off / uRes * cp.w * 2.0;
|
|
211
|
+
vCol = aCol; vZ = cp.w; vGA = uGA[g]; vSide = aSide;
|
|
212
|
+
gl_Position = cp;
|
|
207
213
|
}`;
|
|
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);
|
|
214
|
+
export const RIBBON_FS = `#version 300 es
|
|
215
|
+
precision mediump float;
|
|
216
|
+
in vec4 vCol; in float vZ; in float vGA; in float vSide;
|
|
217
|
+
uniform float uFogK; uniform float uInk;
|
|
218
|
+
out vec4 fragColor;
|
|
219
|
+
void main(){
|
|
220
|
+
float edge = 1.0 - smoothstep(0.55, 1.0, abs(vSide));
|
|
221
|
+
float fog = clamp(uFogK / vZ, 0.08, 1.0);
|
|
222
|
+
float a = vCol.a * edge * fog * vGA;
|
|
223
|
+
if (uInk > 0.5) fragColor = vec4(vCol.rgb, min(1.0, a * 1.6));
|
|
224
|
+
else fragColor = vec4(vCol.rgb * a, a);
|
|
219
225
|
}`;
|
|
220
226
|
// ── post chain ──
|
|
221
227
|
// 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);
|
|
228
|
+
export const FST_VS = `#version 300 es
|
|
229
|
+
out vec2 vUv;
|
|
230
|
+
void main(){
|
|
231
|
+
vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
|
232
|
+
vUv = p; gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
|
|
227
233
|
}`;
|
|
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);
|
|
234
|
+
export const BRIGHT_FS = `#version 300 es
|
|
235
|
+
precision mediump float;
|
|
236
|
+
in vec2 vUv; uniform sampler2D uTex; uniform float uThresh; uniform float uKnee;
|
|
237
|
+
out vec4 fragColor;
|
|
238
|
+
void main(){
|
|
239
|
+
vec3 c = texture(uTex, vUv).rgb;
|
|
240
|
+
float luma = max(c.r, max(c.g, c.b));
|
|
241
|
+
float w = smoothstep(uThresh, uThresh + uKnee, luma);
|
|
242
|
+
fragColor = vec4(c * w, 1.0);
|
|
237
243
|
}`;
|
|
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);
|
|
244
|
+
export const BLUR_FS = `#version 300 es
|
|
245
|
+
precision mediump float;
|
|
246
|
+
in vec2 vUv; uniform sampler2D uTex; uniform vec2 uDir;
|
|
247
|
+
out vec4 fragColor;
|
|
248
|
+
void main(){
|
|
249
|
+
vec3 s = texture(uTex, vUv).rgb * 0.227027;
|
|
250
|
+
vec2 o1 = uDir * 1.3846153846, o2 = uDir * 3.2307692308;
|
|
251
|
+
s += (texture(uTex, vUv + o1).rgb + texture(uTex, vUv - o1).rgb) * 0.3162162162;
|
|
252
|
+
s += (texture(uTex, vUv + o2).rgb + texture(uTex, vUv - o2).rgb) * 0.0702702703;
|
|
253
|
+
fragColor = vec4(s, 1.0);
|
|
248
254
|
}`;
|
|
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);
|
|
255
|
+
export const SSAO_FS = `#version 300 es
|
|
256
|
+
precision highp float;
|
|
257
|
+
in vec2 vUv; uniform sampler2D uDepth;
|
|
258
|
+
uniform float uNear; uniform float uFar; uniform float uRadius; uniform vec2 uRes;
|
|
259
|
+
out vec4 fragColor;
|
|
260
|
+
float viewZ(vec2 uv){
|
|
261
|
+
float d = texture(uDepth, uv).r * 2.0 - 1.0;
|
|
262
|
+
return 2.0 * uNear * uFar / (uFar + uNear - d * (uFar - uNear));
|
|
263
|
+
}
|
|
264
|
+
float hash(vec2 p){ return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
|
|
265
|
+
void main(){
|
|
266
|
+
float z0 = viewZ(vUv);
|
|
267
|
+
// screen-space sample radius shrinks with distance (a world-ish radius)
|
|
268
|
+
float rad = uRadius / z0;
|
|
269
|
+
float ang = hash(vUv * uRes) * 6.2831853;
|
|
270
|
+
float occ = 0.0;
|
|
271
|
+
for (int i = 0; i < 12; i++) {
|
|
272
|
+
float fi = float(i);
|
|
273
|
+
float a = ang + fi * 2.3999632; // golden-angle spiral
|
|
274
|
+
float r = rad * (0.25 + 0.75 * fract(fi * 0.618034));
|
|
275
|
+
vec2 uv = vUv + vec2(cos(a), sin(a)) * r;
|
|
276
|
+
float dz = z0 - viewZ(uv); // positive => sample nearer camera => occluder
|
|
277
|
+
occ += smoothstep(z0 * 0.002, z0 * 0.02, dz) * smoothstep(z0 * 0.35, 0.0, dz);
|
|
278
|
+
}
|
|
279
|
+
float ao = 1.0 - occ / 12.0;
|
|
280
|
+
fragColor = vec4(vec3(ao), 1.0);
|
|
275
281
|
}`;
|
|
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);
|
|
282
|
+
export const AOBLUR_FS = `#version 300 es
|
|
283
|
+
precision mediump float;
|
|
284
|
+
in vec2 vUv; uniform sampler2D uTex; uniform vec2 uPx;
|
|
285
|
+
out vec4 fragColor;
|
|
286
|
+
void main(){
|
|
287
|
+
float s = 0.0;
|
|
288
|
+
for (int y = -1; y <= 2; y++)
|
|
289
|
+
for (int x = -1; x <= 2; x++)
|
|
290
|
+
s += texture(uTex, vUv + vec2(float(x) - 0.5, float(y) - 0.5) * uPx).r;
|
|
291
|
+
fragColor = vec4(vec3(s / 16.0), 1.0);
|
|
286
292
|
}`;
|
|
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);
|
|
293
|
+
export const COMPOSITE_FS = `#version 300 es
|
|
294
|
+
precision mediump float;
|
|
295
|
+
in vec2 vUv;
|
|
296
|
+
uniform sampler2D uScene; uniform sampler2D uBloom; uniform sampler2D uAO;
|
|
297
|
+
uniform float uBloomStr; uniform float uAOStr;
|
|
298
|
+
uniform float uFilm; uniform float uGrade; uniform float uVig; uniform float uInk;
|
|
299
|
+
out vec4 fragColor;
|
|
300
|
+
void main(){
|
|
301
|
+
vec3 c = texture(uScene, vUv).rgb;
|
|
302
|
+
c *= mix(1.0, texture(uAO, vUv).r, uAOStr);
|
|
303
|
+
c += texture(uBloom, vUv).rgb * uBloomStr;
|
|
304
|
+
if (uFilm > 0.5) { // ACES-ish filmic: highlights roll off instead of clipping
|
|
305
|
+
c = (c * (2.51 * c + 0.03)) / (c * (2.43 * c + 0.59) + 0.14);
|
|
306
|
+
}
|
|
307
|
+
c = clamp(c, 0.0, 1.0);
|
|
308
|
+
if (uGrade > 0.5) { // violet-cool shadows, warm highlights
|
|
309
|
+
float luma = dot(c, vec3(0.299, 0.587, 0.114));
|
|
310
|
+
c *= mix(vec3(0.85, 0.83, 1.07), vec3(1.07, 0.99, 0.92), smoothstep(0.12, 0.72, luma));
|
|
311
|
+
c = clamp(c, 0.0, 1.0);
|
|
312
|
+
}
|
|
313
|
+
if (uVig > 0.0) { // cinematic vignette, in the composite now (off the overlay)
|
|
314
|
+
vec2 q = vUv - vec2(0.5, 0.54);
|
|
315
|
+
float r = smoothstep(0.35, 0.9, length(q * vec2(1.15, 1.0)));
|
|
316
|
+
vec3 dk = uInk > 0.5 ? vec3(0.20, 0.19, 0.30) : vec3(0.024, 0.016, 0.055);
|
|
317
|
+
c = mix(c, dk, uVig * r * (uInk > 0.5 ? 0.3 : 1.0));
|
|
318
|
+
}
|
|
319
|
+
fragColor = vec4(c, 1.0);
|
|
314
320
|
}`;
|
|
315
321
|
// 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);
|
|
322
|
+
export const DOF_FS = `#version 300 es
|
|
323
|
+
precision highp float;
|
|
324
|
+
in vec2 vUv; uniform sampler2D uTex; uniform sampler2D uDepth;
|
|
325
|
+
uniform float uNear; uniform float uFar; uniform float uFocus; uniform float uRange;
|
|
326
|
+
uniform float uMaxBlur; uniform vec2 uPx;
|
|
327
|
+
out vec4 fragColor;
|
|
328
|
+
float lin(float d){ return uNear * uFar / (uFar - d * (uFar - uNear)); }
|
|
329
|
+
const vec2 DISC[12] = vec2[](
|
|
330
|
+
vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621),
|
|
331
|
+
vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893),
|
|
332
|
+
vec2(0.507, 0.064), vec2(0.896, 0.412), vec2(-0.322, -0.933), vec2(-0.792, -0.598));
|
|
333
|
+
void main(){
|
|
334
|
+
float z = lin(texture(uDepth, vUv).r);
|
|
335
|
+
float coc = clamp(abs(z - uFocus) / max(1.0, uRange), 0.0, 1.0) * uMaxBlur;
|
|
336
|
+
vec3 acc = texture(uTex, vUv).rgb;
|
|
337
|
+
float wsum = 1.0;
|
|
338
|
+
for (int i = 0; i < 12; i++) {
|
|
339
|
+
vec2 off = DISC[i] * coc * uPx;
|
|
340
|
+
// a sharp (in-focus) neighbour must not bleed into a blurred one: weight each tap
|
|
341
|
+
// by ITS OWN CoC so foreground focus stays crisp against a soft background
|
|
342
|
+
float zt = lin(texture(uDepth, vUv + off).r);
|
|
343
|
+
float ct = clamp(abs(zt - uFocus) / max(1.0, uRange), 0.0, 1.0);
|
|
344
|
+
float w = 0.25 + 0.75 * ct;
|
|
345
|
+
acc += texture(uTex, vUv + off).rgb * w;
|
|
346
|
+
wsum += w;
|
|
347
|
+
}
|
|
348
|
+
fragColor = vec4(acc / wsum, 1.0);
|
|
343
349
|
}`;
|
|
344
350
|
// camera motion blur — reconstruct world position from depth, reproject into the
|
|
345
351
|
// 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);
|
|
352
|
+
export const MOBLUR_FS = `#version 300 es
|
|
353
|
+
precision highp float;
|
|
354
|
+
in vec2 vUv; uniform sampler2D uTex; uniform sampler2D uDepth;
|
|
355
|
+
uniform mat4 uInvVP; uniform mat4 uPrevVP; uniform float uStrength;
|
|
356
|
+
out vec4 fragColor;
|
|
357
|
+
void main(){
|
|
358
|
+
float d = texture(uDepth, vUv).r;
|
|
359
|
+
vec4 ndc = vec4(vUv * 2.0 - 1.0, d * 2.0 - 1.0, 1.0);
|
|
360
|
+
vec4 wp = uInvVP * ndc; wp /= wp.w;
|
|
361
|
+
vec4 pc = uPrevVP * wp;
|
|
362
|
+
vec2 prevUv = (pc.xy / pc.w) * 0.5 + 0.5;
|
|
363
|
+
vec2 vel = (vUv - prevUv) * uStrength;
|
|
364
|
+
float vlen = length(vel);
|
|
365
|
+
if (vlen < 1e-5 || pc.w <= 0.0) { fragColor = vec4(texture(uTex, vUv).rgb, 1.0); return; }
|
|
366
|
+
vel = vlen > 0.04 ? vel * (0.04 / vlen) : vel; // clamp the streak to 4% of the screen
|
|
367
|
+
vec3 acc = vec3(0.0);
|
|
368
|
+
for (int i = 0; i < 8; i++) {
|
|
369
|
+
float t = (float(i) / 7.0) - 0.5;
|
|
370
|
+
acc += texture(uTex, clamp(vUv + vel * t, vec2(0.001), vec2(0.999))).rgb;
|
|
371
|
+
}
|
|
372
|
+
fragColor = vec4(acc / 8.0, 1.0);
|
|
367
373
|
}`;
|
|
368
374
|
// 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);
|
|
375
|
+
export const FXAA_FS = `#version 300 es
|
|
376
|
+
precision mediump float;
|
|
377
|
+
in vec2 vUv; uniform sampler2D uTex; uniform vec2 uPx;
|
|
378
|
+
out vec4 fragColor;
|
|
379
|
+
float luma(vec3 c){ return dot(c, vec3(0.299, 0.587, 0.114)); }
|
|
380
|
+
void main(){
|
|
381
|
+
vec3 cM = texture(uTex, vUv).rgb;
|
|
382
|
+
float lM = luma(cM);
|
|
383
|
+
float lN = luma(texture(uTex, vUv + vec2(0.0, -uPx.y)).rgb);
|
|
384
|
+
float lS = luma(texture(uTex, vUv + vec2(0.0, uPx.y)).rgb);
|
|
385
|
+
float lW = luma(texture(uTex, vUv + vec2(-uPx.x, 0.0)).rgb);
|
|
386
|
+
float lE = luma(texture(uTex, vUv + vec2(uPx.x, 0.0)).rgb);
|
|
387
|
+
float lMin = min(lM, min(min(lN, lS), min(lW, lE)));
|
|
388
|
+
float lMax = max(lM, max(max(lN, lS), max(lW, lE)));
|
|
389
|
+
float range = lMax - lMin;
|
|
390
|
+
if (range < max(0.0312, lMax * 0.125)) { fragColor = vec4(cM, 1.0); return; }
|
|
391
|
+
vec2 dir = normalize(vec2((lE + lS) - (lW + lN), (lN + lE) - (lS + lW)) + 1e-6);
|
|
392
|
+
vec3 a = texture(uTex, vUv + dir * uPx * 0.5).rgb;
|
|
393
|
+
vec3 b = texture(uTex, vUv - dir * uPx * 0.5).rgb;
|
|
394
|
+
fragColor = vec4(mix(cM, (a + b) * 0.5, 0.75), 1.0);
|
|
389
395
|
}`;
|
package/dist/world.d.ts
CHANGED
|
@@ -91,6 +91,11 @@ export declare class World {
|
|
|
91
91
|
private pFaceI;
|
|
92
92
|
private pDepthI;
|
|
93
93
|
private instanced;
|
|
94
|
+
/** W9 — the one wind that blows through every swaying instanced batch */
|
|
95
|
+
wind: {
|
|
96
|
+
dir: V3;
|
|
97
|
+
speed: number;
|
|
98
|
+
};
|
|
94
99
|
private post;
|
|
95
100
|
private ink;
|
|
96
101
|
private bg;
|
|
@@ -243,7 +248,14 @@ export declare class World {
|
|
|
243
248
|
/** W8 — GPU instancing: upload a Mesh ONCE and draw it at every given transform in a
|
|
244
249
|
* single instanced call (scene + shadow pass alike). A forest is one draw. Instances
|
|
245
250
|
* ride their group's fade / morph / transform like any other geometry. */
|
|
246
|
-
meshInstanced(geo: Mesh, instances: (M4 | TransformSpec)[], o: FaceOpts
|
|
251
|
+
meshInstanced(geo: Mesh, instances: (M4 | TransformSpec)[], o: FaceOpts & {
|
|
252
|
+
/** W9 GPU wind: per-instance sway computed in the vertex stage (shadows included).
|
|
253
|
+
* amp scales with vertex height; freq in rad/s; phases auto-scatter per instance. */
|
|
254
|
+
sway?: {
|
|
255
|
+
amp: number;
|
|
256
|
+
freq?: number;
|
|
257
|
+
};
|
|
258
|
+
}): void;
|
|
247
259
|
grow(fn: GrowFn): void;
|
|
248
260
|
drawLine(a: V3, b: V3, color: string, alpha: number, width?: number): void;
|
|
249
261
|
drawPath(pts: {
|
package/dist/world.js
CHANGED
|
@@ -34,6 +34,8 @@ export class World {
|
|
|
34
34
|
}
|
|
35
35
|
constructor(o) {
|
|
36
36
|
this.instanced = [];
|
|
37
|
+
/** W9 — the one wind that blows through every swaying instanced batch */
|
|
38
|
+
this.wind = { dir: v3(1, 0, 0.35), speed: 1 };
|
|
37
39
|
this.ink = false;
|
|
38
40
|
this.bg = [0.024, 0.016, 0.055];
|
|
39
41
|
this.transparent = false;
|
|
@@ -392,8 +394,15 @@ export class World {
|
|
|
392
394
|
for (let i = 0; i < P.length; i += 9) {
|
|
393
395
|
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]));
|
|
394
396
|
}
|
|
395
|
-
const mats = new Float32Array(instances.length *
|
|
396
|
-
instances.forEach((m, i) =>
|
|
397
|
+
const mats = new Float32Array(instances.length * 20); // mat4 + sway vec4, one stride
|
|
398
|
+
instances.forEach((m, i) => {
|
|
399
|
+
mats.set(m instanceof Float32Array ? m : mCompose(m), i * 20);
|
|
400
|
+
const ph = ((i * 2654435761) % 6283) / 1000; // scattered phase
|
|
401
|
+
mats[i * 20 + 16] = ph;
|
|
402
|
+
mats[i * 20 + 17] = (o.sway?.freq ?? 1.3) * (0.85 + ((i * 97) % 30) / 100);
|
|
403
|
+
mats[i * 20 + 18] = (o.sway?.amp ?? 0) * 0.01;
|
|
404
|
+
mats[i * 20 + 19] = 0;
|
|
405
|
+
});
|
|
397
406
|
const vbuf = gl.createBuffer();
|
|
398
407
|
gl.bindBuffer(gl.ARRAY_BUFFER, vbuf);
|
|
399
408
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(store), gl.STATIC_DRAW);
|
|
@@ -615,12 +624,12 @@ export class World {
|
|
|
615
624
|
this.attrib(p, "aNorm2", 3, FACE_STRIDE, 15);
|
|
616
625
|
gl.bindBuffer(gl.ARRAY_BUFFER, rec.ibuf);
|
|
617
626
|
const locs = [];
|
|
618
|
-
for (let k = 0; k <
|
|
619
|
-
const loc = gl.getAttribLocation(p.prog, "aI" + k);
|
|
627
|
+
for (let k = 0; k < 5; k++) { // aI0..3 = the matrix, aIP = the sway params
|
|
628
|
+
const loc = gl.getAttribLocation(p.prog, k < 4 ? "aI" + k : "aIP");
|
|
620
629
|
if (loc < 0)
|
|
621
630
|
continue;
|
|
622
631
|
gl.enableVertexAttribArray(loc);
|
|
623
|
-
gl.vertexAttribPointer(loc, 4, gl.FLOAT, false,
|
|
632
|
+
gl.vertexAttribPointer(loc, 4, gl.FLOAT, false, 80, k * 16);
|
|
624
633
|
gl.vertexAttribDivisor(loc, 1);
|
|
625
634
|
locs.push(loc);
|
|
626
635
|
}
|
|
@@ -716,6 +725,8 @@ export class World {
|
|
|
716
725
|
if (this.instanced.length) {
|
|
717
726
|
this.pDepthI.use();
|
|
718
727
|
gl.uniformMatrix4fv(this.pDepthI.u("uVP"), false, this.svp);
|
|
728
|
+
gl.uniform1f(this.pDepthI.u("uT"), t * this.wind.speed);
|
|
729
|
+
gl.uniform3f(this.pDepthI.u("uWindDir"), this.wind.dir.x, this.wind.dir.y, this.wind.dir.z);
|
|
719
730
|
this.setGroups(this.pDepthI);
|
|
720
731
|
this.drawInstancedAll(this.pDepthI, true);
|
|
721
732
|
}
|
|
@@ -747,6 +758,8 @@ export class World {
|
|
|
747
758
|
this.pFaceI.use();
|
|
748
759
|
this.setVP(this.pFaceI);
|
|
749
760
|
this.setLights(this.pFaceI, fogK);
|
|
761
|
+
gl.uniform1f(this.pFaceI.u("uT"), t * this.wind.speed);
|
|
762
|
+
gl.uniform3f(this.pFaceI.u("uWindDir"), this.wind.dir.x, this.wind.dir.y, this.wind.dir.z);
|
|
750
763
|
this.drawInstancedAll(this.pFaceI, false);
|
|
751
764
|
}
|
|
752
765
|
// the light — additive, depth-TESTED but not written (ink: normal alpha blend)
|
package/package.json
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@chromatic-coherence/generative-engine",
|
|
3
|
-
"version": "1.
|
|
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
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"module": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/index.js"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
"README.md",
|
|
18
|
-
"LICENSE"
|
|
19
|
-
],
|
|
20
|
-
"sideEffects": false,
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
|
-
},
|
|
24
|
-
"scripts": {
|
|
25
|
-
"build": "tsc -p tsconfig.json",
|
|
26
|
-
"test": "node test/run.mjs",
|
|
27
|
-
"prepublishOnly": "npm run build"
|
|
28
|
-
},
|
|
29
|
-
"keywords": [
|
|
30
|
-
"3d",
|
|
31
|
-
"webgl2",
|
|
32
|
-
"engine",
|
|
33
|
-
"graphics",
|
|
34
|
-
"bloom",
|
|
35
|
-
"ssao",
|
|
36
|
-
"shadows",
|
|
37
|
-
"zero-dependency"
|
|
38
|
-
],
|
|
39
|
-
"author": "Chromatic Coherence",
|
|
40
|
-
"license": "SEE LICENSE IN LICENSE",
|
|
41
|
-
"devDependencies": {
|
|
42
|
-
"typescript": "^5"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@chromatic-coherence/generative-engine",
|
|
3
|
+
"version": "1.3.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.json",
|
|
26
|
+
"test": "node test/run.mjs",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"3d",
|
|
31
|
+
"webgl2",
|
|
32
|
+
"engine",
|
|
33
|
+
"graphics",
|
|
34
|
+
"bloom",
|
|
35
|
+
"ssao",
|
|
36
|
+
"shadows",
|
|
37
|
+
"zero-dependency"
|
|
38
|
+
],
|
|
39
|
+
"author": "Chromatic Coherence",
|
|
40
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"typescript": "^5"
|
|
43
|
+
}
|
|
44
|
+
}
|