/**
* Экранные шейдеры игры (пост-проход движка): виньетка — глубина кадра,
* усиливается к ночи; лёгкое статичное зерно — «плёнка» поверх пиксель-
* арта; дисторсия «жары» — только в духотных областях (зал Машины).
* Юниформы тикает tick: при выключенной атмосфере (мост setAsh(false) —
* режим кадр-равенств гейтов) uTime заморожен и дисторсия гаснет — кадр
* становится полностью детерминированным.
*/
import { postPass, type PostPassHandle, type VoxelRenderer } from '@rpg/engine';
const VIGNETTE_DAY = 0.32; // днём — лёгкая глубина кадра
const VIGNETTE_NIGHT = 0.5; // к ночи края сжимаются (свет ламп не в кадре)
const GRAIN = 0.02; // едва заметное зерно
const WOBBLE_AMP = 0.004; // амплитуда дрожания жары (доля высоты кадра)
/** Экранный пост-проход игры: один на рендерер, юниформы меняются на тике. */
export class PostFx {
private handle: PostPassHandle | null;
constructor(renderer: VoxelRenderer, /** дисторсия жары включена (зал Машины) */ wobble: boolean) {
const spec = postPass({
vignette: VIGNETTE_DAY,
grain: GRAIN,
wobble: wobble ? WOBBLE_AMP : undefined,
});
this.handle = renderer.setPostPass(spec.fragment, spec.uniforms);
}
/** Поза прохода: ночь усиливает виньетку; active=false — заморозка (гейты). */
update(nightK: number, time: number, active: boolean): void {
if (!this.handle) return;
const u = this.handle.uniforms;
u.uVignette!.value = VIGNETTE_DAY + (VIGNETTE_NIGHT - VIGNETTE_DAY) * nightK;
u.uTime!.value = active ? time : 0;
if (u.uWobble) u.uWobble.value = active ? WOBBLE_AMP : 0;
}
dispose(): void {
this.handle?.dispose();
this.handle = null;
}
}