ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Phaser 4.0 Beta 7 相机矩阵系统重写与渲染管线变更深度解析

Phaser 4.0 Beta 7 相机矩阵系统重写与渲染管线变更深度解析 Phaser 4.0 Beta 7 相机矩阵系统重写与渲染管线变更深度解析【免费下载链接】phaserPhaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.项目地址: https://gitcode.com/gh_mirrors/ph/phaser导读本文围绕 Phaser 4.0 beta 7 的核心变更展开相机系统矩阵计算被整体重写从单矩阵模型演进为视图矩阵 外部矩阵双矩阵模型同时引入matrixExternal、matrixCombined与copyWithScrollFactorFrom等新 API。文章将结合仓库源码Camera.js、GetCalcMatrix.js、TransformMatrix.js与单元测试GetCalcMatrix.test.js逐项拆解这次重写的设计动机、实现细节与迁移影响并同步梳理SpriteGPULayer批量渲染增强、roundPixels默认值调整等一系列修复与优化帮助你准确评估升级风险并适配新 API。一、为什么重写相机矩阵系统Phaser 3 时代相机将位置position、旋转rotation与缩放zoom统一合并进Camera#matrix一个矩阵中滚动偏移scroll则在渲染流程的后续阶段被追加进去。这种单矩阵方案在常规场景下工作良好但一旦涉及嵌套变换nested transforms、滤镜filters以及其他依赖变换矩阵的内部系统就会出现难以排查的错位问题——因为相机在哪和相机看到什么被耦合在同一个矩阵里无法干净地分离。beta 7 的核心思路是把这两种语义拆开视图View相机的旋转、缩放与滚动 —— 决定世界被如何观察位置Position相机在屏幕上的平移 —— 决定相机本身在哪里。从源码可以看到这一拆分的落点。在 Camera.js 的preRender流程中matrix只承载视图变换ITRS 顺序应用 origin、rotation、zoom 与滚动平移而matrixExternal通过applyITRS(this.x, this.y, 0, 1, 1)单独记录相机位置最后用matrixExternal.multiply(matrix, this.matrixCombined)合成出组合矩阵// src/cameras/2d/Camera.js节选 matrix.applyITRS(originX, originY, this.rotation, zoomX, zoomY); matrix.translate(-sx - originX, -sy - originY); matrixExternal.applyITRS(this.x, this.y, 0, 1, 1); this.shakeEffect.preRender(); matrixExternal.multiply(matrix, this.matrixCombined);这个重写不改变开发者设置相机属性的方式setScroll、setZoom、setRotation等用法不变它主要影响内部渲染系统。只有当你直接读取相机矩阵时才会感知到差异。二、双矩阵模型matrix / matrixExternal / matrixCombinedbeta 7 之后相机持有三份矩阵语义截然不同矩阵属性包含内容典型用途Camera#matrix旋转 缩放 滚动不含位置渲染到 framebuffer / 滤镜合成场景下的视图矩阵Camera#matrixExternal相机位置新增将相机摆放到屏幕上的外部变换Camera#matrixCombinedmatrix × matrixExternal两者相乘常规渲染时使用的最终视图矩阵选择逻辑集中体现在新增的Camera#getViewMatrix(forceComposite)方法Camera.js当相机需要渲染到 framebuffer或挂载了内部/外部滤镜filters.external.length 0 || filters.internal.length 0或forceComposite为true时返回不包含位置的matrix否则返回组合矩阵matrixCombined。getViewMatrix: function (forceComposite) { if ( forceComposite || this.forceComposite || this.filters.external.length 0 || this.filters.internal.length 0 ) { return this.matrix; } else { return this.matrixCombined; } }之所以在渲染到 framebuffer场景下要剔除位置是因为 framebuffer 是一个离屏目标相机位置此时属于屏幕空间的概念不应被带入纹理坐标空间。这正是ignoreCameraPosition参数的用途详见下一节。三、GetCalcMatrix 的变化与 GetCalcMatrixResults 新成员GetCalcMatrix(src, camera, parentMatrix, ignoreCameraPosition)是 Phaser 渲染管线中计算游戏对象最终变换矩阵的核心入口几乎所有 WebGL/Canvas 渲染器都会调用它。beta 7 给它增加了一个新参数ignoreCameraPosition布尔值默认false为true时返回结果中的外部矩阵使用单位矩阵而不是相机的matrixExternal从而让对象变换完全脱离相机位置影响。这个标志在相机渲染到 framebuffer滤镜、RenderTexture 内部时非常关键。对应实现见 GetCalcMatrix.jsvar GetCalcMatrix function (src, camera, parentMatrix, ignoreCameraPosition) { if (ignoreCameraPosition) { camExternalMatrix.loadIdentity(); } else { camExternalMatrix.copyFrom(camera.matrixExternal); } camMatrix.copyWithScrollFactorFrom( ignoreCameraPosition ? camera.matrix : camera.matrixCombined, camera.scrollX, camera.scrollY, src.scrollFactorX, src.scrollFactorY ); calcMatrix.copyFrom(camMatrix); if (parentMatrix) { calcMatrix.multiply(parentMatrix); } spriteMatrix.applyITRS(src.x, src.y, src.rotation, src.scaleX, src.scaleY); calcMatrix.multiply(spriteMatrix); return result; };返回值result在 GetCalcMatrixResults.js 中定义beta 7 新增了cameraExternal属性结果属性类型含义cameraExternalTransformMatrix相机外部矩阵相机在屏幕上的位置ignoreCameraPosition时为单位矩阵cameraTransformMatrix相机视图矩阵已按对象scrollFactor修正滚动偏移spriteTransformMatrix游戏对象自身的世界变换矩阵calcTransformMatrix最终合成矩阵相机 × 父级 × 对象配套的单元测试GetCalcMatrix.test.js精确验证了这一行为ignoreCameraPosition true时cameraExternal的a/b/c/d/tx/ty全部回到单位矩阵值a1, d1, tx0, ty0ignoreCameraPosition false时cameraExternal完整复制相机的matrixExternal测试中验证了缩放4与平移(10, 20)。同一测试还覆盖了滚动偏移与scrollFactor的组合[L204-L228]scrollFactor 1时滚动不产生位移scrollFactor 0时完整应用滚动偏移。此外GetCalcMatrix返回的是同一个结果对象引用每次调用复用内部矩阵实例见 [L230-L239]这意味着拿到结果后必须立即使用或自行复制否则下一次渲染会覆盖这些值——这是从 Phaser 3.50 起就一直存在的约定。四、新方法 copyWithScrollFactorFrom替代手工滚动运算在 Phaser 3 时代渲染代码里经常出现这类手工修正spriteMatrix.e - camera.scrollX * src.scrollFactorX; spriteMatrix.f - camera.scrollY * src.scrollFactorY;beta 7 提供了标准替代方案TransformMatrix#copyWithScrollFactorFrom(src, scrollX, scrollY, scrollFactorX, scrollFactorY)TransformMatrix.jscopyWithScrollFactorFrom: function (src, scrollX, scrollY, scrollFactorX, scrollFactorY) { var matrix this.matrix; matrix[0] src.a; matrix[1] src.b; matrix[2] src.c; matrix[3] src.d; var sx scrollX * (1.0 - scrollFactorX); var sy scrollY * (1.0 - scrollFactorY); matrix[4] src.a * sx src.c * sy src.e; matrix[5] src.b * sx src.d * sy src.f; return this; }它从源矩阵复制旋转/缩放部分a、b、c、d并把滚动偏移按(1 - scrollFactor)的比例折算进平移分量e、f。注意一个易混淆点scrollFactor越大滚动对矩阵的影响越小——scrollFactor 1表示对象跟随世界不受相机滚动影响scrollFactor 0表示对象完全锁定在屏幕上如 HUD。上面的公式中(1.0 - scrollFactor)恰好体现了这一语义。TransformMatrix.test.js 对该方法做了系统验证滚动为(0, 0)时结果与源矩阵一致scrollFactor 1时不产生滚动位移scrollFactor 0时完整应用(100, 200)的滚动偏移scrollFactor 0.5时平移分量取半。对内部系统的实际影响CanvasRenderersrc/renderer/canvas/CanvasRenderer.js与TilemapLayerCanvasRenderersrc/tilemaps/TilemapLayerCanvasRenderer.js等渲染器均改为消费新的矩阵体系GetCalcMatrix调用方在ignoreCameraPosition为true时改用camera.matrix而非camera.matrixCombined确保 framebuffer 渲染不再被相机屏幕位置污染新系统修复了大量嵌套变换、滤镜与变换矩阵组合使用时的历史问题changelog 原文fixes many issues with nested transforms, filters, and other uses of transforms。五、SpriteGPULayer 批量渲染增强SpriteGPULayer是 Phaser 4 引入的 GPU 批量精灵层beta 7 为其补齐了一批面向性能调优的 API 与文档5.1 新增成员操作方法SpriteGPULayer#insertMembers(index, members)SpriteGPULayer.js在指定索引处批量插入成员SpriteGPULayer#insertMembersData(index, data)SpriteGPULayer.js直接以底层数据块形式插入成员用于零拷贝的高效写入路径SpriteGPULayer#getDataByteSize()SpriteGPULayer.js返回单个成员的字节大小内部在分配成员缓冲区this.nextMember new ArrayBuffer(this.getDataByteSize())时使用外部在按字节写入成员数据时同样需要它来推算偏移。5.2 非循环动画与动态粒子beta 7 允许SpriteGPULayer成员动画设置loop: false一次性播放后即终止。这一能力特别适合一次性粒子特效与动态来源如程序生成的临时成员——不需要为了单次播放创建并回收整个层。从源码看循环标志被编码进 delay 的符号位SpriteGPULayer.jsvalue.loop ! undefined ? value.loop : true为默认值if (!loop)时以负 delay 写入解码端通过var loop delay 0还原。同时Gravity 模式动画现在支持负加速度即向上的喷发效果该模式使用SpriteGPULayer#gravity属性默认值为1024像素/秒²SpriteGPULayer.js。5.3 分段segment处理修正一个值得注意的底层修复SpriteGPULayer的段数从 32 改为 24。原因是 32 段会与32 位数字处理发生冲突segment 编码与位移位运算在 32 位整数边界上产生错误24 段避开了这一陷阱。若你的代码直接操作 segment 索引或依赖原始编码布局请注意段数上限已改为 24this._segments 24见 SpriteGPULayer.js成员数据的位编码被重新排列changelogRearrange SpriteGPULayer data encoding因此自定义 shader 或直接读写成员数据的代码需要同步更新修复了从配置对象config object生成帧动画失败的问题。六、其他小改动与新增能力Extern#render 文档新增了编写Extern#render函数的说明文档Extern是外部渲染对象用于把自定义渲染逻辑挂进 Phaser 渲染管线。Tilemap 父矩阵支持TilemapLayer与TilemapGPULayer渲染时支持父矩阵parent matrix可以正确嵌套在Container等带变换的父级之下。Shape 默认filtersFocusContext trueShape.js防止滤镜上下文聚焦时把描边stroke裁剪到边缘之外。若你在Shape上叠加滤镜将不再需要手动设置该标志。七、修复与行为调整清单7.1roundPixels默认改为false这是 beta 7 中最可能影响现有项目观感的行为变化。在 Config.js 中roundPixels的默认值由true调整为falseGetValue(renderConfig, roundPixels, false, config)开启pixelArt时仍会自动把roundPixels置为trueWhen enabled, this also setsantialiasandantialiasGLtofalseandroundPixelstotrue。官方注释给出了理由该选项极易产生messy results脏乱像素边缘因此默认关闭但它仍保留给确实需要整数像素对齐的场景。迁移建议追求像素风、需要纹理对齐像素网格的项目显式配置roundPixels: true或直接开启pixelArt: true其他项目保持默认即可无需改动。7.2 DOMElement 无容器时报错DOMElement依赖一个 DOM 容器来挂载元素。beta 7 起若游戏配置中没有设置 DOM 容器构造时会直接抛出明确错误throw new Error(No DOM Container set in game config);见 DOMElement.js。这要求使用DOMElement前必须在 Game 配置中提供dom.createContainer或等价配置错误信息比静默失败更容易定位问题。7.3 TextureSource#setFlipY 全局生效TextureSource#setFlipY(value)TextureSource.js现在能影响所有纹理——除了压缩纹理compressed textures——因为压缩纹理的方向是固定的。该方法控制 WebGL 纹理上传时的UNPACK_FLIP_Y_WEBGL标志值为undefined时默认为true且会触发this.update()刷新纹理。7.4 WebGLProgramWrapper 对 undefined uniform 的识别WebGLProgramWrapper现在能正确识别值为undefined的 uniform并判断其是否真的发生了变化如果 uniform 仍是undefined且没有更新则跳过无意义的 GPU uniform 上传减少不必要的状态更新开销。7.5 TileSprite 与 smoothPixelArt修复了TileSprite错误应用smoothPixelArt游戏选项的问题——此前该选项在TileSprite上可能被错误地套用导致平铺精灵纹理出现意外的线性/最近邻过滤行为。7.6 BatchHandler 事件引用修复BatchHandler中缺少对 Renderer 事件Renderer events的引用问题感谢 mikuso 的反馈避免在特定事件分发路径上出现引用错误。八、升级与迁移清单针对 beta 7 的变更升级时建议逐项核对不要直接读取/改写Camera#matrix来叠加滚动改用camera.matrixExternal、camera.matrixCombined或通过GetCalcMatrix的结果消费矩阵需要带滚动因子的副本时使用copyWithScrollFactorFrom。自定义渲染器 / 自定义 shader确认渲染管线传入的矩阵语义。渲染到 framebuffer 时传camera.matrix不含位置常规渲染时传camera.matrixCombinedGetCalcMatrixResults的新属性cameraExternal对应相机屏幕位置。SpriteGPULayer段数若依赖 32 段布局需调整为 24 段并适配新的位编码一次性粒子特效请使用loop: false。roundPixels若此前依赖默认开启的像素对齐需在配置中显式声明。DOMElement确保 Game 配置中创建了 DOM 容器否则直接抛错。Shape 滤镜无需再手动开启filtersFocusContext默认已开启以避免描边被裁剪。结语Phaser 4.0 beta 7 的相机矩阵重写是一次向内的架构级变更对外 API 保持稳定对内则将视图与位置彻底解耦并通过GetCalcMatrix(ignoreCameraPosition)为 framebuffer 渲染提供了一条干净路径从根上解决了嵌套变换与滤镜场景下的矩阵污染问题。配合SpriteGPULayer的批量能力扩展与一系列渲染修复这一版本为 Phaser 4 的正式发布奠定了更稳固的变换与渲染基础。后续版本中这些 API 已持续演进建议以仓库内 changelog/v4/4.0-rc 目录下的相邻版本记录如 beta-8、rc.1rc.7对照阅读掌握完整演进脉络。【免费下载链接】phaserPhaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.项目地址: https://gitcode.com/gh_mirrors/ph/phaser创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进