← ソースコード説明書

packages/web — フィルター & エフェクト実装(GLSL + JSON)

作成日: 2026-06-10 / 更新日: 2026-06-10

スクリーンフィルター(GLSL)と JSON 宣言型エフェクト(effect.json)の実装。概要は 09-pkg-web-engine.md、コマンドは 07-pkg-compiler.md、ネイティブ側は 13-pkg-native-engine.md を参照。

概要

  • GLSL フィルター:約 48 種のスクリーンフィルター。ScreenFilter.ts が管理し、PixiJS の Container.filters に適用する。
  • JSON 宣言型エフェクトeffect.json で「素材+レイヤー+Timeline+画面効果」を宣言し、EffectPlayer が再生。@effect <id> から配線。
  • ネイティブ parityFilterShaders.hpp が同じフィルターを移植 GLSL で持ち、GLRenderer(OpenGL/GLES3)で適用。

1. GLSL フィルター(Web)

ScreenFilter.ts — 管理と適用

packages/web/src/renderer/ScreenFilter.ts が全フィルターの単一定義源。

  • createFilter(type, intensity, options)switch (type) で約 48 種のフィルター名 → Filter インスタンスを生成(factory。EffectPlayer がレイヤー別シェーダにも再利用)。
  • apply(type, intensity, options)stage.filters = [filter] で単一適用。
  • applyMix(layers, target) — 2 レイヤーを順次適用(stage.filters = [f1, f2] → texture→f1→f2→出力)。
  • applyColorAdjust(brightness, contrast, saturation, temperature) — 色調補正(後述の ColorMatrix 経路)。
  • 品質スケーリング:低品質時は重いフィルター(bloom/rain 等)を簡易版へ差し替え。
  • アニメ系:ticker が毎フレーム filter.time を更新(rain / snow など手続き的演出)。

filters/ — 各フィルタークラス

packages/web/src/renderer/filters/*.ts(約 38 クラス)。共通パターンは PixiJS Filter 派生+ GLSL フラグメント文字列

export class RainFilter extends Filter {
  constructor() {
    const glProgram = GlProgram.from({
      vertex: defaultFilterVertex,   // 中心基準は centeredFilterVertex
      fragment: RAIN_FRAG,           // GLSL 文字列
      name: "rain",
    });
    super({ glProgram, resources: { rainUniforms: new UniformGroup({
      uTime: { value: 0, type: "f32" }, uIntensity: { value: 1, type: "f32" }, /* ... */
    }) } });
  }
}
  • intensity:多くは 0..1uIntensity で強度を掛ける(rain *= uIntensity 等)。一部は別ユニフォームへマップ(GameBoy は contrast、CRT は曲率/走査線を個別公開)。
  • 例:RainFilter(手続き的多層雨、uTime/uSpeed/uAngle)、BloomFilter(7x7 ガウシアン+スポット、centeredFilterVertex)、CRTFilter(樽歪み+走査線+ビネット)。

ColorMatrix → 単一 GLSL(色調系)

色調系フィルターは個別シェーダではなく、CPU で 4x5 カラーマトリクスを組み、1 本の GLSL(ColorTransformFilter)で適用する。

  • filters/colorMatrices.tssepiaMatrix() / warmPattern() / colorAdjustMatrix(...) 等(CPU 計算)。
  • filters/ColorTransformFilter.ts — 行列+オフセットを 5 つの vec4 ユニフォームで受け、outColor = M·color + offset を計算する単一フラグメント。
  • 理由:行列をフラグメントで毎回組むと 1280×720×60 ≈ 5500 万回/秒の無駄になり、型ごとに分岐してシェーダが膨らむ。CPU で 1 回組めば「1 シェーダで色調系をまとめて表現」できる。
  • 対象(約 10):sepia / grayscale / desaturate / warm / cool / vivid / muted / bright / dark / overcast
  • applyColorAdjustcolorAdjustMatrix(brightness, contrast, saturation, temperature)(各 -100..100、0 で no-op)を合成して同じ経路で適用。

PC98Filter.ts

packages/web/src/renderer/PC98Filter.ts — PC-98 風レトロ。16 色パレット / 256 色 RGB332、2x2・4x4 オーダードディザ、PC-98 ピクセル単位での量子化(640×360 相当)。

2. JSON 宣言型エフェクト

スキーマ(effectTypes.ts)

packages/web/src/effects/effectTypes.tseffect.json は「素材+レイヤー+既存 Timeline+画面効果」を束ねる合成コンテナ

interface EffectDef {
  id: string;
  durationMs: number;
  assets: EffectAssetsDef;       // textureSequences / particles / meshes
  layers: EffectLayerDef[];      // 描画レイヤー
  timeline?: TimelineRoot;       // ★ @kaedevn/core の Timeline v1.1 を再利用
  screen?: EffectScreenDef;      // flash / shake / 全画面 filter
}
interface EffectLayerDef {
  name: string;                  // timeline の targetId
  kind: "sprite" | "spriteSequence" | "particle" | "mesh";
  source: string;                // assets キー
  blend?: "normal" | "add" | "multiply" | "screen";
  shader?: string;               // FilterType 名(レイヤー別 GLSL)
  shaderParams?: Record<string, number>;
  x?: number; y?: number; scale?: number; anchor?: [number, number]; alpha?: number;
}
  • Timeline 再利用:独自タイムラインは持たず、Timeline v1.1(frame/x/y/scale/alpha/shaderParams.<name> チャンネル、フレーム保持は easing: "step")をそのまま使う。
  • mesh レイヤー:GLB を Filament で描画(3D の閃光・グロー)。sprite/particle は PixiJS。

再生(EffectPlayer / EffectClipDelegate)

  • effects/EffectPlayer.tsload(def, basePath, screenTarget, overrides) で素材解決→レイヤー構築→レイヤー別シェーダを ScreenFilter.createFilter() で装着→screen.filter を全画面に適用。play() + ticker で Timeline を評価し、テクスチャ/座標/ユニフォームを更新、完了で破棄。
  • effects/MeshEffectRenderer.ts — mesh レイヤーの Filament 描画。

@effect の配線

@effect fire_full_01 scale=1.5 intensity=2
  → EFFECT_PLAY op(commandRegistry.ts)
  → EffectClipDelegate.effectPlay(id, opts)(packages/web/src/renderer/delegates/)
    → /assets/effects/{id}.effect.json を fetch
    → EffectPlayer.load(...)(テクスチャ/パーティクルは json 相対で解決=可搬バンドル)
    → overlayLayer(シーンと UI の間)に追加、screen.filter は全シーンへ
    → player.play() を ticker 登録、wait=true なら完了待ち

effect.jsonpackages/web/public/assets/effects/ に 5 本(fire_full_01 / explosion_01 / smoke_01 / heal_01 / fire/fire)。

3. ネイティブ GLSL(parity)

  • packages/native-engine/src/engine/FilterShaders.hpp — Web と同じフィルターの移植 GLSL(ヘッダ内インライン文字列)。移植ルール:#version 300 es → 330 coreprecision mediump 除去、vTextureCoord → vUVfinalColor → fragColor、premultiplied→straight alpha。
  • 自動バインドされるユニフォーム:uTexture / uIntensity / uTime / uResolution。多パラメータ系は現状 const 既定値(GLRenderer のパラメータ API は今後拡張余地)。
  • filterDefaults.cpp/.hpp — 各フィルターの既定パラメータ(Web コンストラクタ既定と対応)と mergeDefaults(user, defaults)
  • GLRenderer(OpenGL 3.3 / GLES3)が同じフィルター集合を適用=Web/ネイティブで見た目を揃える。

4. 数とフェーズ

  • フィルター約 48 種ScreenFilter.ts の switch)。内訳の目安:色調系 約 10(ColorMatrix→単一 GLSL)+ 専用 GLSL クラス 約 38filters/*.ts)。
  • フェーズ移行:Phase 1=色調系を ColorMatrix 化、Phase 2=時間帯・雰囲気・季節フィルターを専用 GLSL クラス化(overcast 等一部は ColorMatrix のまま)。applyMix は行列合成をやめ順次適用に変更(分類漏れ・後勝ちの解消)。
  • effect.json 5 本(いずれも Timeline v1.1 でフレーム/アニメ制御、particle+sprite+任意の screen.filter)。

主要ファイル

ファイル役割
renderer/ScreenFilter.tsフィルター管理・適用・factory(48 種 switch)
renderer/filters/*.ts個別 GLSL フィルター(約 38 クラス)
renderer/filters/colorMatrices.ts色調系の CPU カラーマトリクス計算
renderer/filters/ColorTransformFilter.ts行列適用の単一 GLSL
renderer/PC98Filter.tsPC-98 レトロ(パレット/ディザ)
effects/effectTypes.tseffect.json スキーマ
effects/EffectPlayer.tseffect.json の読込・再生
effects/MeshEffectRenderer.tsmesh レイヤーの Filament 描画
renderer/delegates/EffectClipDelegate.ts@effect(EFFECT_PLAY)配線
public/assets/effects/*.effect.json宣言型エフェクト定義(5 本)
native-engine/src/engine/FilterShaders.hppネイティブ移植 GLSL
native-engine/src/engine/filterDefaults.cppネイティブ既定パラメータ
Ad: stickyBottom (728x90)
kaedevn - ノベルゲームを作れるプラットフォーム