PixiJS v8 遮罩(Masking)完全指南:AlphaMask、StencilMask、ScissorMask 与 ColorMask
2026/9/19 0:02:09 网站建设 项目流程

PixiJS v8 遮罩(Masking)完全指南:AlphaMask、StencilMask、ScissorMask 与 ColorMask

【免费下载链接】pixijsThe HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.项目地址: https://gitcode.com/gh_mirrors/pi/pixijs

导读

遮罩(Masking)是 PixiJS 中最常用的视觉裁剪手段之一:它允许你用任意形状"裁剪"显示对象的渲染区域,实现圆形头像、图片挖洞、渐隐过渡、UI 蒙版等效果。本指南以 PixiJS 场景容器(scene/container)体系中的遮罩能力为核心,系统讲解 PixiJS v8 支持的四种遮罩类型——基于 Sprite 纹理的 AlphaMask、基于 Graphics/Container 的 StencilMask(模板缓冲)、轴对齐矩形的 ScissorMask,以及按通道位掩码裁剪的 ColorMask——并通过源码级解析与仓库内的真实示例,帮助你掌握遮罩类型自动选择机制、通道选择、反向遮罩、遮罩移除等全部核心用法与性能取舍。

遮罩体系总览:四种类型与自动选择

在 PixiJS 中,遮罩的本质是"用一个显示对象(或数值)限制另一个容器及其子树的可见区域"。PixiJS v8 支持四种遮罩类型:

遮罩对象实际使用的类型性能开销说明
GraphicsContainerStencilMask中等使用模板缓冲(stencil buffer)
SpriteAlphaMask昂贵内部走滤镜(filter)管线
Number(例如0xFColorMask最便宜对 RGBA 四个通道做位掩码

性能层级从低到高为:ColorMask(最便宜)< StencilMask < AlphaMask(最昂贵)。在视觉效果允许的前提下,应优先选择最简单的遮罩类型。

值得注意的一个细节是:代码库中虽然存在ScissorMask(src/rendering/mask/scissor/ScissorMask.ts),但遮罩系统不会自动选中它——只有AlphaMaskStencilMaskColorMask被注册为遮罩效果(MaskEffect)。从 MaskEffectManager.ts 的扩展注册机制可以确认这一点:StencilMaskAlphaMaskColorMask三个类都声明了public static extension: ExtensionMetadata = ExtensionType.MaskEffect,而ScissorMask没有extension静态属性,因此不会被自动使用。

快速上手:三步完成基础遮罩

最简单的遮罩使用方式是把一个显示对象赋给容器的mask属性:

const photoGroup = new Container(); photoGroup.addChild(new Sprite(await Assets.load("photo.png"))); const mask = new Graphics().circle(100, 100, 80).fill(0xffffff); photoGroup.mask = mask; photoGroup.addChild(mask); app.stage.addChild(photoGroup);

这里有两条重要的使用约定:

  1. 遮罩必须位于被遮罩对象父级的场景图中(典型做法是作为被遮罩Container的子节点),这样它的变换(transform)才能跟随被遮罩的子树同步。这一点在 effectsMixin.ts 的类型注释中也有强调:"A mask of an object must be in the subtree of its parent. Otherwise,getLocalBoundsmay calculate incorrect bounds"(遮罩必须位于对象父级的子树中,否则getLocalBounds可能计算出错误的边界,进而导致容器宽高异常)。
  2. SpriteText等叶子节点不能持有遮罩作为子节点(这些类allowChildren = false)。如果需要给 Sprite 或 Text 加遮罩,请像上面的示例一样把它们包进一个Container中。

为容器设置container.mask后,PixiJS 会根据所赋对象的类型自动选择遮罩类型:赋Graphics/Container走 Stencil,赋Sprite走 Alpha,赋数字走 Color。

核心模式(Core Patterns)

Stencil 遮罩(基于 Graphics)

模板缓冲遮罩是处理不规则几何形状时的默认选择,也是性价比最高的"形状裁剪"方案:

const container = new Container(); const mask = new Graphics().roundRect(0, 0, 200, 150, 20).fill(0xffffff); container.mask = mask; container.addChild(mask);

要点:遮罩Graphics应该是被遮罩容器的子节点(或与被遮罩对象共享同一坐标空间)。填充颜色无关紧要——只有形状参与裁剪。从源码看,StencilMask.ts 的init方法会把遮罩对象标记为includeInBuild = falsemeasurable = false,即它参与模板裁剪但不会被重复渲染进画面、也不参与父级边界测量;reset时恢复这两个标志。此外,StencilMask.test的判断条件是mask instanceof Container,所以任何 Container 子类(Graphics、Sprite、甚至普通 Container)作为mask赋值时,理论上都会先命中 Stencil 分支——但实际类型判定顺序由注册顺序决定,见下文"类型自动选择的源码实现"。

Alpha 遮罩(基于 Sprite)

当需要用纹理的灰度/透明度做渐变式裁剪(例如羽化边缘、渐隐过渡)时,使用 Sprite 作为遮罩:

const maskTexture = await Assets.load("gradient-mask.png"); const maskSprite = new Sprite(maskTexture); const photoGroup = new Container(); photoGroup.addChild(new Sprite(await Assets.load("photo.png"))); photoGroup.mask = maskSprite; photoGroup.addChild(maskSprite);

Alpha 遮罩**默认读取 Sprite 纹理的红色通道(red channel)**来控制可见性:红色值高 = 完全可见,红色为 0 = 隐藏。这是最昂贵的遮罩类型,因为它在内部使用了滤镜管线(filter pipeline)——在 AlphaMask.ts 中可以看到它的pipe = 'alphaMask',而实际渲染由MaskFilter(src/filters/mask/MaskFilter.ts)驱动,它是一个继承自Filter的着色器过滤器,通过 uniformuChannel(0 表示 red、1 表示 alpha)和uInverse(0/1)控制采样通道与反向逻辑。

从 AlphaMask.ts 的init还可以看到一个内部优化细节:renderMaskToTexture = !(mask instanceof Sprite)——如果遮罩对象不是 Sprite,会先被渲染到一张纹理上再参与 alpha 遮罩计算。

遮罩通道选择(Mask Channel)

如果遮罩纹理本身使用的是透明度(例如一张带 alpha 渐变的 PNG),可以通过setMask切换到 alpha 通道:

const maskSprite = new Sprite(await Assets.load("alpha-gradient.png")); const photoGroup = new Container(); photoGroup.addChild(new Sprite(await Assets.load("photo.png"))); photoGroup.setMask({ mask: maskSprite, channel: "alpha" }); photoGroup.addChild(maskSprite);

可用通道:'red'(默认)、'alpha'。当同一张遮罩纹理在不同通道中编码了不同形状时(例如红色通道画了星形、alpha 通道画了圆形),这一能力尤其有用。仓库中的 examples/container_alpha-channel-mask.ts 就是一个完整的实战演示:它用Graphics绘制一张"红色星形 + 半透明蓝色圆形"的遮罩纹理,然后分别以channel: 'red'channel: 'alpha'应用到两张相同的照片上——前者只露出星形区域,后者露出完整的圆形。该示例还印证了通道默认值与选项定义:在 effectsMixin.ts 中,_maskOptions的默认值即为{ inverse: false, channel: 'red' },且MaskChannel类型定义('red' | 'alpha')位于 MaskFilter.ts。

反向遮罩(Inverse Masking)

使用setMask并传入inverse: true,可以显示遮罩形状之外的所有内容(即"挖洞"效果):

const holeMask = new Graphics().circle(100, 100, 80).fill(0xffffff); const container = new Container(); container.setMask({ mask: holeMask, inverse: true }); container.addChild(holeMask); const maskSprite = new Sprite(await Assets.load("mask.png")); const photoGroup = new Container(); photoGroup.addChild(new Sprite(await Assets.load("photo.png"))); photoGroup.setMask({ mask: maskSprite, inverse: true }); photoGroup.addChild(maskSprite);

Alpha 遮罩与 Stencil 遮罩在WebGL 和 WebGPU 上都支持反向Canvas2D 不支持反向 Stencil 遮罩——它会记录一条警告并忽略该标志。反向标志的底层实现同样可以在 MaskFilter.ts 中看到:uInverseuniform 被写入片段着色器参与计算;而在 AlphaMask.ts 的addBounds中,反向遮罩时遮罩对象本身不参与边界计算(if (!this.inverse) addMaskBounds(...)),这保证了反向遮罩的边界语义正确。

仓库中的 examples/container_inverse-mask.ts 提供了一个开箱即用的反向遮罩示例:一个红色矩形配上五角星形状的反向遮罩,形成"星形区域被挖空"的视觉效果,完整展示了rect.setMask({ mask: masky, inverse: true })的用法。

移除遮罩

container.mask = null; container.mask = null; mask.destroy();

清除遮罩时必须使用container.mask = nullsetMask({ mask: null })不起作用,原因在于 effectsMixin.ts 中setMask的实现存在一次内部真值判断(if (options.mask)才继续设置),而masknull时该分支不会执行。另外,在销毁遮罩对象或被遮罩对象之前,务必先移除遮罩引用(置为null),否则渲染管线可能访问已销毁的纹理——MaskFilter.ts 中专门有一处防御逻辑,检测到遮罩纹理已销毁时会降级为Texture.EMPTY并在调试模式下输出警告:"The mask texture was destroyed while the mask is still in use. Remove the mask before destroying its texture."

源码深挖:遮罩类型自动选择的实现原理

为什么"赋什么对象就自动选什么遮罩类型"?答案在 MaskEffectManager.ts 与 effectsMixin.ts 的协同机制中:

  1. 注册AlphaMaskStencilMaskColorMask各自声明extension = ExtensionType.MaskEffect,通过extensions.handleByList(ExtensionType.MaskEffect, MaskEffectManager._effectClasses)被收集到MaskEffectManager
  2. 测试:每个遮罩类实现静态test(mask)方法——ColorMask.test判断typeof mask === 'number'StencilMask.test判断mask instanceof ContainerAlphaMask.test判断mask instanceof SpriteMaskEffectManager.getMaskEffect(item)按注册顺序遍历这些测试,命中第一个匹配的类。
  3. 转换与对象池:命中后从BigPool取出对应的 Effect 实例(StencilMask/AlphaMask实现PoolItem接口支持对象池复用),交给容器。
  4. 接入容器effectsMixin中的masksetter 在赋值时通过MaskEffectManager.getMaskEffect(value)把原始对象转换为 Effect,加入容器的effects列表;_markStructureAsChanged会通知渲染组结构发生变化,触发重排。
  5. 渲染:每种遮罩类型有独立的 pipe('stencilMask''alphaMask''colorMask''scissorMask'),如 ColorMaskPipe.ts 维护一个颜色掩码栈(初始为0xF,push 时与父级掩码做按位与,pop 时恢复),最终调用渲染器的colorMask.setMask写入 WebGL/WebGPU 的 color mask 状态。

这也解释了文档中"ScissorMask存在于代码库但不会被自动选择"的原因:它没有注册为MaskEffect扩展,其构造函数需要显式传入Container(见 ScissorMask.ts),适合在需要轴对齐矩形裁剪且追求近零开销的场景中手动使用。

常见误区(Common Mistakes)

[高] 将 cacheAsTexture 与遮罩组合使用

cacheAsTexture()与遮罩的组合是脆弱的。在 Firefox 中,它可能需要在设置遮罩与启用缓存之间加入一个超时(timeout)才能正常工作;在其他浏览器中则可能静默失败。能避免就尽量避免组合使用;如果确实需要,务必在目标浏览器中充分测试。

[中] 使用过多的 Sprite 遮罩

Sprite 遮罩(AlphaMask)在内部使用滤镜管线,是最昂贵的遮罩类型。同时使用大量 Sprite 遮罩会显著降低渲染性能。当视觉效果允许时,优先使用 Stencil 遮罩(Graphics 形状)或 Scissor 遮罩(轴对齐矩形)。

性能开销对比:Scissor(接近零)< Stencil(一次额外绘制)< Alpha(完整滤镜通道)

API 参考

以下 API 的完整定义与实现均可直接在仓库源码中查看:

  • AlphaMask:基于 Sprite 纹理的 alpha 遮罩效果,支持channel(red/alpha)与inverse,非 Sprite 遮罩会被自动渲染到纹理。
  • StencilMask:基于模板缓冲的 Container/Graphics 遮罩效果,includeInBuild/measurable会在挂载时被自动关闭。
  • ScissorMask:轴对齐矩形裁剪效果,需手动构造,未被注册为自动 MaskEffect。
  • ColorMask:RGBA 通道位掩码效果,test接受任意number
  • MaskEffectManager:遮罩到 Effect 的转换管理器,负责类型测试、实例化与对象池回收。
  • 配套实现:ColorMaskPipe(颜色掩码栈)、MaskFilter(alpha 遮罩着色器与通道/uniform 定义)、effectsMixin.ts(mask/setMask/MaskOptions的类型与默认值)。

小结

PixiJS v8 的遮罩体系可以用一句话概括:把遮罩对象赋给container.mask,引擎自动帮你选择最合适的实现。理解四种遮罩类型的差异(Stencil 的形状裁剪、Alpha 的纹理渐变、Color 的通道位掩码、Scissor 的矩形裁剪)、掌握setMaskinversechannel选项、牢记"遮罩要放进父级子树、移除要用mask = null"这两条约定,再配合 examples/container_inverse-mask.ts 与 examples/container_alpha-channel-mask.ts 两个可运行示例实践,你就能在各种裁剪场景中做出正确且高效的技术选型。

【免费下载链接】pixijsThe HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.项目地址: https://gitcode.com/gh_mirrors/pi/pixijs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询