@flare-engine/postfx 0.2.0 → 0.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/LICENSE +201 -21
- package/NOTICE +2 -0
- package/README.md +15 -5
- package/dist/index.d.mts +206 -216
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +206 -216
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +669 -679
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +641 -625
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -8
package/dist/index.mjs
CHANGED
|
@@ -1,258 +1,105 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
1
|
+
//#region src/chain.ts
|
|
2
|
+
/**
|
|
3
|
+
* Ordered post-processing pipeline. Pure data structure — the runtime reads
|
|
4
|
+
* the enabled entries in order and feeds each through its compiled shader.
|
|
5
|
+
*/
|
|
157
6
|
var EffectChain = class {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
}
|
|
7
|
+
_effects = [];
|
|
8
|
+
_globalIntensity = 1;
|
|
9
|
+
get length() {
|
|
10
|
+
return this._effects.length;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Global intensity multiplier applied to every enabled effect's
|
|
14
|
+
* "strength" uniform when the pass is built. Default 1 (no-op).
|
|
15
|
+
*
|
|
16
|
+
* Scales the following uniform fields at pass-build time:
|
|
17
|
+
* - vignette.intensity
|
|
18
|
+
* - grayscale-flash.intensity
|
|
19
|
+
* - chromatic-aberration.amount
|
|
20
|
+
* - crt.scanlineIntensity
|
|
21
|
+
* - bloom.intensity
|
|
22
|
+
*
|
|
23
|
+
* Geometry-like uniforms (radius, curvature, threshold) are not scaled.
|
|
24
|
+
* Intended for a11y reduced-motion (set to 0 to null all dampen-able
|
|
25
|
+
* effects in one call) without touching per-effect state.
|
|
26
|
+
*
|
|
27
|
+
* Clamped to `>= 0`. Negative values are clamped to 0.
|
|
28
|
+
*/
|
|
29
|
+
get globalIntensity() {
|
|
30
|
+
return this._globalIntensity;
|
|
31
|
+
}
|
|
32
|
+
set globalIntensity(value) {
|
|
33
|
+
this._globalIntensity = value < 0 ? 0 : value;
|
|
34
|
+
}
|
|
35
|
+
/** Convenience setter — mirrors the getter/setter. */
|
|
36
|
+
setGlobalIntensity(value) {
|
|
37
|
+
this.globalIntensity = value;
|
|
38
|
+
}
|
|
39
|
+
/** Snapshot view of the chain. Do not mutate. */
|
|
40
|
+
all() {
|
|
41
|
+
return this._effects;
|
|
42
|
+
}
|
|
43
|
+
/** Append an effect to the end of the chain. */
|
|
44
|
+
add(effect) {
|
|
45
|
+
if (this._effects.some((e) => e.id === effect.id)) throw new Error(`EffectChain: duplicate id "${effect.id}"`);
|
|
46
|
+
this._effects.push(effect);
|
|
47
|
+
}
|
|
48
|
+
/** Insert an effect at a specific index (clamped to [0, length]). */
|
|
49
|
+
insert(index, effect) {
|
|
50
|
+
if (this._effects.some((e) => e.id === effect.id)) throw new Error(`EffectChain: duplicate id "${effect.id}"`);
|
|
51
|
+
const i = Math.max(0, Math.min(this._effects.length, index));
|
|
52
|
+
this._effects.splice(i, 0, effect);
|
|
53
|
+
}
|
|
54
|
+
remove(id) {
|
|
55
|
+
const i = this._effects.findIndex((e) => e.id === id);
|
|
56
|
+
if (i === -1) return false;
|
|
57
|
+
this._effects.splice(i, 1);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
get(id) {
|
|
61
|
+
return this._effects.find((e) => e.id === id) ?? null;
|
|
62
|
+
}
|
|
63
|
+
setEnabled(id, enabled) {
|
|
64
|
+
const e = this.get(id);
|
|
65
|
+
if (e) e.enabled = enabled;
|
|
66
|
+
}
|
|
67
|
+
/** Update uniforms in place. Kind must match the existing effect's kind. */
|
|
68
|
+
setUniforms(id, uniforms) {
|
|
69
|
+
const e = this.get(id);
|
|
70
|
+
if (!e) return;
|
|
71
|
+
if (e.uniforms.kind !== uniforms.kind) throw new Error(`EffectChain: cannot change kind of "${id}" from "${e.uniforms.kind}" to "${uniforms.kind}"`);
|
|
72
|
+
e.uniforms = uniforms;
|
|
73
|
+
}
|
|
74
|
+
move(id, newIndex) {
|
|
75
|
+
const i = this._effects.findIndex((e) => e.id === id);
|
|
76
|
+
if (i === -1) return;
|
|
77
|
+
const [removed] = this._effects.splice(i, 1);
|
|
78
|
+
if (!removed) return;
|
|
79
|
+
const clamped = Math.max(0, Math.min(this._effects.length, newIndex));
|
|
80
|
+
this._effects.splice(clamped, 0, removed);
|
|
81
|
+
}
|
|
82
|
+
clear() {
|
|
83
|
+
this._effects.length = 0;
|
|
84
|
+
}
|
|
85
|
+
/** Iterate only over enabled effects, in order. */
|
|
86
|
+
*enabled() {
|
|
87
|
+
for (const e of this._effects) if (e.enabled) yield e;
|
|
88
|
+
}
|
|
250
89
|
};
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/shaders.ts
|
|
92
|
+
/**
|
|
93
|
+
* SkSL runtime-effect shaders used by the postfx stack.
|
|
94
|
+
*
|
|
95
|
+
* Each source declares a `uniform shader src;` plus per-effect uniforms. They
|
|
96
|
+
* are sampled as `src.eval(fragCoord)` against the scene's offscreen surface.
|
|
97
|
+
*
|
|
98
|
+
* Shader strings are emitted from the package as-is. Fallback: when
|
|
99
|
+
* `Skia.RuntimeEffect.Make` is unavailable, `runtime.ts` short-circuits the
|
|
100
|
+
* chain to a passthrough.
|
|
101
|
+
*/
|
|
102
|
+
const VIGNETTE_SKSL = `
|
|
256
103
|
uniform shader src;
|
|
257
104
|
uniform float2 resolution;
|
|
258
105
|
uniform float intensity;
|
|
@@ -267,11 +114,8 @@ half4 main(float2 coord) {
|
|
|
267
114
|
float v = smoothstep(radius, radius * 0.5, d);
|
|
268
115
|
return half4(mix(half3(color), c.rgb, mix(1.0 - intensity, 1.0, v)), c.a);
|
|
269
116
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
var GRAYSCALE_FLASH_SKSL = (
|
|
273
|
-
/* sksl */
|
|
274
|
-
`
|
|
117
|
+
`;
|
|
118
|
+
const GRAYSCALE_FLASH_SKSL = `
|
|
275
119
|
uniform shader src;
|
|
276
120
|
uniform float intensity;
|
|
277
121
|
|
|
@@ -282,11 +126,8 @@ half4 main(float2 coord) {
|
|
|
282
126
|
half3 mixed = mix(c.rgb, gray, half(intensity));
|
|
283
127
|
return half4(mixed, c.a);
|
|
284
128
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
var CHROMATIC_ABERRATION_SKSL = (
|
|
288
|
-
/* sksl */
|
|
289
|
-
`
|
|
129
|
+
`;
|
|
130
|
+
const CHROMATIC_ABERRATION_SKSL = `
|
|
290
131
|
uniform shader src;
|
|
291
132
|
uniform float2 resolution;
|
|
292
133
|
uniform float amount;
|
|
@@ -300,11 +141,8 @@ half4 main(float2 coord) {
|
|
|
300
141
|
half a = src.eval(coord).a;
|
|
301
142
|
return half4(r, g, b, a);
|
|
302
143
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
var CRT_SKSL = (
|
|
306
|
-
/* sksl */
|
|
307
|
-
`
|
|
144
|
+
`;
|
|
145
|
+
const CRT_SKSL = `
|
|
308
146
|
uniform shader src;
|
|
309
147
|
uniform float2 resolution;
|
|
310
148
|
uniform float scanlineIntensity;
|
|
@@ -323,11 +161,8 @@ half4 main(float2 coord) {
|
|
|
323
161
|
float dim = mix(1.0, scan, scanlineIntensity);
|
|
324
162
|
return half4(c.rgb * dim, c.a);
|
|
325
163
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
var BLOOM_SKSL = (
|
|
329
|
-
/* sksl */
|
|
330
|
-
`
|
|
164
|
+
`;
|
|
165
|
+
const BLOOM_SKSL = `
|
|
331
166
|
uniform shader src;
|
|
332
167
|
uniform float2 resolution;
|
|
333
168
|
uniform float threshold;
|
|
@@ -340,11 +175,8 @@ half4 main(float2 coord) {
|
|
|
340
175
|
half3 boost = c.rgb * mask * intensity;
|
|
341
176
|
return half4(c.rgb + boost, c.a);
|
|
342
177
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
var COLOR_GRADING_LUT_SKSL = (
|
|
346
|
-
/* sksl */
|
|
347
|
-
`
|
|
178
|
+
`;
|
|
179
|
+
const COLOR_GRADING_LUT_SKSL = `
|
|
348
180
|
uniform shader src;
|
|
349
181
|
uniform shader lut;
|
|
350
182
|
uniform float intensity;
|
|
@@ -373,11 +205,8 @@ half4 main(float2 coord) {
|
|
|
373
205
|
|
|
374
206
|
return half4(result, scene.a);
|
|
375
207
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
var WEATHER_RAIN_SKSL = (
|
|
379
|
-
/* sksl */
|
|
380
|
-
`
|
|
208
|
+
`;
|
|
209
|
+
const WEATHER_RAIN_SKSL = `
|
|
381
210
|
uniform shader src;
|
|
382
211
|
uniform float2 resolution;
|
|
383
212
|
uniform float intensity;
|
|
@@ -405,7 +234,7 @@ half4 main(float2 coord) {
|
|
|
405
234
|
|
|
406
235
|
float coverage = 0.0;
|
|
407
236
|
|
|
408
|
-
// near layer
|
|
237
|
+
// near layer — COLS=45, tiling=6, brightness=1.0
|
|
409
238
|
{
|
|
410
239
|
float COLS = 45.0;
|
|
411
240
|
float tiling = 6.0;
|
|
@@ -424,7 +253,7 @@ half4 main(float2 coord) {
|
|
|
424
253
|
}
|
|
425
254
|
}
|
|
426
255
|
|
|
427
|
-
// far layer
|
|
256
|
+
// far layer — COLS=85, tiling=11, brightness=0.5
|
|
428
257
|
{
|
|
429
258
|
float COLS = 85.0;
|
|
430
259
|
float tiling = 11.0;
|
|
@@ -446,11 +275,8 @@ half4 main(float2 coord) {
|
|
|
446
275
|
half3 mixed = mix(scene.rgb, half3(color), half(coverage * intensity));
|
|
447
276
|
return half4(mixed, scene.a);
|
|
448
277
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
var WEATHER_SUN_SHAFT_SKSL = (
|
|
452
|
-
/* sksl */
|
|
453
|
-
`
|
|
278
|
+
`;
|
|
279
|
+
const WEATHER_SUN_SHAFT_SKSL = `
|
|
454
280
|
uniform shader src;
|
|
455
281
|
uniform float2 resolution;
|
|
456
282
|
uniform float intensity;
|
|
@@ -469,11 +295,8 @@ half4 main(float2 coord) {
|
|
|
469
295
|
half shaft = half(shafts * falloff * intensity * 0.35);
|
|
470
296
|
return half4(scene.rgb + half3(shaft * 0.95, shaft * 0.9, shaft * 0.6), scene.a);
|
|
471
297
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
var WEATHER_DARK_VIGNETTE_SKSL = (
|
|
475
|
-
/* sksl */
|
|
476
|
-
`
|
|
298
|
+
`;
|
|
299
|
+
const WEATHER_DARK_VIGNETTE_SKSL = `
|
|
477
300
|
uniform shader src;
|
|
478
301
|
uniform float2 resolution;
|
|
479
302
|
uniform float intensity;
|
|
@@ -486,15 +309,13 @@ half4 main(float2 coord) {
|
|
|
486
309
|
half dark = half(v * intensity);
|
|
487
310
|
return half4(scene.rgb * (1.0 - dark), scene.a);
|
|
488
311
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
var WEATHER_CLOUDS_SKSL = (
|
|
492
|
-
/* sksl */
|
|
493
|
-
`
|
|
312
|
+
`;
|
|
313
|
+
const WEATHER_CLOUDS_SKSL = `
|
|
494
314
|
uniform shader src;
|
|
495
315
|
uniform float2 resolution;
|
|
496
316
|
uniform float intensity;
|
|
497
317
|
uniform float scrollOffsetX;
|
|
318
|
+
uniform float direction;
|
|
498
319
|
|
|
499
320
|
float hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }
|
|
500
321
|
float vnoise(float2 p) {
|
|
@@ -508,18 +329,21 @@ float fbm(float2 p) { return vnoise(p) * 0.6 + vnoise(p * 2.3) * 0.4; }
|
|
|
508
329
|
half4 main(float2 coord) {
|
|
509
330
|
half4 scene = src.eval(coord);
|
|
510
331
|
float2 uv = coord / resolution;
|
|
511
|
-
|
|
332
|
+
// direction=0 → pure horizontal scroll (cos=1, sin=0), matching the pre-direction default.
|
|
333
|
+
float rad = direction * 3.14159 / 180.0;
|
|
334
|
+
float2 scrollDir = float2(cos(rad), sin(rad));
|
|
335
|
+
float2 cloudUv = float2(
|
|
336
|
+
uv.x + scrollOffsetX * scrollDir.x / resolution.x,
|
|
337
|
+
uv.y * 0.5 + scrollOffsetX * scrollDir.y / resolution.y
|
|
338
|
+
);
|
|
512
339
|
float cloud = fbm(cloudUv * 3.0);
|
|
513
340
|
float heightFade = smoothstep(0.6, 0.2, uv.y);
|
|
514
341
|
float alpha = cloud * heightFade * intensity * 0.5;
|
|
515
342
|
half3 cloudColor = half3(0.9, 0.9, 0.95);
|
|
516
343
|
return half4(mix(scene.rgb, cloudColor, half(alpha)), scene.a);
|
|
517
344
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
var COMPOSITE_SKSL = (
|
|
521
|
-
/* sksl */
|
|
522
|
-
`
|
|
345
|
+
`;
|
|
346
|
+
const COMPOSITE_SKSL = `
|
|
523
347
|
uniform shader src;
|
|
524
348
|
|
|
525
349
|
uniform float2 resolution;
|
|
@@ -586,7 +410,7 @@ half4 main(float2 coord) {
|
|
|
586
410
|
{
|
|
587
411
|
float wk = weatherKind;
|
|
588
412
|
if (wk >= 0.5 && wk < 1.5) {
|
|
589
|
-
// rain
|
|
413
|
+
// rain — falling sawtooth streaks, 2 parallax layers, alpha-over blend
|
|
590
414
|
// weatherScrollY is normalized [0,1) fall phase (increment speed*dt/screenH each tick)
|
|
591
415
|
float rad = weatherDirection * 3.14159 / 180.0;
|
|
592
416
|
float sinA = sin(rad);
|
|
@@ -597,7 +421,7 @@ half4 main(float2 coord) {
|
|
|
597
421
|
|
|
598
422
|
float coverage = 0.0;
|
|
599
423
|
|
|
600
|
-
// near layer
|
|
424
|
+
// near layer — COLS=45, tiling=6, brightness=1.0
|
|
601
425
|
{
|
|
602
426
|
float COLS = 45.0;
|
|
603
427
|
float tiling = 6.0;
|
|
@@ -616,7 +440,7 @@ half4 main(float2 coord) {
|
|
|
616
440
|
}
|
|
617
441
|
}
|
|
618
442
|
|
|
619
|
-
// far layer
|
|
443
|
+
// far layer — COLS=85, tiling=11, brightness=0.5
|
|
620
444
|
{
|
|
621
445
|
float COLS = 85.0;
|
|
622
446
|
float tiling = 11.0;
|
|
@@ -678,11 +502,8 @@ half4 main(float2 coord) {
|
|
|
678
502
|
|
|
679
503
|
return half4(c, 1.0);
|
|
680
504
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
var GOD_RAYS_SKSL = (
|
|
684
|
-
/* sksl */
|
|
685
|
-
`
|
|
505
|
+
`;
|
|
506
|
+
const GOD_RAYS_SKSL = `
|
|
686
507
|
uniform shader src;
|
|
687
508
|
uniform float2 resolution;
|
|
688
509
|
uniform float2 u_lightPos;
|
|
@@ -714,324 +535,519 @@ half4 main(float2 coord) {
|
|
|
714
535
|
half3 rays = accumulated * half3(u_color) * half(u_weight) * half(u_intensity);
|
|
715
536
|
return half4(orig.rgb + rays, orig.a);
|
|
716
537
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
538
|
+
`;
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region src/runtime.ts
|
|
541
|
+
/** Shared frozen sentinel — every pass with no extra children points here. */
|
|
542
|
+
const EMPTY_CHILDREN = Object.freeze([]);
|
|
543
|
+
const SKSL_SOURCE = {
|
|
544
|
+
vignette: VIGNETTE_SKSL,
|
|
545
|
+
"grayscale-flash": GRAYSCALE_FLASH_SKSL,
|
|
546
|
+
"chromatic-aberration": CHROMATIC_ABERRATION_SKSL,
|
|
547
|
+
crt: CRT_SKSL,
|
|
548
|
+
bloom: BLOOM_SKSL,
|
|
549
|
+
"god-rays": GOD_RAYS_SKSL,
|
|
550
|
+
"color-grading-lut": COLOR_GRADING_LUT_SKSL,
|
|
551
|
+
"weather-rain": WEATHER_RAIN_SKSL,
|
|
552
|
+
"weather-sun-shaft": WEATHER_SUN_SHAFT_SKSL,
|
|
553
|
+
"weather-dark-vignette": WEATHER_DARK_VIGNETTE_SKSL,
|
|
554
|
+
"weather-clouds": WEATHER_CLOUDS_SKSL,
|
|
555
|
+
composite: COMPOSITE_SKSL
|
|
735
556
|
};
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
557
|
+
/**
|
|
558
|
+
* Create a runtime against the given Skia-like module. If the module is
|
|
559
|
+
* absent or lacks `RuntimeEffect.Make`, returns a runtime with
|
|
560
|
+
* `supported: false` — callers should fall through to a passthrough pipeline.
|
|
561
|
+
*/
|
|
562
|
+
function createPostFxRuntime(skia, opts) {
|
|
563
|
+
const cache = /* @__PURE__ */ new Map();
|
|
564
|
+
const _loggedCompileFailures = /* @__PURE__ */ new Set();
|
|
565
|
+
const _loggedCompileSuccesses = /* @__PURE__ */ new Set();
|
|
566
|
+
const supported = skia !== null && typeof skia.RuntimeEffect?.Make === "function";
|
|
567
|
+
return {
|
|
568
|
+
supported,
|
|
569
|
+
compile(kind) {
|
|
570
|
+
if (!supported || !skia) return null;
|
|
571
|
+
const cached = cache.get(kind);
|
|
572
|
+
if (cached) return cached;
|
|
573
|
+
const effect = skia.RuntimeEffect.Make(SKSL_SOURCE[kind]);
|
|
574
|
+
if (!effect) {
|
|
575
|
+
if (!_loggedCompileFailures.has(kind)) {
|
|
576
|
+
_loggedCompileFailures.add(kind);
|
|
577
|
+
console.warn(`[postfx] RuntimeEffect.Make returned null for "${kind}" — shader failed to compile. Pass will be silently skipped.`);
|
|
578
|
+
}
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
if (!_loggedCompileSuccesses.has(kind)) {
|
|
582
|
+
_loggedCompileSuccesses.add(kind);
|
|
583
|
+
if (opts?.verbose === true && typeof effect.getUniformFloatCount === "function") console.info(`[postfx] compiled "${kind}" — getUniformFloatCount=${effect.getUniformFloatCount()}`);
|
|
584
|
+
}
|
|
585
|
+
const compiled = {
|
|
586
|
+
kind,
|
|
587
|
+
effect
|
|
588
|
+
};
|
|
589
|
+
cache.set(kind, compiled);
|
|
590
|
+
return compiled;
|
|
591
|
+
},
|
|
592
|
+
packUniforms(effect, width, height) {
|
|
593
|
+
return packUniforms(effect.uniforms, width, height);
|
|
594
|
+
},
|
|
595
|
+
buildPass(chain, width, height) {
|
|
596
|
+
const out = [];
|
|
597
|
+
if (!supported) return out;
|
|
598
|
+
const scale = chain.globalIntensity;
|
|
599
|
+
for (const effect of chain.enabled()) {
|
|
600
|
+
const compiled = this.compile(effect.uniforms.kind);
|
|
601
|
+
if (!compiled) continue;
|
|
602
|
+
const uniforms = packUniforms(effect.uniforms, width, height);
|
|
603
|
+
if (scale !== 1) scaleIntensity(effect.uniforms.kind, uniforms, scale);
|
|
604
|
+
const children = effect.uniforms.kind === "color-grading-lut" ? [effect.uniforms.lut] : EMPTY_CHILDREN;
|
|
605
|
+
out.push({
|
|
606
|
+
id: effect.id,
|
|
607
|
+
kind: effect.uniforms.kind,
|
|
608
|
+
shader: compiled.effect,
|
|
609
|
+
uniforms,
|
|
610
|
+
children
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
return out;
|
|
614
|
+
},
|
|
615
|
+
reset() {
|
|
616
|
+
cache.clear();
|
|
617
|
+
}
|
|
618
|
+
};
|
|
796
619
|
}
|
|
797
620
|
function packUniforms(u, width, height) {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
621
|
+
switch (u.kind) {
|
|
622
|
+
case "vignette": {
|
|
623
|
+
const col = u.color ?? [
|
|
624
|
+
0,
|
|
625
|
+
0,
|
|
626
|
+
0
|
|
627
|
+
];
|
|
628
|
+
return new Float32Array([
|
|
629
|
+
width,
|
|
630
|
+
height,
|
|
631
|
+
u.intensity,
|
|
632
|
+
u.radius,
|
|
633
|
+
col[0],
|
|
634
|
+
col[1],
|
|
635
|
+
col[2]
|
|
636
|
+
]);
|
|
637
|
+
}
|
|
638
|
+
case "grayscale-flash": return new Float32Array([u.intensity]);
|
|
639
|
+
case "chromatic-aberration": return new Float32Array([
|
|
640
|
+
width,
|
|
641
|
+
height,
|
|
642
|
+
u.amount
|
|
643
|
+
]);
|
|
644
|
+
case "crt": return new Float32Array([
|
|
645
|
+
width,
|
|
646
|
+
height,
|
|
647
|
+
u.scanlineIntensity,
|
|
648
|
+
u.curvature
|
|
649
|
+
]);
|
|
650
|
+
case "bloom": return new Float32Array([
|
|
651
|
+
width,
|
|
652
|
+
height,
|
|
653
|
+
u.threshold,
|
|
654
|
+
u.intensity
|
|
655
|
+
]);
|
|
656
|
+
case "god-rays": {
|
|
657
|
+
const samples = Math.max(1, Math.min(64, Math.round(u.samples)));
|
|
658
|
+
return new Float32Array([
|
|
659
|
+
width,
|
|
660
|
+
height,
|
|
661
|
+
u.lightX,
|
|
662
|
+
u.lightY,
|
|
663
|
+
u.color[0],
|
|
664
|
+
u.color[1],
|
|
665
|
+
u.color[2],
|
|
666
|
+
u.intensity,
|
|
667
|
+
u.decay,
|
|
668
|
+
u.density,
|
|
669
|
+
u.weight,
|
|
670
|
+
samples
|
|
671
|
+
]);
|
|
672
|
+
}
|
|
673
|
+
case "color-grading-lut": return new Float32Array([u.intensity]);
|
|
674
|
+
case "weather-rain": {
|
|
675
|
+
const col = u.color ?? [
|
|
676
|
+
1,
|
|
677
|
+
1,
|
|
678
|
+
1
|
|
679
|
+
];
|
|
680
|
+
return new Float32Array([
|
|
681
|
+
width,
|
|
682
|
+
height,
|
|
683
|
+
u.intensity,
|
|
684
|
+
u.direction,
|
|
685
|
+
u.scrollOffsetY,
|
|
686
|
+
col[0],
|
|
687
|
+
col[1],
|
|
688
|
+
col[2],
|
|
689
|
+
u.density ?? .5,
|
|
690
|
+
u.streakLen ?? .15
|
|
691
|
+
]);
|
|
692
|
+
}
|
|
693
|
+
case "weather-sun-shaft": return new Float32Array([
|
|
694
|
+
width,
|
|
695
|
+
height,
|
|
696
|
+
u.intensity,
|
|
697
|
+
u.direction
|
|
698
|
+
]);
|
|
699
|
+
case "weather-dark-vignette": return new Float32Array([
|
|
700
|
+
width,
|
|
701
|
+
height,
|
|
702
|
+
u.intensity
|
|
703
|
+
]);
|
|
704
|
+
case "weather-clouds": return new Float32Array([
|
|
705
|
+
width,
|
|
706
|
+
height,
|
|
707
|
+
u.intensity,
|
|
708
|
+
u.scrollOffsetX,
|
|
709
|
+
u.direction
|
|
710
|
+
]);
|
|
711
|
+
case "composite": return new Float32Array([
|
|
712
|
+
width,
|
|
713
|
+
height,
|
|
714
|
+
u.vignetteIntensity,
|
|
715
|
+
u.vignetteRadius,
|
|
716
|
+
u.vignetteColor[0],
|
|
717
|
+
u.vignetteColor[1],
|
|
718
|
+
u.vignetteColor[2],
|
|
719
|
+
u.bloomThreshold,
|
|
720
|
+
u.bloomIntensity,
|
|
721
|
+
u.flashIntensity,
|
|
722
|
+
u.chromaAmount,
|
|
723
|
+
u.crtScanlineIntensity,
|
|
724
|
+
u.crtCurvature,
|
|
725
|
+
u.crtEnabled ? 1 : 0,
|
|
726
|
+
u.weatherKind,
|
|
727
|
+
u.weatherIntensity,
|
|
728
|
+
u.weatherDirection,
|
|
729
|
+
u.weatherScrollX,
|
|
730
|
+
u.weatherScrollY,
|
|
731
|
+
u.weatherColor[0],
|
|
732
|
+
u.weatherColor[1],
|
|
733
|
+
u.weatherColor[2],
|
|
734
|
+
u.weatherDensity,
|
|
735
|
+
u.weatherStreakLen
|
|
736
|
+
]);
|
|
737
|
+
}
|
|
881
738
|
}
|
|
739
|
+
/**
|
|
740
|
+
* Scale the "strength" slot of a packed uniform buffer in place. Geometry
|
|
741
|
+
* fields (radius, curvature, threshold) are intentionally left alone.
|
|
742
|
+
*/
|
|
882
743
|
function scaleIntensity(kind, packed, scale) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
744
|
+
switch (kind) {
|
|
745
|
+
case "vignette":
|
|
746
|
+
packed[2] = (packed[2] ?? 0) * scale;
|
|
747
|
+
return;
|
|
748
|
+
case "grayscale-flash":
|
|
749
|
+
packed[0] = (packed[0] ?? 0) * scale;
|
|
750
|
+
return;
|
|
751
|
+
case "chromatic-aberration":
|
|
752
|
+
packed[2] = (packed[2] ?? 0) * scale;
|
|
753
|
+
return;
|
|
754
|
+
case "crt":
|
|
755
|
+
packed[2] = (packed[2] ?? 0) * scale;
|
|
756
|
+
return;
|
|
757
|
+
case "bloom":
|
|
758
|
+
packed[3] = (packed[3] ?? 0) * scale;
|
|
759
|
+
return;
|
|
760
|
+
case "god-rays":
|
|
761
|
+
packed[7] = (packed[7] ?? 0) * scale;
|
|
762
|
+
return;
|
|
763
|
+
case "color-grading-lut":
|
|
764
|
+
packed[0] = (packed[0] ?? 0) * scale;
|
|
765
|
+
return;
|
|
766
|
+
case "weather-rain":
|
|
767
|
+
case "weather-sun-shaft":
|
|
768
|
+
case "weather-dark-vignette":
|
|
769
|
+
case "weather-clouds":
|
|
770
|
+
packed[2] = (packed[2] ?? 0) * scale;
|
|
771
|
+
return;
|
|
772
|
+
case "composite":
|
|
773
|
+
packed[2] = (packed[2] ?? 0) * scale;
|
|
774
|
+
packed[8] = (packed[8] ?? 0) * scale;
|
|
775
|
+
packed[9] = (packed[9] ?? 0) * scale;
|
|
776
|
+
packed[10] = (packed[10] ?? 0) * scale;
|
|
777
|
+
packed[15] = (packed[15] ?? 0) * scale;
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
919
780
|
}
|
|
920
|
-
|
|
921
|
-
|
|
781
|
+
//#endregion
|
|
782
|
+
//#region src/controller.ts
|
|
783
|
+
/**
|
|
784
|
+
* PostFxController — a reusable bridge that owns an EffectChain + PostFxRuntime
|
|
785
|
+
* and adds two game-common conveniences:
|
|
786
|
+
*
|
|
787
|
+
* - hit-flash: spike a grayscale-flash effect to 1 and auto-decay it.
|
|
788
|
+
* - motion scale: forward a single scalar to `EffectChain.setGlobalIntensity`
|
|
789
|
+
* so a11y reduced-motion can null the whole stack at once.
|
|
790
|
+
*
|
|
791
|
+
* Games can instantiate multiple controllers and swap implementations freely.
|
|
792
|
+
*/
|
|
793
|
+
const DEFAULT_FLASH_DECAY = 4.2;
|
|
794
|
+
function createPostFxController(options) {
|
|
795
|
+
const chain = new EffectChain();
|
|
796
|
+
for (let i = 0; i < options.effects.length; i++) {
|
|
797
|
+
const e = options.effects[i];
|
|
798
|
+
if (e) chain.add(e);
|
|
799
|
+
}
|
|
800
|
+
const runtime = createPostFxRuntime(options.skia ?? null);
|
|
801
|
+
const flashCfg = options.flash;
|
|
802
|
+
const flashDecay = flashCfg?.decay ?? DEFAULT_FLASH_DECAY;
|
|
803
|
+
let flashIntensity = 0;
|
|
804
|
+
const timedRemaining = /* @__PURE__ */ new Map();
|
|
805
|
+
const timedRestore = /* @__PURE__ */ new Map();
|
|
806
|
+
return {
|
|
807
|
+
chain,
|
|
808
|
+
runtime,
|
|
809
|
+
triggerHitFlash() {
|
|
810
|
+
if (!flashCfg) return;
|
|
811
|
+
flashIntensity = 1;
|
|
812
|
+
chain.setUniforms(flashCfg.id, {
|
|
813
|
+
kind: "grayscale-flash",
|
|
814
|
+
intensity: 1
|
|
815
|
+
});
|
|
816
|
+
},
|
|
817
|
+
triggerTimed(effectId, decayMs) {
|
|
818
|
+
const effect = chain.get(effectId);
|
|
819
|
+
if (!effect) return;
|
|
820
|
+
timedRestore.set(effectId, { ...effect.uniforms });
|
|
821
|
+
chain.setEnabled(effectId, true);
|
|
822
|
+
timedRemaining.set(effectId, decayMs);
|
|
823
|
+
},
|
|
824
|
+
update(dt) {
|
|
825
|
+
if (flashCfg && flashIntensity > 0) {
|
|
826
|
+
flashIntensity = Math.max(0, flashIntensity - dt * flashDecay);
|
|
827
|
+
chain.setUniforms(flashCfg.id, {
|
|
828
|
+
kind: "grayscale-flash",
|
|
829
|
+
intensity: flashIntensity
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
if (timedRemaining.size > 0) {
|
|
833
|
+
const dtMs = dt * 1e3;
|
|
834
|
+
const expired = [];
|
|
835
|
+
for (const [id, rem] of timedRemaining) {
|
|
836
|
+
const next = rem - dtMs;
|
|
837
|
+
if (next <= 0) expired.push(id);
|
|
838
|
+
else timedRemaining.set(id, next);
|
|
839
|
+
}
|
|
840
|
+
if (expired.length > 0) for (const id of expired) {
|
|
841
|
+
const snapshot = timedRestore.get(id);
|
|
842
|
+
if (snapshot !== void 0) chain.setUniforms(id, snapshot);
|
|
843
|
+
chain.setEnabled(id, false);
|
|
844
|
+
timedRemaining.delete(id);
|
|
845
|
+
timedRestore.delete(id);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
buildPass(width, height) {
|
|
850
|
+
return runtime.buildPass(chain, width, height);
|
|
851
|
+
},
|
|
852
|
+
setMotionScale(k) {
|
|
853
|
+
chain.setGlobalIntensity(k);
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
//#endregion
|
|
858
|
+
//#region src/effect.ts
|
|
859
|
+
function createVignette(id = "vignette", uniforms = {}) {
|
|
860
|
+
return {
|
|
861
|
+
id,
|
|
862
|
+
enabled: true,
|
|
863
|
+
uniforms: {
|
|
864
|
+
kind: "vignette",
|
|
865
|
+
intensity: uniforms.intensity ?? .4,
|
|
866
|
+
radius: uniforms.radius ?? .8,
|
|
867
|
+
color: uniforms.color ?? [
|
|
868
|
+
0,
|
|
869
|
+
0,
|
|
870
|
+
0
|
|
871
|
+
]
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function createGrayscaleFlash(id = "grayscale-flash", uniforms = {}) {
|
|
876
|
+
return {
|
|
877
|
+
id,
|
|
878
|
+
enabled: true,
|
|
879
|
+
uniforms: {
|
|
880
|
+
kind: "grayscale-flash",
|
|
881
|
+
intensity: uniforms.intensity ?? 0
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function createChromaticAberration(id = "chromatic-aberration", uniforms = {}) {
|
|
886
|
+
return {
|
|
887
|
+
id,
|
|
888
|
+
enabled: true,
|
|
889
|
+
uniforms: {
|
|
890
|
+
kind: "chromatic-aberration",
|
|
891
|
+
amount: uniforms.amount ?? .005
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function createCrt(id = "crt", uniforms = {}) {
|
|
896
|
+
return {
|
|
897
|
+
id,
|
|
898
|
+
enabled: true,
|
|
899
|
+
uniforms: {
|
|
900
|
+
kind: "crt",
|
|
901
|
+
scanlineIntensity: uniforms.scanlineIntensity ?? .25,
|
|
902
|
+
curvature: uniforms.curvature ?? .12
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function createBloom(id = "bloom", uniforms = {}) {
|
|
907
|
+
return {
|
|
908
|
+
id,
|
|
909
|
+
enabled: true,
|
|
910
|
+
uniforms: {
|
|
911
|
+
kind: "bloom",
|
|
912
|
+
threshold: uniforms.threshold ?? .7,
|
|
913
|
+
intensity: uniforms.intensity ?? .6
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
function createGodRays(id = "god-rays", uniforms = {}) {
|
|
918
|
+
return {
|
|
919
|
+
id,
|
|
920
|
+
enabled: true,
|
|
921
|
+
uniforms: {
|
|
922
|
+
kind: "god-rays",
|
|
923
|
+
lightX: uniforms.lightX ?? .5,
|
|
924
|
+
lightY: uniforms.lightY ?? .1,
|
|
925
|
+
color: uniforms.color ?? [
|
|
926
|
+
1,
|
|
927
|
+
.95,
|
|
928
|
+
.8
|
|
929
|
+
],
|
|
930
|
+
intensity: uniforms.intensity ?? .6,
|
|
931
|
+
decay: uniforms.decay ?? .96,
|
|
932
|
+
density: uniforms.density ?? .5,
|
|
933
|
+
weight: uniforms.weight ?? .4,
|
|
934
|
+
samples: uniforms.samples ?? 48
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function createColorGradingLut(id, uniforms) {
|
|
939
|
+
return {
|
|
940
|
+
id,
|
|
941
|
+
enabled: true,
|
|
942
|
+
uniforms: {
|
|
943
|
+
kind: "color-grading-lut",
|
|
944
|
+
lut: uniforms.lut,
|
|
945
|
+
intensity: uniforms.intensity ?? 1
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
function createWeatherRain(id = "weather-rain", uniforms = {}) {
|
|
950
|
+
return {
|
|
951
|
+
id,
|
|
952
|
+
enabled: true,
|
|
953
|
+
uniforms: {
|
|
954
|
+
kind: "weather-rain",
|
|
955
|
+
intensity: uniforms.intensity ?? .5,
|
|
956
|
+
direction: uniforms.direction ?? 0,
|
|
957
|
+
scrollOffsetY: uniforms.scrollOffsetY ?? 0,
|
|
958
|
+
color: uniforms.color ?? [
|
|
959
|
+
1,
|
|
960
|
+
1,
|
|
961
|
+
1
|
|
962
|
+
],
|
|
963
|
+
density: uniforms.density ?? .5,
|
|
964
|
+
streakLen: uniforms.streakLen ?? .15
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function createWeatherSunShaft(id = "weather-sun-shaft", uniforms = {}) {
|
|
969
|
+
return {
|
|
970
|
+
id,
|
|
971
|
+
enabled: true,
|
|
972
|
+
uniforms: {
|
|
973
|
+
kind: "weather-sun-shaft",
|
|
974
|
+
intensity: uniforms.intensity ?? .5,
|
|
975
|
+
direction: uniforms.direction ?? 0
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
function createWeatherDarkVignette(id = "weather-dark-vignette", uniforms = {}) {
|
|
980
|
+
return {
|
|
981
|
+
id,
|
|
982
|
+
enabled: true,
|
|
983
|
+
uniforms: {
|
|
984
|
+
kind: "weather-dark-vignette",
|
|
985
|
+
intensity: uniforms.intensity ?? .5
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
function createWeatherClouds(id = "weather-clouds", uniforms = {}) {
|
|
990
|
+
return {
|
|
991
|
+
id,
|
|
992
|
+
enabled: true,
|
|
993
|
+
uniforms: {
|
|
994
|
+
kind: "weather-clouds",
|
|
995
|
+
intensity: uniforms.intensity ?? .5,
|
|
996
|
+
direction: uniforms.direction ?? 0,
|
|
997
|
+
scrollOffsetX: uniforms.scrollOffsetX ?? 0
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function createPostFxComposite(id = "composite", uniforms = {}) {
|
|
1002
|
+
return {
|
|
1003
|
+
id,
|
|
1004
|
+
enabled: true,
|
|
1005
|
+
uniforms: {
|
|
1006
|
+
kind: "composite",
|
|
1007
|
+
vignetteIntensity: uniforms.vignetteIntensity ?? 0,
|
|
1008
|
+
vignetteRadius: uniforms.vignetteRadius ?? .8,
|
|
1009
|
+
vignetteColor: uniforms.vignetteColor ?? [
|
|
1010
|
+
0,
|
|
1011
|
+
0,
|
|
1012
|
+
0
|
|
1013
|
+
],
|
|
1014
|
+
bloomThreshold: uniforms.bloomThreshold ?? .7,
|
|
1015
|
+
bloomIntensity: uniforms.bloomIntensity ?? 0,
|
|
1016
|
+
flashIntensity: uniforms.flashIntensity ?? 0,
|
|
1017
|
+
chromaAmount: uniforms.chromaAmount ?? 0,
|
|
1018
|
+
crtScanlineIntensity: uniforms.crtScanlineIntensity ?? .25,
|
|
1019
|
+
crtCurvature: uniforms.crtCurvature ?? .12,
|
|
1020
|
+
crtEnabled: uniforms.crtEnabled ?? false,
|
|
1021
|
+
weatherKind: uniforms.weatherKind ?? 0,
|
|
1022
|
+
weatherIntensity: uniforms.weatherIntensity ?? 0,
|
|
1023
|
+
weatherDirection: uniforms.weatherDirection ?? 0,
|
|
1024
|
+
weatherScrollX: uniforms.weatherScrollX ?? 0,
|
|
1025
|
+
weatherScrollY: uniforms.weatherScrollY ?? 0,
|
|
1026
|
+
weatherColor: uniforms.weatherColor ?? [
|
|
1027
|
+
1,
|
|
1028
|
+
1,
|
|
1029
|
+
1
|
|
1030
|
+
],
|
|
1031
|
+
weatherDensity: uniforms.weatherDensity ?? .5,
|
|
1032
|
+
weatherStreakLen: uniforms.weatherStreakLen ?? .15
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/lut.ts
|
|
922
1038
|
function createNeutralLutBytes() {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
buf[off + 3] = 255;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
return buf;
|
|
1039
|
+
const buf = new Uint8Array(4096 * 64 * 4);
|
|
1040
|
+
for (let b = 0; b < 64; b++) for (let g = 0; g < 64; g++) for (let r = 0; r < 64; r++) {
|
|
1041
|
+
const x = b * 64 + r;
|
|
1042
|
+
const off = (g * 4096 + x) * 4;
|
|
1043
|
+
buf[off] = Math.round(r / 63 * 255);
|
|
1044
|
+
buf[off + 1] = Math.round(g / 63 * 255);
|
|
1045
|
+
buf[off + 2] = Math.round(b / 63 * 255);
|
|
1046
|
+
buf[off + 3] = 255;
|
|
1047
|
+
}
|
|
1048
|
+
return buf;
|
|
938
1049
|
}
|
|
1050
|
+
//#endregion
|
|
1051
|
+
export { BLOOM_SKSL, CHROMATIC_ABERRATION_SKSL, COLOR_GRADING_LUT_SKSL, COMPOSITE_SKSL, CRT_SKSL, EffectChain, GOD_RAYS_SKSL, GRAYSCALE_FLASH_SKSL, VIGNETTE_SKSL, WEATHER_CLOUDS_SKSL, WEATHER_DARK_VIGNETTE_SKSL, WEATHER_RAIN_SKSL, WEATHER_SUN_SHAFT_SKSL, createBloom, createChromaticAberration, createColorGradingLut, createCrt, createGodRays, createGrayscaleFlash, createNeutralLutBytes, createPostFxComposite, createPostFxController, createPostFxRuntime, createVignette, createWeatherClouds, createWeatherDarkVignette, createWeatherRain, createWeatherSunShaft };
|
|
939
1052
|
|
|
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
1053
|
//# sourceMappingURL=index.mjs.map
|