@dopaminefx/effect-comic 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/comic-fonts.d.ts +18 -0
- package/dist/comic-fonts.d.ts.map +1 -0
- package/dist/comic-fonts.js +16 -0
- package/dist/comic-fonts.js.map +1 -0
- package/dist/comic-params.d.ts +77 -0
- package/dist/comic-params.d.ts.map +1 -0
- package/dist/comic-params.js +31 -0
- package/dist/comic-params.js.map +1 -0
- package/dist/comic-renderer.d.ts +32 -0
- package/dist/comic-renderer.d.ts.map +1 -0
- package/dist/comic-renderer.js +299 -0
- package/dist/comic-renderer.js.map +1 -0
- package/dist/comic-shader.d.ts +30 -0
- package/dist/comic-shader.d.ts.map +1 -0
- package/dist/comic-shader.js +238 -0
- package/dist/comic-shader.js.map +1 -0
- package/dist/comic-tempo.d.ts +24 -0
- package/dist/comic-tempo.d.ts.map +1 -0
- package/dist/comic-tempo.js +45 -0
- package/dist/comic-tempo.js.map +1 -0
- package/dist/comic.dope.json +842 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
- package/src/comic-fonts.ts +23 -0
- package/src/comic-params.ts +95 -0
- package/src/comic-renderer.ts +336 -0
- package/src/comic-shader.ts +247 -0
- package/src/comic-tempo.ts +46 -0
- package/src/comic.dope.json +842 -0
- package/src/index.ts +80 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comic Impact Canvas2D PANEL drawing + bundled-font loading.
|
|
3
|
+
*
|
|
4
|
+
* The crisp, vector-y parts of Comic Impact — the jagged starburst and the
|
|
5
|
+
* blocky hand-lettered onomatopoeia word with bold ink outlines — are drawn into
|
|
6
|
+
* an OFFSCREEN Canvas2D each frame (cheap: a few paths) and uploaded as the
|
|
7
|
+
* "panel" texture; the fragment shader (comic-shader.ts) adds the Ben-Day
|
|
8
|
+
* halftone, action lines, flash and noir↔pop-art styling. `drawPanel` (the
|
|
9
|
+
* offscreen draw) and `ensureComicFonts` (the bundled-face loader) live here and
|
|
10
|
+
* are consumed by the Comic `EffectFactory` (effects/comic.ts), which owns the
|
|
11
|
+
* GL pass via the shared, program-cached context + the conductor.
|
|
12
|
+
*
|
|
13
|
+
* Panel channel encoding consumed by the shader:
|
|
14
|
+
* R = word fill · G = ink (all black contours) · B = starburst fill
|
|
15
|
+
*/
|
|
16
|
+
import { isCheckmark } from "./comic-params.js";
|
|
17
|
+
import { mulberry32 } from "@dopaminefx/core";
|
|
18
|
+
import { impactScale, impactPresence } from "./comic-tempo.js";
|
|
19
|
+
import { EMBEDDED_FACES } from "./comic-fonts.js";
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// BUNDLED FONT LOADING
|
|
22
|
+
//
|
|
23
|
+
// The effect must NOT silently depend on a host font being installed, so the
|
|
24
|
+
// SIL OFL display faces (Bangers / Anton / Luckiest Guy) ship base64-embedded
|
|
25
|
+
// (comic-fonts.ts) and are registered via the FontFace API. We kick this off
|
|
26
|
+
// once at module import and await it before the first paint; if it fails for
|
|
27
|
+
// any reason the renderer still draws using the robust fallback stack (and the
|
|
28
|
+
// mood/whimsy difference still reads via the procedural treatment).
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
let fontsReady = null;
|
|
31
|
+
function base64ToArrayBuffer(b64) {
|
|
32
|
+
const bin = atob(b64);
|
|
33
|
+
const len = bin.length;
|
|
34
|
+
const bytes = new Uint8Array(len);
|
|
35
|
+
for (let i = 0; i < len; i++)
|
|
36
|
+
bytes[i] = bin.charCodeAt(i);
|
|
37
|
+
return bytes.buffer;
|
|
38
|
+
}
|
|
39
|
+
/** Register + load the embedded faces once. Resolves even if loading fails. */
|
|
40
|
+
export function ensureComicFonts() {
|
|
41
|
+
if (fontsReady)
|
|
42
|
+
return fontsReady;
|
|
43
|
+
if (typeof document === "undefined" ||
|
|
44
|
+
typeof FontFace === "undefined" ||
|
|
45
|
+
!document.fonts) {
|
|
46
|
+
fontsReady = Promise.resolve();
|
|
47
|
+
return fontsReady;
|
|
48
|
+
}
|
|
49
|
+
fontsReady = (async () => {
|
|
50
|
+
await Promise.all(EMBEDDED_FACES.map(async (f) => {
|
|
51
|
+
try {
|
|
52
|
+
// Skip if a face by this family is already registered (e.g. host has it).
|
|
53
|
+
const face = new FontFace(f.family, base64ToArrayBuffer(f.base64));
|
|
54
|
+
await face.load();
|
|
55
|
+
document.fonts.add(face);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
/* fall back to the system stack for this face */
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
try {
|
|
62
|
+
await document.fonts.ready;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
/* ignore */
|
|
66
|
+
}
|
|
67
|
+
})();
|
|
68
|
+
return fontsReady;
|
|
69
|
+
}
|
|
70
|
+
// Begin loading as soon as the module is imported so faces are usually ready by
|
|
71
|
+
// the time the user fires the effect.
|
|
72
|
+
if (typeof document !== "undefined")
|
|
73
|
+
void ensureComicFonts();
|
|
74
|
+
/**
|
|
75
|
+
* Draw the offscreen panel for this frame: a jagged starburst balloon, the
|
|
76
|
+
* onomatopoeia word in blocky outlined block caps, all at the current impact
|
|
77
|
+
* scale + a tiny rotation tilt. Encodes masks into channels:
|
|
78
|
+
* R = word fill, G = ink (contours), B = burst fill.
|
|
79
|
+
*
|
|
80
|
+
* We draw ink as the GREEN channel and the two fills as RED/BLUE so we can
|
|
81
|
+
* blend them independently in the shader. To keep channels independent we draw
|
|
82
|
+
* each layer onto the same 2D context but only write the intended channel.
|
|
83
|
+
*/
|
|
84
|
+
/** Starburst + word size relative to the targeted element box (≈1.5×). See the
|
|
85
|
+
* Swift `COMIC_TARGET_FILL` — keep the two in sync. */
|
|
86
|
+
const COMIC_TARGET_FILL = 1.7;
|
|
87
|
+
/**
|
|
88
|
+
* The per-frame panel draw in the generic `PanelDraw` shape — the ONE
|
|
89
|
+
* code-shaped hook the data-driven factory wires (`registerDopePanelEffect`).
|
|
90
|
+
* Computes the DRAW-SIDE tempo (the per-letter slam SCALE + the panel presence,
|
|
91
|
+
* which stay code by design — the per-frame values the SHADER reads ride
|
|
92
|
+
* `tempo.frame`) and hands off to {@link drawPanel}.
|
|
93
|
+
*/
|
|
94
|
+
export const drawComicFrame = (pctx, w, h, params, info) => {
|
|
95
|
+
const p = params;
|
|
96
|
+
const scale = impactScale(info.elapsedMs, p.overshoot);
|
|
97
|
+
const presence = impactPresence(info.life);
|
|
98
|
+
const span = Math.min(info.targetPx.width, info.targetPx.height);
|
|
99
|
+
drawPanel(pctx, w, h, p, scale, presence, info.dpr, info.centerPx, span);
|
|
100
|
+
};
|
|
101
|
+
export function drawPanel(ctx, w, h, params, scale, presence, dpr, center, span) {
|
|
102
|
+
ctx.clearRect(0, 0, w, h);
|
|
103
|
+
if (presence <= 0.001)
|
|
104
|
+
return;
|
|
105
|
+
// Position + size the word/starburst to the targeted element (defaults to the
|
|
106
|
+
// canvas centre + full canvas, reproducing the old screen-centred pose).
|
|
107
|
+
const cx = center.x;
|
|
108
|
+
const cy = center.y;
|
|
109
|
+
// The starburst + word read at ~150% of the targeted element, clamped to the
|
|
110
|
+
// canvas so a full-page fire (target == canvas) keeps its original size. Kept in
|
|
111
|
+
// sync with ComicPanel.swift. TUNABLE.
|
|
112
|
+
const minDim = Math.min(span * COMIC_TARGET_FILL, Math.min(w, h));
|
|
113
|
+
const rng = mulberry32((params.comicSeed * 1000) >>> 0);
|
|
114
|
+
// Deterministic per-fire tilt so the panel feels hand-placed (a few degrees).
|
|
115
|
+
const tilt = ((params.comicSeed % 1) - 0.5) * 0.18; // ~±5deg
|
|
116
|
+
// ---------- STARBURST BALLOON (B channel) --------------------------------
|
|
117
|
+
// A classic many-pointed jagged star: alternating long/short radii with
|
|
118
|
+
// per-point jitter. Drawn solid into BLUE; its bold outline into GREEN.
|
|
119
|
+
const points = Math.max(8, Math.round(params.burstPoints));
|
|
120
|
+
const outerR = minDim * params.scale * 1.3 * scale;
|
|
121
|
+
const innerR = outerR * 0.64;
|
|
122
|
+
const burstPts = [];
|
|
123
|
+
for (let i = 0; i < points * 2; i++) {
|
|
124
|
+
const t = i / (points * 2);
|
|
125
|
+
const a = t * Math.PI * 2 - Math.PI / 2 + tilt;
|
|
126
|
+
const even = i % 2 === 0;
|
|
127
|
+
const jitter = 0.82 + rng() * 0.36;
|
|
128
|
+
const r = (even ? outerR : innerR) * jitter;
|
|
129
|
+
burstPts.push([cx + Math.cos(a) * r, cy + Math.sin(a) * r]);
|
|
130
|
+
}
|
|
131
|
+
const tracePath = () => {
|
|
132
|
+
ctx.beginPath();
|
|
133
|
+
burstPts.forEach(([x, y], i) => (i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y)));
|
|
134
|
+
ctx.closePath();
|
|
135
|
+
};
|
|
136
|
+
const ink = params.inkWeight * dpr * scale;
|
|
137
|
+
// Burst FILL -> BLUE only.
|
|
138
|
+
ctx.save();
|
|
139
|
+
ctx.globalCompositeOperation = "lighter"; // additive into channels
|
|
140
|
+
tracePath();
|
|
141
|
+
ctx.fillStyle = `rgba(0,0,${Math.round(255 * presence)},1)`;
|
|
142
|
+
ctx.fill();
|
|
143
|
+
ctx.restore();
|
|
144
|
+
// Burst OUTLINE -> GREEN (ink). Thick bold contour.
|
|
145
|
+
ctx.save();
|
|
146
|
+
ctx.globalCompositeOperation = "lighter";
|
|
147
|
+
ctx.lineJoin = "miter";
|
|
148
|
+
ctx.miterLimit = 2;
|
|
149
|
+
tracePath();
|
|
150
|
+
ctx.lineWidth = ink * 1.3;
|
|
151
|
+
ctx.strokeStyle = `rgba(0,${Math.round(255 * presence)},0,1)`;
|
|
152
|
+
ctx.stroke();
|
|
153
|
+
ctx.restore();
|
|
154
|
+
// ---------- LETTERING (success word) or CHECKMARK ------------------------
|
|
155
|
+
// Mood selects the bundled display face + base character (skew/stretch/tilt);
|
|
156
|
+
// whimsy shifts the treatment from restrained inked caps (noir) to fat,
|
|
157
|
+
// inflated, multi-layer-inked, 3D-extruded, per-letter-bounced pop-art. The
|
|
158
|
+
// owner also wants a big bold ✓ as a selectable option — that's drawn as a
|
|
159
|
+
// VECTOR path (not a font glyph) so it's reliable everywhere.
|
|
160
|
+
const fillA = Math.round(255 * presence);
|
|
161
|
+
const inkStyle = `rgba(0,${fillA},0,1)`;
|
|
162
|
+
const fillStyle = `rgba(${fillA},0,0,1)`;
|
|
163
|
+
const round = params.inkRoundness;
|
|
164
|
+
// Per-letter / per-shape deterministic jitter, derived from the per-fire seed.
|
|
165
|
+
const jrng = mulberry32((params.comicSeed * 2654435761) >>> 0);
|
|
166
|
+
ctx.save();
|
|
167
|
+
ctx.translate(cx, cy);
|
|
168
|
+
ctx.rotate(tilt + params.fontTilt);
|
|
169
|
+
// Italic lean + non-uniform stretch as a shared transform on the whole word.
|
|
170
|
+
// matrix: [stretchX, 0, skewX, 1] (a=stretch horiz, c=shear).
|
|
171
|
+
ctx.transform(params.fontStretchX, 0, params.fontSkew, 1, 0, 0);
|
|
172
|
+
ctx.lineJoin = round > 0.5 ? "round" : "miter";
|
|
173
|
+
ctx.lineCap = round > 0.5 ? "round" : "butt";
|
|
174
|
+
ctx.miterLimit = 2;
|
|
175
|
+
ctx.globalCompositeOperation = "lighter"; // additive into channels
|
|
176
|
+
if (isCheckmark(params.word)) {
|
|
177
|
+
// ----- VECTOR CHECKMARK -----------------------------------------------
|
|
178
|
+
// A bold two-segment tick centred on the panel, sized to the burst's inner
|
|
179
|
+
// span. Drawn as a stroked path; ink contour + 3D extrude + bright fill use
|
|
180
|
+
// the same treatment knobs as the word path below.
|
|
181
|
+
const span = innerR * 1.25; // overall check width
|
|
182
|
+
const strokeW = span * 0.24 * (0.85 + round * 0.25);
|
|
183
|
+
const extrude = span * params.extrudeDepth;
|
|
184
|
+
// Check geometry (down-stroke then long up-flick), centred.
|
|
185
|
+
const pts = [
|
|
186
|
+
[-span * 0.42, span * 0.02],
|
|
187
|
+
[-span * 0.12, span * 0.34],
|
|
188
|
+
[span * 0.46, -span * 0.36],
|
|
189
|
+
];
|
|
190
|
+
const traceCheck = () => {
|
|
191
|
+
ctx.beginPath();
|
|
192
|
+
pts.forEach(([x, y], i) => (i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y)));
|
|
193
|
+
};
|
|
194
|
+
// 3D extrude: stacked ink copies stepping down-right (pop-art only).
|
|
195
|
+
if (extrude > 0.5) {
|
|
196
|
+
const steps = 8;
|
|
197
|
+
for (let s = steps; s >= 1; s--) {
|
|
198
|
+
const dx = (extrude * s) / steps;
|
|
199
|
+
const dy = (extrude * s) / steps;
|
|
200
|
+
ctx.save();
|
|
201
|
+
ctx.translate(dx, dy);
|
|
202
|
+
traceCheck();
|
|
203
|
+
ctx.lineWidth = strokeW;
|
|
204
|
+
ctx.strokeStyle = inkStyle;
|
|
205
|
+
ctx.stroke();
|
|
206
|
+
ctx.restore();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Bold ink contour (heavier toward pop-art via outlineLayers).
|
|
210
|
+
traceCheck();
|
|
211
|
+
ctx.lineWidth = strokeW + ink * (1.2 + params.outlineLayers * 0.5);
|
|
212
|
+
ctx.strokeStyle = inkStyle;
|
|
213
|
+
ctx.stroke();
|
|
214
|
+
// Bright fill body.
|
|
215
|
+
traceCheck();
|
|
216
|
+
ctx.lineWidth = strokeW;
|
|
217
|
+
ctx.strokeStyle = fillStyle;
|
|
218
|
+
ctx.stroke();
|
|
219
|
+
ctx.restore();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// ----- WORD RUN ---------------------------------------------------------
|
|
223
|
+
const word = params.word;
|
|
224
|
+
ctx.textAlign = "center";
|
|
225
|
+
ctx.textBaseline = "middle";
|
|
226
|
+
const fontFor = (px) => `${px}px ${params.fontStack}`;
|
|
227
|
+
// Target size, then SHRINK-TO-FIT so longer words (GREAT!/DONE!) never spill
|
|
228
|
+
// out of the burst. Account for the extra horizontal stretch + tracking.
|
|
229
|
+
let fontPx = minDim * params.scale * 0.92 * scale;
|
|
230
|
+
ctx.font = fontFor(fontPx);
|
|
231
|
+
const chars = [...word];
|
|
232
|
+
const trackPx = () => fontPx * params.fontTracking;
|
|
233
|
+
const runWidth = () => {
|
|
234
|
+
let total = 0;
|
|
235
|
+
for (const ch of chars)
|
|
236
|
+
total += ctx.measureText(ch).width + trackPx();
|
|
237
|
+
return Math.max(1, total - trackPx());
|
|
238
|
+
};
|
|
239
|
+
const maxW = (innerR * 1.7) / Math.max(0.6, params.fontStretchX);
|
|
240
|
+
let measured = runWidth();
|
|
241
|
+
if (measured > maxW) {
|
|
242
|
+
fontPx *= maxW / measured;
|
|
243
|
+
ctx.font = fontFor(fontPx);
|
|
244
|
+
measured = runWidth();
|
|
245
|
+
}
|
|
246
|
+
const extrude = fontPx * params.extrudeDepth;
|
|
247
|
+
const inkLine = ink * (1.3 + (params.outlineLayers - 1) * 0.7);
|
|
248
|
+
// Lay out letters individually so we can apply per-letter rotation/baseline
|
|
249
|
+
// jitter (the pop-art bounce). Start at the left edge of the centred run.
|
|
250
|
+
let penX = -measured / 2;
|
|
251
|
+
const letters = chars.map((ch) => {
|
|
252
|
+
const wpx = ctx.measureText(ch).width;
|
|
253
|
+
const x = penX + wpx / 2;
|
|
254
|
+
penX += wpx + trackPx();
|
|
255
|
+
const rot = (jrng() - 0.5) * 2 * params.letterRotJitter;
|
|
256
|
+
const dy = (jrng() - 0.5) * 2 * params.letterBaselineJitter * fontPx;
|
|
257
|
+
return { ch, x, rot, dy, wgt: jrng() };
|
|
258
|
+
});
|
|
259
|
+
const drawLetters = (cb) => {
|
|
260
|
+
for (const l of letters) {
|
|
261
|
+
ctx.save();
|
|
262
|
+
ctx.translate(l.x, l.dy);
|
|
263
|
+
ctx.rotate(l.rot);
|
|
264
|
+
cb(ctx, l);
|
|
265
|
+
ctx.restore();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
// 3D extrude / drop: stacked ink copies stepping down-right behind the body
|
|
269
|
+
// (pop-art pops, flat at noir).
|
|
270
|
+
if (extrude > 0.5) {
|
|
271
|
+
const steps = 8;
|
|
272
|
+
for (let s = steps; s >= 1; s--) {
|
|
273
|
+
const dx = (extrude * s) / steps;
|
|
274
|
+
const dy = (extrude * s) / steps;
|
|
275
|
+
drawLetters((c, l) => {
|
|
276
|
+
c.fillStyle = inkStyle;
|
|
277
|
+
c.fillText(l.ch, dx, dy);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Bold INK contour — drawn under the fill so the outline frames the letters.
|
|
282
|
+
// outlineLayers stacks slightly fattening passes for the inflated balloon look.
|
|
283
|
+
for (let layer = params.outlineLayers; layer >= 1; layer--) {
|
|
284
|
+
const lw = inkLine * (1 + (layer - 1) * 0.5);
|
|
285
|
+
drawLetters((c, l) => {
|
|
286
|
+
c.lineJoin = round > 0.5 ? "round" : "miter";
|
|
287
|
+
c.lineWidth = lw;
|
|
288
|
+
c.strokeStyle = inkStyle;
|
|
289
|
+
c.strokeText(l.ch, 0, 0);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// Bright FILL body on top.
|
|
293
|
+
drawLetters((c, l) => {
|
|
294
|
+
c.fillStyle = fillStyle;
|
|
295
|
+
c.fillText(l.ch, 0, 0);
|
|
296
|
+
});
|
|
297
|
+
ctx.restore();
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=comic-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comic-renderer.js","sourceRoot":"","sources":["../src/comic-renderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLSL ES 3.00 source for **Comic Impact** — Dopamine's third success effect: a
|
|
3
|
+
* Golden/Silver-Age comic-book "BAM! POW!" fight-panel impact.
|
|
4
|
+
*
|
|
5
|
+
* This is a HYBRID effect. Crisp blocky vector lettering and bold ink contours
|
|
6
|
+
* are hard to do well in a pure fragment shader, so the renderer draws the
|
|
7
|
+
* onomatopoeia word + jagged starburst + ink outlines into an OFFSCREEN Canvas2D
|
|
8
|
+
* and hands it to this shader as a single "panel" texture. The shader then does
|
|
9
|
+
* everything that wants to be procedural and screen-space:
|
|
10
|
+
* - Ben-Day / halftone DOT shading (rotated screen, dot radius driven by the
|
|
11
|
+
* underlying value) — subtle/fine at the noir end, loud/large at pop-art.
|
|
12
|
+
* - RADIATING action / speed lines bursting from the impact centre.
|
|
13
|
+
* - A FLASH that throws colored light onto the page (the screen-blend cast).
|
|
14
|
+
* - The NOIR ↔ POP-ART styling: near-monochrome high-contrast chiaroscuro with
|
|
15
|
+
* one spot color → screaming saturated pop, keyed off uStyle/uSaturation.
|
|
16
|
+
*
|
|
17
|
+
* Everything is summed as light (canvas is black, composited via
|
|
18
|
+
* `mix-blend-mode: screen`, so black == no change, bright == cast light).
|
|
19
|
+
*
|
|
20
|
+
* Panel texture channel encoding (see comic-renderer.ts):
|
|
21
|
+
* R = word FILL mask (letter interiors)
|
|
22
|
+
* G = INK mask (all black ink: letter + burst + line outlines)
|
|
23
|
+
* B = burst FILL mask (starburst balloon interior, behind the word)
|
|
24
|
+
* A = unused
|
|
25
|
+
*
|
|
26
|
+
* Pure function of uniforms → frame-perfect & cheap under SwiftShader.
|
|
27
|
+
*/
|
|
28
|
+
export declare const COMIC_VERTEX_SRC = "#version 300 es\nout vec2 vUv;\nvoid main() {\n vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n vUv = pos;\n gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);\n}";
|
|
29
|
+
export declare const COMIC_FRAGMENT_SRC = "#version 300 es\nprecision highp float;\nin vec2 vUv;\nout vec4 fragColor;\n\nuniform sampler2D uPanel; // R=wordFill G=ink B=burstFill\nuniform vec2 uResolution; // device pixels\nuniform vec2 uTarget; // targeted element size (device px); scales the action lines\nuniform vec2 uOrigin; // impact centre (the anchor), device px\nuniform float uLife; // whole-effect progress 0..1\nuniform float uTimeS; // elapsed seconds\nuniform float uPresence; // panel opacity / presence 0..1\nuniform float uFlash; // 0..1 impact flash amount (fast spike, decays)\nuniform float uExposure; // cast-light gain\nuniform float uHalftone; // 0..1 Ben-Day dot strength\nuniform float uDotSize; // Ben-Day cell size in device px\nuniform float uSaturation; // 0..1 panel color saturation (noir->pop)\nuniform float uActionLines; // count of radiating speed lines\nuniform float uInkBoost; // ink darkness/spread multiplier (pop fattens ink)\nuniform float uSeed; // per-fire hash\nuniform float uStyle; // 0..1 noir -> pop-art (whimsy)\nuniform float uShadow; // 0 = light pass (screen), 1 = shadow pass (multiply)\nuniform vec2 uShadowOffset; // device-px offset of the cast silhouette (away from light)\nuniform float uShadowSoft; // penumbra softness in device px (blur tap radius)\nuniform float uShadowStrength;// 0..1 max darkening of the multiply layer\nuniform vec3 uC0; // word fill color\nuniform vec3 uC1; // secondary / burst color\nuniform vec3 uC2; // dot / accent color\n\n\n#define TAU 6.28318530718\n\n\nfloat hash11(float p){ p = fract(p * 0.1031); p *= p + 33.33; p *= p + p; return fract(p); }\nvec2 hash21(float p){\n vec3 p3 = fract(vec3(p) * vec3(0.1031, 0.1030, 0.0973));\n p3 += dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\n\nmat2 rot2(float a){ float s = sin(a), c = cos(a); return mat2(c, -s, s, c); }\n\n\nfloat benday(vec2 frag, float cell, float v, float ang){\n vec2 p = rot2(ang) * frag / cell;\n vec2 g = fract(p) - 0.5;\n float d = length(g);\n float r = 0.52 * sqrt(clamp(v, 0.0, 1.0));\n float aa = 0.7 / cell + fwidth(d);\n return 1.0 - smoothstep(r - aa, r + aa, d);\n}\n\n\nvec3 tonemapACES(vec3 x){\n const float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;\n return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);\n}\n\n\nvec3 ditherAdd(vec3 col, vec2 frag, float t, float fade){\n float dz = hash11(dot(frag, vec2(12.989, 78.233)) + t) - 0.5;\n return col + (dz / 255.0) * fade;\n}\n\n\nvoid main(){\n vec2 frag = vUv * uResolution;\n vec2 res = uResolution;\n float minDim = min(res.x, res.y);\n // The word/burst (panel) are sized to the targeted element box; scale the\n // radiating action lines to the SAME basis so they streak off the word, not the\n // whole canvas. Clamped to the canvas so a full-page fire (uTarget == res) is\n // unchanged. Must match the comic renderer's COMIC_TARGET_FILL (1.7).\n float comicSpan = min(min(uTarget.x, uTarget.y) * 1.7, minDim);\n\n // ---- SHADOW PASS (multiply layer) ---------------------------------------\n // Cheap occlusion: the panel's solid forms (word fill + burst fill) sampled\n // at an offset toward the implied key light, with a small ring blur for a\n // penumbra. White = no shadow (multiply identity); darker = cast shadow. The\n // panel already encodes presence, so the shadow fades with the effect.\n if (uShadow > 0.5) {\n vec2 px = 1.0 / res;\n vec2 souv = vUv - uShadowOffset * px;\n float occ = 0.0;\n for (int i = 0; i < 8; i++) {\n float a = float(i) / 8.0 * TAU;\n vec2 o = vec2(cos(a), sin(a)) * uShadowSoft * px;\n vec2 tuv = souv + o;\n // Gate samples that fall OUTSIDE the panel: the texture is CLAMP_TO_EDGE,\n // so without this an offset sample past an edge smears that edge row into\n // a phantom band (the streaks at the top of the frame). Outside == no\n // occluder == no shadow.\n vec2 inb = step(vec2(0.0), tuv) * step(tuv, vec2(1.0));\n float mask = inb.x * inb.y;\n vec4 s = texture(uPanel, tuv);\n occ += clamp(s.r + s.b, 0.0, 1.0) * mask;\n }\n occ /= 8.0;\n float dark = clamp(occ * uShadowStrength, 0.0, 1.0);\n fragColor = vec4(vec3(1.0 - dark), 1.0);\n return;\n }\n\n vec2 fromC = frag - uOrigin;\n float rad = length(fromC);\n float ang = atan(fromC.y, fromC.x);\n\n vec4 panel = texture(uPanel, vUv);\n float wordFill = panel.r;\n float inkMask = clamp(panel.g * uInkBoost, 0.0, 1.0);\n float burstFill = panel.b;\n\n vec3 col = vec3(0.0);\n\n // ---- RADIATING ACTION / SPEED LINES -------------------------------------\n // Thin wedges bursting outward from the impact centre. Procedural so they're\n // crisp and cheap. They live in a ring OUTSIDE the burst balloon (so they\n // read as motion lines streaking off the hit, not hatching on the word).\n float lineN = max(uActionLines, 1.0);\n float a01 = (ang / TAU) + 0.5; // 0..1 around the circle\n float idx = floor(a01 * lineN);\n // per-line random angular jitter + length so they aren't a clean fan.\n float jr = hash11(idx + uSeed * 3.1);\n float jr2 = hash11(idx * 1.7 + uSeed * 7.3);\n float cellPhase = fract(a01 * lineN);\n float wedge = abs(cellPhase - 0.5);\n // Thin tapered streaks: a sharp spine that fattens slightly outward (classic\n // motion-line wedge), kept narrow so they read as speed lines, not pie slices.\n float thick = mix(0.05, 0.14, jr);\n float lineBody = 1.0 - smoothstep(thick * 0.35, thick, wedge);\n // radial extent: lines start OUTSIDE the burst and streak outward to the edge.\n float innerR = comicSpan * (0.30 + 0.05 * jr2);\n float outerR = comicSpan * (0.46 + 0.30 * jr);\n float radialMask = smoothstep(innerR, innerR + comicSpan * 0.015, rad)\n * (1.0 - smoothstep(outerR - comicSpan * 0.10, outerR, rad));\n // fade the lines in fast on impact, hold, then they thin out late.\n float linePresence = smoothstep(0.0, 0.06, uLife) * (1.0 - smoothstep(0.6, 1.0, uLife));\n // taper opacity along the line so the inner end is boldest (ink-streak feel).\n float taper = 1.0 - smoothstep(innerR, outerR, rad);\n float lines = lineBody * radialMask * linePresence * taper;\n // animate-on-twos flicker toward the pop end (snappy comic motion).\n float beat = floor(uTimeS * 12.0);\n float flick = mix(1.0, step(0.25, hash11(idx + beat + uSeed)), uStyle * 0.5);\n lines *= flick;\n\n // Action lines cast a thin streak of light off the hit. White/cool ink at the\n // noir end (a hard glint), the accent hue at the pop end. Kept dim so they\n // read as speed lines around the panel rather than flooding the frame.\n vec3 lineCol = mix(vec3(0.7, 0.74, 0.82), uC2, uStyle);\n col += lineCol * lines * 0.32 * uExposure;\n\n // ---- STARBURST BALLOON (behind the word) --------------------------------\n // Filled with the secondary hue; gets the strongest Ben-Day shading so it\n // reads as a flat printed color field. In noir it's a pale near-white field\n // with a fine subtle screen; in pop-art it's a saturated yellow/red blast.\n vec3 burstBase = mix(vec3(0.9), uC1, uSaturation);\n // tone for the dots: more dots where the field is \"darker\" value. We want a\n // lively mid coverage so the classic dot field shows.\n float burstTone = mix(0.35, 0.7, uHalftone);\n float dots = benday(frag, uDotSize, burstTone, radians(15.0) + uSeed);\n // Ben-Day strength: subtle at noir, dominant at pop. The dots ADD the accent\n // color on the printed field.\n vec3 burstCol = burstBase + (uC2 - burstBase) * dots * uHalftone * 0.55;\n col += burstCol * burstFill * uPresence * uExposure;\n\n // A second, finer rotated screen on the word fill for that printed sheen at\n // the pop end (kept subtle so letters stay legible). The word is the HERO:\n // a bright, saturated fill that screams off the page (pop) or a luminous\n // near-white with a spot tint (noir). Brighter than the burst so it reads\n // as the foreground shout.\n float wordDots = benday(frag, uDotSize * 0.7, 0.5, radians(75.0) + uSeed);\n vec3 wordBright = clamp(uC0 * 1.35 + 0.25, 0.0, 1.4);\n vec3 wordBase = mix(vec3(0.96, 0.97, 1.0), wordBright, clamp(uSaturation + 0.2, 0.0, 1.0));\n vec3 wordCol = wordBase + (uC2 - wordBase) * wordDots * uHalftone * 0.25 * uStyle;\n // Word fill is largely PROTECTED from ink suppression (its own outline should\n // frame it, not eat it), so render it after a softened ink mask below.\n col += wordCol * wordFill * uPresence * uExposure * 1.7;\n\n // ---- INK ----------------------------------------------------------------\n // Bold black contours. Ink is the ABSENCE of light on a screen-blend canvas,\n // so we can't literally darken the page from here \u2014 instead we let ink CARVE\n // the lit shapes (it suppresses the fills it overlaps) and, at the noir end,\n // we add a faint cool rim so the chiaroscuro edge still reads as light catches\n // the ink ridge. The actual black is achieved by NOT lighting those pixels.\n float ink = inkMask * uPresence;\n // Suppress fills under ink (so outlines punch through as unlit black). But\n // where the ink overlaps the WORD fill we soften the carve a lot, so the\n // outline frames the letters instead of eating their bright bodies.\n // Gentle carve: the outline should FRAME the burst, not gut it. A near-total\n // carve (old 0.96) left the burst outline reading as a transparent gap that\n // masked the balloon \u2014 worse at high whimsy, where inkBoost fattens the ink.\n // Keep the word's soft carve (0.26 at wordFill=1) but darken the burst outline\n // only partway so the balloon shows through.\n float carve = ink * (0.45 - 0.19 * wordFill);\n col *= (1.0 - carve);\n // Subtle chiaroscuro rim-light on ink edges toward the noir end (a glint).\n float rim = ink * (1.0 - uStyle) * 0.18;\n col += mix(uC2, vec3(0.8, 0.85, 1.0), 0.5) * rim * uExposure;\n\n // ---- IMPACT FLASH -------------------------------------------------------\n // A hot radial flash at the moment of impact that throws colored light onto\n // the page (the cast-light proof). Fast spike, quick decay (driven by uFlash).\n float flashFall = exp(-rad / (minDim * 0.42));\n vec3 flashCol = mix(mix(uC0, uC1, 0.5), vec3(1.0), 0.45 + 0.3 * uStyle);\n col += flashCol * flashFall * uFlash * uExposure * 1.4;\n // a tight white-hot core right at the centre on the very first frames.\n float core = exp(-rad / (minDim * 0.10));\n col += vec3(1.0) * core * uFlash * uFlash * 1.6;\n\n // ---- TONE + FINISH ------------------------------------------------------\n // ACES filmic tonemap (shared look/glsl) for a cleaner highlight rolloff than\n // the old x/(1+x) compress \u2014 the impact flash highlights roll off gracefully\n // while the saturated printed mids stay rich. A mild pre-exposure keeps the\n // pop-art color from dimming.\n col = tonemapACES(col * 0.85);\n\n // Pop-art posterize: snap the lit panel to a few flat ink levels toward the\n // pop end (flat printed color), leaving the dark page untouched so we don't\n // shatter it into camouflage. Noir stays smooth chiaroscuro.\n if (uStyle > 0.001) {\n float lit = smoothstep(0.02, 0.2, max(max(col.r, col.g), col.b));\n vec3 q = floor(col * 4.0 + 0.5) / 4.0;\n col = mix(col, mix(col, q, lit), uStyle * 0.7);\n }\n\n // Ordered dither (shared look/glsl) to kill banding the screen-blend reveals\n // (faded toward the pop end where the flat printed look is intended).\n col = ditherAdd(col, frag, uTimeS, 1.0 - uStyle * 0.7);\n\n fragColor = vec4(max(col, 0.0), 1.0);\n}";
|
|
30
|
+
//# sourceMappingURL=comic-shader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comic-shader.d.ts","sourceRoot":"","sources":["../src/comic-shader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAWH,eAAO,MAAM,gBAAgB,gMAM3B,CAAC;AAEH,eAAO,MAAM,kBAAkB,m/WAyM7B,CAAC"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLSL ES 3.00 source for **Comic Impact** — Dopamine's third success effect: a
|
|
3
|
+
* Golden/Silver-Age comic-book "BAM! POW!" fight-panel impact.
|
|
4
|
+
*
|
|
5
|
+
* This is a HYBRID effect. Crisp blocky vector lettering and bold ink contours
|
|
6
|
+
* are hard to do well in a pure fragment shader, so the renderer draws the
|
|
7
|
+
* onomatopoeia word + jagged starburst + ink outlines into an OFFSCREEN Canvas2D
|
|
8
|
+
* and hands it to this shader as a single "panel" texture. The shader then does
|
|
9
|
+
* everything that wants to be procedural and screen-space:
|
|
10
|
+
* - Ben-Day / halftone DOT shading (rotated screen, dot radius driven by the
|
|
11
|
+
* underlying value) — subtle/fine at the noir end, loud/large at pop-art.
|
|
12
|
+
* - RADIATING action / speed lines bursting from the impact centre.
|
|
13
|
+
* - A FLASH that throws colored light onto the page (the screen-blend cast).
|
|
14
|
+
* - The NOIR ↔ POP-ART styling: near-monochrome high-contrast chiaroscuro with
|
|
15
|
+
* one spot color → screaming saturated pop, keyed off uStyle/uSaturation.
|
|
16
|
+
*
|
|
17
|
+
* Everything is summed as light (canvas is black, composited via
|
|
18
|
+
* `mix-blend-mode: screen`, so black == no change, bright == cast light).
|
|
19
|
+
*
|
|
20
|
+
* Panel texture channel encoding (see comic-renderer.ts):
|
|
21
|
+
* R = word FILL mask (letter interiors)
|
|
22
|
+
* G = INK mask (all black ink: letter + burst + line outlines)
|
|
23
|
+
* B = burst FILL mask (starburst balloon interior, behind the word)
|
|
24
|
+
* A = unused
|
|
25
|
+
*
|
|
26
|
+
* Pure function of uniforms → frame-perfect & cheap under SwiftShader.
|
|
27
|
+
*/
|
|
28
|
+
import { GLSL_CONSTANTS, GLSL_DITHER, GLSL_HALFTONE, GLSL_HASH, GLSL_ROT2, GLSL_TONEMAP_ACES, } from "@dopaminefx/core";
|
|
29
|
+
export const COMIC_VERTEX_SRC = /* glsl */ `#version 300 es
|
|
30
|
+
out vec2 vUv;
|
|
31
|
+
void main() {
|
|
32
|
+
vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
|
33
|
+
vUv = pos;
|
|
34
|
+
gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);
|
|
35
|
+
}`;
|
|
36
|
+
export const COMIC_FRAGMENT_SRC = /* glsl */ `#version 300 es
|
|
37
|
+
precision highp float;
|
|
38
|
+
in vec2 vUv;
|
|
39
|
+
out vec4 fragColor;
|
|
40
|
+
|
|
41
|
+
uniform sampler2D uPanel; // R=wordFill G=ink B=burstFill
|
|
42
|
+
uniform vec2 uResolution; // device pixels
|
|
43
|
+
uniform vec2 uTarget; // targeted element size (device px); scales the action lines
|
|
44
|
+
uniform vec2 uOrigin; // impact centre (the anchor), device px
|
|
45
|
+
uniform float uLife; // whole-effect progress 0..1
|
|
46
|
+
uniform float uTimeS; // elapsed seconds
|
|
47
|
+
uniform float uPresence; // panel opacity / presence 0..1
|
|
48
|
+
uniform float uFlash; // 0..1 impact flash amount (fast spike, decays)
|
|
49
|
+
uniform float uExposure; // cast-light gain
|
|
50
|
+
uniform float uHalftone; // 0..1 Ben-Day dot strength
|
|
51
|
+
uniform float uDotSize; // Ben-Day cell size in device px
|
|
52
|
+
uniform float uSaturation; // 0..1 panel color saturation (noir->pop)
|
|
53
|
+
uniform float uActionLines; // count of radiating speed lines
|
|
54
|
+
uniform float uInkBoost; // ink darkness/spread multiplier (pop fattens ink)
|
|
55
|
+
uniform float uSeed; // per-fire hash
|
|
56
|
+
uniform float uStyle; // 0..1 noir -> pop-art (whimsy)
|
|
57
|
+
uniform float uShadow; // 0 = light pass (screen), 1 = shadow pass (multiply)
|
|
58
|
+
uniform vec2 uShadowOffset; // device-px offset of the cast silhouette (away from light)
|
|
59
|
+
uniform float uShadowSoft; // penumbra softness in device px (blur tap radius)
|
|
60
|
+
uniform float uShadowStrength;// 0..1 max darkening of the multiply layer
|
|
61
|
+
uniform vec3 uC0; // word fill color
|
|
62
|
+
uniform vec3 uC1; // secondary / burst color
|
|
63
|
+
uniform vec3 uC2; // dot / accent color
|
|
64
|
+
|
|
65
|
+
${GLSL_CONSTANTS}
|
|
66
|
+
${GLSL_HASH}
|
|
67
|
+
${GLSL_ROT2}
|
|
68
|
+
${GLSL_HALFTONE}
|
|
69
|
+
${GLSL_TONEMAP_ACES}
|
|
70
|
+
${GLSL_DITHER}
|
|
71
|
+
|
|
72
|
+
void main(){
|
|
73
|
+
vec2 frag = vUv * uResolution;
|
|
74
|
+
vec2 res = uResolution;
|
|
75
|
+
float minDim = min(res.x, res.y);
|
|
76
|
+
// The word/burst (panel) are sized to the targeted element box; scale the
|
|
77
|
+
// radiating action lines to the SAME basis so they streak off the word, not the
|
|
78
|
+
// whole canvas. Clamped to the canvas so a full-page fire (uTarget == res) is
|
|
79
|
+
// unchanged. Must match the comic renderer's COMIC_TARGET_FILL (1.7).
|
|
80
|
+
float comicSpan = min(min(uTarget.x, uTarget.y) * 1.7, minDim);
|
|
81
|
+
|
|
82
|
+
// ---- SHADOW PASS (multiply layer) ---------------------------------------
|
|
83
|
+
// Cheap occlusion: the panel's solid forms (word fill + burst fill) sampled
|
|
84
|
+
// at an offset toward the implied key light, with a small ring blur for a
|
|
85
|
+
// penumbra. White = no shadow (multiply identity); darker = cast shadow. The
|
|
86
|
+
// panel already encodes presence, so the shadow fades with the effect.
|
|
87
|
+
if (uShadow > 0.5) {
|
|
88
|
+
vec2 px = 1.0 / res;
|
|
89
|
+
vec2 souv = vUv - uShadowOffset * px;
|
|
90
|
+
float occ = 0.0;
|
|
91
|
+
for (int i = 0; i < 8; i++) {
|
|
92
|
+
float a = float(i) / 8.0 * TAU;
|
|
93
|
+
vec2 o = vec2(cos(a), sin(a)) * uShadowSoft * px;
|
|
94
|
+
vec2 tuv = souv + o;
|
|
95
|
+
// Gate samples that fall OUTSIDE the panel: the texture is CLAMP_TO_EDGE,
|
|
96
|
+
// so without this an offset sample past an edge smears that edge row into
|
|
97
|
+
// a phantom band (the streaks at the top of the frame). Outside == no
|
|
98
|
+
// occluder == no shadow.
|
|
99
|
+
vec2 inb = step(vec2(0.0), tuv) * step(tuv, vec2(1.0));
|
|
100
|
+
float mask = inb.x * inb.y;
|
|
101
|
+
vec4 s = texture(uPanel, tuv);
|
|
102
|
+
occ += clamp(s.r + s.b, 0.0, 1.0) * mask;
|
|
103
|
+
}
|
|
104
|
+
occ /= 8.0;
|
|
105
|
+
float dark = clamp(occ * uShadowStrength, 0.0, 1.0);
|
|
106
|
+
fragColor = vec4(vec3(1.0 - dark), 1.0);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
vec2 fromC = frag - uOrigin;
|
|
111
|
+
float rad = length(fromC);
|
|
112
|
+
float ang = atan(fromC.y, fromC.x);
|
|
113
|
+
|
|
114
|
+
vec4 panel = texture(uPanel, vUv);
|
|
115
|
+
float wordFill = panel.r;
|
|
116
|
+
float inkMask = clamp(panel.g * uInkBoost, 0.0, 1.0);
|
|
117
|
+
float burstFill = panel.b;
|
|
118
|
+
|
|
119
|
+
vec3 col = vec3(0.0);
|
|
120
|
+
|
|
121
|
+
// ---- RADIATING ACTION / SPEED LINES -------------------------------------
|
|
122
|
+
// Thin wedges bursting outward from the impact centre. Procedural so they're
|
|
123
|
+
// crisp and cheap. They live in a ring OUTSIDE the burst balloon (so they
|
|
124
|
+
// read as motion lines streaking off the hit, not hatching on the word).
|
|
125
|
+
float lineN = max(uActionLines, 1.0);
|
|
126
|
+
float a01 = (ang / TAU) + 0.5; // 0..1 around the circle
|
|
127
|
+
float idx = floor(a01 * lineN);
|
|
128
|
+
// per-line random angular jitter + length so they aren't a clean fan.
|
|
129
|
+
float jr = hash11(idx + uSeed * 3.1);
|
|
130
|
+
float jr2 = hash11(idx * 1.7 + uSeed * 7.3);
|
|
131
|
+
float cellPhase = fract(a01 * lineN);
|
|
132
|
+
float wedge = abs(cellPhase - 0.5);
|
|
133
|
+
// Thin tapered streaks: a sharp spine that fattens slightly outward (classic
|
|
134
|
+
// motion-line wedge), kept narrow so they read as speed lines, not pie slices.
|
|
135
|
+
float thick = mix(0.05, 0.14, jr);
|
|
136
|
+
float lineBody = 1.0 - smoothstep(thick * 0.35, thick, wedge);
|
|
137
|
+
// radial extent: lines start OUTSIDE the burst and streak outward to the edge.
|
|
138
|
+
float innerR = comicSpan * (0.30 + 0.05 * jr2);
|
|
139
|
+
float outerR = comicSpan * (0.46 + 0.30 * jr);
|
|
140
|
+
float radialMask = smoothstep(innerR, innerR + comicSpan * 0.015, rad)
|
|
141
|
+
* (1.0 - smoothstep(outerR - comicSpan * 0.10, outerR, rad));
|
|
142
|
+
// fade the lines in fast on impact, hold, then they thin out late.
|
|
143
|
+
float linePresence = smoothstep(0.0, 0.06, uLife) * (1.0 - smoothstep(0.6, 1.0, uLife));
|
|
144
|
+
// taper opacity along the line so the inner end is boldest (ink-streak feel).
|
|
145
|
+
float taper = 1.0 - smoothstep(innerR, outerR, rad);
|
|
146
|
+
float lines = lineBody * radialMask * linePresence * taper;
|
|
147
|
+
// animate-on-twos flicker toward the pop end (snappy comic motion).
|
|
148
|
+
float beat = floor(uTimeS * 12.0);
|
|
149
|
+
float flick = mix(1.0, step(0.25, hash11(idx + beat + uSeed)), uStyle * 0.5);
|
|
150
|
+
lines *= flick;
|
|
151
|
+
|
|
152
|
+
// Action lines cast a thin streak of light off the hit. White/cool ink at the
|
|
153
|
+
// noir end (a hard glint), the accent hue at the pop end. Kept dim so they
|
|
154
|
+
// read as speed lines around the panel rather than flooding the frame.
|
|
155
|
+
vec3 lineCol = mix(vec3(0.7, 0.74, 0.82), uC2, uStyle);
|
|
156
|
+
col += lineCol * lines * 0.32 * uExposure;
|
|
157
|
+
|
|
158
|
+
// ---- STARBURST BALLOON (behind the word) --------------------------------
|
|
159
|
+
// Filled with the secondary hue; gets the strongest Ben-Day shading so it
|
|
160
|
+
// reads as a flat printed color field. In noir it's a pale near-white field
|
|
161
|
+
// with a fine subtle screen; in pop-art it's a saturated yellow/red blast.
|
|
162
|
+
vec3 burstBase = mix(vec3(0.9), uC1, uSaturation);
|
|
163
|
+
// tone for the dots: more dots where the field is "darker" value. We want a
|
|
164
|
+
// lively mid coverage so the classic dot field shows.
|
|
165
|
+
float burstTone = mix(0.35, 0.7, uHalftone);
|
|
166
|
+
float dots = benday(frag, uDotSize, burstTone, radians(15.0) + uSeed);
|
|
167
|
+
// Ben-Day strength: subtle at noir, dominant at pop. The dots ADD the accent
|
|
168
|
+
// color on the printed field.
|
|
169
|
+
vec3 burstCol = burstBase + (uC2 - burstBase) * dots * uHalftone * 0.55;
|
|
170
|
+
col += burstCol * burstFill * uPresence * uExposure;
|
|
171
|
+
|
|
172
|
+
// A second, finer rotated screen on the word fill for that printed sheen at
|
|
173
|
+
// the pop end (kept subtle so letters stay legible). The word is the HERO:
|
|
174
|
+
// a bright, saturated fill that screams off the page (pop) or a luminous
|
|
175
|
+
// near-white with a spot tint (noir). Brighter than the burst so it reads
|
|
176
|
+
// as the foreground shout.
|
|
177
|
+
float wordDots = benday(frag, uDotSize * 0.7, 0.5, radians(75.0) + uSeed);
|
|
178
|
+
vec3 wordBright = clamp(uC0 * 1.35 + 0.25, 0.0, 1.4);
|
|
179
|
+
vec3 wordBase = mix(vec3(0.96, 0.97, 1.0), wordBright, clamp(uSaturation + 0.2, 0.0, 1.0));
|
|
180
|
+
vec3 wordCol = wordBase + (uC2 - wordBase) * wordDots * uHalftone * 0.25 * uStyle;
|
|
181
|
+
// Word fill is largely PROTECTED from ink suppression (its own outline should
|
|
182
|
+
// frame it, not eat it), so render it after a softened ink mask below.
|
|
183
|
+
col += wordCol * wordFill * uPresence * uExposure * 1.7;
|
|
184
|
+
|
|
185
|
+
// ---- INK ----------------------------------------------------------------
|
|
186
|
+
// Bold black contours. Ink is the ABSENCE of light on a screen-blend canvas,
|
|
187
|
+
// so we can't literally darken the page from here — instead we let ink CARVE
|
|
188
|
+
// the lit shapes (it suppresses the fills it overlaps) and, at the noir end,
|
|
189
|
+
// we add a faint cool rim so the chiaroscuro edge still reads as light catches
|
|
190
|
+
// the ink ridge. The actual black is achieved by NOT lighting those pixels.
|
|
191
|
+
float ink = inkMask * uPresence;
|
|
192
|
+
// Suppress fills under ink (so outlines punch through as unlit black). But
|
|
193
|
+
// where the ink overlaps the WORD fill we soften the carve a lot, so the
|
|
194
|
+
// outline frames the letters instead of eating their bright bodies.
|
|
195
|
+
// Gentle carve: the outline should FRAME the burst, not gut it. A near-total
|
|
196
|
+
// carve (old 0.96) left the burst outline reading as a transparent gap that
|
|
197
|
+
// masked the balloon — worse at high whimsy, where inkBoost fattens the ink.
|
|
198
|
+
// Keep the word's soft carve (0.26 at wordFill=1) but darken the burst outline
|
|
199
|
+
// only partway so the balloon shows through.
|
|
200
|
+
float carve = ink * (0.45 - 0.19 * wordFill);
|
|
201
|
+
col *= (1.0 - carve);
|
|
202
|
+
// Subtle chiaroscuro rim-light on ink edges toward the noir end (a glint).
|
|
203
|
+
float rim = ink * (1.0 - uStyle) * 0.18;
|
|
204
|
+
col += mix(uC2, vec3(0.8, 0.85, 1.0), 0.5) * rim * uExposure;
|
|
205
|
+
|
|
206
|
+
// ---- IMPACT FLASH -------------------------------------------------------
|
|
207
|
+
// A hot radial flash at the moment of impact that throws colored light onto
|
|
208
|
+
// the page (the cast-light proof). Fast spike, quick decay (driven by uFlash).
|
|
209
|
+
float flashFall = exp(-rad / (minDim * 0.42));
|
|
210
|
+
vec3 flashCol = mix(mix(uC0, uC1, 0.5), vec3(1.0), 0.45 + 0.3 * uStyle);
|
|
211
|
+
col += flashCol * flashFall * uFlash * uExposure * 1.4;
|
|
212
|
+
// a tight white-hot core right at the centre on the very first frames.
|
|
213
|
+
float core = exp(-rad / (minDim * 0.10));
|
|
214
|
+
col += vec3(1.0) * core * uFlash * uFlash * 1.6;
|
|
215
|
+
|
|
216
|
+
// ---- TONE + FINISH ------------------------------------------------------
|
|
217
|
+
// ACES filmic tonemap (shared look/glsl) for a cleaner highlight rolloff than
|
|
218
|
+
// the old x/(1+x) compress — the impact flash highlights roll off gracefully
|
|
219
|
+
// while the saturated printed mids stay rich. A mild pre-exposure keeps the
|
|
220
|
+
// pop-art color from dimming.
|
|
221
|
+
col = tonemapACES(col * 0.85);
|
|
222
|
+
|
|
223
|
+
// Pop-art posterize: snap the lit panel to a few flat ink levels toward the
|
|
224
|
+
// pop end (flat printed color), leaving the dark page untouched so we don't
|
|
225
|
+
// shatter it into camouflage. Noir stays smooth chiaroscuro.
|
|
226
|
+
if (uStyle > 0.001) {
|
|
227
|
+
float lit = smoothstep(0.02, 0.2, max(max(col.r, col.g), col.b));
|
|
228
|
+
vec3 q = floor(col * 4.0 + 0.5) / 4.0;
|
|
229
|
+
col = mix(col, mix(col, q, lit), uStyle * 0.7);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Ordered dither (shared look/glsl) to kill banding the screen-blend reveals
|
|
233
|
+
// (faded toward the pop end where the flat printed look is intended).
|
|
234
|
+
col = ditherAdd(col, frag, uTimeS, 1.0 - uStyle * 0.7);
|
|
235
|
+
|
|
236
|
+
fragColor = vec4(max(col, 0.0), 1.0);
|
|
237
|
+
}`;
|
|
238
|
+
//# sourceMappingURL=comic-shader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comic-shader.js","sourceRoot":"","sources":["../src/comic-shader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EACL,cAAc,EACd,WAAW,EACX,aAAa,EACb,SAAS,EACT,SAAS,EACT,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;;;;;;EAMzC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6B3C,cAAc;EACd,SAAS;EACT,SAAS;EACT,aAAa;EACb,iBAAiB;EACjB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuKX,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comic Impact bespoke timing — the slam/recoil + proud-hold-then-fade.
|
|
3
|
+
*
|
|
4
|
+
* The word arrives oversized and slams down past its rest size, recoils (a quick
|
|
5
|
+
* spring), holds, then eases out at the tail. Deliberately very short IMPACT so
|
|
6
|
+
* the word reads as a punch landing, not a tween. Built on `easeOutCubic`.
|
|
7
|
+
*/
|
|
8
|
+
/** Window (ms) over which the comic onomatopoeia word SLAMS in. */
|
|
9
|
+
export declare const IMPACT_MS = 200;
|
|
10
|
+
/** Hold (ms) the word sits proud at full size before it begins to settle out. */
|
|
11
|
+
export declare const IMPACT_HOLD_MS = 650;
|
|
12
|
+
/**
|
|
13
|
+
* Comic impact SCALE over elapsed ms. Returns a multiplier on rest size: large
|
|
14
|
+
* at t≈0, slamming to ≈1 by IMPACT_MS (with a small spring), then resting.
|
|
15
|
+
* `overshoot` scales the slam magnitude (driven by intensity).
|
|
16
|
+
*/
|
|
17
|
+
export declare function impactScale(elapsedMs: number, overshoot?: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* Comic impact OPACITY/presence over normalized life (0..1). A near-instant
|
|
20
|
+
* appearance, a long proud hold, then a quick fade at the very end so the panel
|
|
21
|
+
* clears. The fade occupies the last ~18%.
|
|
22
|
+
*/
|
|
23
|
+
export declare function impactPresence(life: number): number;
|
|
24
|
+
//# sourceMappingURL=comic-tempo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comic-tempo.d.ts","sourceRoot":"","sources":["../src/comic-tempo.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,mEAAmE;AACnE,eAAO,MAAM,SAAS,MAAM,CAAC;AAE7B,iFAAiF;AACjF,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,SAAI,GAAG,MAAM,CAWpE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMnD"}
|