@flare-engine/postfx 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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/index.d.mts +317 -0
- package/dist/index.d.ts +317 -0
- package/dist/index.js +1091 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1037 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1037 @@
|
|
|
1
|
+
// src/effect.ts
|
|
2
|
+
function createVignette(id = "vignette", uniforms = {}) {
|
|
3
|
+
return {
|
|
4
|
+
id,
|
|
5
|
+
enabled: true,
|
|
6
|
+
uniforms: {
|
|
7
|
+
kind: "vignette",
|
|
8
|
+
intensity: uniforms.intensity ?? 0.4,
|
|
9
|
+
radius: uniforms.radius ?? 0.8,
|
|
10
|
+
color: uniforms.color ?? [0, 0, 0]
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function createGrayscaleFlash(id = "grayscale-flash", uniforms = {}) {
|
|
15
|
+
return {
|
|
16
|
+
id,
|
|
17
|
+
enabled: true,
|
|
18
|
+
uniforms: { kind: "grayscale-flash", intensity: uniforms.intensity ?? 0 }
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function createChromaticAberration(id = "chromatic-aberration", uniforms = {}) {
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
enabled: true,
|
|
25
|
+
uniforms: { kind: "chromatic-aberration", amount: uniforms.amount ?? 5e-3 }
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function createCrt(id = "crt", uniforms = {}) {
|
|
29
|
+
return {
|
|
30
|
+
id,
|
|
31
|
+
enabled: true,
|
|
32
|
+
uniforms: {
|
|
33
|
+
kind: "crt",
|
|
34
|
+
scanlineIntensity: uniforms.scanlineIntensity ?? 0.25,
|
|
35
|
+
curvature: uniforms.curvature ?? 0.12
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function createBloom(id = "bloom", uniforms = {}) {
|
|
40
|
+
return {
|
|
41
|
+
id,
|
|
42
|
+
enabled: true,
|
|
43
|
+
uniforms: {
|
|
44
|
+
kind: "bloom",
|
|
45
|
+
threshold: uniforms.threshold ?? 0.7,
|
|
46
|
+
intensity: uniforms.intensity ?? 0.6
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function createGodRays(id = "god-rays", uniforms = {}) {
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
enabled: true,
|
|
54
|
+
uniforms: {
|
|
55
|
+
kind: "god-rays",
|
|
56
|
+
lightX: uniforms.lightX ?? 0.5,
|
|
57
|
+
lightY: uniforms.lightY ?? 0.1,
|
|
58
|
+
color: uniforms.color ?? [1, 0.95, 0.8],
|
|
59
|
+
intensity: uniforms.intensity ?? 0.6,
|
|
60
|
+
decay: uniforms.decay ?? 0.96,
|
|
61
|
+
density: uniforms.density ?? 0.5,
|
|
62
|
+
weight: uniforms.weight ?? 0.4,
|
|
63
|
+
samples: uniforms.samples ?? 48
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function createColorGradingLut(uniforms, id = "color-grading-lut") {
|
|
68
|
+
return {
|
|
69
|
+
id,
|
|
70
|
+
enabled: true,
|
|
71
|
+
uniforms: {
|
|
72
|
+
kind: "color-grading-lut",
|
|
73
|
+
lut: uniforms.lut,
|
|
74
|
+
intensity: uniforms.intensity ?? 1
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function createWeatherRain(id = "weather-rain", uniforms = {}) {
|
|
79
|
+
return {
|
|
80
|
+
id,
|
|
81
|
+
enabled: true,
|
|
82
|
+
uniforms: {
|
|
83
|
+
kind: "weather-rain",
|
|
84
|
+
intensity: uniforms.intensity ?? 0.5,
|
|
85
|
+
direction: uniforms.direction ?? 0,
|
|
86
|
+
speed: uniforms.speed ?? 0,
|
|
87
|
+
scrollOffsetY: uniforms.scrollOffsetY ?? 0,
|
|
88
|
+
color: uniforms.color ?? [1, 1, 1],
|
|
89
|
+
density: uniforms.density ?? 0.5,
|
|
90
|
+
streakLen: uniforms.streakLen ?? 0.15
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function createWeatherSunShaft(id = "weather-sun-shaft", uniforms = {}) {
|
|
95
|
+
return {
|
|
96
|
+
id,
|
|
97
|
+
enabled: true,
|
|
98
|
+
uniforms: {
|
|
99
|
+
kind: "weather-sun-shaft",
|
|
100
|
+
intensity: uniforms.intensity ?? 0.5,
|
|
101
|
+
direction: uniforms.direction ?? 0
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function createWeatherDarkVignette(id = "weather-dark-vignette", uniforms = {}) {
|
|
106
|
+
return {
|
|
107
|
+
id,
|
|
108
|
+
enabled: true,
|
|
109
|
+
uniforms: {
|
|
110
|
+
kind: "weather-dark-vignette",
|
|
111
|
+
intensity: uniforms.intensity ?? 0.5
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function createWeatherClouds(id = "weather-clouds", uniforms = {}) {
|
|
116
|
+
return {
|
|
117
|
+
id,
|
|
118
|
+
enabled: true,
|
|
119
|
+
uniforms: {
|
|
120
|
+
kind: "weather-clouds",
|
|
121
|
+
intensity: uniforms.intensity ?? 0.5,
|
|
122
|
+
direction: uniforms.direction ?? 0,
|
|
123
|
+
speed: uniforms.speed ?? 0,
|
|
124
|
+
scrollOffsetX: uniforms.scrollOffsetX ?? 0
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function createPostFxComposite(id = "composite", uniforms = {}) {
|
|
129
|
+
return {
|
|
130
|
+
id,
|
|
131
|
+
enabled: true,
|
|
132
|
+
uniforms: {
|
|
133
|
+
kind: "composite",
|
|
134
|
+
vignetteIntensity: uniforms.vignetteIntensity ?? 0,
|
|
135
|
+
vignetteRadius: uniforms.vignetteRadius ?? 0.8,
|
|
136
|
+
vignetteColor: uniforms.vignetteColor ?? [0, 0, 0],
|
|
137
|
+
bloomThreshold: uniforms.bloomThreshold ?? 0.7,
|
|
138
|
+
bloomIntensity: uniforms.bloomIntensity ?? 0,
|
|
139
|
+
flashIntensity: uniforms.flashIntensity ?? 0,
|
|
140
|
+
chromaAmount: uniforms.chromaAmount ?? 0,
|
|
141
|
+
crtScanlineIntensity: uniforms.crtScanlineIntensity ?? 0.25,
|
|
142
|
+
crtCurvature: uniforms.crtCurvature ?? 0.12,
|
|
143
|
+
crtEnabled: uniforms.crtEnabled ?? false,
|
|
144
|
+
weatherKind: uniforms.weatherKind ?? 0,
|
|
145
|
+
weatherIntensity: uniforms.weatherIntensity ?? 0,
|
|
146
|
+
weatherDirection: uniforms.weatherDirection ?? 0,
|
|
147
|
+
weatherScrollX: uniforms.weatherScrollX ?? 0,
|
|
148
|
+
weatherScrollY: uniforms.weatherScrollY ?? 0,
|
|
149
|
+
weatherColor: uniforms.weatherColor ?? [1, 1, 1],
|
|
150
|
+
weatherDensity: uniforms.weatherDensity ?? 0.5,
|
|
151
|
+
weatherStreakLen: uniforms.weatherStreakLen ?? 0.15
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/chain.ts
|
|
157
|
+
var EffectChain = class {
|
|
158
|
+
_effects = [];
|
|
159
|
+
_globalIntensity = 1;
|
|
160
|
+
get length() {
|
|
161
|
+
return this._effects.length;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Global intensity multiplier applied to every enabled effect's
|
|
165
|
+
* "strength" uniform when the pass is built. Default 1 (no-op).
|
|
166
|
+
*
|
|
167
|
+
* Scales the following uniform fields at pass-build time:
|
|
168
|
+
* - vignette.intensity
|
|
169
|
+
* - grayscale-flash.intensity
|
|
170
|
+
* - chromatic-aberration.amount
|
|
171
|
+
* - crt.scanlineIntensity
|
|
172
|
+
* - bloom.intensity
|
|
173
|
+
*
|
|
174
|
+
* Geometry-like uniforms (radius, curvature, threshold) are not scaled.
|
|
175
|
+
* Intended for a11y reduced-motion (set to 0 to null all dampen-able
|
|
176
|
+
* effects in one call) without touching per-effect state.
|
|
177
|
+
*
|
|
178
|
+
* Clamped to `>= 0`. Negative values are clamped to 0.
|
|
179
|
+
*/
|
|
180
|
+
get globalIntensity() {
|
|
181
|
+
return this._globalIntensity;
|
|
182
|
+
}
|
|
183
|
+
set globalIntensity(value) {
|
|
184
|
+
this._globalIntensity = value < 0 ? 0 : value;
|
|
185
|
+
}
|
|
186
|
+
/** Convenience setter — mirrors the getter/setter. */
|
|
187
|
+
setGlobalIntensity(value) {
|
|
188
|
+
this.globalIntensity = value;
|
|
189
|
+
}
|
|
190
|
+
/** Snapshot view of the chain. Do not mutate. */
|
|
191
|
+
all() {
|
|
192
|
+
return this._effects;
|
|
193
|
+
}
|
|
194
|
+
/** Append an effect to the end of the chain. */
|
|
195
|
+
add(effect) {
|
|
196
|
+
if (this._effects.some((e) => e.id === effect.id)) {
|
|
197
|
+
throw new Error(`EffectChain: duplicate id "${effect.id}"`);
|
|
198
|
+
}
|
|
199
|
+
this._effects.push(effect);
|
|
200
|
+
}
|
|
201
|
+
/** Insert an effect at a specific index (clamped to [0, length]). */
|
|
202
|
+
insert(index, effect) {
|
|
203
|
+
if (this._effects.some((e) => e.id === effect.id)) {
|
|
204
|
+
throw new Error(`EffectChain: duplicate id "${effect.id}"`);
|
|
205
|
+
}
|
|
206
|
+
const i = Math.max(0, Math.min(this._effects.length, index));
|
|
207
|
+
this._effects.splice(i, 0, effect);
|
|
208
|
+
}
|
|
209
|
+
remove(id) {
|
|
210
|
+
const i = this._effects.findIndex((e) => e.id === id);
|
|
211
|
+
if (i === -1) return false;
|
|
212
|
+
this._effects.splice(i, 1);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
get(id) {
|
|
216
|
+
return this._effects.find((e) => e.id === id) ?? null;
|
|
217
|
+
}
|
|
218
|
+
setEnabled(id, enabled) {
|
|
219
|
+
const e = this.get(id);
|
|
220
|
+
if (e) e.enabled = enabled;
|
|
221
|
+
}
|
|
222
|
+
/** Update uniforms in place. Kind must match the existing effect's kind. */
|
|
223
|
+
setUniforms(id, uniforms) {
|
|
224
|
+
const e = this.get(id);
|
|
225
|
+
if (!e) return;
|
|
226
|
+
if (e.uniforms.kind !== uniforms.kind) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`EffectChain: cannot change kind of "${id}" from "${e.uniforms.kind}" to "${uniforms.kind}"`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
e.uniforms = uniforms;
|
|
232
|
+
}
|
|
233
|
+
move(id, newIndex) {
|
|
234
|
+
const i = this._effects.findIndex((e) => e.id === id);
|
|
235
|
+
if (i === -1) return;
|
|
236
|
+
const [removed] = this._effects.splice(i, 1);
|
|
237
|
+
if (!removed) return;
|
|
238
|
+
const clamped = Math.max(0, Math.min(this._effects.length, newIndex));
|
|
239
|
+
this._effects.splice(clamped, 0, removed);
|
|
240
|
+
}
|
|
241
|
+
clear() {
|
|
242
|
+
this._effects.length = 0;
|
|
243
|
+
}
|
|
244
|
+
/** Iterate only over enabled effects, in order. */
|
|
245
|
+
*enabled() {
|
|
246
|
+
for (const e of this._effects) {
|
|
247
|
+
if (e.enabled) yield e;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// src/shaders.ts
|
|
253
|
+
var VIGNETTE_SKSL = (
|
|
254
|
+
/* sksl */
|
|
255
|
+
`
|
|
256
|
+
uniform shader src;
|
|
257
|
+
uniform float2 resolution;
|
|
258
|
+
uniform float intensity;
|
|
259
|
+
uniform float radius;
|
|
260
|
+
uniform float3 color;
|
|
261
|
+
|
|
262
|
+
half4 main(float2 coord) {
|
|
263
|
+
half4 c = src.eval(coord);
|
|
264
|
+
float2 uv = coord / resolution;
|
|
265
|
+
float2 centered = uv - 0.5;
|
|
266
|
+
float d = length(centered);
|
|
267
|
+
float v = smoothstep(radius, radius * 0.5, d);
|
|
268
|
+
return half4(mix(half3(color), c.rgb, mix(1.0 - intensity, 1.0, v)), c.a);
|
|
269
|
+
}
|
|
270
|
+
`
|
|
271
|
+
);
|
|
272
|
+
var GRAYSCALE_FLASH_SKSL = (
|
|
273
|
+
/* sksl */
|
|
274
|
+
`
|
|
275
|
+
uniform shader src;
|
|
276
|
+
uniform float intensity;
|
|
277
|
+
|
|
278
|
+
half4 main(float2 coord) {
|
|
279
|
+
half4 c = src.eval(coord);
|
|
280
|
+
float luma = dot(c.rgb, half3(0.299, 0.587, 0.114));
|
|
281
|
+
half3 gray = half3(luma);
|
|
282
|
+
half3 mixed = mix(c.rgb, gray, half(intensity));
|
|
283
|
+
return half4(mixed, c.a);
|
|
284
|
+
}
|
|
285
|
+
`
|
|
286
|
+
);
|
|
287
|
+
var CHROMATIC_ABERRATION_SKSL = (
|
|
288
|
+
/* sksl */
|
|
289
|
+
`
|
|
290
|
+
uniform shader src;
|
|
291
|
+
uniform float2 resolution;
|
|
292
|
+
uniform float amount;
|
|
293
|
+
|
|
294
|
+
half4 main(float2 coord) {
|
|
295
|
+
float2 uv = coord / resolution;
|
|
296
|
+
float2 offset = (uv - 0.5) * amount;
|
|
297
|
+
half r = src.eval((uv - offset) * resolution).r;
|
|
298
|
+
half g = src.eval(coord).g;
|
|
299
|
+
half b = src.eval((uv + offset) * resolution).b;
|
|
300
|
+
half a = src.eval(coord).a;
|
|
301
|
+
return half4(r, g, b, a);
|
|
302
|
+
}
|
|
303
|
+
`
|
|
304
|
+
);
|
|
305
|
+
var CRT_SKSL = (
|
|
306
|
+
/* sksl */
|
|
307
|
+
`
|
|
308
|
+
uniform shader src;
|
|
309
|
+
uniform float2 resolution;
|
|
310
|
+
uniform float scanlineIntensity;
|
|
311
|
+
uniform float curvature;
|
|
312
|
+
|
|
313
|
+
half4 main(float2 coord) {
|
|
314
|
+
float2 uv = coord / resolution;
|
|
315
|
+
float2 centered = uv - 0.5;
|
|
316
|
+
float2 warp = centered * (1.0 + dot(centered, centered) * curvature);
|
|
317
|
+
float2 warpedUv = warp + 0.5;
|
|
318
|
+
if (warpedUv.x < 0.0 || warpedUv.x > 1.0 || warpedUv.y < 0.0 || warpedUv.y > 1.0) {
|
|
319
|
+
return half4(0.0, 0.0, 0.0, 1.0);
|
|
320
|
+
}
|
|
321
|
+
half4 c = src.eval(warpedUv * resolution);
|
|
322
|
+
float scan = 0.5 + 0.5 * sin(warpedUv.y * resolution.y * 3.14159);
|
|
323
|
+
float dim = mix(1.0, scan, scanlineIntensity);
|
|
324
|
+
return half4(c.rgb * dim, c.a);
|
|
325
|
+
}
|
|
326
|
+
`
|
|
327
|
+
);
|
|
328
|
+
var BLOOM_SKSL = (
|
|
329
|
+
/* sksl */
|
|
330
|
+
`
|
|
331
|
+
uniform shader src;
|
|
332
|
+
uniform float2 resolution;
|
|
333
|
+
uniform float threshold;
|
|
334
|
+
uniform float intensity;
|
|
335
|
+
|
|
336
|
+
half4 main(float2 coord) {
|
|
337
|
+
half4 c = src.eval(coord);
|
|
338
|
+
float luma = dot(c.rgb, half3(0.299, 0.587, 0.114));
|
|
339
|
+
float mask = smoothstep(threshold, 1.0, luma);
|
|
340
|
+
half3 boost = c.rgb * mask * intensity;
|
|
341
|
+
return half4(c.rgb + boost, c.a);
|
|
342
|
+
}
|
|
343
|
+
`
|
|
344
|
+
);
|
|
345
|
+
var COLOR_GRADING_LUT_SKSL = (
|
|
346
|
+
/* sksl */
|
|
347
|
+
`
|
|
348
|
+
uniform shader src;
|
|
349
|
+
uniform shader lut;
|
|
350
|
+
uniform float intensity;
|
|
351
|
+
|
|
352
|
+
half4 main(float2 coord) {
|
|
353
|
+
half4 scene = src.eval(coord);
|
|
354
|
+
|
|
355
|
+
float r = float(scene.r) * 63.0;
|
|
356
|
+
float g = float(scene.g) * 63.0;
|
|
357
|
+
float b = float(scene.b) * 63.0;
|
|
358
|
+
|
|
359
|
+
float bFloor = floor(b);
|
|
360
|
+
float bCeil = min(bFloor + 1.0, 63.0);
|
|
361
|
+
float bFrac = b - bFloor;
|
|
362
|
+
|
|
363
|
+
float2 uvFloor = float2((bFloor * 64.0 + r + 0.5) / 4096.0,
|
|
364
|
+
(g + 0.5) / 64.0);
|
|
365
|
+
float2 uvCeil = float2((bCeil * 64.0 + r + 0.5) / 4096.0,
|
|
366
|
+
(g + 0.5) / 64.0);
|
|
367
|
+
|
|
368
|
+
half3 lutFloor = lut.eval(uvFloor * float2(4096.0, 64.0)).rgb;
|
|
369
|
+
half3 lutCeil = lut.eval(uvCeil * float2(4096.0, 64.0)).rgb;
|
|
370
|
+
|
|
371
|
+
half3 graded = mix(lutFloor, lutCeil, half(bFrac));
|
|
372
|
+
half3 result = mix(scene.rgb, graded, half(intensity));
|
|
373
|
+
|
|
374
|
+
return half4(result, scene.a);
|
|
375
|
+
}
|
|
376
|
+
`
|
|
377
|
+
);
|
|
378
|
+
var WEATHER_RAIN_SKSL = (
|
|
379
|
+
/* sksl */
|
|
380
|
+
`
|
|
381
|
+
uniform shader src;
|
|
382
|
+
uniform float2 resolution;
|
|
383
|
+
uniform float intensity;
|
|
384
|
+
uniform float direction;
|
|
385
|
+
uniform float scrollOffsetY;
|
|
386
|
+
uniform float3 color;
|
|
387
|
+
uniform float density;
|
|
388
|
+
uniform float streakLen;
|
|
389
|
+
|
|
390
|
+
// scrollOffsetY is a normalized fall phase in [0,1). Increase it each tick
|
|
391
|
+
// by (speed * dt / screenHeight) and wrap with fract() on the game side.
|
|
392
|
+
|
|
393
|
+
float hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }
|
|
394
|
+
|
|
395
|
+
half4 main(float2 coord) {
|
|
396
|
+
half4 scene = src.eval(coord);
|
|
397
|
+
float2 uv = coord / resolution;
|
|
398
|
+
float aspect = resolution.x / resolution.y;
|
|
399
|
+
float rad = direction * 3.14159 / 180.0;
|
|
400
|
+
float sinA = sin(rad);
|
|
401
|
+
float cosA = cos(rad);
|
|
402
|
+
// aspect-corrected rotated axes: fallAxis along drop travel, perpAxis across
|
|
403
|
+
float perpAxis = (cosA * uv.x - sinA * uv.y) * aspect;
|
|
404
|
+
float fallAxis = sinA * uv.x + cosA * uv.y;
|
|
405
|
+
|
|
406
|
+
float coverage = 0.0;
|
|
407
|
+
|
|
408
|
+
// near layer \u2014 COLS=45, tiling=6, brightness=1.0
|
|
409
|
+
{
|
|
410
|
+
float COLS = 45.0;
|
|
411
|
+
float tiling = 6.0;
|
|
412
|
+
float brightness = 1.0;
|
|
413
|
+
float cid = floor(perpAxis * COLS);
|
|
414
|
+
float colHash = hash(float2(cid, 0.0));
|
|
415
|
+
if (colHash < density) {
|
|
416
|
+
float phase = hash(float2(cid, 1.0));
|
|
417
|
+
float colBright = brightness * (0.6 + hash(float2(cid, 2.0)) * 0.4);
|
|
418
|
+
float center = 0.5 + (hash(float2(cid, 3.0)) - 0.5) * 0.4;
|
|
419
|
+
float seg = fract(fallAxis * tiling - scrollOffsetY + phase);
|
|
420
|
+
float coverageY = smoothstep(streakLen, 0.0, seg);
|
|
421
|
+
float width = 0.04;
|
|
422
|
+
float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));
|
|
423
|
+
coverage = max(coverage, coverageY * xMask * colBright);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// far layer \u2014 COLS=85, tiling=11, brightness=0.5
|
|
428
|
+
{
|
|
429
|
+
float COLS = 85.0;
|
|
430
|
+
float tiling = 11.0;
|
|
431
|
+
float brightness = 0.5;
|
|
432
|
+
float cid = floor(perpAxis * COLS);
|
|
433
|
+
float colHash = hash(float2(cid, 10.0));
|
|
434
|
+
if (colHash < density) {
|
|
435
|
+
float phase = hash(float2(cid, 11.0));
|
|
436
|
+
float colBright = brightness * (0.6 + hash(float2(cid, 12.0)) * 0.4);
|
|
437
|
+
float center = 0.5 + (hash(float2(cid, 13.0)) - 0.5) * 0.4;
|
|
438
|
+
float seg = fract(fallAxis * tiling - scrollOffsetY + phase);
|
|
439
|
+
float coverageY = smoothstep(streakLen, 0.0, seg);
|
|
440
|
+
float width = 0.03;
|
|
441
|
+
float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));
|
|
442
|
+
coverage = max(coverage, coverageY * xMask * colBright);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
half3 mixed = mix(scene.rgb, half3(color), half(coverage * intensity));
|
|
447
|
+
return half4(mixed, scene.a);
|
|
448
|
+
}
|
|
449
|
+
`
|
|
450
|
+
);
|
|
451
|
+
var WEATHER_SUN_SHAFT_SKSL = (
|
|
452
|
+
/* sksl */
|
|
453
|
+
`
|
|
454
|
+
uniform shader src;
|
|
455
|
+
uniform float2 resolution;
|
|
456
|
+
uniform float intensity;
|
|
457
|
+
uniform float direction;
|
|
458
|
+
|
|
459
|
+
half4 main(float2 coord) {
|
|
460
|
+
half4 scene = src.eval(coord);
|
|
461
|
+
float2 uv = coord / resolution;
|
|
462
|
+
float rad = direction * 3.14159 / 180.0;
|
|
463
|
+
float2 src_point = float2(0.5 + sin(rad) * 0.3, -0.05);
|
|
464
|
+
float2 toFrag = uv - src_point;
|
|
465
|
+
float angle = atan(toFrag.x, toFrag.y);
|
|
466
|
+
float shafts = 0.5 + 0.5 * sin(angle * 8.0);
|
|
467
|
+
float dist = length(toFrag);
|
|
468
|
+
float falloff = 1.0 - smoothstep(0.0, 1.2, dist);
|
|
469
|
+
half shaft = half(shafts * falloff * intensity * 0.35);
|
|
470
|
+
return half4(scene.rgb + half3(shaft * 0.95, shaft * 0.9, shaft * 0.6), scene.a);
|
|
471
|
+
}
|
|
472
|
+
`
|
|
473
|
+
);
|
|
474
|
+
var WEATHER_DARK_VIGNETTE_SKSL = (
|
|
475
|
+
/* sksl */
|
|
476
|
+
`
|
|
477
|
+
uniform shader src;
|
|
478
|
+
uniform float2 resolution;
|
|
479
|
+
uniform float intensity;
|
|
480
|
+
|
|
481
|
+
half4 main(float2 coord) {
|
|
482
|
+
half4 scene = src.eval(coord);
|
|
483
|
+
float2 uv = coord / resolution - 0.5;
|
|
484
|
+
float d = length(uv);
|
|
485
|
+
float v = smoothstep(0.3, 0.85, d);
|
|
486
|
+
half dark = half(v * intensity);
|
|
487
|
+
return half4(scene.rgb * (1.0 - dark), scene.a);
|
|
488
|
+
}
|
|
489
|
+
`
|
|
490
|
+
);
|
|
491
|
+
var WEATHER_CLOUDS_SKSL = (
|
|
492
|
+
/* sksl */
|
|
493
|
+
`
|
|
494
|
+
uniform shader src;
|
|
495
|
+
uniform float2 resolution;
|
|
496
|
+
uniform float intensity;
|
|
497
|
+
uniform float scrollOffsetX;
|
|
498
|
+
|
|
499
|
+
float hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }
|
|
500
|
+
float vnoise(float2 p) {
|
|
501
|
+
float2 i = floor(p); float2 f = fract(p);
|
|
502
|
+
float2 u = f * f * (3.0 - 2.0 * f);
|
|
503
|
+
return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),
|
|
504
|
+
mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);
|
|
505
|
+
}
|
|
506
|
+
float fbm(float2 p) { return vnoise(p) * 0.6 + vnoise(p * 2.3) * 0.4; }
|
|
507
|
+
|
|
508
|
+
half4 main(float2 coord) {
|
|
509
|
+
half4 scene = src.eval(coord);
|
|
510
|
+
float2 uv = coord / resolution;
|
|
511
|
+
float2 cloudUv = float2(uv.x + scrollOffsetX / resolution.x, uv.y * 0.5);
|
|
512
|
+
float cloud = fbm(cloudUv * 3.0);
|
|
513
|
+
float heightFade = smoothstep(0.6, 0.2, uv.y);
|
|
514
|
+
float alpha = cloud * heightFade * intensity * 0.5;
|
|
515
|
+
half3 cloudColor = half3(0.9, 0.9, 0.95);
|
|
516
|
+
return half4(mix(scene.rgb, cloudColor, half(alpha)), scene.a);
|
|
517
|
+
}
|
|
518
|
+
`
|
|
519
|
+
);
|
|
520
|
+
var COMPOSITE_SKSL = (
|
|
521
|
+
/* sksl */
|
|
522
|
+
`
|
|
523
|
+
uniform shader src;
|
|
524
|
+
|
|
525
|
+
uniform float2 resolution;
|
|
526
|
+
uniform float vignetteIntensity;
|
|
527
|
+
uniform float vignetteRadius;
|
|
528
|
+
uniform float3 vignetteColor;
|
|
529
|
+
uniform float bloomThreshold;
|
|
530
|
+
uniform float bloomIntensity;
|
|
531
|
+
uniform float flashIntensity;
|
|
532
|
+
uniform float chromaAmount;
|
|
533
|
+
uniform float crtScanlineIntensity;
|
|
534
|
+
uniform float crtCurvature;
|
|
535
|
+
uniform float crtEnabled;
|
|
536
|
+
uniform float weatherKind;
|
|
537
|
+
uniform float weatherIntensity;
|
|
538
|
+
uniform float weatherDirection;
|
|
539
|
+
uniform float weatherScrollX;
|
|
540
|
+
uniform float weatherScrollY;
|
|
541
|
+
uniform float3 weatherColor;
|
|
542
|
+
uniform float weatherDensity;
|
|
543
|
+
uniform float weatherStreakLen;
|
|
544
|
+
|
|
545
|
+
float hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }
|
|
546
|
+
float vnoise(float2 p) {
|
|
547
|
+
float2 i = floor(p); float2 f = fract(p);
|
|
548
|
+
float2 u = f * f * (3.0 - 2.0 * f);
|
|
549
|
+
return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),
|
|
550
|
+
mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);
|
|
551
|
+
}
|
|
552
|
+
float fbm(float2 p) { return vnoise(p) * 0.6 + vnoise(p * 2.3) * 0.4; }
|
|
553
|
+
|
|
554
|
+
half4 main(float2 coord) {
|
|
555
|
+
// --- chromatic aberration ---
|
|
556
|
+
float2 uv = coord / resolution;
|
|
557
|
+
float2 chromaOffset = (uv - 0.5) * chromaAmount;
|
|
558
|
+
half r = src.eval((uv - chromaOffset) * resolution).r;
|
|
559
|
+
half g = src.eval(coord).g;
|
|
560
|
+
half b = src.eval((uv + chromaOffset) * resolution).b;
|
|
561
|
+
half3 c = half3(r, g, b);
|
|
562
|
+
|
|
563
|
+
// --- CRT ---
|
|
564
|
+
float2 crtCentered = uv - 0.5;
|
|
565
|
+
float2 crtWarp = crtCentered * (1.0 + dot(crtCentered, crtCentered) * crtCurvature);
|
|
566
|
+
float2 crtUv = crtWarp + 0.5;
|
|
567
|
+
float crtMask = float(crtUv.x >= 0.0 && crtUv.x <= 1.0 && crtUv.y >= 0.0 && crtUv.y <= 1.0);
|
|
568
|
+
{
|
|
569
|
+
float2 chromaOffsetCrt = (crtUv - 0.5) * chromaAmount;
|
|
570
|
+
half rCrt = src.eval((crtUv - chromaOffsetCrt) * resolution).r;
|
|
571
|
+
half gCrt = src.eval(crtUv * resolution).g;
|
|
572
|
+
half bCrt = src.eval((crtUv + chromaOffsetCrt) * resolution).b;
|
|
573
|
+
half3 crtColor = half3(rCrt, gCrt, bCrt);
|
|
574
|
+
float scan = 0.5 + 0.5 * sin(crtUv.y * resolution.y * 3.14159);
|
|
575
|
+
float dim = mix(1.0, scan, crtScanlineIntensity);
|
|
576
|
+
half3 crtResult = crtColor * half(dim);
|
|
577
|
+
c = mix(c, mix(half3(0.0), crtResult, half(crtMask)), half(crtEnabled));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// --- bloom ---
|
|
581
|
+
float luma = dot(c, half3(0.299, 0.587, 0.114));
|
|
582
|
+
float bloomMask = smoothstep(bloomThreshold, 1.0, luma);
|
|
583
|
+
c = c + c * half(bloomMask * bloomIntensity);
|
|
584
|
+
|
|
585
|
+
// --- weather ---
|
|
586
|
+
{
|
|
587
|
+
float wk = weatherKind;
|
|
588
|
+
if (wk >= 0.5 && wk < 1.5) {
|
|
589
|
+
// rain \u2014 falling sawtooth streaks, 2 parallax layers, alpha-over blend
|
|
590
|
+
// weatherScrollY is normalized [0,1) fall phase (increment speed*dt/screenH each tick)
|
|
591
|
+
float rad = weatherDirection * 3.14159 / 180.0;
|
|
592
|
+
float sinA = sin(rad);
|
|
593
|
+
float cosA = cos(rad);
|
|
594
|
+
float aspect = resolution.x / resolution.y;
|
|
595
|
+
float perpAxis = (cosA * uv.x - sinA * uv.y) * aspect;
|
|
596
|
+
float fallAxis = sinA * uv.x + cosA * uv.y;
|
|
597
|
+
|
|
598
|
+
float coverage = 0.0;
|
|
599
|
+
|
|
600
|
+
// near layer \u2014 COLS=45, tiling=6, brightness=1.0
|
|
601
|
+
{
|
|
602
|
+
float COLS = 45.0;
|
|
603
|
+
float tiling = 6.0;
|
|
604
|
+
float brightness = 1.0;
|
|
605
|
+
float cid = floor(perpAxis * COLS);
|
|
606
|
+
float colHash = hash(float2(cid, 0.0));
|
|
607
|
+
if (colHash < weatherDensity) {
|
|
608
|
+
float phase = hash(float2(cid, 1.0));
|
|
609
|
+
float colBright = brightness * (0.6 + hash(float2(cid, 2.0)) * 0.4);
|
|
610
|
+
float center = 0.5 + (hash(float2(cid, 3.0)) - 0.5) * 0.4;
|
|
611
|
+
float seg = fract(fallAxis * tiling - weatherScrollY + phase);
|
|
612
|
+
float coverageY = smoothstep(weatherStreakLen, 0.0, seg);
|
|
613
|
+
float width = 0.04;
|
|
614
|
+
float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));
|
|
615
|
+
coverage = max(coverage, coverageY * xMask * colBright);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// far layer \u2014 COLS=85, tiling=11, brightness=0.5
|
|
620
|
+
{
|
|
621
|
+
float COLS = 85.0;
|
|
622
|
+
float tiling = 11.0;
|
|
623
|
+
float brightness = 0.5;
|
|
624
|
+
float cid = floor(perpAxis * COLS);
|
|
625
|
+
float colHash = hash(float2(cid, 10.0));
|
|
626
|
+
if (colHash < weatherDensity) {
|
|
627
|
+
float phase = hash(float2(cid, 11.0));
|
|
628
|
+
float colBright = brightness * (0.6 + hash(float2(cid, 12.0)) * 0.4);
|
|
629
|
+
float center = 0.5 + (hash(float2(cid, 13.0)) - 0.5) * 0.4;
|
|
630
|
+
float seg = fract(fallAxis * tiling - weatherScrollY + phase);
|
|
631
|
+
float coverageY = smoothstep(weatherStreakLen, 0.0, seg);
|
|
632
|
+
float width = 0.03;
|
|
633
|
+
float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));
|
|
634
|
+
coverage = max(coverage, coverageY * xMask * colBright);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
c = mix(c, half3(weatherColor), half(coverage * weatherIntensity));
|
|
639
|
+
} else if (wk >= 1.5 && wk < 2.5) {
|
|
640
|
+
// sun-shaft
|
|
641
|
+
float rad = weatherDirection * 3.14159 / 180.0;
|
|
642
|
+
float2 srcPoint = float2(0.5 + sin(rad) * 0.3, -0.05);
|
|
643
|
+
float2 toFrag = uv - srcPoint;
|
|
644
|
+
float angle = atan(toFrag.x, toFrag.y);
|
|
645
|
+
float shafts = 0.5 + 0.5 * sin(angle * 8.0);
|
|
646
|
+
float dist = length(toFrag);
|
|
647
|
+
float falloff = 1.0 - smoothstep(0.0, 1.2, dist);
|
|
648
|
+
half shaft = half(shafts * falloff * weatherIntensity * 0.35);
|
|
649
|
+
c = c + half3(shaft * 0.95, shaft * 0.9, shaft * 0.6);
|
|
650
|
+
} else if (wk >= 2.5 && wk < 3.5) {
|
|
651
|
+
// dark-vignette
|
|
652
|
+
float2 uvShift = uv - 0.5;
|
|
653
|
+
float d2 = length(uvShift);
|
|
654
|
+
float vd = smoothstep(0.3, 0.85, d2);
|
|
655
|
+
half dark = half(vd * weatherIntensity);
|
|
656
|
+
c = c * (1.0 - dark);
|
|
657
|
+
} else if (wk >= 3.5) {
|
|
658
|
+
// clouds
|
|
659
|
+
float2 cloudUv = float2(uv.x + weatherScrollX / resolution.x, uv.y * 0.5);
|
|
660
|
+
float cloud = fbm(cloudUv * 3.0);
|
|
661
|
+
float heightFade = smoothstep(0.6, 0.2, uv.y);
|
|
662
|
+
float alpha = cloud * heightFade * weatherIntensity * 0.5;
|
|
663
|
+
half3 cloudColor = half3(0.9, 0.9, 0.95);
|
|
664
|
+
c = mix(c, cloudColor, half(alpha));
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// --- grayscale flash ---
|
|
669
|
+
float lumaFlash = dot(c, half3(0.299, 0.587, 0.114));
|
|
670
|
+
half3 gray = half3(lumaFlash);
|
|
671
|
+
c = mix(c, gray, half(flashIntensity));
|
|
672
|
+
|
|
673
|
+
// --- vignette ---
|
|
674
|
+
float2 vigCentered = uv - 0.5;
|
|
675
|
+
float vigD = length(vigCentered);
|
|
676
|
+
float vigV = smoothstep(vignetteRadius, vignetteRadius * 0.5, vigD);
|
|
677
|
+
c = mix(half3(vignetteColor), c, half(mix(1.0 - vignetteIntensity, 1.0, vigV)));
|
|
678
|
+
|
|
679
|
+
return half4(c, 1.0);
|
|
680
|
+
}
|
|
681
|
+
`
|
|
682
|
+
);
|
|
683
|
+
var GOD_RAYS_SKSL = (
|
|
684
|
+
/* sksl */
|
|
685
|
+
`
|
|
686
|
+
uniform shader src;
|
|
687
|
+
uniform float2 resolution;
|
|
688
|
+
uniform float2 u_lightPos;
|
|
689
|
+
uniform float3 u_color;
|
|
690
|
+
uniform float u_intensity;
|
|
691
|
+
uniform float u_decay;
|
|
692
|
+
uniform float u_density;
|
|
693
|
+
uniform float u_weight;
|
|
694
|
+
uniform float u_samples;
|
|
695
|
+
|
|
696
|
+
const int MAX_SAMPLES = 64;
|
|
697
|
+
|
|
698
|
+
half4 main(float2 coord) {
|
|
699
|
+
float2 uv = coord / resolution;
|
|
700
|
+
float2 delta = (u_lightPos - uv) * u_density / float(MAX_SAMPLES);
|
|
701
|
+
float2 sampleUv = uv;
|
|
702
|
+
float illuminationDecay = 1.0;
|
|
703
|
+
half3 accumulated = half3(0.0);
|
|
704
|
+
|
|
705
|
+
for (int i = 0; i < MAX_SAMPLES; i++) {
|
|
706
|
+
if (float(i) >= u_samples) break;
|
|
707
|
+
sampleUv += delta;
|
|
708
|
+
half3 s = src.eval(sampleUv * resolution).rgb;
|
|
709
|
+
accumulated += s * half(illuminationDecay) * half(u_weight);
|
|
710
|
+
illuminationDecay *= u_decay;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
half4 orig = src.eval(coord);
|
|
714
|
+
half3 rays = accumulated * half3(u_color) * half(u_weight) * half(u_intensity);
|
|
715
|
+
return half4(orig.rgb + rays, orig.a);
|
|
716
|
+
}
|
|
717
|
+
`
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
// src/runtime.ts
|
|
721
|
+
var EMPTY_CHILDREN = Object.freeze([]);
|
|
722
|
+
var SKSL_SOURCE = {
|
|
723
|
+
vignette: VIGNETTE_SKSL,
|
|
724
|
+
"grayscale-flash": GRAYSCALE_FLASH_SKSL,
|
|
725
|
+
"chromatic-aberration": CHROMATIC_ABERRATION_SKSL,
|
|
726
|
+
crt: CRT_SKSL,
|
|
727
|
+
bloom: BLOOM_SKSL,
|
|
728
|
+
"god-rays": GOD_RAYS_SKSL,
|
|
729
|
+
"color-grading-lut": COLOR_GRADING_LUT_SKSL,
|
|
730
|
+
"weather-rain": WEATHER_RAIN_SKSL,
|
|
731
|
+
"weather-sun-shaft": WEATHER_SUN_SHAFT_SKSL,
|
|
732
|
+
"weather-dark-vignette": WEATHER_DARK_VIGNETTE_SKSL,
|
|
733
|
+
"weather-clouds": WEATHER_CLOUDS_SKSL,
|
|
734
|
+
composite: COMPOSITE_SKSL
|
|
735
|
+
};
|
|
736
|
+
function createPostFxRuntime(skia) {
|
|
737
|
+
const cache = /* @__PURE__ */ new Map();
|
|
738
|
+
const _loggedCompileFailures = /* @__PURE__ */ new Set();
|
|
739
|
+
const _loggedCompileSuccesses = /* @__PURE__ */ new Set();
|
|
740
|
+
const supported = skia !== null && typeof skia.RuntimeEffect?.Make === "function";
|
|
741
|
+
return {
|
|
742
|
+
supported,
|
|
743
|
+
compile(kind) {
|
|
744
|
+
if (!supported || !skia) return null;
|
|
745
|
+
const cached = cache.get(kind);
|
|
746
|
+
if (cached) return cached;
|
|
747
|
+
const effect = skia.RuntimeEffect.Make(SKSL_SOURCE[kind]);
|
|
748
|
+
if (!effect) {
|
|
749
|
+
if (!_loggedCompileFailures.has(kind)) {
|
|
750
|
+
_loggedCompileFailures.add(kind);
|
|
751
|
+
console.warn(
|
|
752
|
+
`[postfx] RuntimeEffect.Make returned null for "${kind}" \u2014 shader failed to compile. Pass will be silently skipped.`
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
if (!_loggedCompileSuccesses.has(kind)) {
|
|
758
|
+
_loggedCompileSuccesses.add(kind);
|
|
759
|
+
if (typeof effect.getUniformFloatCount === "function") {
|
|
760
|
+
console.info(
|
|
761
|
+
`[postfx] compiled "${kind}" \u2014 getUniformFloatCount=${effect.getUniformFloatCount()}`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const compiled = { kind, effect };
|
|
766
|
+
cache.set(kind, compiled);
|
|
767
|
+
return compiled;
|
|
768
|
+
},
|
|
769
|
+
packUniforms(effect, width, height) {
|
|
770
|
+
return packUniforms(effect.uniforms, width, height);
|
|
771
|
+
},
|
|
772
|
+
buildPass(chain, width, height) {
|
|
773
|
+
const out = [];
|
|
774
|
+
if (!supported) return out;
|
|
775
|
+
const scale = chain.globalIntensity;
|
|
776
|
+
for (const effect of chain.enabled()) {
|
|
777
|
+
const compiled = this.compile(effect.uniforms.kind);
|
|
778
|
+
if (!compiled) continue;
|
|
779
|
+
const uniforms = packUniforms(effect.uniforms, width, height);
|
|
780
|
+
if (scale !== 1) scaleIntensity(effect.uniforms.kind, uniforms, scale);
|
|
781
|
+
const children = effect.uniforms.kind === "color-grading-lut" ? [effect.uniforms.lut] : EMPTY_CHILDREN;
|
|
782
|
+
out.push({
|
|
783
|
+
id: effect.id,
|
|
784
|
+
kind: effect.uniforms.kind,
|
|
785
|
+
shader: compiled.effect,
|
|
786
|
+
uniforms,
|
|
787
|
+
children
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
return out;
|
|
791
|
+
},
|
|
792
|
+
reset() {
|
|
793
|
+
cache.clear();
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function packUniforms(u, width, height) {
|
|
798
|
+
switch (u.kind) {
|
|
799
|
+
case "vignette": {
|
|
800
|
+
const col = u.color ?? [0, 0, 0];
|
|
801
|
+
return new Float32Array([width, height, u.intensity, u.radius, col[0], col[1], col[2]]);
|
|
802
|
+
}
|
|
803
|
+
case "grayscale-flash":
|
|
804
|
+
return new Float32Array([u.intensity]);
|
|
805
|
+
case "chromatic-aberration":
|
|
806
|
+
return new Float32Array([width, height, u.amount]);
|
|
807
|
+
case "crt":
|
|
808
|
+
return new Float32Array([width, height, u.scanlineIntensity, u.curvature]);
|
|
809
|
+
case "bloom":
|
|
810
|
+
return new Float32Array([width, height, u.threshold, u.intensity]);
|
|
811
|
+
case "god-rays": {
|
|
812
|
+
const samples = Math.max(1, Math.min(64, Math.round(u.samples)));
|
|
813
|
+
return new Float32Array([
|
|
814
|
+
width,
|
|
815
|
+
height,
|
|
816
|
+
u.lightX,
|
|
817
|
+
u.lightY,
|
|
818
|
+
u.color[0],
|
|
819
|
+
u.color[1],
|
|
820
|
+
u.color[2],
|
|
821
|
+
u.intensity,
|
|
822
|
+
u.decay,
|
|
823
|
+
u.density,
|
|
824
|
+
u.weight,
|
|
825
|
+
samples
|
|
826
|
+
]);
|
|
827
|
+
}
|
|
828
|
+
case "color-grading-lut":
|
|
829
|
+
return new Float32Array([u.intensity]);
|
|
830
|
+
// Weather kinds: TS interfaces are wider than the packed SkSL inputs (speed/direction
|
|
831
|
+
// for clouds, speed for rain are B5-passthrough fields; the shader doesn't consume them).
|
|
832
|
+
case "weather-rain": {
|
|
833
|
+
const col = u.color ?? [1, 1, 1];
|
|
834
|
+
return new Float32Array([
|
|
835
|
+
width,
|
|
836
|
+
height,
|
|
837
|
+
u.intensity,
|
|
838
|
+
u.direction,
|
|
839
|
+
u.scrollOffsetY,
|
|
840
|
+
col[0],
|
|
841
|
+
col[1],
|
|
842
|
+
col[2],
|
|
843
|
+
u.density ?? 0.5,
|
|
844
|
+
u.streakLen ?? 0.15
|
|
845
|
+
]);
|
|
846
|
+
}
|
|
847
|
+
case "weather-sun-shaft":
|
|
848
|
+
return new Float32Array([width, height, u.intensity, u.direction]);
|
|
849
|
+
case "weather-dark-vignette":
|
|
850
|
+
return new Float32Array([width, height, u.intensity]);
|
|
851
|
+
case "weather-clouds":
|
|
852
|
+
return new Float32Array([width, height, u.intensity, u.scrollOffsetX]);
|
|
853
|
+
case "composite":
|
|
854
|
+
return new Float32Array([
|
|
855
|
+
width,
|
|
856
|
+
height,
|
|
857
|
+
u.vignetteIntensity,
|
|
858
|
+
u.vignetteRadius,
|
|
859
|
+
u.vignetteColor[0],
|
|
860
|
+
u.vignetteColor[1],
|
|
861
|
+
u.vignetteColor[2],
|
|
862
|
+
u.bloomThreshold,
|
|
863
|
+
u.bloomIntensity,
|
|
864
|
+
u.flashIntensity,
|
|
865
|
+
u.chromaAmount,
|
|
866
|
+
u.crtScanlineIntensity,
|
|
867
|
+
u.crtCurvature,
|
|
868
|
+
u.crtEnabled ? 1 : 0,
|
|
869
|
+
u.weatherKind,
|
|
870
|
+
u.weatherIntensity,
|
|
871
|
+
u.weatherDirection,
|
|
872
|
+
u.weatherScrollX,
|
|
873
|
+
u.weatherScrollY,
|
|
874
|
+
u.weatherColor[0],
|
|
875
|
+
u.weatherColor[1],
|
|
876
|
+
u.weatherColor[2],
|
|
877
|
+
u.weatherDensity,
|
|
878
|
+
u.weatherStreakLen
|
|
879
|
+
]);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function scaleIntensity(kind, packed, scale) {
|
|
883
|
+
switch (kind) {
|
|
884
|
+
case "vignette":
|
|
885
|
+
packed[2] = (packed[2] ?? 0) * scale;
|
|
886
|
+
return;
|
|
887
|
+
case "grayscale-flash":
|
|
888
|
+
packed[0] = (packed[0] ?? 0) * scale;
|
|
889
|
+
return;
|
|
890
|
+
case "chromatic-aberration":
|
|
891
|
+
packed[2] = (packed[2] ?? 0) * scale;
|
|
892
|
+
return;
|
|
893
|
+
case "crt":
|
|
894
|
+
packed[2] = (packed[2] ?? 0) * scale;
|
|
895
|
+
return;
|
|
896
|
+
case "bloom":
|
|
897
|
+
packed[3] = (packed[3] ?? 0) * scale;
|
|
898
|
+
return;
|
|
899
|
+
case "god-rays":
|
|
900
|
+
packed[7] = (packed[7] ?? 0) * scale;
|
|
901
|
+
return;
|
|
902
|
+
case "color-grading-lut":
|
|
903
|
+
packed[0] = (packed[0] ?? 0) * scale;
|
|
904
|
+
return;
|
|
905
|
+
case "weather-rain":
|
|
906
|
+
case "weather-sun-shaft":
|
|
907
|
+
case "weather-dark-vignette":
|
|
908
|
+
case "weather-clouds":
|
|
909
|
+
packed[2] = (packed[2] ?? 0) * scale;
|
|
910
|
+
return;
|
|
911
|
+
case "composite":
|
|
912
|
+
packed[2] = (packed[2] ?? 0) * scale;
|
|
913
|
+
packed[8] = (packed[8] ?? 0) * scale;
|
|
914
|
+
packed[9] = (packed[9] ?? 0) * scale;
|
|
915
|
+
packed[10] = (packed[10] ?? 0) * scale;
|
|
916
|
+
packed[15] = (packed[15] ?? 0) * scale;
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/lut.ts
|
|
922
|
+
function createNeutralLutBytes() {
|
|
923
|
+
const buf = new Uint8Array(4096 * 64 * 4);
|
|
924
|
+
for (let b = 0; b < 64; b++) {
|
|
925
|
+
for (let g = 0; g < 64; g++) {
|
|
926
|
+
for (let r = 0; r < 64; r++) {
|
|
927
|
+
const x = b * 64 + r;
|
|
928
|
+
const y = g;
|
|
929
|
+
const off = (y * 4096 + x) * 4;
|
|
930
|
+
buf[off] = Math.round(r / 63 * 255);
|
|
931
|
+
buf[off + 1] = Math.round(g / 63 * 255);
|
|
932
|
+
buf[off + 2] = Math.round(b / 63 * 255);
|
|
933
|
+
buf[off + 3] = 255;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return buf;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// src/controller.ts
|
|
941
|
+
var DEFAULT_FLASH_DECAY = 4.2;
|
|
942
|
+
function createPostFxController(options) {
|
|
943
|
+
const chain = new EffectChain();
|
|
944
|
+
for (let i = 0; i < options.effects.length; i++) {
|
|
945
|
+
const e = options.effects[i];
|
|
946
|
+
if (e) chain.add(e);
|
|
947
|
+
}
|
|
948
|
+
const runtime = createPostFxRuntime(options.skia ?? null);
|
|
949
|
+
const flashCfg = options.flash;
|
|
950
|
+
const flashDecay = flashCfg?.decay ?? DEFAULT_FLASH_DECAY;
|
|
951
|
+
let flashIntensity = 0;
|
|
952
|
+
const timedRemaining = /* @__PURE__ */ new Map();
|
|
953
|
+
const timedRestore = /* @__PURE__ */ new Map();
|
|
954
|
+
return {
|
|
955
|
+
chain,
|
|
956
|
+
runtime,
|
|
957
|
+
triggerHitFlash() {
|
|
958
|
+
if (!flashCfg) return;
|
|
959
|
+
flashIntensity = 1;
|
|
960
|
+
chain.setUniforms(flashCfg.id, { kind: "grayscale-flash", intensity: 1 });
|
|
961
|
+
},
|
|
962
|
+
triggerTimed(effectId, decayMs) {
|
|
963
|
+
const effect = chain.get(effectId);
|
|
964
|
+
if (!effect) return;
|
|
965
|
+
timedRestore.set(effectId, { ...effect.uniforms });
|
|
966
|
+
chain.setEnabled(effectId, true);
|
|
967
|
+
timedRemaining.set(effectId, decayMs);
|
|
968
|
+
},
|
|
969
|
+
update(dt) {
|
|
970
|
+
if (flashCfg && flashIntensity > 0) {
|
|
971
|
+
flashIntensity = Math.max(0, flashIntensity - dt * flashDecay);
|
|
972
|
+
chain.setUniforms(flashCfg.id, {
|
|
973
|
+
kind: "grayscale-flash",
|
|
974
|
+
intensity: flashIntensity
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
if (timedRemaining.size > 0) {
|
|
978
|
+
const dtMs = dt * 1e3;
|
|
979
|
+
const expired = [];
|
|
980
|
+
for (const [id, rem] of timedRemaining) {
|
|
981
|
+
const next = rem - dtMs;
|
|
982
|
+
if (next <= 0) {
|
|
983
|
+
expired.push(id);
|
|
984
|
+
} else {
|
|
985
|
+
timedRemaining.set(id, next);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (expired.length > 0) {
|
|
989
|
+
for (const id of expired) {
|
|
990
|
+
const snapshot = timedRestore.get(id);
|
|
991
|
+
if (snapshot !== void 0) chain.setUniforms(id, snapshot);
|
|
992
|
+
chain.setEnabled(id, false);
|
|
993
|
+
timedRemaining.delete(id);
|
|
994
|
+
timedRestore.delete(id);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
buildPass(width, height) {
|
|
1000
|
+
return runtime.buildPass(chain, width, height);
|
|
1001
|
+
},
|
|
1002
|
+
setMotionScale(k) {
|
|
1003
|
+
chain.setGlobalIntensity(k);
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
export {
|
|
1008
|
+
BLOOM_SKSL,
|
|
1009
|
+
CHROMATIC_ABERRATION_SKSL,
|
|
1010
|
+
COLOR_GRADING_LUT_SKSL,
|
|
1011
|
+
COMPOSITE_SKSL,
|
|
1012
|
+
CRT_SKSL,
|
|
1013
|
+
EffectChain,
|
|
1014
|
+
GOD_RAYS_SKSL,
|
|
1015
|
+
GRAYSCALE_FLASH_SKSL,
|
|
1016
|
+
VIGNETTE_SKSL,
|
|
1017
|
+
WEATHER_CLOUDS_SKSL,
|
|
1018
|
+
WEATHER_DARK_VIGNETTE_SKSL,
|
|
1019
|
+
WEATHER_RAIN_SKSL,
|
|
1020
|
+
WEATHER_SUN_SHAFT_SKSL,
|
|
1021
|
+
createBloom,
|
|
1022
|
+
createChromaticAberration,
|
|
1023
|
+
createColorGradingLut,
|
|
1024
|
+
createCrt,
|
|
1025
|
+
createGodRays,
|
|
1026
|
+
createGrayscaleFlash,
|
|
1027
|
+
createNeutralLutBytes,
|
|
1028
|
+
createPostFxComposite,
|
|
1029
|
+
createPostFxController,
|
|
1030
|
+
createPostFxRuntime,
|
|
1031
|
+
createVignette,
|
|
1032
|
+
createWeatherClouds,
|
|
1033
|
+
createWeatherDarkVignette,
|
|
1034
|
+
createWeatherRain,
|
|
1035
|
+
createWeatherSunShaft
|
|
1036
|
+
};
|
|
1037
|
+
//# sourceMappingURL=index.mjs.map
|