使用multi_compile_local与shader_feature的区别

解读

国内 Unity 项目面试时,面试官抛出这对关键字,核心想验证三件事:

  1. 你是否真的在移动端性能敏感场景里做过 Shader 变体管理;
  2. 能否把“宏开关”与“打包体积”“运行时内存”“加载卡顿”这些国内发行痛点联系起来;
  3. 是否具备团队协作规范意识——因为一旦用错,打出的 APK/IPA 会让渠道包体审核直接超标,或者低端机第一次加载 Shader 时卡成 PPT。

multi_compile_local 是 Unity 2021.2 之后才在国内逐渐普及的“局部变体”方案,而 shader_feature 是“老牌”功能开关;两者都能产生条件编译,但工作域、打包策略、运行时可见性完全不同。答不出差异,基本会被判定“只写过 Demo,没上过线”。

知识点

  1. 变体产生时机
    shader_feature:只有材质里实际用到的宏组合才会被打包;编辑器里没勾的宏不会进包,包体省尺寸
    multi_compile:所有排列组合全部进包,包体膨胀,但运行时无编译卡顿。
    multi_compile_local:只对当前 Shader 文件内部产生排列,不会把宏泄漏到全局关键字池,既隔离又省关键字配额

  2. 关键字配额
    Unity 全局关键字上限 384(OpenGL ES 3.0 以下只有 128),shader_feature 与 multi_compile 都占全局名额;multi_compile_local 占用的是“本地关键字”池,上限 256,与全局池隔离,对国内大量低端 Android 机是救命稻草。

  3. 运行时切换能力
    shader_feature 未打包的变体在运行时通过 Material.EnableKeyword 强行打开会触发运行时编译,造成卡顿甚至闪退;
    multi_compile 与 multi_compile_local 因为变体已提前打包,运行时切换无编译开销,适合剧情动画、皮肤特效等需要动态开关宏的业务

  4. SRP Batcher 兼容性
    在 URP/HDRP 的 SRP Batcher 下,只有材质关键字一致才能合批。shader_feature 因为变体少,更容易关键字一致;multi_compile 容易关键字爆炸导致合批失败;multi_compile_local 由于关键字是“局部”的,只要材质不用冲突宏,就不会打断合批,兼顾性能与灵活。

  5. 国内项目实战口诀
    “外观差异用 shader_feature,性能核心用 multi_compile,局部开关用 multi_compile_local,全局关键字超过 200 立刻重构。”

答案

multi_compile_local 与 shader_feature 的核心区别体现在作用域、打包体积、关键字配额、运行时开销四个维度:

  1. 作用域:shader_feature 与 multi_compile 都是全局关键字,跨 Shader 共享;multi_compile_local 的宏只在当前 Shader 文件内部生效,不会污染全局池。
  2. 打包体积:shader_feature 采用“用到才打包”,显著减小包体;multi_compile 会打全排列,包体最大;multi_compile_local 虽打全排列,但宏被隔离,不会与其他 Shader 产生笛卡尔积,整体体积增量可控
  3. 关键字配额:shader_feature/multi_compile 占用宝贵的全局 384 关键字上限;multi_compile_local 占用独立 256 本地上限国内低端机项目必须优先用 local 关键字保平安
  4. 运行时开销:shader_feature 若材质未勾选对应宏,运行时再开启会触发 JIT 编译,造成卡顿;multi_compile 与 multi_compile_local 因变体已预编,切换无延迟,适合剧情动画、GPU Skinning 开关等需要实时切换的场景。

一句话总结:shader_feature 省包体但怕运行时突变,multi_compile 稳但关键字爆炸,multi_compile_local 在“局部宏”场景下既稳又省关键字,是国内移动端 Shader 变体治理的最佳折中。

拓展思考

  1. 国内渠道包体 4G 限制下,如何把 200+ 全局关键字压到 120 以内?
    答:先把所有“外观差异”改为 shader_feature,再把“局部开关”全部迁移到 multi_compile_local,最后对剩余 multi_compile 做排列剪枝脚本,把“理论上存在、实际材质未用”的变体在打包阶段剔除,可再省 30% 关键字

  2. 热更新框架(如 HybridCLR)动态下载新皮肤时,新 Shader 变体如何兼容?
    答:把皮肤差异全部做成 multi_compile_local,本地关键字池在 App 启动时一次性申请足够余量;热更下来的 AssetBundle 里只带 local 宏,不会与主包全局关键字冲突,同时因为变体已预编,首次穿戴无卡顿,符合国内“秒穿皮肤”的付费体验要求。

  3. SRP Batcher 打断如何快速定位?
    答:在 FrameDebugger 里看到“SRP Batcher: OFF – reason: shader keyword mismatch”时,优先检查是否混用了 multi_compile 与 shader_feature 导致关键字数量不一致;若宏确实需要,把该宏改为 multi_compile_local,保证材质之间关键字集合一致,DrawCall 立刻从 300 降到 30,帧率提升 10 fps 以上,这个优化点在国内面试中属于“一击必杀”加分项