Skip to content

第11章:滤镜与特效

11.1 章节概述

滤镜(Filter)是 PixiJS 中实现视觉特效的强大工具。通过 GPU 着色器,可以实现模糊、发光、颜色调整等各种效果。

本章将深入讲解:

  • 滤镜基础:使用方法、性能考虑
  • 内置滤镜:BlurFilter、ColorMatrixFilter 等
  • 滤镜库:pixi-filters 扩展滤镜
  • 自定义滤镜:编写 GLSL 着色器
  • 滤镜组合:多滤镜叠加
  • 性能优化:滤镜使用最佳实践

11.2 滤镜基础

11.2.1 滤镜工作原理

滤镜工作原理

┌─────────────────────────────────────────────────────────────┐
│                    渲染流程                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  DisplayObject                                              │
│       │                                                     │
│       ▼                                                     │
│  ┌─────────────────────────────────────┐                   │
│  │     渲染到临时 RenderTexture        │                   │
│  └─────────────────────────────────────┘                   │
│       │                                                     │
│       ▼                                                     │
│  ┌─────────────────────────────────────┐                   │
│  │     应用 Filter(GPU 着色器)       │                   │
│  │     - 顶点着色器处理位置            │                   │
│  │     - 片段着色器处理颜色            │                   │
│  └─────────────────────────────────────┘                   │
│       │                                                     │
│       ▼                                                     │
│  ┌─────────────────────────────────────┐                   │
│  │     输出到屏幕或下一个滤镜          │                   │
│  └─────────────────────────────────────┘                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘


性能影响:
- 每个滤镜需要额外的渲染通道
- 需要创建临时纹理
- GPU 着色器计算

11.2.2 使用滤镜

typescript
/**
 * 基本滤镜使用
 */

// 创建滤镜
const blurFilter = new PIXI.BlurFilter();

// 应用到显示对象
sprite.filters = [blurFilter];

// 应用多个滤镜
sprite.filters = [blurFilter, colorFilter, glowFilter];

// 移除滤镜
sprite.filters = null;
// 或
sprite.filters = [];

// 动态修改滤镜参数
blurFilter.blur = 10;
blurFilter.quality = 4;

11.2.3 滤镜属性

typescript
/**
 * 滤镜通用属性
 */

const filter = new PIXI.BlurFilter();

// 启用/禁用
filter.enabled = true;

// 分辨率
filter.resolution = 1;  // 默认跟随渲染器

// 填充(扩展渲染区域)
filter.padding = 10;  // 防止边缘裁剪

// 自动适应
filter.autoFit = true;  // 自动计算 padding

// 混合模式
filter.blendMode = PIXI.BLEND_MODES.NORMAL;

11.3 内置滤镜

11.3.1 BlurFilter 模糊

typescript
/**
 * BlurFilter 模糊滤镜
 */

const blurFilter = new PIXI.BlurFilter();

// 属性
blurFilter.blur = 8;           // 模糊强度
blurFilter.blurX = 8;          // X 方向模糊
blurFilter.blurY = 8;          // Y 方向模糊
blurFilter.quality = 4;        // 质量(1-10)
blurFilter.repeatEdgePixels = false;  // 边缘像素重复

sprite.filters = [blurFilter];


// 动态模糊效果
let targetBlur = 0;
app.ticker.add(() => {
    blurFilter.blur += (targetBlur - blurFilter.blur) * 0.1;
});

// 鼠标悬停时取消模糊
sprite.on('pointerover', () => { targetBlur = 0; });
sprite.on('pointerout', () => { targetBlur = 8; });

11.3.2 AlphaFilter 透明度

typescript
/**
 * AlphaFilter 透明度滤镜
 */

const alphaFilter = new PIXI.AlphaFilter();

// 设置透明度
alphaFilter.alpha = 0.5;

sprite.filters = [alphaFilter];

// 与直接设置 alpha 的区别:
// sprite.alpha 影响子对象
// AlphaFilter 只影响当前对象的渲染结果

11.3.3 ColorMatrixFilter 颜色矩阵

typescript
/**
 * ColorMatrixFilter 颜色矩阵滤镜
 */

const colorMatrix = new PIXI.ColorMatrixFilter();

// 预设效果
colorMatrix.greyscale(0.5);      // 灰度
colorMatrix.sepia();             // 复古
colorMatrix.negative();          // 负片
colorMatrix.brightness(1.5);     // 亮度
colorMatrix.contrast(1.5);       // 对比度
colorMatrix.saturate(2);         // 饱和度
colorMatrix.desaturate();        // 去饱和
colorMatrix.hue(90);             // 色相旋转
colorMatrix.vintage();           // 复古风格
colorMatrix.kodachrome();        // 柯达色
colorMatrix.browni();            // 棕色调
colorMatrix.technicolor();       // 三色技术
colorMatrix.polaroid();          // 宝丽来
colorMatrix.lsd();               // 迷幻效果
colorMatrix.night(0.5);          // 夜视效果
colorMatrix.predator(0.5);       // 热成像效果
colorMatrix.toBGR();             // BGR 颜色交换

sprite.filters = [colorMatrix];


// 自定义颜色矩阵
colorMatrix.matrix = [
    1, 0, 0, 0, 0,  // R
    0, 1, 0, 0, 0,  // G
    0, 0, 1, 0, 0,  // B
    0, 0, 0, 1, 0   // A
];

// 重置
colorMatrix.reset();

11.3.4 NoiseFilter 噪点

typescript
/**
 * NoiseFilter 噪点滤镜
 */

const noiseFilter = new PIXI.NoiseFilter();

// 属性
noiseFilter.noise = 0.5;   // 噪点强度 (0-1)
noiseFilter.seed = Math.random();  // 随机种子

sprite.filters = [noiseFilter];

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

11.3.5 DisplacementFilter 位移

typescript
/**
 * DisplacementFilter 位移滤镜
 */

// 创建位移贴图
const displacementSprite = PIXI.Sprite.from('displacement.png');
displacementSprite.texture.baseTexture.wrapMode = PIXI.WRAP_MODES.REPEAT;

const displacementFilter = new PIXI.DisplacementFilter(displacementSprite);

// 属性
displacementFilter.scale.x = 30;
displacementFilter.scale.y = 30;

sprite.filters = [displacementFilter];

// 动态位移(水波效果)
app.ticker.add(() => {
    displacementSprite.x += 1;
    displacementSprite.y += 1;
});

11.4 pixi-filters 扩展滤镜

11.4.1 安装和使用

typescript
/**
 * pixi-filters 扩展滤镜库
 */

// 安装
// npm install pixi-filters

// 导入
import * as filters from 'pixi-filters';
// 或单独导入
import { GlowFilter, OutlineFilter } from 'pixi-filters';

11.4.2 常用扩展滤镜

typescript
/**
 * 常用扩展滤镜
 */

// GlowFilter 发光
const glowFilter = new filters.GlowFilter({
    distance: 15,
    outerStrength: 2,
    innerStrength: 1,
    color: 0xFF0000,
    quality: 0.5
});

// OutlineFilter 描边
const outlineFilter = new filters.OutlineFilter(2, 0x000000);

// DropShadowFilter 阴影
const shadowFilter = new filters.DropShadowFilter({
    offset: { x: 5, y: 5 },
    color: 0x000000,
    alpha: 0.5,
    blur: 4
});

// BevelFilter 斜角
const bevelFilter = new filters.BevelFilter({
    rotation: 45,
    thickness: 2,
    lightColor: 0xFFFFFF,
    shadowColor: 0x000000
});

// EmbossFilter 浮雕
const embossFilter = new filters.EmbossFilter();

// PixelateFilter 像素化
const pixelateFilter = new filters.PixelateFilter(10);

// AsciiFilter ASCII 艺术
const asciiFilter = new filters.AsciiFilter(8);

// CRTFilter CRT 显示器效果
const crtFilter = new filters.CRTFilter({
    curvature: 1,
    lineWidth: 1,
    lineContrast: 0.25,
    noise: 0.3,
    noiseSize: 1,
    vignetting: 0.3,
    vignettingAlpha: 1,
    vignettingBlur: 0.3
});

// GodrayFilter 光线效果
const godrayFilter = new filters.GodrayFilter({
    angle: 30,
    gain: 0.5,
    lacunarity: 2.5,
    parallel: true,
    time: 0
});

// ShockwaveFilter 冲击波
const shockwaveFilter = new filters.ShockwaveFilter(
    [app.screen.width / 2, app.screen.height / 2],
    {
        amplitude: 30,
        wavelength: 160,
        speed: 500,
        radius: -1
    }
);

// MotionBlurFilter 运动模糊
const motionBlurFilter = new filters.MotionBlurFilter(
    [10, 0],  // 速度向量
    9         // 核大小
);

// ZoomBlurFilter 缩放模糊
const zoomBlurFilter = new filters.ZoomBlurFilter({
    strength: 0.1,
    center: [400, 300],
    innerRadius: 100
});

// TiltShiftFilter 移轴效果
const tiltShiftFilter = new filters.TiltShiftFilter({
    blur: 100,
    gradientBlur: 600,
    start: { x: 0, y: 300 },
    end: { x: 800, y: 300 }
});

11.4.3 滤镜动画示例

typescript
/**
 * 滤镜动画示例
 */

// 发光脉冲
const glowFilter = new filters.GlowFilter({
    distance: 15,
    outerStrength: 0,
    color: 0x00FFFF
});

sprite.filters = [glowFilter];

let time = 0;
app.ticker.add((delta) => {
    time += delta * 0.05;
    glowFilter.outerStrength = Math.sin(time) * 2 + 2;
});


// 冲击波效果
const shockwave = new filters.ShockwaveFilter(
    [400, 300],
    { amplitude: 30, wavelength: 160, speed: 500 }
);

container.filters = [shockwave];

function triggerShockwave(x: number, y: number) {
    shockwave.center = [x, y];
    shockwave.time = 0;
}

app.ticker.add((delta) => {
    shockwave.time += delta * 0.01;
});


// 光线动画
const godray = new filters.GodrayFilter({
    angle: 30,
    gain: 0.5,
    time: 0
});

container.filters = [godray];

app.ticker.add((delta) => {
    godray.time += delta * 0.01;
});

11.5 自定义滤镜

11.5.1 滤镜结构

typescript
/**
 * 自定义滤镜结构
 */

// 顶点着色器(通常使用默认)
const vertexShader = `
    attribute vec2 aVertexPosition;
    attribute vec2 aTextureCoord;
    
    uniform mat3 projectionMatrix;
    
    varying vec2 vTextureCoord;
    
    void main(void) {
        gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
        vTextureCoord = aTextureCoord;
    }
`;

// 片段着色器
const fragmentShader = `
    varying vec2 vTextureCoord;
    
    uniform sampler2D uSampler;
    uniform float uTime;
    
    void main(void) {
        vec4 color = texture2D(uSampler, vTextureCoord);
        // 自定义处理
        gl_FragColor = color;
    }
`;

// 创建滤镜
const customFilter = new PIXI.Filter(vertexShader, fragmentShader, {
    uTime: 0
});

11.5.2 简单自定义滤镜

typescript
/**
 * 简单自定义滤镜示例
 */

// 反色滤镜
const invertFragment = `
    varying vec2 vTextureCoord;
    uniform sampler2D uSampler;
    
    void main(void) {
        vec4 color = texture2D(uSampler, vTextureCoord);
        gl_FragColor = vec4(1.0 - color.rgb, color.a);
    }
`;

const invertFilter = new PIXI.Filter(null, invertFragment);


// 色调分离滤镜
const posterizeFragment = `
    varying vec2 vTextureCoord;
    uniform sampler2D uSampler;
    uniform float uLevels;
    
    void main(void) {
        vec4 color = texture2D(uSampler, vTextureCoord);
        color.rgb = floor(color.rgb * uLevels) / uLevels;
        gl_FragColor = color;
    }
`;

const posterizeFilter = new PIXI.Filter(null, posterizeFragment, {
    uLevels: 4.0
});


// 波浪滤镜
const waveFragment = `
    varying vec2 vTextureCoord;
    uniform sampler2D uSampler;
    uniform float uTime;
    uniform float uAmplitude;
    uniform float uFrequency;
    
    void main(void) {
        vec2 uv = vTextureCoord;
        uv.x += sin(uv.y * uFrequency + uTime) * uAmplitude;
        gl_FragColor = texture2D(uSampler, uv);
    }
`;

class WaveFilter extends PIXI.Filter {
    constructor() {
        super(null, waveFragment, {
            uTime: 0,
            uAmplitude: 0.02,
            uFrequency: 20.0
        });
    }
    
    get time(): number {
        return this.uniforms.uTime;
    }
    
    set time(value: number) {
        this.uniforms.uTime = value;
    }
    
    get amplitude(): number {
        return this.uniforms.uAmplitude;
    }
    
    set amplitude(value: number) {
        this.uniforms.uAmplitude = value;
    }
}

// 使用
const waveFilter = new WaveFilter();
sprite.filters = [waveFilter];

app.ticker.add((delta) => {
    waveFilter.time += delta * 0.1;
});

11.5.3 高级自定义滤镜

typescript
/**
 * 高级自定义滤镜
 */

// 径向模糊滤镜
const radialBlurFragment = `
    precision mediump float;
    
    varying vec2 vTextureCoord;
    uniform sampler2D uSampler;
    uniform vec2 uCenter;
    uniform float uStrength;
    uniform float uInnerRadius;
    
    const int SAMPLES = 32;
    
    void main(void) {
        vec2 dir = vTextureCoord - uCenter;
        float dist = length(dir);
        
        if (dist < uInnerRadius) {
            gl_FragColor = texture2D(uSampler, vTextureCoord);
            return;
        }
        
        dir = normalize(dir);
        
        vec4 color = vec4(0.0);
        float total = 0.0;
        
        for (int i = 0; i < SAMPLES; i++) {
            float offset = float(i) / float(SAMPLES) * uStrength;
            vec2 samplePos = vTextureCoord - dir * offset;
            color += texture2D(uSampler, samplePos);
            total += 1.0;
        }
        
        gl_FragColor = color / total;
    }
`;

class RadialBlurFilter extends PIXI.Filter {
    constructor() {
        super(null, radialBlurFragment, {
            uCenter: [0.5, 0.5],
            uStrength: 0.1,
            uInnerRadius: 0.0
        });
    }
    
    get center(): [number, number] {
        return this.uniforms.uCenter;
    }
    
    set center(value: [number, number]) {
        this.uniforms.uCenter = value;
    }
    
    get strength(): number {
        return this.uniforms.uStrength;
    }
    
    set strength(value: number) {
        this.uniforms.uStrength = value;
    }
    
    get innerRadius(): number {
        return this.uniforms.uInnerRadius;
    }
    
    set innerRadius(value: number) {
        this.uniforms.uInnerRadius = value;
    }
}

11.6 滤镜组合

11.6.1 多滤镜叠加

typescript
/**
 * 多滤镜叠加
 */

// 创建多个滤镜
const blurFilter = new PIXI.BlurFilter(4);
const colorMatrix = new PIXI.ColorMatrixFilter();
colorMatrix.brightness(1.2);
const glowFilter = new filters.GlowFilter({ color: 0x00FFFF });

// 叠加应用
sprite.filters = [blurFilter, colorMatrix, glowFilter];

// 滤镜按数组顺序依次应用
// 1. 先应用模糊
// 2. 再调整颜色
// 3. 最后添加发光

11.6.2 条件滤镜

typescript
/**
 * 条件滤镜
 */

class FilterManager {
    private target: PIXI.DisplayObject;
    private filters: Map<string, PIXI.Filter> = new Map();
    private activeFilters: Set<string> = new Set();
    
    constructor(target: PIXI.DisplayObject) {
        this.target = target;
    }
    
    register(name: string, filter: PIXI.Filter) {
        this.filters.set(name, filter);
    }
    
    enable(name: string) {
        if (this.filters.has(name)) {
            this.activeFilters.add(name);
            this.updateFilters();
        }
    }
    
    disable(name: string) {
        this.activeFilters.delete(name);
        this.updateFilters();
    }
    
    toggle(name: string) {
        if (this.activeFilters.has(name)) {
            this.disable(name);
        } else {
            this.enable(name);
        }
    }
    
    private updateFilters() {
        const activeFilterArray: PIXI.Filter[] = [];
        
        for (const name of this.activeFilters) {
            const filter = this.filters.get(name);
            if (filter) {
                activeFilterArray.push(filter);
            }
        }
        
        this.target.filters = activeFilterArray.length > 0 ? activeFilterArray : null;
    }
}

// 使用
const filterManager = new FilterManager(sprite);
filterManager.register('blur', new PIXI.BlurFilter(8));
filterManager.register('glow', new filters.GlowFilter());
filterManager.register('grayscale', new PIXI.ColorMatrixFilter().greyscale(1));

// 动态切换
filterManager.enable('blur');
filterManager.toggle('glow');

11.6.3 滤镜过渡

typescript
/**
 * 滤镜过渡动画
 */

// 模糊过渡
async function blurTransition(
    sprite: PIXI.Sprite,
    targetBlur: number,
    duration: number
): Promise<void> {
    const blurFilter = new PIXI.BlurFilter(0);
    sprite.filters = [blurFilter];
    
    const startBlur = blurFilter.blur;
    const startTime = performance.now();
    
    return new Promise((resolve) => {
        const update = () => {
            const elapsed = performance.now() - startTime;
            const progress = Math.min(elapsed / duration, 1);
            
            blurFilter.blur = startBlur + (targetBlur - startBlur) * progress;
            
            if (progress < 1) {
                requestAnimationFrame(update);
            } else {
                if (targetBlur === 0) {
                    sprite.filters = null;
                }
                resolve();
            }
        };
        
        update();
    });
}

// 颜色过渡
async function colorTransition(
    sprite: PIXI.Sprite,
    effect: 'grayscale' | 'sepia' | 'normal',
    duration: number
): Promise<void> {
    const colorMatrix = new PIXI.ColorMatrixFilter();
    sprite.filters = [colorMatrix];
    
    const startTime = performance.now();
    
    return new Promise((resolve) => {
        const update = () => {
            const elapsed = performance.now() - startTime;
            const progress = Math.min(elapsed / duration, 1);
            
            colorMatrix.reset();
            
            switch (effect) {
                case 'grayscale':
                    colorMatrix.greyscale(progress);
                    break;
                case 'sepia':
                    colorMatrix.sepia(progress > 0);
                    break;
                case 'normal':
                    // 保持原样
                    break;
            }
            
            if (progress < 1) {
                requestAnimationFrame(update);
            } else {
                resolve();
            }
        };
        
        update();
    });
}

11.7 性能优化

11.7.1 滤镜性能考虑

typescript
/**
 * 滤镜性能优化
 */

// 1. 减少滤镜数量
// 不好:多个简单滤镜
sprite.filters = [blur1, blur2, blur3];

// 好:合并为一个滤镜
const combinedBlur = new PIXI.BlurFilter(blur1.blur + blur2.blur + blur3.blur);
sprite.filters = [combinedBlur];


// 2. 降低滤镜质量
const blurFilter = new PIXI.BlurFilter();
blurFilter.quality = 2;  // 降低质量(默认 4)


// 3. 降低分辨率
const filter = new PIXI.BlurFilter();
filter.resolution = 0.5;  // 半分辨率


// 4. 禁用不需要的滤镜
if (!needBlur) {
    blurFilter.enabled = false;
}


// 5. 使用 cacheAsBitmap
// 对于静态对象,缓存滤镜结果
sprite.filters = [complexFilter];
sprite.cacheAsBitmap = true;

11.7.2 滤镜区域优化

typescript
/**
 * 滤镜区域优化
 */

// 设置滤镜区域
const filter = new PIXI.BlurFilter();

// 限制滤镜影响区域
filter.filterArea = new PIXI.Rectangle(0, 0, 200, 200);

// 使用 padding 控制边缘
filter.padding = 10;  // 只在需要时增加


// 对于容器,使用 filterArea
container.filterArea = app.screen;  // 限制为屏幕大小
container.filters = [filter];

11.7.3 按需应用滤镜

typescript
/**
 * 按需应用滤镜
 */

// 只在可见时应用滤镜
class OptimizedFilterSprite extends PIXI.Sprite {
    private _filters: PIXI.Filter[] | null = null;
    
    setFilters(filters: PIXI.Filter[] | null) {
        this._filters = filters;
        this.updateFilters();
    }
    
    private updateFilters() {
        if (this.visible && this.worldVisible) {
            this.filters = this._filters;
        } else {
            this.filters = null;
        }
    }
    
    // 重写 visible setter
    set visible(value: boolean) {
        super.visible = value;
        this.updateFilters();
    }
}


// 视口内才应用滤镜
function updateFiltersInViewport(
    objects: PIXI.DisplayObject[],
    viewport: PIXI.Rectangle,
    filter: PIXI.Filter
) {
    for (const obj of objects) {
        const bounds = obj.getBounds();
        const inViewport = bounds.intersects(viewport);
        
        if (inViewport && !obj.filters) {
            obj.filters = [filter];
        } else if (!inViewport && obj.filters) {
            obj.filters = null;
        }
    }
}

11.8 本章小结

核心概念

概念说明
Filter滤镜基类
BlurFilter模糊滤镜
ColorMatrixFilter颜色矩阵滤镜
DisplacementFilter位移滤镜
pixi-filters扩展滤镜库
自定义滤镜GLSL 着色器
filters 数组滤镜叠加

关键代码

typescript
// 应用滤镜
sprite.filters = [new PIXI.BlurFilter(8)];

// 颜色矩阵
const colorMatrix = new PIXI.ColorMatrixFilter();
colorMatrix.greyscale(0.5);

// 扩展滤镜
import { GlowFilter } from 'pixi-filters';
sprite.filters = [new GlowFilter({ color: 0xFF0000 })];

// 自定义滤镜
const customFilter = new PIXI.Filter(null, fragmentShader, {
    uTime: 0
});

// 多滤镜
sprite.filters = [blurFilter, colorMatrix, glowFilter];

// 性能优化
filter.quality = 2;
filter.resolution = 0.5;
sprite.cacheAsBitmap = true;

11.9 练习题

基础练习

  1. 实现一个带模糊效果的弹出框

  2. 使用 ColorMatrixFilter 实现图片滤镜切换

  3. 实现发光按钮效果

进阶练习

  1. 创建一个自定义波浪滤镜

  2. 实现滤镜过渡动画

挑战练习

  1. 实现一个完整的图片编辑器,支持多种滤镜效果

下一章预告:在第12章中,我们将深入学习遮罩与混合模式。


文档版本:v1.0
字数统计:约 11,000 字
代码示例:45+ 个

如有转载或 CV 的请标注本站原文地址