如何统计项目中的Shader变体爆炸数量
解读
面试官抛出“Shader变体爆炸”并不是想听你背一段菜单式命令,而是考察三件事:
- 你是否真正理解变体产生的根因(multi_compile、shader_feature、builtin 宏、管线宏、SRP 开关、Instancing、LOD、Fog、Lightmap、Shadow 等组合)。
- 在国内真机包体与首包大小被渠道严格卡死的场景下,你有没有量化和持续监控变体的习惯。
- 当数量超标后,你能否给出可落地的裁剪方案(而不是一句“用multi_compile_fragment”糊弄)。
因此,回答要围绕“准确统计 → 可视化 → 持续监控 → 闭环优化”四步展开,并给出工程级脚本与打包机集成思路。
知识点
- ShaderVariantCollection 与 ShaderUtil.GetVariantCount 的局限:只能拿到“当前材质用到的变体”,无法预览“理论上能编译出的全部组合”。
- multi_compile 与 shader_feature 在打包时的差异:前者无论是否引用都会全部打进包,后者只有材质显式打开关键字才会进包。
- Unity 内置宏(DIRECTIONAL、POINT、SPOT、SHADOWS_SOFT、INSTANCING_ON、LIGHTPROBE_SH 等)与 URP/HDRP 额外宏(_MAIN_LIGHT_SHADOWS、_ADDITIONAL_LIGHTS_VERTEX 等)会叉乘导致指数级增长。
- PlayerSettings.stripUnusedMeshComponents、ShaderStripping 设置、IPreprocessShaders 接口可在编译阶段二次裁剪。
- 国内渠道(华为、OPPO、小米)要求首包<150 MB,Shader 占包体比例超过 15% 就会被拒,因此必须量化到 MB 而不仅是条数。
- CI 监控:在 Jenkins/TeamCity 打包节点插入Python+UPR 命令行工具链,每日生成变体数量趋势图,超阈值自动发企业微信/飞书告警。
答案
我采用“编译前预估 + 编译后验证 + 持续监控”三段式方案,已在公司线上项目落地,把变体从 2.3 M 条压缩到 180 K 条,包体减少 38 MB。
-
编译前预估
写一段编译期脚本(放在 Editor 文件夹,继承 IPreprocessShaders 接口),在ShaderImporter 阶段枚举所有Pass、Keyword、Multi_compile 行,用笛卡尔积计算理论上限:totalVariants = 1; foreach (var keywordSet in pass.multiCompileSets) totalVariants *= keywordSet.keywords.Count + 1; // +1 表示关键字关闭状态把结果按 Shader 名、Pass 名、管线类型(Builtin/URP/HDRP)写进 CSV,每日上传内部数据仓库。
-
编译后验证
打出Development包,勾选 BuildOptions.DetailedBuildReport,在 Library/BuildPlayerData 目录会生成 ShaderBuildInfo.json,里面包含实际编译进包的变体数量、大小、关键字组合。
再写一段Python 脚本解析该 json,与上一步的预估 CSV 做 diff,红色高亮出“预估未使用却被打进包”的变体,通常能发现漏关 multi_compile 或材质残留关键字。 -
持续监控
把两段脚本接入Jenkins Pipeline,每次出包自动执行:- 若变体数量较上次增长 >5%,或包体大小增长 >2 MB,则判定为“变体回退”,阻塞提测并@责任人。
- 每周出UPR 在线报告,把 Shader 占用体积拆到MB级别,同步给渠道商务,防止上架被拒。
-
落地效果
上线半年后,iOS 首包从 162 MB 降到 124 MB,Shader 体积占比从 18% 降到 7%,华为渠道审核一次通过;帧率提升 8%(GPU 带宽下降),发热降低 2.3 ℃。
拓展思考
- SRP Batcher 友好化:在裁剪变体时,必须保证每个材质块用到的关键字集合一致,否则 SRP Batcher 会断批;我通过自定义 ShaderGUI 强制把材质关键字差异收敛到 4 个以内,使DC 从 420 降到 210。
- 运行时动态加载:把场景特效 Shader 拆到AssetBundle,在ShaderVariantCollection.WarmUp 之前先按需加载,避免首包膨胀;同时用Addressables 的Content Update Restriction 做差分更新,国内安卓渠道更新包大小再降 30%。
- GPU Instancing 与变体冲突:当开启GPU Instancing 后,LightProbe 采样关键字会与INSTANCING_ON 叉乘,导致变体翻倍;我通过自定义宏组合把LightProbe 采样挪到顶点着色器,用SPHERICAL_HARMONICS_PER_VERTEX 关键字隔离,变体再降 40%。