Newer
Older
rpg / v2 / apps / game / src / screenShader.ts
/**
 * Экранные шейдеры игры (пост-проход движка): виньетка — глубина кадра,
 * усиливается к ночи; лёгкое статичное зерно — «плёнка» поверх пиксель-
 * арта; дисторсия «жары» — только в духотных областях (зал Машины).
 * Юниформы тикает 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;
    }
}