shader 成本模型:ALU、采样、带宽与 overdraw
「这个 shader 贵不贵」不该靠猜。这篇给一个可以先算后测的成本模型:一个 Draw 的成本 = 每次绘制开销 + 顶点成本 + 片元成本 × 有效像素数,每一项都有明确的量纲和平台前提。
先分清 CPU 瓶颈还是 GPU 瓶颈
- CPU 侧:Draw Call 提交、状态切换、参数上传。SRP Batcher 削的是这块(见 /notes/unity/structure-4/),GPU Instancing 削的是相同网格的提交次数。
- GPU 侧:本模型讨论的 shader 成本。Profiler 的 Render 模块 + Frame Debugger 的 Draw 数先定位是哪侧(见 /notes/unity/performance-2/)。
片元成本 = 执行次数 × 每次的代价
执行次数由覆盖像素数与 overdraw 决定,这里平台差异是决定性的:
| 桌面(IMR) | 移动(TBDR,如 Mali/PowerVR/Apple) | |
|---|---|---|
| 架构 | 按画序直接写帧缓冲 | 先把三角形分块进 tile memory,块内做完再写出 |
| overdraw | 每一层都付全价片元成本 | 不透明且 ZWrite On、不读颜色、无 discard 时,被挡住的片元根本不执行(HSR) |
| 推论 | 减少全屏大透明、砍多余 Pass | 不透明 overdraw 便宜;透明/混合/discard 破坏 HSR,才是移动端杀手 |
所以「全屏大透明特效」在桌面尚可、在移动端是头号成本——同一个 shader 两平台的账完全不同,任何优化结论都要带平台前提。
每次的代价按贵到便宜排:
- 带宽(纹理采样):移动端头号瓶颈。成本 = 读的字节数:大贴图、各向异性过滤、mip 选错(放大采样)都在读更多字节。缓解:合理分辨率与压缩格式(ASTC)、图集、避免无谓的高精度格式。
- 依赖采样链:第二次采样的 UV 来自第一次采样结果,流水线停顿(见 /notes/unity/principles-4/)。
- ALU:现代 GPU 吞吐极高,PBR 全套公式(见 /notes/unity/lighting-3/)在移动端也通常不是第一瓶颈——但 fp32 在移动 GPU 上常是 fp16 的一半吞吐,精度选择见 /notes/unity/performance-3/。
- 插值器与寄存器:超限会 spill 到显存,成本跳变——看编译产物的寄存器数(见 /notes/unity/performance-2/)。
顶点成本
顶点数 × 每次顶点程序代价。常规网格下远小于片元成本;例外:高模角色(数万顶点 × 多 Pass)、顶点动画 + 蒙皮、以及多 Pass 效果把顶点成本乘遍数(倒模描边 = 2 倍几何,见 /notes/unity/effects-3/)。
每次绘制开销
每个 Draw Call 的固定成本(绑定、提交)。变体切换(keyword 改动引发程序切换)、材质参数重传(SRP Batcher 不兼容时)都落在这里(见 /notes/unity/engineering-3/、/notes/unity/engineering-1/)。
改之前的预估清单
- Frame Debugger 数 Draw:这个材质画了几遍(Pass 数)?
- 估算片元次数:屏幕覆盖面积 × overdraw 层数(透明特效重点数这个)。
- Shader Inspector 看变体的指令数/采样数/寄存器数(每平台)。
- 按上表判断瓶颈类别,再动手改——改完必须复测(见 /notes/unity/performance-2/)。
移动端优化顺序(经验值)
- 砍全屏透明/混合 overdraw(特效面积、粒子叠加)。
- 砍带宽:贴图分辨率与格式、采样次数、依赖链。
- 砍精度:能 half 不 float(见 /notes/unity/performance-3/)。
- 砍 Pass 数与变体数。
- 最后才抠 ALU 公式——通常收益最小。
桌面顺序不同:overdraw 排第一(IMR 无 HSR),Draw Call 数在大型场景常先于单 shader 成本爆掉。
自检三问
- 同一个全屏透明特效,为什么移动端比桌面贵一个量级?
discard在 TBDR 上为什么特别贵?- 预估一个描边材质的总成本,要乘哪些系数?