变体与 keyword:multi_compile vs shader_feature

Unity 知识 · Shader 区域 · 管线与工程 · 第 3 篇 更新于 2026-10-15

一个 shader 文件编译出来往往不是「一个程序」,而是几十上百个程序——每个 keyword 组合一个变体。变体数量决定包体大小、编译时长、首帧卡顿,是 shader 工程里最容易被忽视的成本项。

变体是什么

#pragma multi_compile _ _NORMALMAP 声明一个 keyword:编译器生成两份完整代码(开/关各一份),运行时按 keyword 状态选用哪份。这不是运行时分支——每个变体是独立编译的程序,切换 keyword 只是切换程序,没有分支代价(对比见 /notes/unity/principles-4/)。

N 个布尔 keyword = 2^N 个变体,再乘以每个平台(DX11、Vulkan、GLES3……各编译一遍)。10 个 keyword × 5 平台 = 5120 份程序——这就是「一个 shader 导入半分钟、包体里 shader 占几百 MB」的来源。

multi_compile 与 shader_feature 的区别

multi_compile A Bshader_feature A B
保留哪些变体全部保留只保留场景里真有材质用到的
谁控制开关引擎/代码全局设置(Shader.EnableKeyword)材质勾选/材质参数
典型用途引擎级状态:阴影模式、lightmap 开关材质级特性:法线贴图、自发光
风险变体爆炸主力运行时才开 keyword 的材质 → 变体已被裁掉,静默失效

URP 内置的 multi_compile _ _MAIN_LIGHT_SHADOWS ... 属引擎状态,必须全保留;自己加材质级开关时默认用 shader_feature。

_local:变体爆炸的第一解药

keyword 是全局字符串:两个不相干 shader 都定义 _ENABLE_X,Unity 会视为同一 keyword,变体组合互相牵连。shader_feature_local / multi_compile_local 把 keyword 作用域缩到单个 shader,互不污染——新代码一律用 _local 版本,这是减少全局变体污染最便宜的改动。

查看与裁剪

  • Shader Inspector 直接显示「Compiles into X variants」;Compile and show variants 按钮逐变体列出 keyword 组合与每平台指令数/采样数/临时寄存器——这份数据同时是性能优化的输入(见 /notes/unity/performance-1/)。
  • 引擎自动裁剪:URP asset 按质量档裁高特性变体(低档不编译级联阴影变体);未用的 shader_feature 变体构建时剥离。
  • 自查清单:把 multi_compile 换 shader_feature_local;删掉没人用的 Properties+keyword 对;一个 keyword 只控制一个特性(别用三个 keyword 表达一个三态选择——用一个枚举 keyword 组 multi_compile A B C 是 3 变体而不是 8)。

运行时 keyword 的坑

  • shader_feature 的 keyword 在构建时按当时场景里的材质裁剪;AssetBundle/资源包里动态 EnableKeyword 打开一个构建时没用的组合 → 没有对应变体,效果静默不出现。资源包项目要么用 Always Included Shaders/shadecabor 收集,要么改 multi_compile。
  • MaterialPropertyBlock 传参数没问题,但不能跨变体切 keyword——它只改 uniform。
  • 首帧卡顿:变体在首次使用时才上传 GPU 编译(异步编译可缓解,预热用 Shader Variant Collection)。

决策速查

要加的开关用什么
材质面板勾选(法线贴图、rim)shader_feature_local
引擎/管线状态(阴影、lightmap)multi_compile(照抄 URP 库现成的)
运行时按 Draw 变化的连续量不是 keyword——uniform + 数学(lerp/step),见 /notes/unity/principles-4/
运行时切换的离散模式(少)multi_compile_local + keyword 切换

自检三问

  • 10 个布尔 keyword 为什么不是 10 个变体而是 1024 个?
  • AssetBundle 里动态开 keyword 效果没出现,最可能缺什么?
  • 三态选择用两个布尔 keyword 和用一个三值 keyword 组,变体数各是多少?