尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Shader 关键字断批原理:为什么“一个开关不同“就合不了批

Shader 关键字断批原理:为什么“一个开关不同“就合不了批 开场一个材质明明一样却断批的诡异小王遇到怪事两个物体用的同一个材质顶点也少可 Frame Debugger 提示——因为Shader 关键字不同断批“同一个材质怎么会关键字不同什么是 Shader 关键字为什么关键字不同就合不了批这背后的原理到底是什么”老鸟说“这触及了 Shader 的核心机制——变体Variant一个 Shader 因为关键字不同其实会编译成很多份不同的程序。关键字不同 用的其实是两个不同的程序 当然合不了批今天把这个原理从底层讲透” 第一幕先搞懂什么是 Shader 关键字Shader 关键字是什么Shader关键字(Keyword) Shader里的功能开关 ↓ 通过开关的开/关,让同一个Shader 能表现出不同的效果 ↓ 比如: _NORMALMAP → 开关:用不用法线贴图 _EMISSION → 开关:用不用自发光 _ALPHATEST_ON → 开关:用不用透明裁剪代码里的关键字// Shader里用关键字控制分支编译 #pragma multi_compile _ _NORMALMAP half4 frag(v2f i) : SV_Target { half4 col tex2D(_MainTex, i.uv); #ifdef _NORMALMAP // 如果开了法线贴图关键字,执行这段 half3 normal UnpackNormal( tex2D(_BumpMap, i.uv)); // ...法线相关计算 #endif return col; }关键: #ifdef _NORMALMAP → 关键字开: 编译带法线贴图的代码 → 关键字关: 编译不带的代码 ↓ 这是编译期的分支!(不是运行期)生动理解关键字Shader关键字像菜谱里的可选步骤 一份炒饭菜谱有可选开关: ☑ 加鸡蛋(_EGG) ☑ 加火腿(_HAM) ☐ 加青豆(_PEAS) ↓ 不同的勾选组合 → 做出不同版本的炒饭 ↓ 这些开关就是关键字! 第二幕核心概念——Shader 变体(Variant)⭐关键字如何变成变体关键的原理来了 一个Shader有多个关键字开关 → 每种开关组合 → 会编译成一个独立的Shader程序 ↓ 这些独立程序 Shader变体(Variant)! ↓ 一个Shader源码 → 编译出N个变体程序!变体爆炸示例假设一个Shader有3个关键字开关 _NORMALMAP (开/关) _EMISSION (开/关) _ALPHATEST (开/关) ↓ 组合数 2 × 2 × 2 8种变体! ↓ 每种组合编译成一个独立的GPU程序: 变体1: 都关 变体2: 只开NORMALMAP 变体3: 只开EMISSION ... 变体8: 都开 ↓ 它们是8个不同的程序!生动理解变体Shader变体像同一菜谱的不同成品 一份炒饭菜谱(Shader源码) ↓ 根据开关组合 不同版本的成品(变体): - 蛋炒饭 - 火腿炒饭 - 蛋火腿炒饭 ... ↓ 虽然来自同一菜谱 但成品是不同的菜! ↓ 关键: 变体是编译好的不同程序! 第三幕为什么变体不同就断批⭐核心原理断批的根本原因动态批处理要求: 合批的物体用完全相同的渲染 → 包括用同一个GPU程序! ↓ 但如果两个物体关键字不同: → 用的是不同的变体 → 就是不同的GPU程序! ↓ GPU没法用一次DrawCall 同时跑两个不同的程序! → 只能分开画 → 断批!关键洞察变体 不同程序核心理解 物体A: 材质开了_NORMALMAP → 用带法线贴图的变体程序 物体B: 材质没开_NORMALMAP → 用不带法线贴图的变体程序 ↓ 虽然是同一个Shader、甚至同一个材质资产 但实际运行的是两个不同的编译程序! ↓ 两个不同程序 → 无法合并成一个DrawCall → 断批!生动理解程序不同断批像两道不同的菜不能一锅炒 物体A用蛋炒饭程序 物体B用火腿炒饭程序 ↓ 虽然都来自炒饭菜谱 但做法不同(不同变体程序) ↓ 厨师(GPU)没法一锅同时炒 两种不同做法的饭! → 只能分两锅(分DrawCall) → 断批!更深一层GPU 的工作方式为什么一个DrawCall只能用一个程序? DrawCall 用这个程序,画这批顶点 → 一次DrawCall绑定一个Shader程序 ↓ 如果物体要用不同程序(变体) → 必须发不同的DrawCall (每个绑定各自的程序) ↓ 这是GPU渲染的基本规则! 变体不同 → 程序不同 → DrawCall必须分开 第四幕关键字的类型与来源关键字从哪来关键字的开关状态来源 ① 材质设置: 材质Inspector里开关某功能 (如勾选启用法线贴图) → 设置对应关键字 ② 代码设置: material.EnableKeyword(_NORMALMAP); material.DisableKeyword(_NORMALMAP); ③ 全局设置: Shader.EnableKeyword(...) 全局关键字 ↓ 不同物体的关键字状态可能不同 → 用不同变体关键字的类型① multi_compile: #pragma multi_compile _ _FEATURE → 所有组合的变体都编译保留 → 常用于全局/运行时切换的功能 ② shader_feature: #pragma shader_feature _ _FEATURE → 只保留材质实际用到的变体 → 没用到的变体会被剔除(省包体) ↓ 两种都会产生变体,只是保留策略不同生动理解关键字来源关键字开关像点菜时的选项 材质设置 → 我这份要加蛋 代码设置 → 临时改成加火腿 全局设置 → 今天全店都加葱 ↓ 不同顾客(物体)选项不同 → 上的是不同版本的菜(变体) → 不能合并出餐(断批)!️ 第五幕如何避免关键字导致的断批方法1统一关键字状态✅ 让要合批的物体关键字一致 - 用相同的材质设置 - 都开或都不开某功能 ↓ 关键字一致 → 用同一变体 → 能合批方法2减少不必要的关键字✅ Shader设计时 - 别滥用关键字 - 能合并的功能合并 - 减少变体数量 ↓ 变体少 → 物体用同一变体的机会大方法3用 Frame Debugger 定位✅ 断批提示different shader keywords时 1. 检查两个物体的材质设置差异 2. 看哪个关键字不同 3. 统一它们 ↓ 针对性解决!方法4现代方案 SRP Batcher⚠️ 注意: SRP Batcher也看变体! SRP Batcher要求: 同一个Shader变体才能批 → 关键字不同(变体不同)同样断! ↓ 但SRP Batcher对材质不同宽容 (只要变体同,不同材质也能批) ↓ 所以: 关键字/变体是各种批处理的共同底线!生动理解统一关键字避免断批像让大家点同款: 要合批 → 让物体用同样的关键字组合 → 大家都点蛋炒饭 → 用同一个程序(变体) → 能一起出餐(合批)! ↓ 关键字对齐 变体对齐 能合批! 第六幕关键字断批的完整逻辑链从关键字到断批的推导【完整因果链】 Shader有关键字开关 ↓ 不同关键字组合 → 编译成不同变体 ↓ 变体 独立的GPU程序 ↓ 物体关键字不同 → 用不同变体 → 用不同程序 ↓ 一个DrawCall只能绑定一个程序 ↓ 用不同程序的物体 → 必须分开DrawCall ↓ 断批! ↓ 这就是完整原理!一图理解物体A(开_NORMALMAP) 物体B(关_NORMALMAP) ↓ ↓ 变体X程序 变体Y程序 (不同程序!) ↓ ↓ DrawCall 1 DrawCall 2 ↓ ↓ 无法合并 断批!✅ 关键字断批理解检查清单概念 □ 明白关键字是Shader功能开关 □ 明白关键字组合产生变体⭐ □ 明白变体是独立编译的GPU程序⭐ 原理 □ 明白变体不同 程序不同 □ 明白一个DrawCall只能绑一个程序 □ 明白用不同程序必须分DrawCall → 断批 来源 □ 知道关键字来自材质/代码/全局设置 □ 知道multi_compile和shader_feature 解决 □ 会统一关键字状态 □ 会减少不必要的关键字 □ 会用Frame Debugger定位 □ 知道SRP Batcher也看变体 一句话总结Shader 关键字断批的原理关键字是 Shader 里的功能开关不同的关键字组合会被编译器编译成不同的变体Variant——每个变体是一份独立的 GPU 程序。核心原理链物体关键字不同 → 用的是不同变体 → 就是不同的 GPU 程序而一个 DrawCall 只能绑定一个 Shader 程序 → 用不同程序的物体必须发不同的 DrawCall → 断批哪怕是同一个 Shader、同一个材质资产只要关键字状态不同比如一个开了法线贴图、一个没开实际跑的就是两个不同的编译程序自然合不了批。解决统一关键字状态、减少不必要关键字注意 SRP Batcher 同样以变体一致为底线核心口诀关键字是功能开关不同组合编译成不同变体变体就是不同GPU程序一个DrawCall只绑一个程序程序不同必然断批统一关键字才能合 关键字断批原理速查表概念说明关键字Shader的功能开关变体关键字组合编译出的独立程序⭐断批根因变体不同程序不同DrawCall规则一次只能绑一个程序推导关键字异→变体异→程序异→断批来源材质/代码/全局设置解决统一关键字状态注意SRP Batcher也看变体 一句话记住核心关键字不同 → 变体不同 → GPU 程序不同 → 必须分开画 → 断批。变体是编译期就分好的不同程序哪怕材质一样关键字一变就是两个程序——想合批先让关键字对齐 延伸从关键字理解变体爆炸问题【关键字还带来另一个大问题:变体爆炸】 关键字越多 → 变体组合爆炸式增长: 10个关键字 → 最多2^10 1024个变体! ↓ 后果: ① 编译时间暴涨 ② 包体变大(存所有变体) ③ 运行时切换变体有开销 ④ 合批更难(变体多,对齐难) ↓ 所以Shader设计: - 精简关键字 - 用shader_feature剔除没用的变体 - 合理组织功能 ↓ 关键字既影响合批,又影响包体和编译! 是Shader优化的重要一环!
返回列表