特效问题经常从一个闪白开始
角色受击闪白、可交互物描边、Boss 登场屏幕扭曲、低血量红边、冰冻变蓝,这些效果看起来只是美术表现,落到 Phaser WebGL 项目里却会影响渲染路径、批处理、纹理采样和低端设备性能。最常见的起点是“能不能让怪物受击时闪一下白”。
我见过一个动作 H5 项目,第一版受击闪白直接复制一张白色贴图叠在怪物上。小怪少时没问题,后面怪物数量增加,叠图、透明混合和对象创建把渲染压力推高。后来改成 shader 统一处理,效果稳定很多,但又遇到低端机 WebGL 精度和兼容问题。视觉效果不是单纯加层图,必须进入渲染预算。
Phaser 的 WebGL Pipeline 和 PostFX 能做很多效果,但要先分清对象级效果、材质级效果和屏幕级效果。每种效果的成本和生命周期不同,不能所有需求都用同一种方案。
flowchart TD
A[视觉需求] --> B{作用范围}
B -->|单个对象| C[对象 Pipeline 或 Tint]
B -->|一组对象| D[共享 Pipeline 参数]
B -->|全屏| E[Camera/PostFX]
C --> F[检查批处理和对象数量]
D --> F
E --> G[检查 RenderTexture 和采样成本]
F --> H[设备档位降级]
G --> H
先用简单手段解决简单问题
不是所有效果都需要自定义 shader。Phaser 自带 tint、alpha、blend mode、mask、camera effects 已经能覆盖很多需求。受击轻微闪烁可以先用 tint 和时间控制,按钮高亮可以用贴图状态,屏幕震动可以用 camera shake。只有当简单手段无法满足质量或性能时,再考虑 Pipeline。
自定义 shader 的成本不只是代码。它增加了调试难度、兼容风险和美术协作成本。移动浏览器 WebGL 实现差异仍然存在,某些低端设备对 precision、循环、纹理采样都更敏感。能用稳定 API 完成的效果,不要为了炫技上 shader。
但简单手段也有边界。大量对象叠加白色贴图、频繁创建遮罩、全屏半透明层叠太多,都可能比一个清楚的 shader 更贵。选择方案要看对象数量、效果频率和目标设备。
对象级效果要考虑批处理
对象级 Pipeline 可以让某个 sprite 使用特殊 shader,比如描边、闪白、溶解。问题是不同 Pipeline 或不同纹理状态可能打断批处理。单个 Boss 用特殊效果没问题,几百个小怪每个都有独立参数就要小心。
如果很多对象使用同类效果,尽量共享 Pipeline,并把参数设计成可批处理或少切换。比如受击闪白可以在对象数据里保存 flashAmount,统一 shader 读取;但如果每个对象都频繁切换 pipeline,渲染批次会变多。
描边效果也要谨慎。经典描边需要多次采样周围像素,采样次数越多越贵。小型角色可以接受,满屏敌人都描边就不现实。可以只给当前目标、可交互物或 Boss 描边,普通小怪用更便宜的 tint 或轮廓贴图。
屏幕后处理要有明确场景
全屏后处理适合低血量红边、暂停模糊、Boss 大招变色、回忆滤镜等场景。它通常作用于 Camera 或 RenderTexture,对整个画面采样。成本和屏幕分辨率直接相关。高分辨率手机上,全屏效果比单个对象效果更容易烧预算。
全屏 blur 尤其危险。模糊需要多次采样,移动端很容易掉帧。如果只是暂停背景虚化,可以考虑截一张低分辨率 RenderTexture 后模糊,或者用暗色遮罩和缩放截图替代。玩家感知到的是层级和聚焦,不一定需要真实高质量模糊。
全屏效果还要注意 UI。低血量红边可以覆盖 UI,剧情滤镜可能不该影响字幕,暂停模糊通常只模糊游戏世界不模糊弹窗。Camera 分层和 UI Scene 分离能让后处理范围更清楚。
受击闪白的工程做法
受击闪白看似简单,实际有三种常见方案。第一种是 tint 或 fillColor,便宜但效果有限。第二种是叠加白色副本,兼容但增加对象和混合成本。第三种是 shader 混合原贴图和白色,效果好但要管理 pipeline。
如果怪物数量不多,shader 方案很干净。每个敌人保存 flashUntil,渲染时计算 flashAmount,shader 输出 mix(color, white, flashAmount)。如果怪物数量很多,可以降低闪白频率,或者只对屏幕内、距离玩家近的敌人生效。
闪白不应该阻塞受击逻辑。命中系统发出 enemy.hit 事件,表现系统设置闪白时间。敌人死亡或回收时,要清理闪白状态。对象池复用时尤其要注意,否则下一只怪出生时可能还带着旧闪白。
Shader 参数要集中管理
很多项目把 shader 参数散在对象回调里:这里改 time,那里改 intensity,另一个地方改 color。后期调试很痛。建议为每种效果建立小型控制器,统一管理参数、默认值、设备档位和生命周期。
比如 OutlineEffect 控制当前描边目标、颜色、强度;HitFlashEffect 控制闪白时长和曲线;ScreenFx 控制全屏后处理栈。业务系统只发语义事件,不直接操作 shader uniform。
集中管理还有利于降级。低端设备可以关闭描边采样、减少全屏效果、降低 RenderTexture 分辨率。若参数散落各处,就很难统一降级。
兼容性和降级不能最后补
WebGL 效果上线前必须测多设备。Chrome 桌面、安卓 Chrome、微信 WebView、iOS Safari 都要看。某些 shader 在桌面正常,移动端因为 precision 或纹理格式表现不同。不要只测是否显示,还要测长时间运行是否掉帧、切后台回来是否恢复。
降级策略要提前定义。设备不支持 WebGL 或 shader 编译失败时,能不能退回 Canvas 或基础表现?描边失败是否可以隐藏,受击闪白是否可以用 tint 替代,全屏滤镜是否可以用半透明遮罩替代?效果失败不应该阻止玩家进入游戏。
Shader 编译错误要能记录。开发版直接显示错误,线上可以记录设备、浏览器、shader 名称和错误摘要。否则玩家只说“画面黑了”,工程很难定位。
一个闪白 Pipeline 的接口形状
下面示例不展开完整 GLSL,只展示业务侧如何使用效果。重点是业务代码不直接关心 shader 细节。
class HitFlashController {
private flashing = new Map<Phaser.GameObjects.Sprite, number>();
hit(sprite: Phaser.GameObjects.Sprite, duration = 120) {
this.flashing.set(sprite, performance.now() + duration);
sprite.setPipeline('HitFlashPipeline');
}
update(now: number) {
for (const [sprite, until] of this.flashing) {
if (now >= until) {
sprite.resetPipeline();
this.flashing.delete(sprite);
}
}
}
}
真实项目里还要处理对象销毁、Scene shutdown、对象池 reset 和低端设备禁用。控制器存在的意义是让效果生命周期可见,而不是在每个敌人类里复制一段闪白逻辑。
美术协作要给预览
Shader 效果很难只靠参数表协作。美术需要看到不同强度、不同颜色、不同角色尺寸下的效果。可以做一个效果预览 Scene,列出所有角色和特效,支持调参数、切背景、切设备档位。这样美术和工程能在同一个画面讨论。
预览 Scene 还可以作为回归工具。每次改 Pipeline 后,打开预览看受击闪白、描边、冰冻、低血量滤镜是否正常。视觉效果不像业务逻辑那样容易单测,预览工具就是最实际的测试入口。
如果项目有多套皮肤,效果要在深色、浅色、高饱和、低饱和角色上都看。白色角色的闪白可能看不出来,黑色角色的描边可能过强。参数不能只按一个角色调。
上线前检查清单
上线前检查:效果是否真的需要 shader,Pipeline 是否会打断大量批处理,全屏后处理是否影响 UI,低端设备是否有降级,shader 编译失败是否有兜底,对象池回收是否清理效果状态,切后台恢复后 pipeline 是否正常,预览 Scene 是否覆盖主要角色。
还要压测最坏场景:满屏敌人同时受击、Boss 大招全屏滤镜、暂停模糊叠加弹窗、低电量模式、iOS Safari 长时间运行。视觉效果要在最热闹的时候仍然服务可读性,而不是抢走性能预算。
让效果参数进入内容流程
视觉效果的参数不要只存在工程代码里。受击闪白时长、描边颜色、冰冻饱和度、低血量红边强度,这些都会被美术和策划反复调整。可以把参数放进 effect config,并在预览 Scene 里实时读取。工程负责定义参数范围和默认值,内容团队负责在范围内调表现。
参数范围很重要。闪白强度超过 1 没意义,描边宽度过大就会采样过重,模糊半径过大会直接掉帧。配置加载时应该校验并裁剪,而不是让错误参数进入 shader。否则一次远程配置失误,就可能让所有低端设备黑屏或掉帧。
还可以按设备档位提供效果表。高档设备使用 shader 描边和细腻闪白,中档设备降低采样,低档设备退回 tint 和简单遮罩。这样视觉品质不是一个开关,而是一组有预算的表现策略。
渲染问题的排查路径
遇到画面异常时,不要一开始就怀疑 shader 数学。先确认对象是否还在显示列表,纹理 key 是否正确,pipeline 是否成功设置,uniform 是否更新,camera 是否忽略了该对象,blend mode 是否被其他代码改过。很多所谓 shader 问题,最后其实是对象池复用时没有 reset pipeline。
如果只有部分设备异常,再看 WebGL 能力和 shader 编译日志。precision 不足、循环写法、纹理尺寸、透明预乘和上下文丢失都可能造成差异。开发版可以在启动时打印 renderer、WebGL version、max texture size 和启用的效果档位。诊断信息越完整,越少依赖猜测。
排查性能时,要区分 CPU 和 GPU。对象太多、频繁切 pipeline、每帧创建 RenderTexture 偏 CPU 或提交成本;全屏多次采样、过高分辨率后处理更偏 GPU。不同瓶颈的解决方式不同,不能只靠减少敌人数量解决所有渲染问题。
效果上线后还要观察真实设备数据。某个滤镜在测试机上顺滑,不代表渠道 WebView 里也稳定。把效果档位写进性能日志,后续才能判断是哪一类效果拖慢了哪些设备。
如果某个效果只能在少数高端机稳定运行,就把它当成增强项,而不是核心反馈。
结语
Phaser WebGL Pipeline 能显著提升画面表现,但它不是免费装饰。受击闪白、描边、滤镜、溶解和扭曲都要进入渲染预算、生命周期和设备兼容设计。越是高频效果,越要工程化管理。
好的后处理系统让美术有表达空间,让玩家获得清楚反馈,也让工程知道成本在哪里。不要把每个效果都写成临时 shader。用简单手段解决简单问题,用 Pipeline 解决真正需要的效果,并且从第一天就准备降级。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。