
PixiJS Culler Plugin 视锥剔除实战跳过屏幕外对象的渲染【免费下载链接】pixijsThe HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.项目地址: https://gitcode.com/gh_mirrors/pi/pixijsPixiJS 的 CullerPlugin 用于在渲染前自动跳过视口viewport之外的对象通过将不可见对象标记为culled来减少绘制调用draw calls与更新开销是大型场景、可滚动地图与相机驱动世界视图的经典性能优化手段。本文将围绕 src/app/docs/culler-plugin.md 的完整内容结合仓库中 Culler.ts、CullerPlugin.ts 与 Culler.test.ts 的源码与测试实现讲解插件的注册方式、容器配置、自定义裁剪区域、手动裁剪调用以及底层工作原语。CullerPlugin 解决了什么问题PixiJS 的渲染器会遍历场景图scene graph中所有renderable的对象。当一个场景包含大量元素而其中大部分位于当前屏幕之外时例如超大的 tilemap 世界、滚动的关卡地图、随机铺满上千 Sprite 的战场渲染器仍然会为这些不可见对象执行更新变换、收集绘制指令等工作造成无谓的性能浪费。CullerPlugin 的核心思路非常简单在每一帧渲染之前检查每个 Container及其子节点是否与可见屏幕区域相交完全位于视口外的对象被标记为 culled隐藏渲染器随后跳过它们从而减少绘制调用与更新开销。这一点在 culler-plugin.md 中已有明确说明且 CullerPlugin.ts 的类注释将其定位为自动裁剪屏幕区域外显示对象的 Application 插件。[!IMPORTANT] 裁剪并非万能的银弹。正如 Culler.ts 顶部注释所强调的culling is not always a golden bullet, it can be more expensive than rendering objects that are not visible。当可见对象占绝大多数、边界计算本身代价高昂时裁剪反而可能比直接渲染更慢。它最适合大量对象同时不可见的场景例如大型场景或包含许多 Sprite 的游戏。何时使用裁剪culler-plugin.md 给出了三个典型的适用场景包含大量屏幕外元素的大型场景对象数量越多跳过渲染的收益越明显可滚动或相机驱动的环境如 tilemap 瓦片地图、卷轴世界视图视野外的区块可以被整体跳过希望在不重构场景图的前提下降低渲染成本裁剪只需要给对象设置属性不需要改变层级组织方式。快速开始注册插件并启用裁剪PixiJS默认不会启用CullerPlugin必须通过extensions系统手动注册。注册后插件会接管Application实例的render()方法在渲染前自动执行裁剪。1. 注册插件import { extensions, CullerPlugin } from pixi.js; extensions.add(CullerPlugin);2. 创建应用与可裁剪的容器import { Application, Container, Sprite, extensions, CullerPlugin } from pixi.js; extensions.add(CullerPlugin); const app new Application(); await app.init({ width: 800, height: 600, backgroundColor: 0x222222, }); const world new Container(); world.cullable true; const sprite Sprite.from(path/to/image.png); sprite.cullable true; world.addChild(sprite); app.stage.addChild(world);3. 插件内部做了什么注册后CullerPlugin 会覆盖Application实例上的render()方法在真正渲染前插入一次裁剪调用。这一行为在 CullerPlugin.ts 中实现public static init(options?: PixiMixins.ApplicationOptions): void { this._renderRef this.render.bind(this); this.render (): void { // default to true for updateTransform, unless specified otherwise const updateTransform options?.culler?.updateTransform ! true; Culler.shared.cull(this.stage, this.renderer.screen, updateTransform); this.renderer.render({ container: this.stage }); }; }即文档中所描述的等价替换// Internally replaces: app.renderer.render({ container: app.stage }); // With: Culler.shared.cull(app.stage, app.renderer.screen); app.renderer.render({ container: app.stage });裁剪使用的可见区域正是渲染器的屏幕矩形app.renderer.screen因此无需手动传入视口。当调用app.destroy()时插件的destroy()会把render恢复为原始引用见 CullerPlugin.ts。插件本身通过ExtensionType.Application类型注册元数据为{ priority: 10, type: ExtensionType.Application, name: culler }见 CullerPlugin.ts与 TickerPlugin、ResizePlugin 一同作为 Application 的内置插件 管理。配置容器的裁剪行为裁剪的粒度是容器/显示对象。相关属性由 cullingMixin.ts 中的CullingMixinConstructor接口定义并通过 CullingMixins.d.ts 混入到所有Container及其子类上。三个核心属性及默认值如下属性类型默认值说明cullablebooleanfalse该对象是否参与裁剪。为true时若其边界超出可见区域则被标记为culled渲染器将跳过它cullableChildrenbooleantrue是否递归裁剪子节点。为false时跳过递归检查直接渲染所有子节点前提是父容器可见cullAreaRectangle \| nullnull自定义裁剪区域局部坐标系下的矩形。设置后替代基于全局边界的默认检测默认情况下容器不参与裁剪cullable为false而cullableChildren默认为true因此你只需要在希望被裁剪的容器上设置cullablecontainer.cullable true; // This container will be culled when offscreen如果需要关闭对子节点的递归裁剪例如子节点始终位于容器边界内或者你想整体控制一块区域container.cullableChildren false; // Children wont be individually culled从 cullingMixin.ts 的注释可以确认这三条语义cullable只影响当前对象自身不影响其变换更新Does not affect transform updates子节点遵循各自的cullable设置cullableChildren用于静态场景等子节点始终在容器范围内的情况关闭后父容器仍会被裁剪但子节点不会逐个检查cullArea设置为null可恢复为使用对象自身边界。从源码看递归裁剪的完整逻辑Culler.ts 中的_cullRecursive方法完整呈现了每次裁剪的决策流程是否参与裁剪仅当container.cullable container.measurable container.includeInBuild同时成立时才执行可见性检测否则culled被强制置为false永不裁剪。可见性检测若设置了cullArea则基于容器的变换worldTransform或getGlobalTransform将视图矩形与cullArea做intersects相交检测不相交则标记culled true否则调用getGlobalBounds计算全局边界检查边界是否完全落在视图矩形的左侧、右侧、上方或下方。是否递归子节点若cullableChildren为false、当前容器已被裁剪culled、或容器renderable/measurable/includeInBuild不满足条件则提前返回不再遍历 children——父容器被裁剪时整棵子树都会被跳过这正是收益的主要来源dont process children if not needed。culled标记最终通过 Container.ts 中的 getter/setter 写入localDisplayStatus的第三位0b100与visible第二位、renderable第一位共同构成渲染器判断是否绘制该对象的依据。自定义裁剪区域cullArea默认的裁剪检测使用全局边界global bounds。当某个容器的子节点很多、边界计算代价高昂或不够准确时可以为它指定一个自定义的cullArea矩形裁剪检测将基于该矩形而非逐个子节点的边界计算import { Rectangle } from pixi.js; container.cullArea new Rectangle(0, 0, 100, 100);几点需要留意的语义结合 cullingMixin.ts 与 Culler.ts 源码cullArea定义在对象的局部坐标系中Defined in local space coordinates relative to the object检测时会结合对象的世界变换旋转、缩放、平移与视图矩形做相交判断见_cullRecursive中tempRectangle.intersects(container.cullArea, transform)一段在 Culler.test.ts 中容器被设置旋转Math.PI / 4并移动到不同位置cullArea与视图矩形恰好相交/不相交的两组断言验证了变换参与相交检测的正确性边界检测是保守近似只要容器自身边界与视口相交即使其子对象都在屏幕外容器也不会被裁剪测试 cullable container should be rendered if bounds intersects the frame 证实了这一点。应用级选项updateTransform在注册插件后还可以通过app.init()的culler选项控制裁剪时的变换更新行为见 CullerPlugin.ts 中的CullerPluginOptionsimport { Application, CullerPlugin, extensions } from pixi.js; extensions.add(CullerPlugin); const app new Application(); await app.init({ culler: { updateTransform: false, // 跳过对被裁剪对象变换的更新 }, });该选项的语义在源码注释与测试中有明确说明updateTransform默认为true即裁剪时会同步更新对象的变换保证用于裁剪判断的变换是最新的设置为false可跳过不必要的变换计算以进一步提升性能但如果对象在最近一次渲染后移动过用于裁剪的变换可能不是最新的见 CullerPlugin.ts 的 IMPORTANT 提示可能导致裁剪判断短暂滞后该选项最终会作为Culler.shared.cull(stage, renderer.screen, skipUpdateTransform)的第三个参数传入。测试用例验证了对应关系见 Culler.test.tsculler.updateTransform缺省或为false时cull收到skipUpdateTransform trueculler.updateTransform: true时cull收到skipUpdateTransform false。手动裁剪不使用插件如果你不使用 Application 插件体系例如直接操作Container与renderer也可以在渲染前手动调用Culler.shared.cull()完成相同的裁剪import { Culler } from pixi.js; const stage new Container(); // Configure stage and children... Culler.shared.cull(stage, { x: 0, y: 0, width: 800, height: 600 }); renderer.render({ container: stage });Culler是独立于插件的底层实现类见 Culler.ts通过Culler.shared获取全局共享实例public static shared new Culler()也可自行new Culler()创建独立实例cull(container, view, skipUpdateTransform true)接收三个参数container要裁剪的根容器必须为Container实例view可见区域类型为RectangleLike含x、y、width、height四个属性的对象见 Culler.ts可直接传入app.renderer.screen或自定义矩形skipUpdateTransform是否跳过变换更新默认true。测试用例中的行为验证仓库的 Culler.test.ts 覆盖了裁剪行为的大量边界情况可以作为理解语义的权威参考场景预期行为子 Sprite 位于视口外x100, y100视口 100×100child.culled true容器本身culled false对象移回视口内culled恢复为falsecullable设为false的对象即使边界超出视口也始终渲染cullableChildren false不递归子节点子节点culled保持false父容器被裁剪不再递归子节点整棵子树跳过measurable false或includeInBuild false强制culled false不参与裁剪带 filter padding 的子对象filter 外扩区域与视口相交容器被裁剪但子对象保留确保滤镜效果不会跳变多层嵌套grandparent/parent/child祖先被裁剪后子树整体跳过此外 transform-visibility.test.ts 验证了culled状态会与visible、renderable一起通过globalDisplayStatus传播到子节点从而在渲染管线中真正生效。最佳实践小结结合 culler-plugin.md、CullerPlugin.ts 的类注释与测试推荐的做法如下按需注册只在确实存在大量屏幕外对象时extensions.add(CullerPlugin)对象很少时裁剪带来的计算开销可能超过收益。空间分组将空间上相关的对象放进同一个容器如一个 tilemap 区块对容器设置cullable true让整块区域一次性参与检测。善用cullArea对子节点众多、边界计算昂贵的容器设置自定义cullArea避免每帧做全局边界计算。cullableChildren false当容器始终完整可见或子节点永远在容器范围内时关闭子节点递归检查。权衡updateTransform默认保持变换更新以保证判断准确仅在裁剪判断允许滞后、且确实需要省掉变换计算时改为false。API 参考CullerPluginApplication 插件注册后自动在渲染前执行裁剪Culler底层裁剪实现提供shared单例与cull()方法cullingMixin.tscullable、cullableChildren、cullArea属性的定义与默认值CullingMixins.d.ts将裁剪属性混入Container与ApplicationOptions的类型声明Culler.test.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),仅供参考