ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Unity Shader 中的 multi-compile 关键字详解

2026/8/24 4:57:12 拓冰建站 浏览量
Unity Shader 中的 multi-compile 关键字详解 #pragma multi_compile是 Unity Shader 里用来生成**多个 shader 变体variants**的机制。理解它能帮你搞懂为什么一个 shader 文件能应对开关阴影、开关雾效、切换光照模式等无数种情况。一、先搞懂核心问题为什么需要 shader 变体假设你写一个 shader想支持雾效开/关两种情况。最笨的办法是写两个 shader 文件。但这样太蠢了——代码几乎一样只差几行。更聪明的做法是在一份代码里用条件编译#ifdef FOG_ON color ApplyFog(color); // 开雾时才执行 #endif问题是FOG_ON这个宏是开还是关如果写死那就只能编译出一种情况。我们希望的是——同一份代码编译出开雾和关雾两个版本运行时按需切换。这就是multi_compile干的事让编译器把一份代码按关键字的不同取值编译成多个版本变体。二、基本用法#pragma multi_compile FOG_OFF FOG_ON这行的意思是定义一组关键字FOG_OFF和FOG_ON编译器会分别编译两个版本版本 1定义了FOG_OFF版本 2定义了FOG_ON配合#ifdef使用#pragma multi_compile FOG_OFF FOG_ON // ... 顶点/片元着色器里 ... fixed4 frag(...) : SV_Target { fixed4 color tex2D(_MainTex, i.uv); #ifdef FOG_ON color ApplyFog(color); // 只有 FOG_ON 版本会包含这段 #endif return color; }编译结果Unity 生成了两个 shader 变体。运行时通过代码决定用哪个// C# 端切换material.EnableKeyword(FOG_ON);// 用开雾版本material.DisableKeyword(FOG_ON);// 用关雾版本回到 FOG_OFF三、几种写法及其含义1. 普通多选一#pragma multi_compile _A _B _C生成3 个变体分别定义_A、_B、_C。2. 用下划线表示什么都不定义#pragma multi_compile _ FANCY_EFFECT第一个_是个占位符代表不定义任何关键字默认版本第二个是FANCY_EFFECT生成 2 个变体一个啥也没定义一个定义了FANCY_EFFECT。这是开关型关键字的常见写法_ 关FANCY_EFFECT 开。3. 多组关键字会相乘#pragma multi_compile _ SHADOWS_ON #pragma multi_compile _ FOG_ON这不是 4 个变体而是 2×2 4 个组合变体① 无阴影 无雾 ② 有阴影 无雾 ③ 无阴影 有雾 ④ 有阴影 有雾⚠️关键警告多组关键字是相乘关系。每加一组变体数量翻倍。这会导致后面要讲的变体爆炸问题。四、multi_compile vs shader_feature重要区别Unity 里有两个很像的指令新手常搞混#pragma multi_compile _ MY_KEYWORD #pragma shader_feature _ MY_KEYWORD它们语法一样行为也几乎一样唯一区别在打包build时multi_compileshader_feature打包时所有变体都保留只保留实际被用到的变体适用场景运行时动态切换的关键字只在材质面板上勾选、且打包后不变的关键字未使用的变体仍打进包里被剔除节省包体通俗理解shader_feature如果没有任何材质用到MY_KEYWORD这个版本打包时就把它扔掉。适合美术在 Inspector 里勾选的开关——因为最终用哪些是确定的。multi_compile无论如何都保留所有变体。适合运行时用 C# 代码EnableKeyword动态切换的情况——因为编译器不知道运行时会用哪个只能全留着。一句话选择运行时代码动态开关关键字 → multi_compile必须全保留 美术在材质面板勾选、打包后固定 → shader_feature可剔除未用的省包体五、变体爆炸Variant Explosion——最大的坑前面说过多组关键字是相乘的。看这个例子#pragma multi_compile _ A B // 3 种 #pragma multi_compile _ C D // 3 种 #pragma multi_compile _ E F G // 4 种 #pragma multi_compile _ H // 2 种总变体数 3 × 3 × 4 × 2 72 个变体而 Unity 自带的一些内置multi_compile如阴影、光照贴图、实例化也会叠加进来。真实项目里一个 shader 编译出上千甚至上万个变体是常事。变体爆炸的危害变体太多 → ① 打包时间暴增编译上万个变体几十分钟起步 → ② 包体变大每个变体都是一段编译后的代码 → ③ 运行时加载/切换变体有卡顿如何缓解1. 优先用shader_feature能自动剔除未用变体2. 用_local变体Unity 2019.1#pragma multi_compile_local _ MY_KEYWORD_local表示这个关键字只在本 shader 内部有效不占用全局关键字槽位Unity 全局关键字数量有限早期是 256 个上限。3. 主动剔除不需要的组合用 Unity 的IPreprocessShaders接口在打包时过滤掉用不到的变体。4. 减少关键字组数能合并的尽量合并别滥用。六、Unity 内置的常用 multi_compileUnity 提供了一些快捷指令一行展开成一整组内置关键字#pragma multi_compile_fog // 展开雾效相关关键字FOG_LINEAR/EXP/EXP2 #pragma multi_compile_fwdbase // 前向渲染 base pass 需要的一整套 #pragma multi_compile_fwdadd // 前向渲染 additive pass #pragma multi_compile_instancing // GPU Instancing 支持 #pragma multi_compile_shadowcaster // 阴影投射 pass例如#pragma multi_compile_fog等价于展开雾效的多种模式变体你不用手写。七、C# 端如何控制关键字// 针对某个材质material.EnableKeyword(FOG_ON);material.DisableKeyword(FOG_ON);boolisOnmaterial.IsKeywordEnabled(FOG_ON);// 全局开关影响所有用到该关键字的 shaderShader.EnableKeyword(GLOBAL_EFFECT);Shader.DisableKeyword(GLOBAL_EFFECT);URP/HDRP 里更推荐用LocalKeyword结构体配合_local变体性能和管理更好。八、完整示例Shader Custom/MultiCompileDemo { Properties { _MainTex (Texture, 2D) white {} } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // 声明关键字组开关雾效 #pragma multi_compile _ FOG_ON // 声明关键字组三选一的光照模式 #pragma multi_compile LIGHT_SIMPLE LIGHT_NORMAL LIGHT_ADVANCED #include UnityCG.cginc sampler2D _MainTex; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert(appdata_base v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.uv v.texcoord; return o; } fixed4 frag(v2f i) : SV_Target { fixed4 color tex2D(_MainTex, i.uv); // 根据光照模式关键字走不同分支 #if defined(LIGHT_ADVANCED) color.rgb * 1.5; // 高级光照 #elif defined(LIGHT_NORMAL) color.rgb * 1.0; // 普通光照 #else // LIGHT_SIMPLE color.rgb * 0.8; // 简单光照 #endif // 雾效开关 #ifdef FOG_ON color.rgb lerp(color.rgb, fixed3(0.5,0.5,0.5), 0.3); #endif return color; } ENDCG } } }这个 shader 会生成2雾开关× 3光照模式 6 个变体。九、总结速记multi_compile 是什么 → 让一份 shader 代码按关键字取值编译出多个变体运行时切换 核心语法 #pragma multi_compile _ KEYWORD_A KEYWORD_B #ifdef KEYWORD_A ... #endif 关键要点 ① 多组关键字是【相乘】关系 → 小心变体爆炸 ② _ 表示不定义任何关键字常用作默认/关 ③ multi_compile 全部保留运行时动态切换用 shader_feature 剔除未用材质面板勾选、打包固定用 ④ _local 变体不占全局关键字槽位优先使用 避免变体爆炸 优先 shader_feature → 用 _local → 打包剔除 → 精简关键字组一句话multi_compile是用编译期分身术换取运行期灵活性——牺牲编译时间和包体换来一份代码应对多种运行时状态的能力。用好它的关键是时刻警惕变体数量的乘法爆炸。