学习进度
0/9 已勾只保存在这台浏览器;勾满 9 项即视为本题完成。
先把题目说成人话
角色放技能时,皮肤流光、轮廓亮边、电弧、粒子和拖尾像同一股能量在接力;若每一项各自决定颜色、节奏和开关,效果很快变成互相打架的一团。更稳的做法是让每个部分只负责一个可见任务,共享少量时间和强度信息,并能在低配时逐层关闭。这就是模块化特效(Effect Modules):既要让画面统一,也要让质量降级可控。
这是虚构教学故事,不是个人项目经历。
场景:先让问题变得看得见
移动角色的电光经历蓄力、爆发和消散。表面亮纹先聚向手臂,随后跳出几道电弧,挥击留下短拖尾,最强的一瞬才扭曲背景。如果把所有东西塞进同一块角色材质,改一处节奏就可能牵动整套效果,低配设备也无法只拿掉最贵的一层。
先把它看成一场分工明确的演出:总控只发“现在到哪一拍、能量多强、是什么颜色、从哪些骨骼位置出发”,每组演员只领取自己需要的信息。
(右脑)画面: 电光皮肤像一场由同一鼓点指挥的舞台演出。贴在皮肤上的亮纹负责让能量沿身体流动,轮廓灯负责让角色在混战里仍能被认出,手臂间的电弧和挥击拖尾负责画出动作方向,烟点与火花负责补充爆发,背景扭曲只在最强一拍短暂登场。所有演员听同一个蓄力、爆发、消散节奏,却各自拥有上场和退场开关;低配版本减少的是次要演员、出现频率和整屏工作,不能让保留下来的角色轮廓与技能时机改口径。
(左脑)对表: 先由皮肤特效控制器(SkinEffectController)接收技能事件,把三段节奏映射为归一化相位(phase)、强度、颜色、质量档与有效骨骼锚点。角色材质读取表面遮罩和这些连续参数;轮廓、电弧、粒子、拖尾与扭曲分别拥有自己的 Renderer、Pass 或 ParticleSystem 生命周期。连续数值走材质参数或共享缓冲,只有真正改变编译路径的有限功能才使用着色器关键字。构建报告证明实际变体数量,帧调试器证明关闭档位后对应绘制是否消失,目标设备计时再证明收益。
小明:先形成一个完整猜测
小明为每个模块做一个 Shader Keyword:FLOW_ON、RIM_ON、ARC_ON、DISTORT_ON……这样确实能在编译时完全关闭功能,也方便组合。
但多个独立布尔关键字会形成组合空间。Build Report 里实际保留的 Variant 接近 500 个,许多组合只是同一视觉模块在不同强度下开关;而材质参数的数值变化被误做成了关键字,运行阶段也不断切材质。
小红:把猜测放回现实检查
小红先区分“会改变编译路径的功能”与“只是同一路径的数值”。额外算法或纹理采样可以使用少量 local shader_feature;颜色、强度、相位、流速和锚点则是参数,可由 MaterialPropertyBlock 或共享 Buffer 传入,不需要生成 Variant。独立 Pass、Renderer、ParticleSystem 或后处理模块不会因为一个 Keyword 自动停止提交,必须由模块生命周期显式启停。Material.SetShaderPassEnabled 只在 Material 级别开关 Pass;若多个角色共享同一材质,调用它会一起受影响,不能冒充逐 Renderer 开关。逐角色或逐质量档要么使用明确分开的材质实例,要么在 Renderer/Render Feature 层停止提交。
她逐个移除三个只控制数值效果的独立关键字,并给不可能出现的质量组合加构建期 Variant Stripping。新的 Build Report 从约 500 个实际保留 Variant 降到约 200 个。这个下降是报告测出的合并结果,原因是 Variant key 变少且无效组合被剔除;不能拿 2³ 直接推导最终一定降八倍。
我:用检查结果修正模型
我把模块和档位固定成清楚的数据契约:
- 角色材质读取统一 Mask、Flow Map、phase、intensity 与 color,负责表面发光。
- 轮廓模块只强化角色外形,可按遮挡与镜头距离启停。
- 电弧、粒子、拖尾订阅蓄力/爆发/消散事件,并使用骨骼锚点;对象池负责生命周期。
- 扭曲/后处理是独立可选 Pass,不让低档角色材质背着它运行。
高档保留全部模块;中档去掉全屏扭曲并减少电弧频率;低档只保留表面脉冲、关键轮廓和少量爆发粒子。三个档位仍使用同一套美术 Mask 和事件,不额外复制一套皮肤资产。每档分别抓 GPU 帧,确认关闭模块后对应 Pass 与资源真的消失,而不是 intensity 归零却仍执行全部计算。
(右脑)画面修正: 小明给每个效果和数值都配独立 Keyword,Build Report 却亮出接近 500 个实际 Variant,证明“开关越多越模块化”并不成立。小红把相位、颜色、强度等连续变化改回参数,只把真正改变算法或 Pass 的有限功能留给编译分支,再配合 stripping,把实际保留量降到约 200;她还在 Frame Debugger 中确认低档关闭模块后对应 Pass 确实消失。于是模块边界最终由输入契约、编译路径和生命周期共同决定,而不是把 intensity 设为零却继续执行全部工作。
(左脑)严格对表: 先定义 Controller 输出契约:phase∈[0,1]、intensity、color、qualityTier 与有效骨骼锚点;为每个模块列输入、Renderer/Pass、创建/启停/回收和无支持时 fallback。连续数值走参数或共享 Buffer,只有改变编译路径的有限功能使用 shader_feature_local,并建立允许组合与 stripping 清单。每档在构建报告核对 Variant,在 Frame Debugger核对 Draw/Pass,在目标设备核对 GPU ms、内存和画面阶段;关闭模块必须不再提交或采样,而非仅把强度设零。
老师解惑
- 参数不等于 Keyword:参数改变同一 Shader 路径中的数值;
shader_feature/multi_compileKeyword 选择编译 Variant,而dynamic_branchKeyword 不生成 Variant。它们都不会自动替你停掉独立 Renderer 或 Pass。 - 模块化要同时有视觉职责、输入数据、生命周期、质量档和失败回退,不只是把文件拆开。
- MaterialPropertyBlock适合给共享材质传每 Renderer 参数,避免为了颜色或相位复制材质;在本页锚定的 Unity 2022.3 SRP Batcher 路径中,它会让该 Renderer 失去 SRP Batcher 兼容,因此还要和 GPU Instancing、共享 Buffer 方案在目标管线上比较。
- Material.SetShaderPassEnabled 是每材质开关,不是每渲染器开关;共享材质使用者会一起受影响。
- Variant 数量以构建报告为准,理论组合只能说明风险,不能代替实际保留结果。
工具分工
- Shader Variant 日志/Build Report:确认关键字组合和 stripping 后的实际数量。
- Frame Debugger:检查每个档位是否真的移除了对应 Pass、材质切换和绘制。
- Unity Profiler 与目标设备 GPU 工具:比较模块时间、粒子覆盖和全屏扭曲成本。
- skin-rim-light Lab:观察 Mask、轮廓与共享参数怎样组合成可降级效果。
记忆钩子
按视觉职责拆模块,用事件和参数接线;只有真正改变编译路径的功能,才配占一个 Keyword。
这是教学场景,不是目标项目的真实捕获或个人经历。
下一步:验证这条因果
这张模块因果图能证明六个模块怎样接线,以及每个中文控件只改变对应图形层。
教学边界:这个浏览器实验只是一张模块因果图,不会编译 Unity Shader,也不会运行 VFX Graph;真实画面和性能必须回到 Unity 与目标手机验收。