@chromatic-coherence/generative-engine 1.1.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 -120
- package/dist/post.d.ts +13 -0
- package/dist/post.js +44 -4
- package/dist/shaders.d.ts +4 -0
- package/dist/shaders.js +362 -274
- package/dist/world.d.ts +37 -0
- package/dist/world.js +108 -2
- package/package.json +44 -44
package/dist/world.d.ts
CHANGED
|
@@ -88,6 +88,14 @@ export declare class World {
|
|
|
88
88
|
private pPt;
|
|
89
89
|
private pRib;
|
|
90
90
|
private pDepth;
|
|
91
|
+
private pFaceI;
|
|
92
|
+
private pDepthI;
|
|
93
|
+
private instanced;
|
|
94
|
+
/** W9 — the one wind that blows through every swaying instanced batch */
|
|
95
|
+
wind: {
|
|
96
|
+
dir: V3;
|
|
97
|
+
speed: number;
|
|
98
|
+
};
|
|
91
99
|
private post;
|
|
92
100
|
private ink;
|
|
93
101
|
private bg;
|
|
@@ -171,6 +179,20 @@ export declare class World {
|
|
|
171
179
|
};
|
|
172
180
|
/** FXAA on the final composite */
|
|
173
181
|
fxaa: boolean;
|
|
182
|
+
/** depth of field (W8) — blur grows with distance from the focus plane (world units) */
|
|
183
|
+
dof: {
|
|
184
|
+
on: boolean;
|
|
185
|
+
focus: number;
|
|
186
|
+
range: number;
|
|
187
|
+
maxBlur: number;
|
|
188
|
+
};
|
|
189
|
+
/** camera motion blur (W8) — depth reprojection into the previous frame's view */
|
|
190
|
+
motionBlur: {
|
|
191
|
+
on: boolean;
|
|
192
|
+
strength: number;
|
|
193
|
+
};
|
|
194
|
+
private prevVP;
|
|
195
|
+
private prevVPValid;
|
|
174
196
|
private groupIdx;
|
|
175
197
|
private groupAlpha;
|
|
176
198
|
private groupTarget;
|
|
@@ -223,6 +245,17 @@ export declare class World {
|
|
|
223
245
|
private pushRibbon;
|
|
224
246
|
/** A static ribbon stroke — real pixel width, feathered edges, depth-tested. */
|
|
225
247
|
ribbon(pts: V3[], o: RibbonOpts): void;
|
|
248
|
+
/** W8 — GPU instancing: upload a Mesh ONCE and draw it at every given transform in a
|
|
249
|
+
* single instanced call (scene + shadow pass alike). A forest is one draw. Instances
|
|
250
|
+
* ride their group's fade / morph / transform like any other geometry. */
|
|
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;
|
|
226
259
|
grow(fn: GrowFn): void;
|
|
227
260
|
drawLine(a: V3, b: V3, color: string, alpha: number, width?: number): void;
|
|
228
261
|
drawPath(pts: {
|
|
@@ -250,6 +283,10 @@ export declare class World {
|
|
|
250
283
|
private drawLines;
|
|
251
284
|
private drawPts;
|
|
252
285
|
private drawRibbons;
|
|
286
|
+
/** Instanced records under a given program (scene or depth pass). Divisors are reset
|
|
287
|
+
* after each draw — attribute divisor is global-per-index state and would otherwise
|
|
288
|
+
* poison the non-instanced draws that share those indices. */
|
|
289
|
+
private drawInstancedAll;
|
|
253
290
|
private renderFrame;
|
|
254
291
|
private frameFn;
|
|
255
292
|
private pausedByUser;
|
package/dist/world.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// WebGL2 renderer: HDR scene target + post chain (bloom, SSAO, composite, FXAA),
|
|
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
|
-
import { v3, add, sub, scale, cross, norm, clamp, hsl, mIdent, mPerspective, mLookAt, mMul, mCompose, 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, } from "./shaders.js";
|
|
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";
|
|
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
|
|
@@ -33,6 +33,9 @@ export class World {
|
|
|
33
33
|
this.groupNM.set(normalMat3(mat), i * 9);
|
|
34
34
|
}
|
|
35
35
|
constructor(o) {
|
|
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 };
|
|
36
39
|
this.ink = false;
|
|
37
40
|
this.bg = [0.024, 0.016, 0.055];
|
|
38
41
|
this.transparent = false;
|
|
@@ -84,6 +87,12 @@ export class World {
|
|
|
84
87
|
this.ssao = { on: true, strength: 0.55, radius: 22 };
|
|
85
88
|
/** FXAA on the final composite */
|
|
86
89
|
this.fxaa = true;
|
|
90
|
+
/** depth of field (W8) — blur grows with distance from the focus plane (world units) */
|
|
91
|
+
this.dof = { on: false, focus: 700, range: 420, maxBlur: 10 };
|
|
92
|
+
/** camera motion blur (W8) — depth reprojection into the previous frame's view */
|
|
93
|
+
this.motionBlur = { on: false, strength: 0.6 };
|
|
94
|
+
this.prevVP = mIdent();
|
|
95
|
+
this.prevVPValid = false;
|
|
87
96
|
// groups: fade alpha + morph weight + transform, resolved in the vertex stage
|
|
88
97
|
this.groupIdx = new Map([["default", 0]]);
|
|
89
98
|
this.groupAlpha = new Float32Array(8).fill(1);
|
|
@@ -156,6 +165,8 @@ export class World {
|
|
|
156
165
|
this.pPt = new PInfo(gl, PT_VS, PT_FS);
|
|
157
166
|
this.pRib = new PInfo(gl, RIBBON_VS, RIBBON_FS);
|
|
158
167
|
this.pDepth = new PInfo(gl, DEPTH_VS, DEPTH_FS);
|
|
168
|
+
this.pFaceI = new PInfo(gl, FACE_INST_VS, FACE_FS);
|
|
169
|
+
this.pDepthI = new PInfo(gl, DEPTH_INST_VS, DEPTH_FS);
|
|
159
170
|
this.post = (o.post ?? true) && !this.transparent ? new PostChain(gl) : null;
|
|
160
171
|
this.shadowsOn = !!o.shadows;
|
|
161
172
|
if (o.shadows && typeof o.shadows === "object") {
|
|
@@ -371,6 +382,35 @@ export class World {
|
|
|
371
382
|
}
|
|
372
383
|
/** A static ribbon stroke — real pixel width, feathered edges, depth-tested. */
|
|
373
384
|
ribbon(pts, o) { this.pushRibbon(this.sRib, pts, o); this.staticDirty = true; }
|
|
385
|
+
/** W8 — GPU instancing: upload a Mesh ONCE and draw it at every given transform in a
|
|
386
|
+
* single instanced call (scene + shadow pass alike). A forest is one draw. Instances
|
|
387
|
+
* ride their group's fade / morph / transform like any other geometry. */
|
|
388
|
+
meshInstanced(geo, instances, o) {
|
|
389
|
+
if (!instances.length)
|
|
390
|
+
return;
|
|
391
|
+
const gl = this.gl;
|
|
392
|
+
const store = [];
|
|
393
|
+
const P = geo.pos, N = geo.nrm;
|
|
394
|
+
for (let i = 0; i < P.length; i += 9) {
|
|
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]));
|
|
396
|
+
}
|
|
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
|
+
});
|
|
406
|
+
const vbuf = gl.createBuffer();
|
|
407
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbuf);
|
|
408
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(store), gl.STATIC_DRAW);
|
|
409
|
+
const ibuf = gl.createBuffer();
|
|
410
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, ibuf);
|
|
411
|
+
gl.bufferData(gl.ARRAY_BUFFER, mats, gl.STATIC_DRAW);
|
|
412
|
+
this.instanced.push({ vbuf, ibuf, verts: store.length / FACE_STRIDE, count: instances.length });
|
|
413
|
+
}
|
|
374
414
|
grow(fn) { this.hooks.push(fn); }
|
|
375
415
|
// ── the living verbs (gathered per frame) ──
|
|
376
416
|
drawLine(a, b, color, alpha, width = 1) {
|
|
@@ -563,11 +603,53 @@ export class World {
|
|
|
563
603
|
this.attrib(this.pRib, "aGrp", 1, RIB_STRIDE, 15);
|
|
564
604
|
gl.drawArrays(gl.TRIANGLES, 0, verts);
|
|
565
605
|
}
|
|
606
|
+
/** Instanced records under a given program (scene or depth pass). Divisors are reset
|
|
607
|
+
* after each draw — attribute divisor is global-per-index state and would otherwise
|
|
608
|
+
* poison the non-instanced draws that share those indices. */
|
|
609
|
+
drawInstancedAll(p, depthOnly) {
|
|
610
|
+
if (!this.instanced.length)
|
|
611
|
+
return;
|
|
612
|
+
const gl = this.gl;
|
|
613
|
+
for (const rec of this.instanced) {
|
|
614
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, rec.vbuf);
|
|
615
|
+
this.attrib(p, "aPos", 3, FACE_STRIDE, 0);
|
|
616
|
+
if (!depthOnly) {
|
|
617
|
+
this.attrib(p, "aNorm", 3, FACE_STRIDE, 3);
|
|
618
|
+
this.attrib(p, "aCol", 4, FACE_STRIDE, 6);
|
|
619
|
+
this.attrib(p, "aBlood", 1, FACE_STRIDE, 10);
|
|
620
|
+
}
|
|
621
|
+
this.attrib(p, "aGrp", 1, FACE_STRIDE, 11);
|
|
622
|
+
this.attrib(p, "aPos2", 3, FACE_STRIDE, 12);
|
|
623
|
+
if (!depthOnly)
|
|
624
|
+
this.attrib(p, "aNorm2", 3, FACE_STRIDE, 15);
|
|
625
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, rec.ibuf);
|
|
626
|
+
const locs = [];
|
|
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");
|
|
629
|
+
if (loc < 0)
|
|
630
|
+
continue;
|
|
631
|
+
gl.enableVertexAttribArray(loc);
|
|
632
|
+
gl.vertexAttribPointer(loc, 4, gl.FLOAT, false, 80, k * 16);
|
|
633
|
+
gl.vertexAttribDivisor(loc, 1);
|
|
634
|
+
locs.push(loc);
|
|
635
|
+
}
|
|
636
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, rec.verts, rec.count);
|
|
637
|
+
for (const loc of locs)
|
|
638
|
+
gl.vertexAttribDivisor(loc, 0);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
566
641
|
// ── the frame ──
|
|
567
642
|
renderFrame(t, dt) {
|
|
568
643
|
const gl = this.gl;
|
|
569
644
|
this.lastT = t;
|
|
645
|
+
if (this.motionBlur.on && this.prevVPValid)
|
|
646
|
+
this.prevVP = new Float32Array(this.vp);
|
|
570
647
|
this.setupCamera(t, dt);
|
|
648
|
+
if (this.motionBlur.on && !this.prevVPValid) {
|
|
649
|
+
// first blurred frame: previous = current, so velocity is zero (no identity streak)
|
|
650
|
+
this.prevVP = new Float32Array(this.vp);
|
|
651
|
+
this.prevVPValid = true;
|
|
652
|
+
}
|
|
571
653
|
this.fLight.length = 0;
|
|
572
654
|
this.fRib.length = 0;
|
|
573
655
|
this.dFaceA.length = 0;
|
|
@@ -640,6 +722,14 @@ export class World {
|
|
|
640
722
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.bFaceD);
|
|
641
723
|
this.depthDraw(this.bFaceD, this.dFaceA.length / FACE_STRIDE);
|
|
642
724
|
}
|
|
725
|
+
if (this.instanced.length) {
|
|
726
|
+
this.pDepthI.use();
|
|
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);
|
|
730
|
+
this.setGroups(this.pDepthI);
|
|
731
|
+
this.drawInstancedAll(this.pDepthI, true);
|
|
732
|
+
}
|
|
643
733
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
644
734
|
}
|
|
645
735
|
// the scene pass — into the HDR target when the post chain is on
|
|
@@ -664,6 +754,14 @@ export class World {
|
|
|
664
754
|
this.drawFaces(this.bFace, this.sFace.length / FACE_STRIDE, null);
|
|
665
755
|
if (this.dFaceA.length)
|
|
666
756
|
this.drawFaces(this.bFaceD, this.dFaceA.length / FACE_STRIDE, new Float32Array(this.dFaceA));
|
|
757
|
+
if (this.instanced.length) {
|
|
758
|
+
this.pFaceI.use();
|
|
759
|
+
this.setVP(this.pFaceI);
|
|
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);
|
|
763
|
+
this.drawInstancedAll(this.pFaceI, false);
|
|
764
|
+
}
|
|
667
765
|
// the light — additive, depth-TESTED but not written (ink: normal alpha blend)
|
|
668
766
|
gl.depthMask(false);
|
|
669
767
|
gl.enable(gl.BLEND);
|
|
@@ -701,6 +799,9 @@ export class World {
|
|
|
701
799
|
ssaoOn: this.ssao.on, ssaoStrength: this.ssao.strength, ssaoRadius: this.ssao.radius,
|
|
702
800
|
filmic: this.filmic && !this.ink, grade: this.grade, vignette: this.vignette, ink: this.ink,
|
|
703
801
|
fxaa: this.fxaa, near: this.zNear, far: this.zFar,
|
|
802
|
+
dofOn: this.dof.on, dofFocus: this.dof.focus, dofRange: this.dof.range, dofMaxBlur: this.dof.maxBlur,
|
|
803
|
+
moOn: this.motionBlur.on, moStrength: this.motionBlur.strength,
|
|
804
|
+
invVP: this.motionBlur.on ? mInvert(this.vp) : undefined, prevVP: this.prevVP,
|
|
704
805
|
});
|
|
705
806
|
}
|
|
706
807
|
// labels — 2D overlay, never occluded (the honest exception)
|
|
@@ -784,6 +885,11 @@ export class World {
|
|
|
784
885
|
c();
|
|
785
886
|
this.cleanup = [];
|
|
786
887
|
this.overlay.remove();
|
|
888
|
+
for (const rec of this.instanced) {
|
|
889
|
+
this.gl.deleteBuffer(rec.vbuf);
|
|
890
|
+
this.gl.deleteBuffer(rec.ibuf);
|
|
891
|
+
}
|
|
892
|
+
this.instanced = [];
|
|
787
893
|
this.post?.dispose();
|
|
788
894
|
if (releaseContext)
|
|
789
895
|
this.gl.getExtension("WEBGL_lose_context")?.loseContext();
|
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
|
+
}
|