@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.d.ts
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordered post-processing pipeline. Pure data structure — the runtime reads
|
|
3
|
+
* the enabled entries in order and feeds each through its compiled shader.
|
|
4
|
+
*/
|
|
5
|
+
declare class EffectChain {
|
|
6
|
+
private _effects;
|
|
7
|
+
private _globalIntensity;
|
|
8
|
+
get length(): number;
|
|
9
|
+
/**
|
|
10
|
+
* Global intensity multiplier applied to every enabled effect's
|
|
11
|
+
* "strength" uniform when the pass is built. Default 1 (no-op).
|
|
12
|
+
*
|
|
13
|
+
* Scales the following uniform fields at pass-build time:
|
|
14
|
+
* - vignette.intensity
|
|
15
|
+
* - grayscale-flash.intensity
|
|
16
|
+
* - chromatic-aberration.amount
|
|
17
|
+
* - crt.scanlineIntensity
|
|
18
|
+
* - bloom.intensity
|
|
19
|
+
*
|
|
20
|
+
* Geometry-like uniforms (radius, curvature, threshold) are not scaled.
|
|
21
|
+
* Intended for a11y reduced-motion (set to 0 to null all dampen-able
|
|
22
|
+
* effects in one call) without touching per-effect state.
|
|
23
|
+
*
|
|
24
|
+
* Clamped to `>= 0`. Negative values are clamped to 0.
|
|
25
|
+
*/
|
|
26
|
+
get globalIntensity(): number;
|
|
27
|
+
set globalIntensity(value: number);
|
|
28
|
+
/** Convenience setter — mirrors the getter/setter. */
|
|
29
|
+
setGlobalIntensity(value: number): void;
|
|
30
|
+
/** Snapshot view of the chain. Do not mutate. */
|
|
31
|
+
all(): readonly Effect[];
|
|
32
|
+
/** Append an effect to the end of the chain. */
|
|
33
|
+
add(effect: Effect): void;
|
|
34
|
+
/** Insert an effect at a specific index (clamped to [0, length]). */
|
|
35
|
+
insert(index: number, effect: Effect): void;
|
|
36
|
+
remove(id: string): boolean;
|
|
37
|
+
get(id: string): Effect | null;
|
|
38
|
+
setEnabled(id: string, enabled: boolean): void;
|
|
39
|
+
/** Update uniforms in place. Kind must match the existing effect's kind. */
|
|
40
|
+
setUniforms(id: string, uniforms: EffectUniforms): void;
|
|
41
|
+
move(id: string, newIndex: number): void;
|
|
42
|
+
clear(): void;
|
|
43
|
+
/** Iterate only over enabled effects, in order. */
|
|
44
|
+
enabled(): IterableIterator<Effect>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Runtime — compiles SkSL sources on demand, builds uniform buffers per effect,
|
|
49
|
+
* and exposes `buildPass` that the bridge calls to get pass descriptors for
|
|
50
|
+
* rendering. Rendering itself is owned by the bridge in `@flare-engine/react`.
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Structural stub for a Skia image handle. No Skia import is needed here
|
|
54
|
+
* because the renderer owns the actual image objects; this type exists only
|
|
55
|
+
* so callers can assign real `SkImage` values to `CompiledPass.children`
|
|
56
|
+
* without coupling this package to a specific Skia version.
|
|
57
|
+
*/
|
|
58
|
+
interface SkImageLike {
|
|
59
|
+
readonly _skImageBrand?: never;
|
|
60
|
+
}
|
|
61
|
+
interface SkiaRuntimeEffectLike {
|
|
62
|
+
makeShaderWithChildren(uniforms: number[], children?: unknown[]): unknown | null;
|
|
63
|
+
/** Optional diagnostic — total float count expected by makeShaderWithChildren. */
|
|
64
|
+
getUniformFloatCount?(): number;
|
|
65
|
+
}
|
|
66
|
+
interface SkiaRuntimeEffectFactory {
|
|
67
|
+
Make(sksl: string): SkiaRuntimeEffectLike | null;
|
|
68
|
+
}
|
|
69
|
+
interface SkiaPostFxModuleLike {
|
|
70
|
+
RuntimeEffect: SkiaRuntimeEffectFactory;
|
|
71
|
+
}
|
|
72
|
+
interface CompiledShader {
|
|
73
|
+
kind: EffectKind;
|
|
74
|
+
effect: SkiaRuntimeEffectLike;
|
|
75
|
+
}
|
|
76
|
+
interface PostFxRuntime {
|
|
77
|
+
/** True when the underlying Skia module reported a working RuntimeEffect. */
|
|
78
|
+
readonly supported: boolean;
|
|
79
|
+
/** Lazily compile (and cache) the shader for a given effect. Returns null on unsupported. */
|
|
80
|
+
compile(kind: EffectKind): CompiledShader | null;
|
|
81
|
+
/** Pack an effect's uniforms into a Float32Array in the order the shader declares. */
|
|
82
|
+
packUniforms(effect: Effect, width: number, height: number): Float32Array;
|
|
83
|
+
/** Build a list of compiled pass descriptors in chain order for enabled effects. */
|
|
84
|
+
buildPass(chain: EffectChain, width: number, height: number): CompiledPass[];
|
|
85
|
+
/** Drop all cached compiled shaders. */
|
|
86
|
+
reset(): void;
|
|
87
|
+
}
|
|
88
|
+
interface CompiledPass {
|
|
89
|
+
id: string;
|
|
90
|
+
kind: EffectKind;
|
|
91
|
+
shader: SkiaRuntimeEffectLike;
|
|
92
|
+
uniforms: Float32Array;
|
|
93
|
+
/**
|
|
94
|
+
* Convention A — extra sampler children only (excludes src).
|
|
95
|
+
*
|
|
96
|
+
* The renderer binds the implicit scene texture as `children[0]` at draw
|
|
97
|
+
* time. This array holds any *additional* `uniform shader` slots declared
|
|
98
|
+
* beyond `src` (e.g. a noise texture, an LUT). For every built-in effect
|
|
99
|
+
* today there are no extra children, so this is always the frozen
|
|
100
|
+
* `EMPTY_CHILDREN` sentinel — enabling zero-alloc sharing across passes.
|
|
101
|
+
*/
|
|
102
|
+
children: readonly SkImageLike[];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create a runtime against the given Skia-like module. If the module is
|
|
106
|
+
* absent or lacks `RuntimeEffect.Make`, returns a runtime with
|
|
107
|
+
* `supported: false` — callers should fall through to a passthrough pipeline.
|
|
108
|
+
*/
|
|
109
|
+
declare function createPostFxRuntime(skia: SkiaPostFxModuleLike | null): PostFxRuntime;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Effect — an ordered entry in the post-processing chain.
|
|
113
|
+
*
|
|
114
|
+
* `kind` selects a bundled shader (see `shaders.ts`). Each effect owns its
|
|
115
|
+
* own uniform object; the runtime feeds these straight into the compiled
|
|
116
|
+
* RuntimeEffect at draw time.
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
type EffectKind = 'vignette' | 'grayscale-flash' | 'chromatic-aberration' | 'crt' | 'bloom' | 'god-rays' | 'color-grading-lut' | 'weather-rain' | 'weather-sun-shaft' | 'weather-dark-vignette' | 'weather-clouds' | 'composite';
|
|
120
|
+
interface VignetteUniforms {
|
|
121
|
+
intensity: number;
|
|
122
|
+
radius: number;
|
|
123
|
+
color?: readonly [number, number, number];
|
|
124
|
+
}
|
|
125
|
+
interface GrayscaleFlashUniforms {
|
|
126
|
+
intensity: number;
|
|
127
|
+
}
|
|
128
|
+
interface ChromaticAberrationUniforms {
|
|
129
|
+
amount: number;
|
|
130
|
+
}
|
|
131
|
+
interface CrtUniforms {
|
|
132
|
+
scanlineIntensity: number;
|
|
133
|
+
curvature: number;
|
|
134
|
+
}
|
|
135
|
+
interface BloomUniforms {
|
|
136
|
+
threshold: number;
|
|
137
|
+
intensity: number;
|
|
138
|
+
}
|
|
139
|
+
interface GodRaysUniforms {
|
|
140
|
+
lightX: number;
|
|
141
|
+
lightY: number;
|
|
142
|
+
color: readonly [number, number, number];
|
|
143
|
+
intensity: number;
|
|
144
|
+
decay: number;
|
|
145
|
+
density: number;
|
|
146
|
+
weight: number;
|
|
147
|
+
samples: number;
|
|
148
|
+
}
|
|
149
|
+
interface ColorGradingLutUniforms {
|
|
150
|
+
lut: SkImageLike;
|
|
151
|
+
intensity: number;
|
|
152
|
+
}
|
|
153
|
+
interface WeatherRainUniforms {
|
|
154
|
+
intensity: number;
|
|
155
|
+
direction: number;
|
|
156
|
+
speed: number;
|
|
157
|
+
scrollOffsetY: number;
|
|
158
|
+
color?: readonly [number, number, number];
|
|
159
|
+
density?: number;
|
|
160
|
+
streakLen?: number;
|
|
161
|
+
}
|
|
162
|
+
interface WeatherSunShaftUniforms {
|
|
163
|
+
intensity: number;
|
|
164
|
+
direction: number;
|
|
165
|
+
}
|
|
166
|
+
interface WeatherDarkVignetteUniforms {
|
|
167
|
+
intensity: number;
|
|
168
|
+
}
|
|
169
|
+
interface WeatherCloudsUniforms {
|
|
170
|
+
intensity: number;
|
|
171
|
+
direction: number;
|
|
172
|
+
speed: number;
|
|
173
|
+
scrollOffsetX: number;
|
|
174
|
+
}
|
|
175
|
+
interface CompositeUniforms {
|
|
176
|
+
vignetteIntensity: number;
|
|
177
|
+
vignetteRadius: number;
|
|
178
|
+
vignetteColor: readonly [number, number, number];
|
|
179
|
+
bloomThreshold: number;
|
|
180
|
+
bloomIntensity: number;
|
|
181
|
+
flashIntensity: number;
|
|
182
|
+
chromaAmount: number;
|
|
183
|
+
crtScanlineIntensity: number;
|
|
184
|
+
crtCurvature: number;
|
|
185
|
+
crtEnabled: boolean;
|
|
186
|
+
weatherKind: 0 | 1 | 2 | 3 | 4;
|
|
187
|
+
weatherIntensity: number;
|
|
188
|
+
weatherDirection: number;
|
|
189
|
+
weatherScrollX: number;
|
|
190
|
+
weatherScrollY: number;
|
|
191
|
+
weatherColor: readonly [number, number, number];
|
|
192
|
+
weatherDensity: number;
|
|
193
|
+
weatherStreakLen: number;
|
|
194
|
+
}
|
|
195
|
+
type EffectUniforms = ({
|
|
196
|
+
kind: 'vignette';
|
|
197
|
+
} & VignetteUniforms) | ({
|
|
198
|
+
kind: 'grayscale-flash';
|
|
199
|
+
} & GrayscaleFlashUniforms) | ({
|
|
200
|
+
kind: 'chromatic-aberration';
|
|
201
|
+
} & ChromaticAberrationUniforms) | ({
|
|
202
|
+
kind: 'crt';
|
|
203
|
+
} & CrtUniforms) | ({
|
|
204
|
+
kind: 'bloom';
|
|
205
|
+
} & BloomUniforms) | ({
|
|
206
|
+
kind: 'god-rays';
|
|
207
|
+
} & GodRaysUniforms) | ({
|
|
208
|
+
kind: 'color-grading-lut';
|
|
209
|
+
} & ColorGradingLutUniforms) | ({
|
|
210
|
+
kind: 'weather-rain';
|
|
211
|
+
} & WeatherRainUniforms) | ({
|
|
212
|
+
kind: 'weather-sun-shaft';
|
|
213
|
+
} & WeatherSunShaftUniforms) | ({
|
|
214
|
+
kind: 'weather-dark-vignette';
|
|
215
|
+
} & WeatherDarkVignetteUniforms) | ({
|
|
216
|
+
kind: 'weather-clouds';
|
|
217
|
+
} & WeatherCloudsUniforms) | ({
|
|
218
|
+
kind: 'composite';
|
|
219
|
+
} & CompositeUniforms);
|
|
220
|
+
interface Effect {
|
|
221
|
+
id: string;
|
|
222
|
+
enabled: boolean;
|
|
223
|
+
uniforms: EffectUniforms;
|
|
224
|
+
}
|
|
225
|
+
declare function createVignette(id?: string, uniforms?: Partial<VignetteUniforms>): Effect;
|
|
226
|
+
declare function createGrayscaleFlash(id?: string, uniforms?: Partial<GrayscaleFlashUniforms>): Effect;
|
|
227
|
+
declare function createChromaticAberration(id?: string, uniforms?: Partial<ChromaticAberrationUniforms>): Effect;
|
|
228
|
+
declare function createCrt(id?: string, uniforms?: Partial<CrtUniforms>): Effect;
|
|
229
|
+
declare function createBloom(id?: string, uniforms?: Partial<BloomUniforms>): Effect;
|
|
230
|
+
declare function createGodRays(id?: string, uniforms?: Partial<GodRaysUniforms>): Effect;
|
|
231
|
+
declare function createColorGradingLut(uniforms: {
|
|
232
|
+
lut: SkImageLike;
|
|
233
|
+
intensity?: number;
|
|
234
|
+
}, id?: string): Effect;
|
|
235
|
+
declare function createWeatherRain(id?: string, uniforms?: Partial<WeatherRainUniforms>): Effect;
|
|
236
|
+
declare function createWeatherSunShaft(id?: string, uniforms?: Partial<WeatherSunShaftUniforms>): Effect;
|
|
237
|
+
declare function createWeatherDarkVignette(id?: string, uniforms?: Partial<WeatherDarkVignetteUniforms>): Effect;
|
|
238
|
+
declare function createWeatherClouds(id?: string, uniforms?: Partial<WeatherCloudsUniforms>): Effect;
|
|
239
|
+
declare function createPostFxComposite(id?: string, uniforms?: Partial<CompositeUniforms>): Effect;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* SkSL runtime-effect shaders used by the postfx stack.
|
|
243
|
+
*
|
|
244
|
+
* Each source declares a `uniform shader src;` plus per-effect uniforms. They
|
|
245
|
+
* are sampled as `src.eval(fragCoord)` against the scene's offscreen surface.
|
|
246
|
+
*
|
|
247
|
+
* Shader strings are emitted from the package as-is. Fallback: when
|
|
248
|
+
* `Skia.RuntimeEffect.Make` is unavailable, `runtime.ts` short-circuits the
|
|
249
|
+
* chain to a passthrough.
|
|
250
|
+
*/
|
|
251
|
+
declare const VIGNETTE_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float radius;\nuniform float3 color;\n\nhalf4 main(float2 coord) {\n half4 c = src.eval(coord);\n float2 uv = coord / resolution;\n float2 centered = uv - 0.5;\n float d = length(centered);\n float v = smoothstep(radius, radius * 0.5, d);\n return half4(mix(half3(color), c.rgb, mix(1.0 - intensity, 1.0, v)), c.a);\n}\n";
|
|
252
|
+
declare const GRAYSCALE_FLASH_SKSL = "\nuniform shader src;\nuniform float intensity;\n\nhalf4 main(float2 coord) {\n half4 c = src.eval(coord);\n float luma = dot(c.rgb, half3(0.299, 0.587, 0.114));\n half3 gray = half3(luma);\n half3 mixed = mix(c.rgb, gray, half(intensity));\n return half4(mixed, c.a);\n}\n";
|
|
253
|
+
declare const CHROMATIC_ABERRATION_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float amount;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / resolution;\n float2 offset = (uv - 0.5) * amount;\n half r = src.eval((uv - offset) * resolution).r;\n half g = src.eval(coord).g;\n half b = src.eval((uv + offset) * resolution).b;\n half a = src.eval(coord).a;\n return half4(r, g, b, a);\n}\n";
|
|
254
|
+
declare const CRT_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float scanlineIntensity;\nuniform float curvature;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / resolution;\n float2 centered = uv - 0.5;\n float2 warp = centered * (1.0 + dot(centered, centered) * curvature);\n float2 warpedUv = warp + 0.5;\n if (warpedUv.x < 0.0 || warpedUv.x > 1.0 || warpedUv.y < 0.0 || warpedUv.y > 1.0) {\n return half4(0.0, 0.0, 0.0, 1.0);\n }\n half4 c = src.eval(warpedUv * resolution);\n float scan = 0.5 + 0.5 * sin(warpedUv.y * resolution.y * 3.14159);\n float dim = mix(1.0, scan, scanlineIntensity);\n return half4(c.rgb * dim, c.a);\n}\n";
|
|
255
|
+
declare const BLOOM_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float threshold;\nuniform float intensity;\n\nhalf4 main(float2 coord) {\n half4 c = src.eval(coord);\n float luma = dot(c.rgb, half3(0.299, 0.587, 0.114));\n float mask = smoothstep(threshold, 1.0, luma);\n half3 boost = c.rgb * mask * intensity;\n return half4(c.rgb + boost, c.a);\n}\n";
|
|
256
|
+
declare const COLOR_GRADING_LUT_SKSL = "\nuniform shader src;\nuniform shader lut;\nuniform float intensity;\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n\n float r = float(scene.r) * 63.0;\n float g = float(scene.g) * 63.0;\n float b = float(scene.b) * 63.0;\n\n float bFloor = floor(b);\n float bCeil = min(bFloor + 1.0, 63.0);\n float bFrac = b - bFloor;\n\n float2 uvFloor = float2((bFloor * 64.0 + r + 0.5) / 4096.0,\n (g + 0.5) / 64.0);\n float2 uvCeil = float2((bCeil * 64.0 + r + 0.5) / 4096.0,\n (g + 0.5) / 64.0);\n\n half3 lutFloor = lut.eval(uvFloor * float2(4096.0, 64.0)).rgb;\n half3 lutCeil = lut.eval(uvCeil * float2(4096.0, 64.0)).rgb;\n\n half3 graded = mix(lutFloor, lutCeil, half(bFrac));\n half3 result = mix(scene.rgb, graded, half(intensity));\n\n return half4(result, scene.a);\n}\n";
|
|
257
|
+
declare const WEATHER_RAIN_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float direction;\nuniform float scrollOffsetY;\nuniform float3 color;\nuniform float density;\nuniform float streakLen;\n\n// scrollOffsetY is a normalized fall phase in [0,1). Increase it each tick\n// by (speed * dt / screenHeight) and wrap with fract() on the game side.\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n float2 uv = coord / resolution;\n float aspect = resolution.x / resolution.y;\n float rad = direction * 3.14159 / 180.0;\n float sinA = sin(rad);\n float cosA = cos(rad);\n // aspect-corrected rotated axes: fallAxis along drop travel, perpAxis across\n float perpAxis = (cosA * uv.x - sinA * uv.y) * aspect;\n float fallAxis = sinA * uv.x + cosA * uv.y;\n\n float coverage = 0.0;\n\n // near layer \u2014 COLS=45, tiling=6, brightness=1.0\n {\n float COLS = 45.0;\n float tiling = 6.0;\n float brightness = 1.0;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 0.0));\n if (colHash < density) {\n float phase = hash(float2(cid, 1.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 2.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 3.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - scrollOffsetY + phase);\n float coverageY = smoothstep(streakLen, 0.0, seg);\n float width = 0.04;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n // far layer \u2014 COLS=85, tiling=11, brightness=0.5\n {\n float COLS = 85.0;\n float tiling = 11.0;\n float brightness = 0.5;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 10.0));\n if (colHash < density) {\n float phase = hash(float2(cid, 11.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 12.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 13.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - scrollOffsetY + phase);\n float coverageY = smoothstep(streakLen, 0.0, seg);\n float width = 0.03;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n half3 mixed = mix(scene.rgb, half3(color), half(coverage * intensity));\n return half4(mixed, scene.a);\n}\n";
|
|
258
|
+
declare const WEATHER_SUN_SHAFT_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float direction;\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n float2 uv = coord / resolution;\n float rad = direction * 3.14159 / 180.0;\n float2 src_point = float2(0.5 + sin(rad) * 0.3, -0.05);\n float2 toFrag = uv - src_point;\n float angle = atan(toFrag.x, toFrag.y);\n float shafts = 0.5 + 0.5 * sin(angle * 8.0);\n float dist = length(toFrag);\n float falloff = 1.0 - smoothstep(0.0, 1.2, dist);\n half shaft = half(shafts * falloff * intensity * 0.35);\n return half4(scene.rgb + half3(shaft * 0.95, shaft * 0.9, shaft * 0.6), scene.a);\n}\n";
|
|
259
|
+
declare const WEATHER_DARK_VIGNETTE_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n float2 uv = coord / resolution - 0.5;\n float d = length(uv);\n float v = smoothstep(0.3, 0.85, d);\n half dark = half(v * intensity);\n return half4(scene.rgb * (1.0 - dark), scene.a);\n}\n";
|
|
260
|
+
declare const WEATHER_CLOUDS_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float scrollOffsetX;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\nfloat vnoise(float2 p) {\n float2 i = floor(p); float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),\n mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);\n}\nfloat fbm(float2 p) { return vnoise(p) * 0.6 + vnoise(p * 2.3) * 0.4; }\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n float2 uv = coord / resolution;\n float2 cloudUv = float2(uv.x + scrollOffsetX / resolution.x, uv.y * 0.5);\n float cloud = fbm(cloudUv * 3.0);\n float heightFade = smoothstep(0.6, 0.2, uv.y);\n float alpha = cloud * heightFade * intensity * 0.5;\n half3 cloudColor = half3(0.9, 0.9, 0.95);\n return half4(mix(scene.rgb, cloudColor, half(alpha)), scene.a);\n}\n";
|
|
261
|
+
declare const COMPOSITE_SKSL = "\nuniform shader src;\n\nuniform float2 resolution;\nuniform float vignetteIntensity;\nuniform float vignetteRadius;\nuniform float3 vignetteColor;\nuniform float bloomThreshold;\nuniform float bloomIntensity;\nuniform float flashIntensity;\nuniform float chromaAmount;\nuniform float crtScanlineIntensity;\nuniform float crtCurvature;\nuniform float crtEnabled;\nuniform float weatherKind;\nuniform float weatherIntensity;\nuniform float weatherDirection;\nuniform float weatherScrollX;\nuniform float weatherScrollY;\nuniform float3 weatherColor;\nuniform float weatherDensity;\nuniform float weatherStreakLen;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\nfloat vnoise(float2 p) {\n float2 i = floor(p); float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),\n mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);\n}\nfloat fbm(float2 p) { return vnoise(p) * 0.6 + vnoise(p * 2.3) * 0.4; }\n\nhalf4 main(float2 coord) {\n // --- chromatic aberration ---\n float2 uv = coord / resolution;\n float2 chromaOffset = (uv - 0.5) * chromaAmount;\n half r = src.eval((uv - chromaOffset) * resolution).r;\n half g = src.eval(coord).g;\n half b = src.eval((uv + chromaOffset) * resolution).b;\n half3 c = half3(r, g, b);\n\n // --- CRT ---\n float2 crtCentered = uv - 0.5;\n float2 crtWarp = crtCentered * (1.0 + dot(crtCentered, crtCentered) * crtCurvature);\n float2 crtUv = crtWarp + 0.5;\n float crtMask = float(crtUv.x >= 0.0 && crtUv.x <= 1.0 && crtUv.y >= 0.0 && crtUv.y <= 1.0);\n {\n float2 chromaOffsetCrt = (crtUv - 0.5) * chromaAmount;\n half rCrt = src.eval((crtUv - chromaOffsetCrt) * resolution).r;\n half gCrt = src.eval(crtUv * resolution).g;\n half bCrt = src.eval((crtUv + chromaOffsetCrt) * resolution).b;\n half3 crtColor = half3(rCrt, gCrt, bCrt);\n float scan = 0.5 + 0.5 * sin(crtUv.y * resolution.y * 3.14159);\n float dim = mix(1.0, scan, crtScanlineIntensity);\n half3 crtResult = crtColor * half(dim);\n c = mix(c, mix(half3(0.0), crtResult, half(crtMask)), half(crtEnabled));\n }\n\n // --- bloom ---\n float luma = dot(c, half3(0.299, 0.587, 0.114));\n float bloomMask = smoothstep(bloomThreshold, 1.0, luma);\n c = c + c * half(bloomMask * bloomIntensity);\n\n // --- weather ---\n {\n float wk = weatherKind;\n if (wk >= 0.5 && wk < 1.5) {\n // rain \u2014 falling sawtooth streaks, 2 parallax layers, alpha-over blend\n // weatherScrollY is normalized [0,1) fall phase (increment speed*dt/screenH each tick)\n float rad = weatherDirection * 3.14159 / 180.0;\n float sinA = sin(rad);\n float cosA = cos(rad);\n float aspect = resolution.x / resolution.y;\n float perpAxis = (cosA * uv.x - sinA * uv.y) * aspect;\n float fallAxis = sinA * uv.x + cosA * uv.y;\n\n float coverage = 0.0;\n\n // near layer \u2014 COLS=45, tiling=6, brightness=1.0\n {\n float COLS = 45.0;\n float tiling = 6.0;\n float brightness = 1.0;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 0.0));\n if (colHash < weatherDensity) {\n float phase = hash(float2(cid, 1.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 2.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 3.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - weatherScrollY + phase);\n float coverageY = smoothstep(weatherStreakLen, 0.0, seg);\n float width = 0.04;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n // far layer \u2014 COLS=85, tiling=11, brightness=0.5\n {\n float COLS = 85.0;\n float tiling = 11.0;\n float brightness = 0.5;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 10.0));\n if (colHash < weatherDensity) {\n float phase = hash(float2(cid, 11.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 12.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 13.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - weatherScrollY + phase);\n float coverageY = smoothstep(weatherStreakLen, 0.0, seg);\n float width = 0.03;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n c = mix(c, half3(weatherColor), half(coverage * weatherIntensity));\n } else if (wk >= 1.5 && wk < 2.5) {\n // sun-shaft\n float rad = weatherDirection * 3.14159 / 180.0;\n float2 srcPoint = float2(0.5 + sin(rad) * 0.3, -0.05);\n float2 toFrag = uv - srcPoint;\n float angle = atan(toFrag.x, toFrag.y);\n float shafts = 0.5 + 0.5 * sin(angle * 8.0);\n float dist = length(toFrag);\n float falloff = 1.0 - smoothstep(0.0, 1.2, dist);\n half shaft = half(shafts * falloff * weatherIntensity * 0.35);\n c = c + half3(shaft * 0.95, shaft * 0.9, shaft * 0.6);\n } else if (wk >= 2.5 && wk < 3.5) {\n // dark-vignette\n float2 uvShift = uv - 0.5;\n float d2 = length(uvShift);\n float vd = smoothstep(0.3, 0.85, d2);\n half dark = half(vd * weatherIntensity);\n c = c * (1.0 - dark);\n } else if (wk >= 3.5) {\n // clouds\n float2 cloudUv = float2(uv.x + weatherScrollX / resolution.x, uv.y * 0.5);\n float cloud = fbm(cloudUv * 3.0);\n float heightFade = smoothstep(0.6, 0.2, uv.y);\n float alpha = cloud * heightFade * weatherIntensity * 0.5;\n half3 cloudColor = half3(0.9, 0.9, 0.95);\n c = mix(c, cloudColor, half(alpha));\n }\n }\n\n // --- grayscale flash ---\n float lumaFlash = dot(c, half3(0.299, 0.587, 0.114));\n half3 gray = half3(lumaFlash);\n c = mix(c, gray, half(flashIntensity));\n\n // --- vignette ---\n float2 vigCentered = uv - 0.5;\n float vigD = length(vigCentered);\n float vigV = smoothstep(vignetteRadius, vignetteRadius * 0.5, vigD);\n c = mix(half3(vignetteColor), c, half(mix(1.0 - vignetteIntensity, 1.0, vigV)));\n\n return half4(c, 1.0);\n}\n";
|
|
262
|
+
declare const GOD_RAYS_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float2 u_lightPos;\nuniform float3 u_color;\nuniform float u_intensity;\nuniform float u_decay;\nuniform float u_density;\nuniform float u_weight;\nuniform float u_samples;\n\nconst int MAX_SAMPLES = 64;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / resolution;\n float2 delta = (u_lightPos - uv) * u_density / float(MAX_SAMPLES);\n float2 sampleUv = uv;\n float illuminationDecay = 1.0;\n half3 accumulated = half3(0.0);\n\n for (int i = 0; i < MAX_SAMPLES; i++) {\n if (float(i) >= u_samples) break;\n sampleUv += delta;\n half3 s = src.eval(sampleUv * resolution).rgb;\n accumulated += s * half(illuminationDecay) * half(u_weight);\n illuminationDecay *= u_decay;\n }\n\n half4 orig = src.eval(coord);\n half3 rays = accumulated * half3(u_color) * half(u_weight) * half(u_intensity);\n return half4(orig.rgb + rays, orig.a);\n}\n";
|
|
263
|
+
|
|
264
|
+
declare function createNeutralLutBytes(): Uint8Array;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* PostFxController — a small bridge that owns an EffectChain + PostFxRuntime
|
|
268
|
+
* and adds two game-common conveniences:
|
|
269
|
+
*
|
|
270
|
+
* - hit-flash: spike a grayscale-flash effect to 1 and auto-decay it.
|
|
271
|
+
* - motion scale: forward a single scalar to `EffectChain.setGlobalIntensity`
|
|
272
|
+
* so a11y reduced-motion can null the whole stack at once.
|
|
273
|
+
*
|
|
274
|
+
* Extracted from Pan Tvardowski per Phase 11.14. Shape-compatible with the
|
|
275
|
+
* PT `createPanTvardowskiPostFx` controller — games can swap implementations.
|
|
276
|
+
*/
|
|
277
|
+
|
|
278
|
+
interface PostFxFlashConfig {
|
|
279
|
+
/** Id of a `grayscale-flash` effect already added to the chain. */
|
|
280
|
+
readonly id: string;
|
|
281
|
+
/** Decay rate in intensity units per second. Defaults to 4.2 (≈0.24 s to fade from 1). */
|
|
282
|
+
readonly decay?: number;
|
|
283
|
+
}
|
|
284
|
+
interface PostFxControllerOptions {
|
|
285
|
+
/** Effects appended to the internal chain in the given order. */
|
|
286
|
+
readonly effects: readonly Effect[];
|
|
287
|
+
/** Skia module — `null` / `undefined` keeps `buildPass` returning `[]`. */
|
|
288
|
+
readonly skia?: SkiaPostFxModuleLike | null;
|
|
289
|
+
/** Optional hit-flash binding. */
|
|
290
|
+
readonly flash?: PostFxFlashConfig;
|
|
291
|
+
}
|
|
292
|
+
interface PostFxController {
|
|
293
|
+
readonly chain: EffectChain;
|
|
294
|
+
readonly runtime: PostFxRuntime;
|
|
295
|
+
/** Spike the configured flash effect to 1. No-op if no flash configured. */
|
|
296
|
+
triggerHitFlash(): void;
|
|
297
|
+
/**
|
|
298
|
+
* Enable `effectId` immediately and auto-disable it after `decayMs` ms of
|
|
299
|
+
* accumulated `update(dt)` time. Uniforms are restored to the pre-trigger
|
|
300
|
+
* snapshot on expiry. Re-triggering before expiry resets the countdown.
|
|
301
|
+
* No-op if `effectId` is not in the chain.
|
|
302
|
+
*/
|
|
303
|
+
/** Optional pending PT wire-up in session #196. */
|
|
304
|
+
triggerTimed?(effectId: string, decayMs: number): void;
|
|
305
|
+
/** Advance auto-decaying effects (flash + timed triggers). */
|
|
306
|
+
update(dt: number): void;
|
|
307
|
+
/** Build the per-frame compiled pass list. Empty if runtime is unsupported. */
|
|
308
|
+
buildPass(width: number, height: number): readonly CompiledPass[];
|
|
309
|
+
/**
|
|
310
|
+
* Global intensity multiplier forwarded to the chain — intended for a11y
|
|
311
|
+
* reduced-motion (0 = fully neutralized, 1 = default).
|
|
312
|
+
*/
|
|
313
|
+
setMotionScale(k: number): void;
|
|
314
|
+
}
|
|
315
|
+
declare function createPostFxController(options: PostFxControllerOptions): PostFxController;
|
|
316
|
+
|
|
317
|
+
export { BLOOM_SKSL, type BloomUniforms, CHROMATIC_ABERRATION_SKSL, COLOR_GRADING_LUT_SKSL, COMPOSITE_SKSL, CRT_SKSL, type ChromaticAberrationUniforms, type ColorGradingLutUniforms, type CompiledPass, type CompiledShader, type CompositeUniforms, type CrtUniforms, type Effect, EffectChain, type EffectKind, type EffectUniforms, GOD_RAYS_SKSL, GRAYSCALE_FLASH_SKSL, type GodRaysUniforms, type GrayscaleFlashUniforms, type PostFxController, type PostFxControllerOptions, type PostFxFlashConfig, type PostFxRuntime, type SkImageLike, type SkiaPostFxModuleLike, type SkiaRuntimeEffectFactory, type SkiaRuntimeEffectLike, VIGNETTE_SKSL, type VignetteUniforms, WEATHER_CLOUDS_SKSL, WEATHER_DARK_VIGNETTE_SKSL, WEATHER_RAIN_SKSL, WEATHER_SUN_SHAFT_SKSL, type WeatherCloudsUniforms, type WeatherDarkVignetteUniforms, type WeatherRainUniforms, type WeatherSunShaftUniforms, createBloom, createChromaticAberration, createColorGradingLut, createCrt, createGodRays, createGrayscaleFlash, createNeutralLutBytes, createPostFxComposite, createPostFxController, createPostFxRuntime, createVignette, createWeatherClouds, createWeatherDarkVignette, createWeatherRain, createWeatherSunShaft };
|