第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 练习题
基础练习
实现一个带模糊效果的弹出框
使用 ColorMatrixFilter 实现图片滤镜切换
实现发光按钮效果
进阶练习
创建一个自定义波浪滤镜
实现滤镜过渡动画
挑战练习
- 实现一个完整的图片编辑器,支持多种滤镜效果
下一章预告:在第12章中,我们将深入学习遮罩与混合模式。
文档版本:v1.0
字数统计:约 11,000 字
代码示例:45+ 个
