强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Pixi.js 游戏开发教程 / Pixi.js 滤镜系统(Filters)

16. 滤镜系统(Filters)

概述

Pixi.js 的滤镜系统基于 GLSL 着色器实现,可以对显示对象应用各种视觉效果。滤镜是创建光影、后处理、UI 特效的核心工具。

内置滤镜一览

Pixi.js 提供了丰富的内置滤镜:

滤镜用途常用参数
BlurFilter高斯模糊strength, quality, kernelSize
ColorMatrixFilter颜色变换矩阵brightness, contrast, saturate, hue
NoiseFilter噪点效果noise, seed
DisplacementFilter位移扭曲scale, displacement sprite
AlphaFilter透明度alpha

Pixi.js v8 中额外需要安装 @pixi/filter-* 包来使用更多滤镜:

npm install @pixi/filter-blur @pixi/filter-color-matrix \
            @pixi/filter-noise @pixi/filter-displacement

BlurFilter 高斯模糊

import { BlurFilter, Sprite, Container } from 'pixi.js';

const sprite = Sprite.from('/image/background.png');

const blur = new BlurFilter();
blur.strength = 8;       // 模糊强度
blur.quality = 4;        // 质量 (1-10)
blur.repeatEdgePixels = true; // 边缘像素重复

sprite.filters = [blur];
app.stage.addChild(sprite);

游戏场景:背景虚化

// 对话框弹出时背景虚化
class DialogManager {
    constructor(gameWorld, uiLayer) {
        this.gameWorld = gameWorld;
        this.uiLayer = uiLayer;
        this.blurFilter = new BlurFilter({ strength: 0 });
        this.gameWorld.filters = [this.blurFilter];
    }

    async showDialog(content) {
        // 淡入模糊
        await this.animateBlur(0, 5, 300);
        // 显示对话框
        const dialog = this.createDialog(content);
        this.uiLayer.addChild(dialog);
        await this.waitForDismiss();
        dialog.destroy();
        // 淡出模糊
        await this.animateBlur(5, 0, 300);
    }

    animateBlur(from, to, duration) {
        return new Promise((resolve) => {
            const start = performance.now();
            const tick = () => {
                const elapsed = performance.now() - start;
                const t = Math.min(1, elapsed / duration);
                this.blurFilter.strength = from + (to - from) * this.easeOut(t);
                if (t < 1) requestAnimationFrame(tick);
                else resolve();
            };
            tick();
        });
    }

    easeOut(t) { return 1 - Math.pow(1 - t, 3); }
}

ColorMatrixFilter 颜色矩阵

import { ColorMatrixFilter } from 'pixi.js';

const colorMatrix = new ColorMatrixFilter();

// 基础操作
colorMatrix.brightness(0.8, false);  // 亮度 (0-1)
colorMatrix.contrast(0.5, false);    // 对比度 (-1 到 1)
colorMatrix.saturate(0.3, false);    // 饱和度 (0 = 灰度)
colorMatrix.hue(120, false);         // 色相旋转 (0-360 度)
colorMatrix.negative(false);         // 反色

// 组合操作(矩阵叠加)
colorMatrix.vintage(false);          // 复古风格
colorMatrix.desaturate();            // 去饱和

// 应用
sprite.filters = [colorMatrix];

// 重置
colorMatrix.reset();

游戏场景:受伤闪红

class DamageFlash {
    constructor(sprite) {
        this.sprite = sprite;
        this.colorMatrix = new ColorMatrixFilter();
        this.flashTime = 0;
        this.flashDuration = 0.2;
        this.isFlashing = false;
    }

    trigger() {
        this.isFlashing = true;
        this.flashTime = 0;

        // 确保滤镜列表中包含 colorMatrix
        if (!this.sprite.filters) this.sprite.filters = [];
        if (!this.sprite.filters.includes(this.colorMatrix)) {
            this.sprite.filters.push(this.colorMatrix);
        }
    }

    update(dt) {
        if (!this.isFlashing) return;

        this.flashTime += dt;
        const t = this.flashTime / this.flashDuration;

        if (t >= 1) {
            this.isFlashing = false;
            this.colorMatrix.reset();
            // 移除滤镜
            this.sprite.filters = this.sprite.filters.filter(f => f !== this.colorMatrix);
            return;
        }

        // 闪烁效果
        const flash = Math.sin(t * Math.PI * 4) * (1 - t);
        this.colorMatrix.tint(0xff0000, false);
        // 叠加红色
        this.colorMatrix.matrix[0] = 1 + flash;   // R 通道增强
        this.colorMatrix.matrix[6] = 1 - flash * 0.5; // G 通道减弱
        this.colorMatrix.matrix[12] = 1 - flash * 0.5; // B 通道减弱
    }
}

// 使用
const damageFlash = new DamageFlash(playerSprite);

player.on('damage', () => damageFlash.trigger());

app.ticker.add((ticker) => {
    damageFlash.update(ticker.deltaMS * 0.001);
});

DisplacementFilter 位移扭曲

位移滤镜使用一张纹理(displacement map)的颜色值来偏移像素,常用于水波、热浪等效果:

import { DisplacementFilter, Sprite } from 'pixi.js';

// 加载位移纹理(通常是灰度图)
const displacementSprite = Sprite.from('/image/water-displacement.png');
displacementSprite.texture.baseTexture.wrapMode = 'repeat';
app.stage.addChild(displacementSprite);

const displacementFilter = new DisplacementFilter({
    sprite: displacementSprite,
    scale: { x: 20, y: 20 }, // 扭曲强度
});

sprite.filters = [displacementFilter];

// 动态动画:移动位移纹理产生流动效果
app.ticker.add(() => {
    displacementSprite.x += 1;
    displacementSprite.y += 0.5;
});

游戏场景:热浪扭曲

// 火焰上方的空气热浪扭曲
const heatSprite = Sprite.from('/image/noise.png');
heatSprite.texture.baseTexture.wrapMode = 'repeat';

const heatFilter = new DisplacementFilter({
    sprite: heatSprite,
    scale: { x: 10, y: 10 },
});

fireEmitterContainer.filters = [heatFilter];

app.ticker.add(() => {
    heatSprite.y -= 2; // 向上移动产生上升热浪效果
    heatSprite.x += Math.sin(heatSprite.y * 0.01) * 0.5;
});

NoiseFilter 噪点效果

import { NoiseFilter } from 'pixi.js';

const noiseFilter = new NoiseFilter({
    noise: 0.3,  // 噪点强度 (0-1)
    seed: Math.random(),
});

sprite.filters = [noiseFilter];

// 动态噪点(每帧更换 seed)
app.ticker.add(() => {
    noiseFilter.seed = Math.random();
});

GlowFilter 发光效果

npm install @pixi/filter-glow
import { GlowFilter } from '@pixi/filter-glow';

const glow = new GlowFilter({
    distance: 15,
    outerStrength: 2,
    innerStrength: 0,
    color: 0x00aaff,
    quality: 0.5,
});

sprite.filters = [glow];

// 脉冲发光动画
app.ticker.add((ticker) => {
    const time = ticker.lastTime * 0.002;
    glow.outerStrength = 1.5 + Math.sin(time) * 0.5;
});

滤镜堆叠

多个滤镜可以叠加使用,按数组顺序依次执行:

const blurFilter = new BlurFilter({ strength: 2 });
const colorFilter = new ColorMatrixFilter();
colorFilter.saturate(1.5, false);
const noiseFilter = new NoiseFilter({ noise: 0.1 });

// 按顺序执行:先模糊 → 饱和度调整 → 加噪点
sprite.filters = [blurFilter, colorFilter, noiseFilter];

⚠️ 注意:滤镜越多,GPU 开销越大。每个滤镜需要一次额外的渲染 Pass。建议同一对象的滤镜不超过 3-4 个。

自定义 GLSL 着色器滤镜

创建自定义滤镜需要编写 GLSL 顶点和片段着色器:

import { Filter } from 'pixi.js';

// 自定义像素化滤镜
const pixelateFrag = `
    precision mediump float;

    varying vec2 vTextureCoord;
    uniform sampler2D uTexture;
    uniform vec2 uResolution;
    uniform float uPixelSize;

    void main() {
        vec2 pixelSize = vec2(uPixelSize) / uResolution;
        vec2 coord = floor(vTextureCoord / pixelSize) * pixelSize + pixelSize * 0.5;
        gl_FragColor = texture2D(uTexture, coord);
    }
`;

const pixelateFilter = new Filter({
    glProgram: undefined, // v8 使用 GlProgram
    // 简化写法:
    fragment: pixelateFrag,
    uniforms: {
        uResolution: [800, 600],
        uPixelSize: 8.0,
    },
});

sprite.filters = [pixelateFilter];

自定义灰度 + 色调滤镜

const tintFrag = `
    precision mediump float;

    varying vec2 vTextureCoord;
    uniform sampler2D uTexture;
    uniform float uGrayAmount;
    uniform vec3 uTintColor;

    void main() {
        vec4 color = texture2D(uTexture, vTextureCoord);
        float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
        vec3 grayColor = vec3(gray);
        color.rgb = mix(color.rgb, grayColor, uGrayAmount);
        color.rgb *= uTintColor;
        gl_FragColor = color;
    }
`;

const tintFilter = new Filter({
    fragment: tintFrag,
    uniforms: {
        uGrayAmount: 0.5,
        uTintColor: [1.0, 0.8, 0.6],
    },
});

💡 提示:编写自定义滤镜时,务必在着色器顶部声明 precision mediump float;,否则在某些设备上会编译失败。

滤镜性能影响

操作GPU 开销建议
BlurFilter高(多次 Pass)降低 quality/使用缓存
ColorMatrixFilter低(单次 Pass)可放心使用
NoiseFilter可放心使用
DisplacementFilter控制纹理尺寸
多个滤镜叠加累加合并到自定义着色器

性能优化技巧

// 1. 使用 filterArea 限制滤镜作用区域(减少渲染面积)
sprite.filterArea = new Rectangle(100, 100, 400, 300);

// 2. 对静态内容使用 cacheAsBitmap + 滤镜
const staticUI = new Container();
staticUI.filters = [new BlurFilter({ strength: 5 })];
staticUI.cacheAsBitmap = true; // 滤镜效果烘焙到缓存

// 3. 不可见时移除滤镜
if (!sprite.visible) {
    sprite.filters = null; // 避免空跑滤镜
}

// 4. 降低模糊质量
const blur = new BlurFilter({ strength: 12, quality: 2 }); // quality 2 比 8 快很多

局部滤镜 vs 全局滤镜

局部滤镜(对象级)

// 只影响单个对象或容器
sprite.filters = [new BlurFilter({ strength: 5 })];

全局滤镜(后处理)

// 影响整个场景(后处理效果)
const vignetteFrag = `
    precision mediump float;
    varying vec2 vTextureCoord;
    uniform sampler2D uTexture;
    uniform vec2 uResolution;

    void main() {
        vec4 color = texture2D(uTexture, vTextureCoord);
        vec2 uv = vTextureCoord - 0.5;
        float dist = length(uv);
        float vignette = smoothstep(0.7, 0.3, dist);
        color.rgb *= mix(0.3, 1.0, vignette);
        gl_FragColor = color;
    }
`;

const vignetteFilter = new Filter({
    fragment: vignetteFrag,
    uniforms: { uResolution: [800, 600] },
});

// 对 stage 应用后处理
app.stage.filters = [vignetteFilter];

黑白/复古/像素化效果

// 黑白效果
function createBWFilter(amount = 1) {
    const filter = new ColorMatrixFilter();
    filter.saturate(amount - 1, false); // 0 = 完全灰度
    return filter;
}

// 复古效果(Warm Vintage)
function createVintageFilter() {
    const filter = new ColorMatrixFilter();
    // 降低饱和度
    filter.saturate(-0.3, true);
    // 增加暖色调
    filter.matrix[4] += 0.05;  // R 偏移
    filter.matrix[9] += 0.02;  // G 偏移
    return filter;
}

// 像素化效果
function createPixelateFilter(pixelSize = 4) {
    const frag = `
        precision mediump float;
        varying vec2 vTextureCoord;
        uniform sampler2D uTexture;
        uniform vec2 uResolution;
        uniform float uPixelSize;
        void main() {
            vec2 pixel = vec2(uPixelSize) / uResolution;
            vec2 coord = floor(vTextureCoord / pixel) * pixel + pixel * 0.5;
            gl_FragColor = texture2D(uTexture, coord);
        }
    `;
    return new Filter({
        fragment: frag,
        uniforms: {
            uResolution: [app.screen.width, app.screen.height],
            uPixelSize: pixelSize,
        },
    });
}

实时滤镜动画

// 溶解效果(用于场景过渡)
const dissolveFrag = `
    precision mediump float;
    varying vec2 vTextureCoord;
    uniform sampler2D uTexture;
    uniform float uProgress;
    uniform sampler2D uNoiseMap;

    void main() {
        vec4 color = texture2D(uTexture, vTextureCoord);
        float noise = texture2D(uNoiseMap, vTextureCoord).r;
        float threshold = uProgress;
        if (noise < threshold) discard;
        // 边缘发光
        float edge = smoothstep(threshold, threshold + 0.05, noise);
        color.rgb += vec3(1.0, 0.8, 0.3) * (1.0 - edge) * 0.5;
        gl_FragColor = color;
    }
`;

const dissolveFilter = new Filter({
    fragment: dissolveFrag,
    uniforms: {
        uProgress: 0.0,
        uNoiseMap: Texture.from('/image/noise.png'),
    },
});

// 动画溶解
async function dissolveTransition(fromScene, toScene) {
    fromScene.filters = [dissolveFilter];

    return new Promise((resolve) => {
        const start = performance.now();
        const duration = 1500;
        const tick = () => {
            const t = (performance.now() - start) / duration;
            dissolveFilter.uniforms.uProgress = Math.min(1, t);
            if (t < 1) requestAnimationFrame(tick);
            else {
                fromScene.filters = [];
                resolve();
            }
        };
        tick();
    });
}

后处理效果链

// 构建后处理管线
class PostProcessPipeline {
    constructor(app) {
        this.app = app;
        this.filters = [];
    }

    add(filter) {
        this.filters.push(filter);
        this.app.stage.filters = [...this.filters];
        return this;
    }

    remove(filter) {
        this.filters = this.filters.filter(f => f !== filter);
        this.app.stage.filters = this.filters.length > 0 ? [...this.filters] : null;
    }

    clear() {
        this.filters = [];
        this.app.stage.filters = null;
    }
}

// 使用示例:添加 CRT 显示器效果
const pipeline = new PostProcessPipeline(app);
pipeline.add(createPixelateFilter(2));
pipeline.add(createVintageFilter());
pipeline.add(vignetteFilter);

💡 提示:后处理效果链会显著影响性能,移动端建议控制在 2 个滤镜以内。

扩展阅读