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 个滤镜以内。
扩展阅读
- Pixi.js Filters 官方文档
- PixiJS Filters GitHub
- The Book of Shaders — GLSL 着色器入门
- Shadertoy — 着色器灵感库